(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.AFRAME=f()}})(function(){var define,module,exports;return(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;o0){this._tweensAddedDuringUpdate={};for(var i=0;i1?1:elapsed;value=this._easingFunction(elapsed);for(property in this._valuesEnd){if(this._valuesStart[property]===undefined){continue;}
var start=this._valuesStart[property]||0;var end=this._valuesEnd[property];if(end instanceof Array){this._object[property]=this._interpolationFunction(end,value);}else{if(typeof(end)==='string'){if(end.charAt(0)==='+'||end.charAt(0)==='-'){end=start+ parseFloat(end);}else{end=parseFloat(end);}}
if(typeof(end)==='number'){this._object[property]=start+(end- start)*value;}}}
if(this._onUpdateCallback!==null){this._onUpdateCallback.call(this._object,value);}
if(elapsed===1){if(this._repeat>0){if(isFinite(this._repeat)){this._repeat--;}
for(property in this._valuesStartRepeat){if(typeof(this._valuesEnd[property])==='string'){this._valuesStartRepeat[property]=this._valuesStartRepeat[property]+ parseFloat(this._valuesEnd[property]);}
if(this._yoyo){var tmp=this._valuesStartRepeat[property];this._valuesStartRepeat[property]=this._valuesEnd[property];this._valuesEnd[property]=tmp;}
this._valuesStart[property]=this._valuesStartRepeat[property];}
if(this._yoyo){this._reversed=!this._reversed;}
if(this._repeatDelayTime!==undefined){this._startTime=time+ this._repeatDelayTime;}else{this._startTime=time+ this._delayTime;}
return true;}else{if(this._onCompleteCallback!==null){this._onCompleteCallback.call(this._object,this._object);}
for(var i=0,numChainedTweens=this._chainedTweens.length;i1){return fn(v[m],v[m- 1],m- f);}
return fn(v[i],v[i+ 1>m?m:i+ 1],f- i);},Bezier:function(v,k){var b=0;var n=v.length- 1;var pw=Math.pow;var bn=TWEEN.Interpolation.Utils.Bernstein;for(var i=0;i<=n;i++){b+=pw(1- k,n- i)*pw(k,i)*v[i]*bn(n,i);}
return b;},CatmullRom:function(v,k){var m=v.length- 1;var f=m*k;var i=Math.floor(f);var fn=TWEEN.Interpolation.Utils.CatmullRom;if(v[0]===v[m]){if(k<0){i=Math.floor(f=m*(1+ k));}
return fn(v[(i- 1+ m)%m],v[i],v[(i+ 1)%m],v[(i+ 2)%m],f- i);}else{if(k<0){return v[0]-(fn(v[0],v[0],v[1],v[1],-f)- v[0]);}
if(k>1){return v[m]-(fn(v[m],v[m],v[m- 1],v[m- 1],f- m)- v[m]);}
return fn(v[i?i- 1:0],v[i],v[m1;i--){s*=i;}
a[n]=s;return s;};})(),CatmullRom:function(p0,p1,p2,p3,t){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(root){if(typeof define==='function'&&define.amd){define([],function(){return TWEEN;});}else if(typeof module!=='undefined'&&typeof exports==='object'){module.exports=TWEEN;}else if(root!==undefined){root.TWEEN=TWEEN;}})(this);}).call(this,_dereq_('_process'))},{"_process":6}],2:[function(_dereq_,module,exports){var str=Object.prototype.toString
module.exports=anArray
function anArray(arr){return(arr.BYTES_PER_ELEMENT&&str.call(arr.buffer)==='[object ArrayBuffer]'||Array.isArray(arr))}},{}],3:[function(_dereq_,module,exports){module.exports=function numtype(num,def){return typeof num==='number'?num:(typeof def==='number'?def:0)}},{}],4:[function(_dereq_,module,exports){'use strict'
exports.byteLength=byteLength
exports.toByteArray=toByteArray
exports.fromByteArray=fromByteArray
var lookup=[]
var revLookup=[]
var Arr=typeof Uint8Array!=='undefined'?Uint8Array:Array
var code='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
for(var i=0,len=code.length;i0){throw new Error('Invalid string. Length must be a multiple of 4')}
return b64[len- 2]==='='?2:b64[len- 1]==='='?1:0}
function byteLength(b64){return(b64.length*3/4)- placeHoldersCount(b64)}
function toByteArray(b64){var i,l,tmp,placeHolders,arr
var len=b64.length
placeHolders=placeHoldersCount(b64)
arr=new Arr((len*3/4)- placeHolders)
l=placeHolders>0?len- 4:len
var L=0
for(i=0;i>16)&0xFF
arr[L++]=(tmp>>8)&0xFF
arr[L++]=tmp&0xFF}
if(placeHolders===2){tmp=(revLookup[b64.charCodeAt(i)]<<2)|(revLookup[b64.charCodeAt(i+ 1)]>>4)
arr[L++]=tmp&0xFF}else if(placeHolders===1){tmp=(revLookup[b64.charCodeAt(i)]<<10)|(revLookup[b64.charCodeAt(i+ 1)]<<4)|(revLookup[b64.charCodeAt(i+ 2)]>>2)
arr[L++]=(tmp>>8)&0xFF
arr[L++]=tmp&0xFF}
return arr}
function tripletToBase64(num){return lookup[num>>18&0x3F]+ lookup[num>>12&0x3F]+ lookup[num>>6&0x3F]+ lookup[num&0x3F]}
function encodeChunk(uint8,start,end){var tmp
var output=[]
for(var i=start;ilen2?len2:(i+ maxChunkLength)))}
if(extraBytes===1){tmp=uint8[len- 1]
output+=lookup[tmp>>2]
output+=lookup[(tmp<<4)&0x3F]
output+='=='}else if(extraBytes===2){tmp=(uint8[len- 2]<<8)+(uint8[len- 1])
output+=lookup[tmp>>10]
output+=lookup[(tmp>>4)&0x3F]
output+=lookup[(tmp<<2)&0x3F]
output+='='}
parts.push(output)
return parts.join('')}},{}],5:[function(_dereq_,module,exports){'use strict';module.exports={createLink:function(href,attributes){var head=document.head||document.getElementsByTagName('head')[0];var link=document.createElement('link');link.href=href;link.rel='stylesheet';for(var key in attributes){if(!attributes.hasOwnProperty(key)){continue;}
var value=attributes[key];link.setAttribute('data-'+ key,value);}
head.appendChild(link);},createStyle:function(cssText,attributes){var head=document.head||document.getElementsByTagName('head')[0],style=document.createElement('style');style.type='text/css';for(var key in attributes){if(!attributes.hasOwnProperty(key)){continue;}
var value=attributes[key];style.setAttribute('data-'+ key,value);}
if(style.sheet){style.innerHTML=cssText;style.sheet.cssText=cssText;head.appendChild(style);}else if(style.styleSheet){head.appendChild(style);style.styleSheet.cssText=cssText;}else{style.appendChild(document.createTextNode(cssText));head.appendChild(style);}}};},{}],6:[function(_dereq_,module,exports){var process=module.exports={};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){return setTimeout(fun,0);}
if((cachedSetTimeout===defaultSetTimout||!cachedSetTimeout)&&setTimeout){cachedSetTimeout=setTimeout;return setTimeout(fun,0);}
try{return cachedSetTimeout(fun,0);}catch(e){try{return cachedSetTimeout.call(null,fun,0);}catch(e){return cachedSetTimeout.call(this,fun,0);}}}
function runClearTimeout(marker){if(cachedClearTimeout===clearTimeout){return clearTimeout(marker);}
if((cachedClearTimeout===defaultClearTimeout||!cachedClearTimeout)&&clearTimeout){cachedClearTimeout=clearTimeout;return clearTimeout(marker);}
try{return cachedClearTimeout(marker);}catch(e){try{return cachedClearTimeout.call(null,marker);}catch(e){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(++queueIndex1){for(var i=1;i=kMaxLength()){throw new RangeError('Attempt to allocate Buffer larger than maximum '+'size: 0x'+ kMaxLength().toString(16)+' bytes')}
return length|0}
function SlowBuffer(length){if(+length!=length){length=0}
return Buffer.alloc(+length)}
Buffer.isBuffer=function isBuffer(b){return!!(b!=null&&b._isBuffer)}
Buffer.compare=function compare(a,b){if(!Buffer.isBuffer(a)||!Buffer.isBuffer(b)){throw new TypeError('Arguments must be Buffers')}
if(a===b)return 0
var x=a.length
var y=b.length
for(var i=0,len=Math.min(x,y);i>>1
case'base64':return base64ToBytes(string).length
default:if(loweredCase)return utf8ToBytes(string).length
encoding=(''+ encoding).toLowerCase()
loweredCase=true}}}
Buffer.byteLength=byteLength
function slowToString(encoding,start,end){var loweredCase=false
if(start===undefined||start<0){start=0}
if(start>this.length){return''}
if(end===undefined||end>this.length){end=this.length}
if(end<=0){return''}
end>>>=0
start>>>=0
if(end<=start){return''}
if(!encoding)encoding='utf8'
while(true){switch(encoding){case'hex':return hexSlice(this,start,end)
case'utf8':case'utf-8':return utf8Slice(this,start,end)
case'ascii':return asciiSlice(this,start,end)
case'latin1':case'binary':return latin1Slice(this,start,end)
case'base64':return base64Slice(this,start,end)
case'ucs2':case'ucs-2':case'utf16le':case'utf-16le':return utf16leSlice(this,start,end)
default:if(loweredCase)throw new TypeError('Unknown encoding: '+ encoding)
encoding=(encoding+'').toLowerCase()
loweredCase=true}}}
Buffer.prototype._isBuffer=true
function swap(b,n,m){var i=b[n]
b[n]=b[m]
b[m]=i}
Buffer.prototype.swap16=function swap16(){var len=this.length
if(len%2!==0){throw new RangeError('Buffer size must be a multiple of 16-bits')}
for(var i=0;i0){str=this.toString('hex',0,max).match(/.{2}/g).join(' ')
if(this.length>max)str+=' ... '}
return''}
Buffer.prototype.compare=function compare(target,start,end,thisStart,thisEnd){if(!Buffer.isBuffer(target)){throw new TypeError('Argument must be a Buffer')}
if(start===undefined){start=0}
if(end===undefined){end=target?target.length:0}
if(thisStart===undefined){thisStart=0}
if(thisEnd===undefined){thisEnd=this.length}
if(start<0||end>target.length||thisStart<0||thisEnd>this.length){throw new RangeError('out of range index')}
if(thisStart>=thisEnd&&start>=end){return 0}
if(thisStart>=thisEnd){return-1}
if(start>=end){return 1}
start>>>=0
end>>>=0
thisStart>>>=0
thisEnd>>>=0
if(this===target)return 0
var x=thisEnd- thisStart
var y=end- start
var len=Math.min(x,y)
var thisCopy=this.slice(thisStart,thisEnd)
var targetCopy=target.slice(start,end)
for(var i=0;i0x7fffffff){byteOffset=0x7fffffff}else if(byteOffset<-0x80000000){byteOffset=-0x80000000}
byteOffset=+byteOffset
if(isNaN(byteOffset)){byteOffset=dir?0:(buffer.length- 1)}
if(byteOffset<0)byteOffset=buffer.length+ byteOffset
if(byteOffset>=buffer.length){if(dir)return-1
else byteOffset=buffer.length- 1}else if(byteOffset<0){if(dir)byteOffset=0
else return-1}
if(typeof val==='string'){val=Buffer.from(val,encoding)}
if(Buffer.isBuffer(val)){if(val.length===0){return-1}
return arrayIndexOf(buffer,val,byteOffset,encoding,dir)}else if(typeof val==='number'){val=val&0xFF
if(Buffer.TYPED_ARRAY_SUPPORT&&typeof Uint8Array.prototype.indexOf==='function'){if(dir){return Uint8Array.prototype.indexOf.call(buffer,val,byteOffset)}else{return Uint8Array.prototype.lastIndexOf.call(buffer,val,byteOffset)}}
return arrayIndexOf(buffer,[val],byteOffset,encoding,dir)}
throw new TypeError('val must be string, number or Buffer')}
function arrayIndexOf(arr,val,byteOffset,encoding,dir){var indexSize=1
var arrLength=arr.length
var valLength=val.length
if(encoding!==undefined){encoding=String(encoding).toLowerCase()
if(encoding==='ucs2'||encoding==='ucs-2'||encoding==='utf16le'||encoding==='utf-16le'){if(arr.length<2||val.length<2){return-1}
indexSize=2
arrLength/=2
valLength/=2
byteOffset/=2}}
function read(buf,i){if(indexSize===1){return buf[i]}else{return buf.readUInt16BE(i*indexSize)}}
var i
if(dir){var foundIndex=-1
for(i=byteOffset;iarrLength)byteOffset=arrLength- valLength
for(i=byteOffset;i>=0;i--){var found=true
for(var j=0;jremaining){length=remaining}}
var strLen=string.length
if(strLen%2!==0)throw new TypeError('Invalid hex string')
if(length>strLen/2){length=strLen/2}
for(var i=0;iremaining)length=remaining
if((string.length>0&&(length<0||offset<0))||offset>this.length){throw new RangeError('Attempt to write outside buffer bounds')}
if(!encoding)encoding='utf8'
var loweredCase=false
for(;;){switch(encoding){case'hex':return hexWrite(this,string,offset,length)
case'utf8':case'utf-8':return utf8Write(this,string,offset,length)
case'ascii':return asciiWrite(this,string,offset,length)
case'latin1':case'binary':return latin1Write(this,string,offset,length)
case'base64':return base64Write(this,string,offset,length)
case'ucs2':case'ucs-2':case'utf16le':case'utf-16le':return ucs2Write(this,string,offset,length)
default:if(loweredCase)throw new TypeError('Unknown encoding: '+ encoding)
encoding=(''+ encoding).toLowerCase()
loweredCase=true}}}
Buffer.prototype.toJSON=function toJSON(){return{type:'Buffer',data:Array.prototype.slice.call(this._arr||this,0)}}
function base64Slice(buf,start,end){if(start===0&&end===buf.length){return base64.fromByteArray(buf)}else{return base64.fromByteArray(buf.slice(start,end))}}
function utf8Slice(buf,start,end){end=Math.min(buf.length,end)
var res=[]
var i=start
while(i0xEF)?4:(firstByte>0xDF)?3:(firstByte>0xBF)?2:1
if(i+ bytesPerSequence<=end){var secondByte,thirdByte,fourthByte,tempCodePoint
switch(bytesPerSequence){case 1:if(firstByte<0x80){codePoint=firstByte}
break
case 2:secondByte=buf[i+ 1]
if((secondByte&0xC0)===0x80){tempCodePoint=(firstByte&0x1F)<<0x6|(secondByte&0x3F)
if(tempCodePoint>0x7F){codePoint=tempCodePoint}}
break
case 3:secondByte=buf[i+ 1]
thirdByte=buf[i+ 2]
if((secondByte&0xC0)===0x80&&(thirdByte&0xC0)===0x80){tempCodePoint=(firstByte&0xF)<<0xC|(secondByte&0x3F)<<0x6|(thirdByte&0x3F)
if(tempCodePoint>0x7FF&&(tempCodePoint<0xD800||tempCodePoint>0xDFFF)){codePoint=tempCodePoint}}
break
case 4:secondByte=buf[i+ 1]
thirdByte=buf[i+ 2]
fourthByte=buf[i+ 3]
if((secondByte&0xC0)===0x80&&(thirdByte&0xC0)===0x80&&(fourthByte&0xC0)===0x80){tempCodePoint=(firstByte&0xF)<<0x12|(secondByte&0x3F)<<0xC|(thirdByte&0x3F)<<0x6|(fourthByte&0x3F)
if(tempCodePoint>0xFFFF&&tempCodePoint<0x110000){codePoint=tempCodePoint}}}}
if(codePoint===null){codePoint=0xFFFD
bytesPerSequence=1}else if(codePoint>0xFFFF){codePoint-=0x10000
res.push(codePoint>>>10&0x3FF|0xD800)
codePoint=0xDC00|codePoint&0x3FF}
res.push(codePoint)
i+=bytesPerSequence}
return decodeCodePointsArray(res)}
var MAX_ARGUMENTS_LENGTH=0x1000
function decodeCodePointsArray(codePoints){var len=codePoints.length
if(len<=MAX_ARGUMENTS_LENGTH){return String.fromCharCode.apply(String,codePoints)}
var res=''
var i=0
while(ilen)end=len
var out=''
for(var i=start;ilen){start=len}
if(end<0){end+=len
if(end<0)end=0}else if(end>len){end=len}
if(endlength)throw new RangeError('Trying to access beyond buffer length')}
Buffer.prototype.readUIntLE=function readUIntLE(offset,byteLength,noAssert){offset=offset|0
byteLength=byteLength|0
if(!noAssert)checkOffset(offset,byteLength,this.length)
var val=this[offset]
var mul=1
var i=0
while(++i0&&(mul*=0x100)){val+=this[offset+--byteLength]*mul}
return val}
Buffer.prototype.readUInt8=function readUInt8(offset,noAssert){if(!noAssert)checkOffset(offset,1,this.length)
return this[offset]}
Buffer.prototype.readUInt16LE=function readUInt16LE(offset,noAssert){if(!noAssert)checkOffset(offset,2,this.length)
return this[offset]|(this[offset+ 1]<<8)}
Buffer.prototype.readUInt16BE=function readUInt16BE(offset,noAssert){if(!noAssert)checkOffset(offset,2,this.length)
return(this[offset]<<8)|this[offset+ 1]}
Buffer.prototype.readUInt32LE=function readUInt32LE(offset,noAssert){if(!noAssert)checkOffset(offset,4,this.length)
return((this[offset])|(this[offset+ 1]<<8)|(this[offset+ 2]<<16))+
(this[offset+ 3]*0x1000000)}
Buffer.prototype.readUInt32BE=function readUInt32BE(offset,noAssert){if(!noAssert)checkOffset(offset,4,this.length)
return(this[offset]*0x1000000)+
((this[offset+ 1]<<16)|(this[offset+ 2]<<8)|this[offset+ 3])}
Buffer.prototype.readIntLE=function readIntLE(offset,byteLength,noAssert){offset=offset|0
byteLength=byteLength|0
if(!noAssert)checkOffset(offset,byteLength,this.length)
var val=this[offset]
var mul=1
var i=0
while(++i=mul)val-=Math.pow(2,8*byteLength)
return val}
Buffer.prototype.readIntBE=function readIntBE(offset,byteLength,noAssert){offset=offset|0
byteLength=byteLength|0
if(!noAssert)checkOffset(offset,byteLength,this.length)
var i=byteLength
var mul=1
var val=this[offset+--i]
while(i>0&&(mul*=0x100)){val+=this[offset+--i]*mul}
mul*=0x80
if(val>=mul)val-=Math.pow(2,8*byteLength)
return val}
Buffer.prototype.readInt8=function readInt8(offset,noAssert){if(!noAssert)checkOffset(offset,1,this.length)
if(!(this[offset]&0x80))return(this[offset])
return((0xff- this[offset]+ 1)*-1)}
Buffer.prototype.readInt16LE=function readInt16LE(offset,noAssert){if(!noAssert)checkOffset(offset,2,this.length)
var val=this[offset]|(this[offset+ 1]<<8)
return(val&0x8000)?val|0xFFFF0000:val}
Buffer.prototype.readInt16BE=function readInt16BE(offset,noAssert){if(!noAssert)checkOffset(offset,2,this.length)
var val=this[offset+ 1]|(this[offset]<<8)
return(val&0x8000)?val|0xFFFF0000:val}
Buffer.prototype.readInt32LE=function readInt32LE(offset,noAssert){if(!noAssert)checkOffset(offset,4,this.length)
return(this[offset])|(this[offset+ 1]<<8)|(this[offset+ 2]<<16)|(this[offset+ 3]<<24)}
Buffer.prototype.readInt32BE=function readInt32BE(offset,noAssert){if(!noAssert)checkOffset(offset,4,this.length)
return(this[offset]<<24)|(this[offset+ 1]<<16)|(this[offset+ 2]<<8)|(this[offset+ 3])}
Buffer.prototype.readFloatLE=function readFloatLE(offset,noAssert){if(!noAssert)checkOffset(offset,4,this.length)
return ieee754.read(this,offset,true,23,4)}
Buffer.prototype.readFloatBE=function readFloatBE(offset,noAssert){if(!noAssert)checkOffset(offset,4,this.length)
return ieee754.read(this,offset,false,23,4)}
Buffer.prototype.readDoubleLE=function readDoubleLE(offset,noAssert){if(!noAssert)checkOffset(offset,8,this.length)
return ieee754.read(this,offset,true,52,8)}
Buffer.prototype.readDoubleBE=function readDoubleBE(offset,noAssert){if(!noAssert)checkOffset(offset,8,this.length)
return ieee754.read(this,offset,false,52,8)}
function checkInt(buf,value,offset,ext,max,min){if(!Buffer.isBuffer(buf))throw new TypeError('"buffer" argument must be a Buffer instance')
if(value>max||valuebuf.length)throw new RangeError('Index out of range')}
Buffer.prototype.writeUIntLE=function writeUIntLE(value,offset,byteLength,noAssert){value=+value
offset=offset|0
byteLength=byteLength|0
if(!noAssert){var maxBytes=Math.pow(2,8*byteLength)- 1
checkInt(this,value,offset,byteLength,maxBytes,0)}
var mul=1
var i=0
this[offset]=value&0xFF
while(++i=0&&(mul*=0x100)){this[offset+ i]=(value/mul)&0xFF}
return offset+ byteLength}
Buffer.prototype.writeUInt8=function writeUInt8(value,offset,noAssert){value=+value
offset=offset|0
if(!noAssert)checkInt(this,value,offset,1,0xff,0)
if(!Buffer.TYPED_ARRAY_SUPPORT)value=Math.floor(value)
this[offset]=(value&0xff)
return offset+ 1}
function objectWriteUInt16(buf,value,offset,littleEndian){if(value<0)value=0xffff+ value+ 1
for(var i=0,j=Math.min(buf.length- offset,2);i>>(littleEndian?i:1- i)*8}}
Buffer.prototype.writeUInt16LE=function writeUInt16LE(value,offset,noAssert){value=+value
offset=offset|0
if(!noAssert)checkInt(this,value,offset,2,0xffff,0)
if(Buffer.TYPED_ARRAY_SUPPORT){this[offset]=(value&0xff)
this[offset+ 1]=(value>>>8)}else{objectWriteUInt16(this,value,offset,true)}
return offset+ 2}
Buffer.prototype.writeUInt16BE=function writeUInt16BE(value,offset,noAssert){value=+value
offset=offset|0
if(!noAssert)checkInt(this,value,offset,2,0xffff,0)
if(Buffer.TYPED_ARRAY_SUPPORT){this[offset]=(value>>>8)
this[offset+ 1]=(value&0xff)}else{objectWriteUInt16(this,value,offset,false)}
return offset+ 2}
function objectWriteUInt32(buf,value,offset,littleEndian){if(value<0)value=0xffffffff+ value+ 1
for(var i=0,j=Math.min(buf.length- offset,4);i>>(littleEndian?i:3- i)*8)&0xff}}
Buffer.prototype.writeUInt32LE=function writeUInt32LE(value,offset,noAssert){value=+value
offset=offset|0
if(!noAssert)checkInt(this,value,offset,4,0xffffffff,0)
if(Buffer.TYPED_ARRAY_SUPPORT){this[offset+ 3]=(value>>>24)
this[offset+ 2]=(value>>>16)
this[offset+ 1]=(value>>>8)
this[offset]=(value&0xff)}else{objectWriteUInt32(this,value,offset,true)}
return offset+ 4}
Buffer.prototype.writeUInt32BE=function writeUInt32BE(value,offset,noAssert){value=+value
offset=offset|0
if(!noAssert)checkInt(this,value,offset,4,0xffffffff,0)
if(Buffer.TYPED_ARRAY_SUPPORT){this[offset]=(value>>>24)
this[offset+ 1]=(value>>>16)
this[offset+ 2]=(value>>>8)
this[offset+ 3]=(value&0xff)}else{objectWriteUInt32(this,value,offset,false)}
return offset+ 4}
Buffer.prototype.writeIntLE=function writeIntLE(value,offset,byteLength,noAssert){value=+value
offset=offset|0
if(!noAssert){var limit=Math.pow(2,8*byteLength- 1)
checkInt(this,value,offset,byteLength,limit- 1,-limit)}
var i=0
var mul=1
var sub=0
this[offset]=value&0xFF
while(++i>0)- sub&0xFF}
return offset+ byteLength}
Buffer.prototype.writeIntBE=function writeIntBE(value,offset,byteLength,noAssert){value=+value
offset=offset|0
if(!noAssert){var limit=Math.pow(2,8*byteLength- 1)
checkInt(this,value,offset,byteLength,limit- 1,-limit)}
var i=byteLength- 1
var mul=1
var sub=0
this[offset+ i]=value&0xFF
while(--i>=0&&(mul*=0x100)){if(value<0&&sub===0&&this[offset+ i+ 1]!==0){sub=1}
this[offset+ i]=((value/mul)>>0)- sub&0xFF}
return offset+ byteLength}
Buffer.prototype.writeInt8=function writeInt8(value,offset,noAssert){value=+value
offset=offset|0
if(!noAssert)checkInt(this,value,offset,1,0x7f,-0x80)
if(!Buffer.TYPED_ARRAY_SUPPORT)value=Math.floor(value)
if(value<0)value=0xff+ value+ 1
this[offset]=(value&0xff)
return offset+ 1}
Buffer.prototype.writeInt16LE=function writeInt16LE(value,offset,noAssert){value=+value
offset=offset|0
if(!noAssert)checkInt(this,value,offset,2,0x7fff,-0x8000)
if(Buffer.TYPED_ARRAY_SUPPORT){this[offset]=(value&0xff)
this[offset+ 1]=(value>>>8)}else{objectWriteUInt16(this,value,offset,true)}
return offset+ 2}
Buffer.prototype.writeInt16BE=function writeInt16BE(value,offset,noAssert){value=+value
offset=offset|0
if(!noAssert)checkInt(this,value,offset,2,0x7fff,-0x8000)
if(Buffer.TYPED_ARRAY_SUPPORT){this[offset]=(value>>>8)
this[offset+ 1]=(value&0xff)}else{objectWriteUInt16(this,value,offset,false)}
return offset+ 2}
Buffer.prototype.writeInt32LE=function writeInt32LE(value,offset,noAssert){value=+value
offset=offset|0
if(!noAssert)checkInt(this,value,offset,4,0x7fffffff,-0x80000000)
if(Buffer.TYPED_ARRAY_SUPPORT){this[offset]=(value&0xff)
this[offset+ 1]=(value>>>8)
this[offset+ 2]=(value>>>16)
this[offset+ 3]=(value>>>24)}else{objectWriteUInt32(this,value,offset,true)}
return offset+ 4}
Buffer.prototype.writeInt32BE=function writeInt32BE(value,offset,noAssert){value=+value
offset=offset|0
if(!noAssert)checkInt(this,value,offset,4,0x7fffffff,-0x80000000)
if(value<0)value=0xffffffff+ value+ 1
if(Buffer.TYPED_ARRAY_SUPPORT){this[offset]=(value>>>24)
this[offset+ 1]=(value>>>16)
this[offset+ 2]=(value>>>8)
this[offset+ 3]=(value&0xff)}else{objectWriteUInt32(this,value,offset,false)}
return offset+ 4}
function checkIEEE754(buf,value,offset,ext,max,min){if(offset+ ext>buf.length)throw new RangeError('Index out of range')
if(offset<0)throw new RangeError('Index out of range')}
function writeFloat(buf,value,offset,littleEndian,noAssert){if(!noAssert){checkIEEE754(buf,value,offset,4,3.4028234663852886e+38,-3.4028234663852886e+38)}
ieee754.write(buf,value,offset,littleEndian,23,4)
return offset+ 4}
Buffer.prototype.writeFloatLE=function writeFloatLE(value,offset,noAssert){return writeFloat(this,value,offset,true,noAssert)}
Buffer.prototype.writeFloatBE=function writeFloatBE(value,offset,noAssert){return writeFloat(this,value,offset,false,noAssert)}
function writeDouble(buf,value,offset,littleEndian,noAssert){if(!noAssert){checkIEEE754(buf,value,offset,8,1.7976931348623157E+308,-1.7976931348623157E+308)}
ieee754.write(buf,value,offset,littleEndian,52,8)
return offset+ 8}
Buffer.prototype.writeDoubleLE=function writeDoubleLE(value,offset,noAssert){return writeDouble(this,value,offset,true,noAssert)}
Buffer.prototype.writeDoubleBE=function writeDoubleBE(value,offset,noAssert){return writeDouble(this,value,offset,false,noAssert)}
Buffer.prototype.copy=function copy(target,targetStart,start,end){if(!start)start=0
if(!end&&end!==0)end=this.length
if(targetStart>=target.length)targetStart=target.length
if(!targetStart)targetStart=0
if(end>0&&end=this.length)throw new RangeError('sourceStart out of bounds')
if(end<0)throw new RangeError('sourceEnd out of bounds')
if(end>this.length)end=this.length
if(target.length- targetStart=0;--i){target[i+ targetStart]=this[i+ start]}}else if(len<1000||!Buffer.TYPED_ARRAY_SUPPORT){for(i=0;i>>0
end=end===undefined?this.length:end>>>0
if(!val)val=0
var i
if(typeof val==='number'){for(i=start;i0xD7FF&&codePoint<0xE000){if(!leadSurrogate){if(codePoint>0xDBFF){if((units-=3)>-1)bytes.push(0xEF,0xBF,0xBD)
continue}else if(i+ 1===length){if((units-=3)>-1)bytes.push(0xEF,0xBF,0xBD)
continue}
leadSurrogate=codePoint
continue}
if(codePoint<0xDC00){if((units-=3)>-1)bytes.push(0xEF,0xBF,0xBD)
leadSurrogate=codePoint
continue}
codePoint=(leadSurrogate- 0xD800<<10|codePoint- 0xDC00)+ 0x10000}else if(leadSurrogate){if((units-=3)>-1)bytes.push(0xEF,0xBF,0xBD)}
leadSurrogate=null
if(codePoint<0x80){if((units-=1)<0)break
bytes.push(codePoint)}else if(codePoint<0x800){if((units-=2)<0)break
bytes.push(codePoint>>0x6|0xC0,codePoint&0x3F|0x80)}else if(codePoint<0x10000){if((units-=3)<0)break
bytes.push(codePoint>>0xC|0xE0,codePoint>>0x6&0x3F|0x80,codePoint&0x3F|0x80)}else if(codePoint<0x110000){if((units-=4)<0)break
bytes.push(codePoint>>0x12|0xF0,codePoint>>0xC&0x3F|0x80,codePoint>>0x6&0x3F|0x80,codePoint&0x3F|0x80)}else{throw new Error('Invalid code point')}}
return bytes}
function asciiToBytes(str){var byteArray=[]
for(var i=0;i>8
lo=c%256
byteArray.push(lo)
byteArray.push(hi)}
return byteArray}
function base64ToBytes(str){return base64.toByteArray(base64clean(str))}
function blitBuffer(src,dst,offset,length){for(var i=0;i=dst.length)||(i>=src.length))break
dst[i+ offset]=src[i]}
return i}
function isnan(val){return val!==val}}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{})},{"base64-js":4,"ieee754":18,"isarray":9}],9:[function(_dereq_,module,exports){var toString={}.toString;module.exports=Array.isArray||function(arr){return toString.call(arr)=='[object Array]';};},{}],10:[function(_dereq_,module,exports){exports=module.exports=_dereq_('./debug');exports.log=log;exports.formatArgs=formatArgs;exports.save=save;exports.load=load;exports.useColors=useColors;exports.storage='undefined'!=typeof chrome&&'undefined'!=typeof chrome.storage?chrome.storage.local:localstorage();exports.colors=['lightseagreen','forestgreen','goldenrod','dodgerblue','darkorchid','crimson'];function useColors(){return('WebkitAppearance'in document.documentElement.style)||(window.console&&(console.firebug||(console.exception&&console.table)))||(navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/)&&parseInt(RegExp.$1,10)>=31);}
exports.formatters.j=function(v){return JSON.stringify(v);};function formatArgs(){var args=arguments;var useColors=this.useColors;args[0]=(useColors?'%c':'')
+ this.namespace
+(useColors?' %c':' ')
+ args[0]
+(useColors?'%c ':' ');if(!useColors)return args;var c='color: '+ this.color;args=[args[0],c,'color: inherit'].concat(Array.prototype.slice.call(args,1));var index=0;var lastC=0;args[0].replace(/%[a-z%]/g,function(match){if('%%'===match)return;index++;if('%c'===match){lastC=index;}});args.splice(lastC,0,c);return args;}
function log(){return'object'===typeof console&&console.log&&Function.prototype.apply.call(console.log,console,arguments);}
function save(namespaces){try{if(null==namespaces){exports.storage.removeItem('debug');}else{exports.storage.debug=namespaces;}}catch(e){}}
function load(){var r;try{r=exports.storage.debug;}catch(e){}
return r;}
exports.enable(load());function localstorage(){try{return window.localStorage;}catch(e){}}},{"./debug":11}],11:[function(_dereq_,module,exports){exports=module.exports=debug;exports.coerce=coerce;exports.disable=disable;exports.enable=enable;exports.enabled=enabled;exports.names=[];exports.skips=[];exports.formatters={};var prevColor=0;function selectColor(){return exports.colors[prevColor++%exports.colors.length];}
function debug(namespace){function disabled(){}
disabled.enabled=false;function enabled(){var self=enabled;if(null==self.useColors)self.useColors=exports.useColors();if(null==self.color&&self.useColors)self.color=selectColor();var args=Array.prototype.slice.call(arguments);args[0]=exports.coerce(args[0]);if('string'!==typeof args[0]){args=['%o'].concat(args);}
var index=0;args[0]=args[0].replace(/%([a-z%])/g,function(match,format){if(match==='%%')return match;index++;var formatter=exports.formatters[format];if('function'===typeof formatter){var val=args[index];match=formatter.call(self,val);args.splice(index,1);index--;}
return match;});if('function'===typeof exports.formatArgs){args=exports.formatArgs.apply(self,args);}
var logFn=enabled.log||exports.log||console.log.bind(console);logFn.apply(self,args);}
enabled.enabled=true;var fn=exports.enabled(namespace)?enabled:disabled;fn.namespace=namespace;return fn;}
function enable(namespaces){exports.save(namespaces);var split=(namespaces||'').split(/[\s,]+/);var len=split.length;for(var i=0;i>0),o="attached",u="detached",a="extends",f="ADDITION",l="MODIFICATION",c="REMOVAL",h="DOMAttrModified",p="DOMContentLoaded",d="DOMSubtreeModified",v="<",m="=",g=/^[A-Z][A-Z0-9]*(?:-[A-Z0-9]+)+$/,y=["ANNOTATION-XML","COLOR-PROFILE","FONT-FACE","FONT-FACE-SRC","FONT-FACE-URI","FONT-FACE-FORMAT","FONT-FACE-NAME","MISSING-GLYPH"],b=[],w=[],E="",S=n.documentElement,x=b.indexOf||function(e){for(var t=this.length;t--&&this[t]!==e;);return t},T=r.prototype,N=T.hasOwnProperty,C=T.isPrototypeOf,k=r.defineProperty,L=r.getOwnPropertyDescriptor,A=r.getOwnPropertyNames,O=r.getPrototypeOf,M=r.setPrototypeOf,_=!!r.__proto__,D=r.create||function yt(e){return e?(yt.prototype=e,new yt):this},P=M||(_?function(e,t){return e.__proto__=t,e}:A&&L?function(){function e(e,t){for(var n,r=A(t),i=0,s=r.length;i>1
var nBits=-7
var i=isLE?(nBytes- 1):0
var d=isLE?-1:1
var s=buffer[offset+ i]
i+=d
e=s&((1<<(-nBits))- 1)
s>>=(-nBits)
nBits+=eLen
for(;nBits>0;e=e*256+ buffer[offset+ i],i+=d,nBits-=8){}
m=e&((1<<(-nBits))- 1)
e>>=(-nBits)
nBits+=mLen
for(;nBits>0;m=m*256+ buffer[offset+ i],i+=d,nBits-=8){}
if(e===0){e=1- eBias}else if(e===eMax){return m?NaN:((s?-1:1)*Infinity)}else{m=m+ Math.pow(2,mLen)
e=e- eBias}
return(s?-1:1)*m*Math.pow(2,e- mLen)}
exports.write=function(buffer,value,offset,isLE,mLen,nBytes){var e,m,c
var eLen=nBytes*8- mLen- 1
var eMax=(1<>1
var rt=(mLen===23?Math.pow(2,-24)- Math.pow(2,-77):0)
var i=isLE?0:(nBytes- 1)
var d=isLE?1:-1
var s=value<0||(value===0&&1/value<0)?1:0
value=Math.abs(value)
if(isNaN(value)||value===Infinity){m=isNaN(value)?1:0
e=eMax}else{e=Math.floor(Math.log(value)/ Math.LN2)
if(value*(c=Math.pow(2,-e))<1){e--
c*=2}
if(e+ eBias>=1){value+=rt/c}else{value+=rt*Math.pow(2,1- eBias)}
if(value*c>=2){e++
c/=2}
if(e+ eBias>=eMax){m=0
e=eMax}else if(e+ eBias>=1){m=(value*c- 1)*Math.pow(2,mLen)
e=e+ eBias}else{m=value*Math.pow(2,eBias- 1)*Math.pow(2,mLen)
e=0}}
for(;mLen>=8;buffer[offset+ i]=m&0xff,i+=d,m/=256,mLen-=8){}
e=(e<0;buffer[offset+ i]=e&0xff,i+=d,e/=256,eLen-=8){}
buffer[offset+ i- d]|=s*128}},{}],19:[function(_dereq_,module,exports){if(typeof Object.create==='function'){module.exports=function inherits(ctor,superCtor){ctor.super_=superCtor
ctor.prototype=Object.create(superCtor.prototype,{constructor:{value:ctor,enumerable:false,writable:true,configurable:true}});};}else{module.exports=function inherits(ctor,superCtor){ctor.super_=superCtor
var TempCtor=function(){}
TempCtor.prototype=superCtor.prototype
ctor.prototype=new TempCtor()
ctor.prototype.constructor=ctor}}},{}],20:[function(_dereq_,module,exports){module.exports=function(obj){return obj!=null&&(isBuffer(obj)||isSlowBuffer(obj)||!!obj._isBuffer)}
function isBuffer(obj){return!!obj.constructor&&typeof obj.constructor.isBuffer==='function'&&obj.constructor.isBuffer(obj)}
function isSlowBuffer(obj){return typeof obj.readFloatLE==='function'&&typeof obj.slice==='function'&&isBuffer(obj.slice(0,0))}},{}],21:[function(_dereq_,module,exports){module.exports=isFunction
var toString=Object.prototype.toString
function isFunction(fn){var string=toString.call(fn)
return string==='[object Function]'||(typeof fn==='function'&&string!=='[object RegExp]')||(typeof window!=='undefined'&&(fn===window.setTimeout||fn===window.alert||fn===window.confirm||fn===window.prompt))};},{}],22:[function(_dereq_,module,exports){'use strict';module.exports=function(x){var type=typeof x;return x!==null&&(type==='object'||type==='function');};},{}],23:[function(_dereq_,module,exports){var wordWrap=_dereq_('word-wrapper')
var xtend=_dereq_('xtend')
var number=_dereq_('as-number')
var X_HEIGHTS=['x','e','a','o','n','s','r','c','u','m','v','w','z']
var M_WIDTHS=['m','w']
var CAP_HEIGHTS=['H','I','N','E','F','K','L','T','U','V','W','X','Y','Z']
var TAB_ID='\t'.charCodeAt(0)
var SPACE_ID=' '.charCodeAt(0)
var ALIGN_LEFT=0,ALIGN_CENTER=1,ALIGN_RIGHT=2
module.exports=function createLayout(opt){return new TextLayout(opt)}
function TextLayout(opt){this.glyphs=[]
this._measure=this.computeMetrics.bind(this)
this.update(opt)}
TextLayout.prototype.update=function(opt){opt=xtend({measure:this._measure},opt)
this._opt=opt
this._opt.tabSize=number(this._opt.tabSize,4)
if(!opt.font)
throw new Error('must provide a valid bitmap font')
var glyphs=this.glyphs
var text=opt.text||''
var font=opt.font
this._setupSpaceGlyphs(font)
var lines=wordWrap.lines(text,opt)
var minWidth=opt.width||0
glyphs.length=0
var maxLineWidth=lines.reduce(function(prev,line){return Math.max(prev,line.width,minWidth)},0)
var x=0
var y=0
var lineHeight=number(opt.lineHeight,font.common.lineHeight)
var baseline=font.common.base
var descender=lineHeight-baseline
var letterSpacing=opt.letterSpacing||0
var height=lineHeight*lines.length- descender
var align=getAlignType(this._opt.align)
y-=height
this._width=maxLineWidth
this._height=height
this._descender=lineHeight- baseline
this._baseline=baseline
this._xHeight=getXHeight(font)
this._capHeight=getCapHeight(font)
this._lineHeight=lineHeight
this._ascender=lineHeight- descender- this._xHeight
var self=this
lines.forEach(function(line,lineIndex){var start=line.start
var end=line.end
var lineWidth=line.width
var lastGlyph
for(var i=start;i=width||nextPen>=width)
break
curPen=nextPen
curWidth=nextWidth
lastGlyph=glyph}
count++}
if(lastGlyph)
curWidth+=lastGlyph.xoffset
return{start:start,end:start+ count,width:curWidth}};['width','height','descender','ascender','xHeight','baseline','capHeight','lineHeight'].forEach(addGetter)
function addGetter(name){Object.defineProperty(TextLayout.prototype,name,{get:wrapper(name),configurable:true})}
function wrapper(name){return(new Function(['return function '+name+'() {',' return this._'+name,'}'].join('\n')))()}
function getGlyphById(font,id){if(!font.chars||font.chars.length===0)
return null
var glyphIdx=findChar(font.chars,id)
if(glyphIdx>=0)
return font.chars[glyphIdx]
return null}
function getXHeight(font){for(var i=0;i=0)
return font.chars[idx].height}
return 0}
function getMGlyph(font){for(var i=0;i=0)
return font.chars[idx]}
return 0}
function getCapHeight(font){for(var i=0;i=0)
return font.chars[idx].height}
return 0}
function getKerning(font,left,right){if(!font.kernings||font.kernings.length===0)
return 0
var table=font.kernings
for(var i=0;i4&&equal(buf.slice(0,4),HEADER)}}).call(this,_dereq_("buffer").Buffer)},{"buffer":8,"buffer-equal":7}],26:[function(_dereq_,module,exports){'use strict';var getOwnPropertySymbols=Object.getOwnPropertySymbols;var hasOwnProperty=Object.prototype.hasOwnProperty;var propIsEnumerable=Object.prototype.propertyIsEnumerable;function toObject(val){if(val===null||val===undefined){throw new TypeError('Object.assign cannot be called with null or undefined');}
return Object(val);}
function shouldUseNative(){try{if(!Object.assign){return false;}
var test1=new String('abc');test1[5]='de';if(Object.getOwnPropertyNames(test1)[0]==='5'){return false;}
var test2={};for(var i=0;i<10;i++){test2['_'+ String.fromCharCode(i)]=i;}
var order2=Object.getOwnPropertyNames(test2).map(function(n){return test2[n];});if(order2.join('')!=='0123456789'){return false;}
var test3={};'abcdefghijklmnopqrst'.split('').forEach(function(letter){test3[letter]=letter;});if(Object.keys(Object.assign({},test3)).join('')!=='abcdefghijklmnopqrst'){return false;}
return true;}catch(err){return false;}}
module.exports=shouldUseNative()?Object.assign:function(target,source){var from;var to=toObject(target);var symbols;for(var s=1;s3)
throw new Error('Only supports BMFont Binary v3 (BMFont App v1.10)')
var target={kernings:[],chars:[]}
for(var b=0;b<5;b++)
i+=readBlock(target,buf,i)
return target}
function readBlock(target,buf,i){if(i>buf.length-1)
return 0
var blockID=buf.readUInt8(i++)
var blockSize=buf.readInt32LE(i)
i+=4
switch(blockID){case 1:target.info=readInfo(buf,i)
break
case 2:target.common=readCommon(buf,i)
break
case 3:target.pages=readPages(buf,i,blockSize)
break
case 4:target.chars=readChars(buf,i,blockSize)
break
case 5:target.kernings=readKernings(buf,i,blockSize)
break}
return 5+ blockSize}
function readInfo(buf,i){var info={}
info.size=buf.readInt16LE(i)
var bitField=buf.readUInt8(i+2)
info.smooth=(bitField>>7)&1
info.unicode=(bitField>>6)&1
info.italic=(bitField>>5)&1
info.bold=(bitField>>4)&1
if((bitField>>3)&1)
info.fixedHeight=1
info.charset=buf.readUInt8(i+3)||''
info.stretchH=buf.readUInt16LE(i+4)
info.aa=buf.readUInt8(i+6)
info.padding=[buf.readInt8(i+7),buf.readInt8(i+8),buf.readInt8(i+9),buf.readInt8(i+10)]
info.spacing=[buf.readInt8(i+11),buf.readInt8(i+12)]
info.outline=buf.readUInt8(i+13)
info.face=readStringNT(buf,i+14)
return info}
function readCommon(buf,i){var common={}
common.lineHeight=buf.readUInt16LE(i)
common.base=buf.readUInt16LE(i+2)
common.scaleW=buf.readUInt16LE(i+4)
common.scaleH=buf.readUInt16LE(i+6)
common.pages=buf.readUInt16LE(i+8)
var bitField=buf.readUInt8(i+10)
common.packed=0
common.alphaChnl=buf.readUInt8(i+11)
common.redChnl=buf.readUInt8(i+12)
common.greenChnl=buf.readUInt8(i+13)
common.blueChnl=buf.readUInt8(i+14)
return common}
function readPages(buf,i,size){var pages=[]
var text=readNameNT(buf,i)
var len=text.length+1
var count=size/len
for(var c=0;c element')
var pages=pageRoot.getElementsByTagName('page')
for(var i=0;i=0;i--){var last=parts[i];if(last==='.'){parts.splice(i,1);}else if(last==='..'){parts.splice(i,1);up++;}else if(up){parts.splice(i,1);up--;}}
if(allowAboveRoot){for(;up--;up){parts.unshift('..');}}
return parts;}
var splitPathRe=/^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/;var splitPath=function(filename){return splitPathRe.exec(filename).slice(1);};exports.resolve=function(){var resolvedPath='',resolvedAbsolute=false;for(var i=arguments.length- 1;i>=-1&&!resolvedAbsolute;i--){var path=(i>=0)?arguments[i]:process.cwd();if(typeof path!=='string'){throw new TypeError('Arguments to path.resolve must be strings');}else if(!path){continue;}
resolvedPath=path+'/'+ resolvedPath;resolvedAbsolute=path.charAt(0)==='/';}
resolvedPath=normalizeArray(filter(resolvedPath.split('/'),function(p){return!!p;}),!resolvedAbsolute).join('/');return((resolvedAbsolute?'/':'')+ resolvedPath)||'.';};exports.normalize=function(path){var isAbsolute=exports.isAbsolute(path),trailingSlash=substr(path,-1)==='/';path=normalizeArray(filter(path.split('/'),function(p){return!!p;}),!isAbsolute).join('/');if(!path&&!isAbsolute){path='.';}
if(path&&trailingSlash){path+='/';}
return(isAbsolute?'/':'')+ path;};exports.isAbsolute=function(path){return path.charAt(0)==='/';};exports.join=function(){var paths=Array.prototype.slice.call(arguments,0);return exports.normalize(filter(paths,function(p,index){if(typeof p!=='string'){throw new TypeError('Arguments to path.join must be strings');}
return p;}).join('/'));};exports.relative=function(from,to){from=exports.resolve(from).substr(1);to=exports.resolve(to).substr(1);function trim(arr){var start=0;for(;start=0;end--){if(arr[end]!=='')break;}
if(start>end)return[];return arr.slice(start,end- start+ 1);}
var fromParts=trim(from.split('/'));var toParts=trim(to.split('/'));var length=Math.min(fromParts.length,toParts.length);var samePartsLength=length;for(var i=0;i0})
this.visibleGlyphs=glyphs
var positions=vertices.positions(glyphs)
var uvs=vertices.uvs(glyphs,texWidth,texHeight,flipY)
var indices=createIndices({clockwise:true,type:'uint16',count:glyphs.length})
buffer.index(this,indices,1,'uint16')
buffer.attr(this,'position',positions,2)
buffer.attr(this,'uv',uvs,2)
if(!opt.multipage&&'page'in this.attributes){this.removeAttribute('page')}else if(opt.multipage){var pages=vertices.pages(glyphs)
buffer.attr(this,'page',pages,1)}}
TextGeometry.prototype.computeBoundingSphere=function(){if(this.boundingSphere===null){this.boundingSphere=new THREE.Sphere()}
var positions=this.attributes.position.array
var itemSize=this.attributes.position.itemSize
if(!positions||!itemSize||positions.length<2){this.boundingSphere.radius=0
this.boundingSphere.center.set(0,0,0)
return}
utils.computeSphere(positions,this.boundingSphere)
if(isNaN(this.boundingSphere.radius)){console.error('THREE.BufferGeometry.computeBoundingSphere(): '+'Computed radius is NaN. The '+'"position" attribute is likely to have NaN values.')}}
TextGeometry.prototype.computeBoundingBox=function(){if(this.boundingBox===null){this.boundingBox=new THREE.Box3()}
var bbox=this.boundingBox
var positions=this.attributes.position.array
var itemSize=this.attributes.position.itemSize
if(!positions||!itemSize||positions.length<2){bbox.makeEmpty()
return}
utils.computeBox(positions,bbox)}},{"./lib/utils":38,"./lib/vertices":39,"inherits":19,"layout-bmfont-text":23,"object-assign":26,"quad-indices":35,"three-buffer-vertex-data":40}],38:[function(_dereq_,module,exports){var itemSize=2
var box={min:[0,0],max:[0,0]}
function bounds(positions){var count=positions.length/itemSize
box.min[0]=positions[0]
box.min[1]=positions[1]
box.max[0]=positions[0]
box.max[1]=positions[1]
for(var i=0;i0)?1:+ x;};}
if(Function.prototype.name===undefined){Object.defineProperty(Function.prototype,'name',{get:function(){return this.toString().match(/^\s*function\s*([^\(\s]*)/)[1];}});}
if(Object.assign===undefined){(function(){Object.assign=function(target){'use strict';if(target===undefined||target===null){throw new TypeError('Cannot convert undefined or null to object');}
var output=Object(target);for(var index=1;index>4;uuid[i]=chars[(i===19)?(r&0x3)|0x8:r];}}
return uuid.join('');};}(),clamp:function(value,min,max){return Math.max(min,Math.min(max,value));},euclideanModulo:function(n,m){return((n%m)+ m)%m;},mapLinear:function(x,a1,a2,b1,b2){return b1+(x- a1)*(b2- b1)/ ( a2 - a1 );
},lerp:function(x,y,t){return(1- t)*x+ t*y;},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);},randInt:function(low,high){return low+ Math.floor(Math.random()*(high- low+ 1));},randFloat:function(low,high){return low+ Math.random()*(high- low);},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;},nearestPowerOfTwo:function(value){return Math.pow(2,Math.round(Math.log(value)/ Math.LN2 ) );
},nextPowerOfTwo:function(value){value--;value|=value>>1;value|=value>>2;value|=value>>4;value|=value>>8;value|=value>>16;value++;return value;}};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);},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){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);},lengthManhattan:function(){return Math.abs(this.x)+ Math.abs(this.y);},normalize:function(){return this.divideScalar(this.length()||1);},angle:function(){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;},distanceToManhattan: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;}});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.generateMipmaps=true;this.premultiplyAlpha=false;this.flipY=true;this.unpackAlignment=4;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.generateMipmaps=source.generateMipmaps;this.premultiplyAlpha=source.premultiplyAlpha;this.flipY=source.flipY;this.unpackAlignment=source.unpackAlignment;this.encoding=source.encoding;return this;},toJSON:function(meta){if(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],wrap:[this.wrapS,this.wrapT],minFilter:this.minFilter,magFilter:this.magFilter,anisotropy:this.anisotropy,flipY:this.flipY};if(this.image!==undefined){var image=this.image;if(image.uuid===undefined){image.uuid=_Math.generateUUID();}
if(meta.images[image.uuid]===undefined){meta.images[image.uuid]={uuid:image.uuid,url:getDataURL(image)};}
output.image=image.uuid;}
meta.textures[this.uuid]=output;return output;},dispose:function(){this.dispatchEvent({type:'dispose'});},transformUv:function(uv){if(this.mapping!==UVMapping)return;uv.multiply(this.repeat);uv.add(this.offset);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;}}});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){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){var angle,x,y,z,epsilon=0.01,epsilon2=0.1,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)yy)&&(xx>zz)){if(xxzz){if(yy=0?1:- 1),sqrSin=1- cos*cos;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;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;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){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){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(){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(rMath.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){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;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(){}});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;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;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){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){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;},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);},lengthManhattan: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);}
var x=this.x,y=this.y,z=this.z;this.x=y*v.z- z*v.y;this.y=z*v.x- x*v.z;this.z=x*v.y- y*v.x;return this;},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(){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() ) );
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;},distanceToManhattan: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;}});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;}
te[3]=0;te[7]=0;te[11]=0;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);te[3]=0;te[7]=0;te[11]=0;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){z.z=1;}
z.normalize();x.crossVectors(up,z);if(x.lengthSq()===0){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;i0)return array;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;}
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;}
function setValue1f(gl,v){gl.uniform1f(this.addr,v);}
function setValue1i(gl,v){gl.uniform1i(this.addr,v);}
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);}
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);}}
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);}
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);}
function getSingularSetter(type){switch(type){case 0x1406:return setValue1f;case 0x8b50:return setValue2fv;case 0x8b51:return setValue3fv;case 0x8b52:return setValue4fv;case 0x8b5a:return setValue2fm;case 0x8b5b:return setValue3fm;case 0x8b5c:return setValue4fm;case 0x8b5e:case 0x8d66:return setValueT1;case 0x8b60:return setValueT6;case 0x1404:case 0x8b56:return setValue1i;case 0x8b53:case 0x8b57:return setValue2iv;case 0x8b54:case 0x8b58:return setValue3iv;case 0x8b55:case 0x8b59:return setValue4iv;}}
function setValue1fv(gl,v){gl.uniform1fv(this.addr,v);}
function setValue1iv(gl,v){gl.uniform1iv(this.addr,v);}
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));}
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));}
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]);}}
function getPureArraySetter(type){switch(type){case 0x1406:return setValue1fv;case 0x8b50:return setValueV2a;case 0x8b51:return setValueV3a;case 0x8b52:return setValueV4a;case 0x8b5a:return setValueM2a;case 0x8b5b:return setValueM3a;case 0x8b5c:return setValueM4a;case 0x8b5e:return setValueT1a;case 0x8b60:return setValueT6a;case 0x1404:case 0x8b56:return setValue1iv;case 0x8b53:case 0x8b57:return setValue2iv;case 0x8b54:case 0x8b58:return setValue3iv;case 0x8b55:case 0x8b59:return setValue4iv;}}
function SingleUniform(id,activeInfo,addr){this.id=id;this.addr=addr;this.setValue=getSingularSetter(activeInfo.type);}
function PureArrayUniform(id,activeInfo,addr){this.id=id;this.addr=addr;this.size=activeInfo.size;this.setValue=getPureArraySetter(activeInfo.type);}
function StructuredUniform(id){this.id=id;UniformContainer.call(this);}
StructuredUniform.prototype.setValue=function(gl,value){var seq=this.seq;for(var i=0,n=seq.length;i!==n;++ i){var u=seq[i];u.setValue(gl,value[u.id]);}};var RePathPart=/([\w\d_]+)(\])?(\[|\.)?/g;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;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;if(subscript===undefined||subscript==='['&&matchEnd+ 2===pathLength){addUniform(container,subscript===undefined?new SingleUniform(id,activeInfo,addr):new PureArrayUniform(id,activeInfo,addr));break;}else{var map=container.map,next=map[id];if(next===undefined){next=new StructuredUniform(id);addUniform(container,next);}
container=next;}}}
function WebGLUniforms(gl,program,renderer){UniformContainer.call(this);this.renderer=renderer;var n=gl.getProgramParameter(program,gl.ACTIVE_UNIFORMS);for(var i=0;i>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=_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)){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)){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)){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)){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)){var hex=m[1];var size=hex.length;if(size===3){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){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){var hex=ColorKeywords[style];if(hex!==undefined){this.setHex(hex);}else{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){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();}});var UniformsLib={common:{diffuse:{value:new Color(0xeeeeee)},opacity:{value:1.0},map:{value:null},offsetRepeat:{value:new Vector4(0,0,1,1)},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:{}}},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},offsetRepeat:{value:new Vector4(0,0,1,1)}}};var UniformsUtils={merge:function(uniforms){var merged={};for(var u=0;uthis.max.x||point.ythis.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){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){return box.max.xthis.max.x||box.max.ythis.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);}});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' ),
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 );
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;',
'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 finalPosition;',
'finalPosition = modelViewMatrix * vec4( 0.0, 0.0, 0.0, 1.0 );',
'finalPosition.xy += rotatedPosition;',
'finalPosition = projectionMatrix * finalPosition;',
'gl_Position = finalPosition;',
'}'
].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;',
'void main() {',
'vec4 texture = texture2D( map, vUV );',
'if ( texture.a < alphaTest ) discard;',
'gl_FragColor = vec4( color * texture.xyz, texture.a * opacity );',
'if ( fogType > 0 ) {',
'float depth = gl_FragCoord.z / gl_FragCoord.w;',
'float fogFactor = 0.0;',
'if ( fogType == 1 ) {',
'fogFactor = smoothstep( fogNear, fogFar, depth );',
'} else {',
'const float LOG2 = 1.442695;',
'fogFactor = exp2( - fogDensity * fogDensity * depth * depth * LOG2 );',
'fogFactor = 1.0 - clamp( fogFactor, 0.0, 1.0 );',
'}',
'gl_FragColor = mix( gl_FragColor, vec4( fogColor, gl_FragColor.w ), 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;
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.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;
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 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 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 v1 = new Vector3();
return function expandByObject( object ) {
var scope = this;
object.updateMatrixWorld( true );
object.traverse( function ( node ) {
var i, l;
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 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 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;
},
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 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;
};
}(),
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();
return function lookAt( vector ) {
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 '' when called from JSON.stringify
var isRootObject = ( meta === undefined || meta === '' );
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 ) {
this.view = {
fullWidth: fullWidth,
fullHeight: fullHeight,
offsetX: x,
offsetY: y,
width: width,
height: height
};
this.updateProjectionMatrix();
},
clearViewOffset: function() {
this.view = null;
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 ) {
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 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;
this.view = {
fullWidth: fullWidth,
fullHeight: fullHeight,
offsetX: x,
offsetY: y,
width: width,
height: height
};
this.updateProjectionMatrix();
},
clearViewOffset: function () {
this.view = null;
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 ( view !== null ) {
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/
* @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 count = 0;
function GeometryIdCount() { return count++; }
function Geometry() {
Object.defineProperty( this, 'id', { value: GeometryIdCount() } );
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;
},
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 ) {
BufferAttribute.call( this, new Int8Array( array ), itemSize );
}
Int8BufferAttribute.prototype = Object.create( BufferAttribute.prototype );
Int8BufferAttribute.prototype.constructor = Int8BufferAttribute;
function Uint8BufferAttribute( array, itemSize ) {
BufferAttribute.call( this, new Uint8Array( array ), itemSize );
}
Uint8BufferAttribute.prototype = Object.create( BufferAttribute.prototype );
Uint8BufferAttribute.prototype.constructor = Uint8BufferAttribute;
function Uint8ClampedBufferAttribute( array, itemSize ) {
BufferAttribute.call( this, new Uint8ClampedArray( array ), itemSize );
}
Uint8ClampedBufferAttribute.prototype = Object.create( BufferAttribute.prototype );
Uint8ClampedBufferAttribute.prototype.constructor = Uint8ClampedBufferAttribute;
function Int16BufferAttribute( array, itemSize ) {
BufferAttribute.call( this, new Int16Array( array ), itemSize );
}
Int16BufferAttribute.prototype = Object.create( BufferAttribute.prototype );
Int16BufferAttribute.prototype.constructor = Int16BufferAttribute;
function Uint16BufferAttribute( array, itemSize ) {
BufferAttribute.call( this, new Uint16Array( array ), itemSize );
}
Uint16BufferAttribute.prototype = Object.create( BufferAttribute.prototype );
Uint16BufferAttribute.prototype.constructor = Uint16BufferAttribute;
function Int32BufferAttribute( array, itemSize ) {
BufferAttribute.call( this, new Int32Array( array ), itemSize );
}
Int32BufferAttribute.prototype = Object.create( BufferAttribute.prototype );
Int32BufferAttribute.prototype.constructor = Int32BufferAttribute;
function Uint32BufferAttribute( array, itemSize ) {
BufferAttribute.call( this, new Uint32Array( array ), itemSize );
}
Uint32BufferAttribute.prototype = Object.create( BufferAttribute.prototype );
Uint32BufferAttribute.prototype.constructor = Uint32BufferAttribute;
function Float32BufferAttribute( array, itemSize ) {
BufferAttribute.call( this, new Float32Array( array ), itemSize );
}
Float32BufferAttribute.prototype = Object.create( BufferAttribute.prototype );
Float32BufferAttribute.prototype.constructor = Float32BufferAttribute;
function Float64BufferAttribute( array, itemSize ) {
BufferAttribute.call( this, new Float64Array( array ), itemSize );
}
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/
*/
function BufferGeometry() {
Object.defineProperty( this, 'id', { value: GeometryIdCount() } );
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 };
}
BufferGeometry.MaxIndex = 65535;
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;
},
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;
// 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
};
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 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;
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 {
// 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,
polygonOffset: true,
fog: false
} )
);
boxMesh.geometry.removeAttribute( 'normal' );
boxMesh.geometry.removeAttribute( 'uv' );
boxMesh.onBeforeRender = function ( renderer, scene, camera ) {
var scale = camera.far;
this.matrixWorld.makeScale( scale, scale, scale );
this.matrixWorld.copyPosition( camera.matrixWorld );
this.material.polygonOffsetUnits = scale * 10;
};
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, identifiers ) {
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,
'\n'
].filter( filterEmptyLine ).join( '\n' );
prefixFragment = [
customExtensions,
customDefines,
'\n'
].filter( filterEmptyLine ).join( '\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 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 ) {
var canvas = document.createElementNS( 'http://www.w3.org/1999/xhtml', 'canvas' );
canvas.width = _Math.nearestPowerOfTwo( image.width );
canvas.height = _Math.nearestPowerOfTwo( 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 && !! extensions.get( 'EXT_frag_depth' );
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/
*/
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 ( '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 );
}
}
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() {
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 }
}
// import { Sphere } from '../math/Sphere';
/**
* @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( 'ANGLE_instanced_arrays' );
if ( extensions.get( 'OES_element_index_uint' ) ) {
BufferGeometry.MaxIndex = 4294967296;
}
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 = background.getClearColor;
this.setClearColor = background.setClearColor;
this.getClearAlpha = background.getClearAlpha;
this.setClearAlpha = background.setClearAlpha;
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;
( vr.getDevice() || window ).requestAnimationFrame( loop );
isAnimating = true;
}
function loop( time ) {
if ( onAnimationFrame !== null ) onAnimationFrame( time );
( vr.getDevice() || 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 ) 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.nextPowerOfTwo( Math.ceil( 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;
}
var offset = uvScaleMap.offset;
var repeat = uvScaleMap.repeat;
uniforms.offsetRepeat.value.set( offset.x, offset.y, repeat.x, repeat.y );
}
}
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 ) {
var offset = material.map.offset;
var repeat = material.map.repeat;
uniforms.offsetRepeat.value.set( offset.x, offset.y, repeat.x, repeat.y );
}
}
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.lengthManhattan();
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.lengthManhattan();
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() {
requestAnimationFrame( update );
if ( video.readyState >= video.HAVE_CURRENT_DATA ) {
scope.needsUpdate = true;
}
}
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 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
var P = path.getPointAt( i / tubularSegments );
// 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 || 100;
tube = tube || 40;
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 || 100;
tube = tube || 40;
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:
* Allows to compute the original undistorted radius from a distorted one.
* See also getApproximateInverseDistortion() for a faster but potentially
* less accurate method.
*
* @param {Number} radius Distorted radius from the lens center in tan-angle units.
* @return {Number} The undistorted radius in tan-angle units.
*/
Distortion.prototype.distortInverse = function(radius) {
// Secant method.
var r0 = 0;
var r1 = 1;
var dr0 = radius - this.distort(r0);
while (Math.abs(r1 - r0) > 0.0001 /** 0.1mm */) {
var dr1 = radius - this.distort(r1);
var r2 = r1 - dr1 * ((r1 - r0) / (dr1 - dr0));
r0 = r1;
r1 = r2;
dr0 = dr1;
}
return r1;
};
/**
* Distorts a radius by its distortion factor from the center of the lenses.
*
* @param {Number} radius Radius from the lens center in tan-angle units.
* @return {Number} The distorted radius in tan-angle units.
*/
Distortion.prototype.distort = function(radius) {
var r2 = radius * radius;
var ret = 0;
for (var i = 0; i < this.coefficients.length; i++) {
ret = r2 * (ret + this.coefficients[i]);
}
return (ret + 1) * radius;
};
module.exports = Distortion;
},{}],56:[function(_dereq_,module,exports){
module.exports={
"format": 1,
"last_updated": "2017-08-27T14:39:31Z",
"devices": [
{
"type": "android",
"rules": [
{
"mdmh": "asus/*/Nexus 7/*"
},
{
"ua": "Nexus 7"
}
],
"dpi": [
320.8,
323
],
"bw": 3,
"ac": 500
},
{
"type": "android",
"rules": [
{
"mdmh": "asus/*/ASUS_Z00AD/*"
},
{
"ua": "ASUS_Z00AD"
}
],
"dpi": [
403,
404.6
],
"bw": 3,
"ac": 1000
},
{
"type": "android",
"rules": [
{
"mdmh": "Google/*/Pixel XL/*"
},
{
"ua": "Pixel XL"
}
],
"dpi": [
537.9,
533
],
"bw": 3,
"ac": 1000
},
{
"type": "android",
"rules": [
{
"mdmh": "Google/*/Pixel/*"
},
{
"ua": "Pixel"
}
],
"dpi": [
432.6,
436.7
],
"bw": 3,
"ac": 1000
},
{
"type": "android",
"rules": [
{
"mdmh": "HTC/*/HTC6435LVW/*"
},
{
"ua": "HTC6435LVW"
}
],
"dpi": [
449.7,
443.3
],
"bw": 3,
"ac": 1000
},
{
"type": "android",
"rules": [
{
"mdmh": "HTC/*/HTC One XL/*"
},
{
"ua": "HTC One XL"
}
],
"dpi": [
315.3,
314.6
],
"bw": 3,
"ac": 1000
},
{
"type": "android",
"rules": [
{
"mdmh": "htc/*/Nexus 9/*"
},
{
"ua": "Nexus 9"
}
],
"dpi": 289,
"bw": 3,
"ac": 500
},
{
"type": "android",
"rules": [
{
"mdmh": "HTC/*/HTC One M9/*"
},
{
"ua": "HTC One M9"
}
],
"dpi": [
442.5,
443.3
],
"bw": 3,
"ac": 500
},
{
"type": "android",
"rules": [
{
"mdmh": "HTC/*/HTC One_M8/*"
},
{
"ua": "HTC One_M8"
}
],
"dpi": [
449.7,
447.4
],
"bw": 3,
"ac": 500
},
{
"type": "android",
"rules": [
{
"mdmh": "HTC/*/HTC One/*"
},
{
"ua": "HTC One"
}
],
"dpi": 472.8,
"bw": 3,
"ac": 1000
},
{
"type": "android",
"rules": [
{
"mdmh": "Huawei/*/Nexus 6P/*"
},
{
"ua": "Nexus 6P"
}
],
"dpi": [
515.1,
518
],
"bw": 3,
"ac": 1000
},
{
"type": "android",
"rules": [
{
"mdmh": "LENOVO/*/Lenovo PB2-690Y/*"
},
{
"ua": "Lenovo PB2-690Y"
}
],
"dpi": [
457.2,
454.713
],
"bw": 3,
"ac": 500
},
{
"type": "android",
"rules": [
{
"mdmh": "LGE/*/Nexus 5X/*"
},
{
"ua": "Nexus 5X"
}
],
"dpi": [
422,
419.9
],
"bw": 3,
"ac": 1000
},
{
"type": "android",
"rules": [
{
"mdmh": "LGE/*/LGMS345/*"
},
{
"ua": "LGMS345"
}
],
"dpi": [
221.7,
219.1
],
"bw": 3,
"ac": 500
},
{
"type": "android",
"rules": [
{
"mdmh": "LGE/*/LG-D800/*"
},
{
"ua": "LG-D800"
}
],
"dpi": [
422,
424.1
],
"bw": 3,
"ac": 500
},
{
"type": "android",
"rules": [
{
"mdmh": "LGE/*/LG-D850/*"
},
{
"ua": "LG-D850"
}
],
"dpi": [
537.9,
541.9
],
"bw": 3,
"ac": 500
},
{
"type": "android",
"rules": [
{
"mdmh": "LGE/*/VS985 4G/*"
},
{
"ua": "VS985 4G"
}
],
"dpi": [
537.9,
535.6
],
"bw": 3,
"ac": 1000
},
{
"type": "android",
"rules": [
{
"mdmh": "LGE/*/Nexus 5/*"
},
{
"ua": "Nexus 5 B"
}
],
"dpi": [
442.4,
444.8
],
"bw": 3,
"ac": 1000
},
{
"type": "android",
"rules": [
{
"mdmh": "LGE/*/Nexus 4/*"
},
{
"ua": "Nexus 4"
}
],
"dpi": [
319.8,
318.4
],
"bw": 3,
"ac": 1000
},
{
"type": "android",
"rules": [
{
"mdmh": "LGE/*/LG-P769/*"
},
{
"ua": "LG-P769"
}
],
"dpi": [
240.6,
247.5
],
"bw": 3,
"ac": 1000
},
{
"type": "android",
"rules": [
{
"mdmh": "LGE/*/LGMS323/*"
},
{
"ua": "LGMS323"
}
],
"dpi": [
206.6,
204.6
],
"bw": 3,
"ac": 1000
},
{
"type": "android",
"rules": [
{
"mdmh": "LGE/*/LGLS996/*"
},
{
"ua": "LGLS996"
}
],
"dpi": [
403.4,
401.5
],
"bw": 3,
"ac": 1000
},
{
"type": "android",
"rules": [
{
"mdmh": "Micromax/*/4560MMX/*"
},
{
"ua": "4560MMX"
}
],
"dpi": [
240,
219.4
],
"bw": 3,
"ac": 1000
},
{
"type": "android",
"rules": [
{
"mdmh": "Micromax/*/A250/*"
},
{
"ua": "Micromax A250"
}
],
"dpi": [
480,
446.4
],
"bw": 3,
"ac": 1000
},
{
"type": "android",
"rules": [
{
"mdmh": "Micromax/*/Micromax AQ4501/*"
},
{
"ua": "Micromax AQ4501"
}
],
"dpi": 240,
"bw": 3,
"ac": 500
},
{
"type": "android",
"rules": [
{
"mdmh": "motorola/*/DROID RAZR/*"
},
{
"ua": "DROID RAZR"
}
],
"dpi": [
368.1,
256.7
],
"bw": 3,
"ac": 1000
},
{
"type": "android",
"rules": [
{
"mdmh": "motorola/*/XT830C/*"
},
{
"ua": "XT830C"
}
],
"dpi": [
254,
255.9
],
"bw": 3,
"ac": 1000
},
{
"type": "android",
"rules": [
{
"mdmh": "motorola/*/XT1021/*"
},
{
"ua": "XT1021"
}
],
"dpi": [
254,
256.7
],
"bw": 3,
"ac": 500
},
{
"type": "android",
"rules": [
{
"mdmh": "motorola/*/XT1023/*"
},
{
"ua": "XT1023"
}
],
"dpi": [
254,
256.7
],
"bw": 3,
"ac": 500
},
{
"type": "android",
"rules": [
{
"mdmh": "motorola/*/XT1028/*"
},
{
"ua": "XT1028"
}
],
"dpi": [
326.6,
327.6
],
"bw": 3,
"ac": 1000
},
{
"type": "android",
"rules": [
{
"mdmh": "motorola/*/XT1034/*"
},
{
"ua": "XT1034"
}
],
"dpi": [
326.6,
328.4
],
"bw": 3,
"ac": 500
},
{
"type": "android",
"rules": [
{
"mdmh": "motorola/*/XT1053/*"
},
{
"ua": "XT1053"
}
],
"dpi": [
315.3,
316.1
],
"bw": 3,
"ac": 1000
},
{
"type": "android",
"rules": [
{
"mdmh": "motorola/*/XT1562/*"
},
{
"ua": "XT1562"
}
],
"dpi": [
403.4,
402.7
],
"bw": 3,
"ac": 1000
},
{
"type": "android",
"rules": [
{
"mdmh": "motorola/*/Nexus 6/*"
},
{
"ua": "Nexus 6 B"
}
],
"dpi": [
494.3,
489.7
],
"bw": 3,
"ac": 1000
},
{
"type": "android",
"rules": [
{
"mdmh": "motorola/*/XT1063/*"
},
{
"ua": "XT1063"
}
],
"dpi": [
295,
296.6
],
"bw": 3,
"ac": 1000
},
{
"type": "android",
"rules": [
{
"mdmh": "motorola/*/XT1064/*"
},
{
"ua": "XT1064"
}
],
"dpi": [
295,
295.6
],
"bw": 3,
"ac": 500
},
{
"type": "android",
"rules": [
{
"mdmh": "motorola/*/XT1092/*"
},
{
"ua": "XT1092"
}
],
"dpi": [
422,
424.1
],
"bw": 3,
"ac": 500
},
{
"type": "android",
"rules": [
{
"mdmh": "motorola/*/XT1095/*"
},
{
"ua": "XT1095"
}
],
"dpi": [
422,
423.4
],
"bw": 3,
"ac": 1000
},
{
"type": "android",
"rules": [
{
"mdmh": "motorola/*/G4/*"
},
{
"ua": "Moto G (4)"
}
],
"dpi": 401,
"bw": 4,
"ac": 1000
},
{
"type": "android",
"rules": [
{
"mdmh": "OnePlus/*/A0001/*"
},
{
"ua": "A0001"
}
],
"dpi": [
403.4,
401
],
"bw": 3,
"ac": 1000
},
{
"type": "android",
"rules": [
{
"mdmh": "OnePlus/*/ONE E1005/*"
},
{
"ua": "ONE E1005"
}
],
"dpi": [
442.4,
441.4
],
"bw": 3,
"ac": 1000
},
{
"type": "android",
"rules": [
{
"mdmh": "OnePlus/*/ONE A2005/*"
},
{
"ua": "ONE A2005"
}
],
"dpi": [
391.9,
405.4
],
"bw": 3,
"ac": 1000
},
{
"type": "android",
"rules": [
{
"mdmh": "OPPO/*/X909/*"
},
{
"ua": "X909"
}
],
"dpi": [
442.4,
444.1
],
"bw": 3,
"ac": 1000
},
{
"type": "android",
"rules": [
{
"mdmh": "samsung/*/GT-I9082/*"
},
{
"ua": "GT-I9082"
}
],
"dpi": [
184.7,
185.4
],
"bw": 3,
"ac": 1000
},
{
"type": "android",
"rules": [
{
"mdmh": "samsung/*/SM-G360P/*"
},
{
"ua": "SM-G360P"
}
],
"dpi": [
196.7,
205.4
],
"bw": 3,
"ac": 1000
},
{
"type": "android",
"rules": [
{
"mdmh": "samsung/*/Nexus S/*"
},
{
"ua": "Nexus S"
}
],
"dpi": [
234.5,
229.8
],
"bw": 3,
"ac": 1000
},
{
"type": "android",
"rules": [
{
"mdmh": "samsung/*/GT-I9300/*"
},
{
"ua": "GT-I9300"
}
],
"dpi": [
304.8,
303.9
],
"bw": 5,
"ac": 500
},
{
"type": "android",
"rules": [
{
"mdmh": "samsung/*/SM-T230NU/*"
},
{
"ua": "SM-T230NU"
}
],
"dpi": 216,
"bw": 3,
"ac": 500
},
{
"type": "android",
"rules": [
{
"mdmh": "samsung/*/SGH-T399/*"
},
{
"ua": "SGH-T399"
}
],
"dpi": [
217.7,
231.4
],
"bw": 3,
"ac": 1000
},
{
"type": "android",
"rules": [
{
"mdmh": "samsung/*/SGH-M919/*"
},
{
"ua": "SGH-M919"
}
],
"dpi": [
440.8,
437.7
],
"bw": 3,
"ac": 1000
},
{
"type": "android",
"rules": [
{
"mdmh": "samsung/*/SM-N9005/*"
},
{
"ua": "SM-N9005"
}
],
"dpi": [
386.4,
387
],
"bw": 3,
"ac": 500
},
{
"type": "android",
"rules": [
{
"mdmh": "samsung/*/SAMSUNG-SM-N900A/*"
},
{
"ua": "SAMSUNG-SM-N900A"
}
],
"dpi": [
386.4,
387.7
],
"bw": 3,
"ac": 1000
},
{
"type": "android",
"rules": [
{
"mdmh": "samsung/*/GT-I9500/*"
},
{
"ua": "GT-I9500"
}
],
"dpi": [
442.5,
443.3
],
"bw": 3,
"ac": 500
},
{
"type": "android",
"rules": [
{
"mdmh": "samsung/*/GT-I9505/*"
},
{
"ua": "GT-I9505"
}
],
"dpi": 439.4,
"bw": 4,
"ac": 1000
},
{
"type": "android",
"rules": [
{
"mdmh": "samsung/*/SM-G900F/*"
},
{
"ua": "SM-G900F"
}
],
"dpi": [
415.6,
431.6
],
"bw": 5,
"ac": 1000
},
{
"type": "android",
"rules": [
{
"mdmh": "samsung/*/SM-G900M/*"
},
{
"ua": "SM-G900M"
}
],
"dpi": [
415.6,
431.6
],
"bw": 5,
"ac": 1000
},
{
"type": "android",
"rules": [
{
"mdmh": "samsung/*/SM-G800F/*"
},
{
"ua": "SM-G800F"
}
],
"dpi": 326.8,
"bw": 3,
"ac": 1000
},
{
"type": "android",
"rules": [
{
"mdmh": "samsung/*/SM-G906S/*"
},
{
"ua": "SM-G906S"
}
],
"dpi": [
562.7,
572.4
],
"bw": 3,
"ac": 1000
},
{
"type": "android",
"rules": [
{
"mdmh": "samsung/*/GT-I9300/*"
},
{
"ua": "GT-I9300"
}
],
"dpi": [
306.7,
304.8
],
"bw": 5,
"ac": 1000
},
{
"type": "android",
"rules": [
{
"mdmh": "samsung/*/SM-T535/*"
},
{
"ua": "SM-T535"
}
],
"dpi": [
142.6,
136.4
],
"bw": 3,
"ac": 500
},
{
"type": "android",
"rules": [
{
"mdmh": "samsung/*/SM-N920C/*"
},
{
"ua": "SM-N920C"
}
],
"dpi": [
515.1,
518.4
],
"bw": 3,
"ac": 1000
},
{
"type": "android",
"rules": [
{
"mdmh": "samsung/*/SM-N920W8/*"
},
{
"ua": "SM-N920W8"
}
],
"dpi": [
515.1,
518.4
],
"bw": 3,
"ac": 1000
},
{
"type": "android",
"rules": [
{
"mdmh": "samsung/*/GT-I9300I/*"
},
{
"ua": "GT-I9300I"
}
],
"dpi": [
304.8,
305.8
],
"bw": 3,
"ac": 1000
},
{
"type": "android",
"rules": [
{
"mdmh": "samsung/*/GT-I9195/*"
},
{
"ua": "GT-I9195"
}
],
"dpi": [
249.4,
256.7
],
"bw": 3,
"ac": 500
},
{
"type": "android",
"rules": [
{
"mdmh": "samsung/*/SPH-L520/*"
},
{
"ua": "SPH-L520"
}
],
"dpi": [
249.4,
255.9
],
"bw": 3,
"ac": 1000
},
{
"type": "android",
"rules": [
{
"mdmh": "samsung/*/SAMSUNG-SGH-I717/*"
},
{
"ua": "SAMSUNG-SGH-I717"
}
],
"dpi": 285.8,
"bw": 3,
"ac": 1000
},
{
"type": "android",
"rules": [
{
"mdmh": "samsung/*/SPH-D710/*"
},
{
"ua": "SPH-D710"
}
],
"dpi": [
217.7,
204.2
],
"bw": 3,
"ac": 1000
},
{
"type": "android",
"rules": [
{
"mdmh": "samsung/*/GT-N7100/*"
},
{
"ua": "GT-N7100"
}
],
"dpi": 265.1,
"bw": 3,
"ac": 1000
},
{
"type": "android",
"rules": [
{
"mdmh": "samsung/*/SCH-I605/*"
},
{
"ua": "SCH-I605"
}
],
"dpi": 265.1,
"bw": 3,
"ac": 1000
},
{
"type": "android",
"rules": [
{
"mdmh": "samsung/*/Galaxy Nexus/*"
},
{
"ua": "Galaxy Nexus"
}
],
"dpi": [
315.3,
314.2
],
"bw": 3,
"ac": 1000
},
{
"type": "android",
"rules": [
{
"mdmh": "samsung/*/SM-N910H/*"
},
{
"ua": "SM-N910H"
}
],
"dpi": [
515.1,
518
],
"bw": 3,
"ac": 1000
},
{
"type": "android",
"rules": [
{
"mdmh": "samsung/*/SM-N910C/*"
},
{
"ua": "SM-N910C"
}
],
"dpi": [
515.2,
520.2
],
"bw": 3,
"ac": 500
},
{
"type": "android",
"rules": [
{
"mdmh": "samsung/*/SM-G130M/*"
},
{
"ua": "SM-G130M"
}
],
"dpi": [
165.9,
164.8
],
"bw": 3,
"ac": 500
},
{
"type": "android",
"rules": [
{
"mdmh": "samsung/*/SM-G928I/*"
},
{
"ua": "SM-G928I"
}
],
"dpi": [
515.1,
518.4
],
"bw": 3,
"ac": 1000
},
{
"type": "android",
"rules": [
{
"mdmh": "samsung/*/SM-G920F/*"
},
{
"ua": "SM-G920F"
}
],
"dpi": 580.6,
"bw": 3,
"ac": 500
},
{
"type": "android",
"rules": [
{
"mdmh": "samsung/*/SM-G920P/*"
},
{
"ua": "SM-G920P"
}
],
"dpi": [
522.5,
577
],
"bw": 3,
"ac": 1000
},
{
"type": "android",
"rules": [
{
"mdmh": "samsung/*/SM-G925F/*"
},
{
"ua": "SM-G925F"
}
],
"dpi": 580.6,
"bw": 3,
"ac": 500
},
{
"type": "android",
"rules": [
{
"mdmh": "samsung/*/SM-G925V/*"
},
{
"ua": "SM-G925V"
}
],
"dpi": [
522.5,
576.6
],
"bw": 3,
"ac": 1000
},
{
"type": "android",
"rules": [
{
"mdmh": "samsung/*/SM-G930F/*"
},
{
"ua": "SM-G930F"
}
],
"dpi": 576.6,
"bw": 3,
"ac": 1000
},
{
"type": "android",
"rules": [
{
"mdmh": "samsung/*/SM-G935F/*"
},
{
"ua": "SM-G935F"
}
],
"dpi": 533,
"bw": 3,
"ac": 500
},
{
"type": "android",
"rules": [
{
"mdmh": "samsung/*/SM-G950F/*"
},
{
"ua": "SM-G950F"
}
],
"dpi": [
562.707,
565.293
],
"bw": 3,
"ac": 500
},
{
"type": "android",
"rules": [
{
"mdmh": "samsung/*/SM-G955U/*"
},
{
"ua": "SM-G955U"
}
],
"dpi": [
522.514,
525.762
],
"bw": 3,
"ac": 500
},
{
"type": "android",
"rules": [
{
"mdmh": "Sony/*/C6903/*"
},
{
"ua": "C6903"
}
],
"dpi": [
442.5,
443.3
],
"bw": 3,
"ac": 500
},
{
"type": "android",
"rules": [
{
"mdmh": "Sony/*/D6653/*"
},
{
"ua": "D6653"
}
],
"dpi": [
428.6,
427.6
],
"bw": 3,
"ac": 1000
},
{
"type": "android",
"rules": [
{
"mdmh": "Sony/*/E6653/*"
},
{
"ua": "E6653"
}
],
"dpi": [
428.6,
425.7
],
"bw": 3,
"ac": 1000
},
{
"type": "android",
"rules": [
{
"mdmh": "Sony/*/E6853/*"
},
{
"ua": "E6853"
}
],
"dpi": [
403.4,
401.9
],
"bw": 3,
"ac": 1000
},
{
"type": "android",
"rules": [
{
"mdmh": "Sony/*/SGP321/*"
},
{
"ua": "SGP321"
}
],
"dpi": [
224.7,
224.1
],
"bw": 3,
"ac": 500
},
{
"type": "android",
"rules": [
{
"mdmh": "TCT/*/ALCATEL ONE TOUCH Fierce/*"
},
{
"ua": "ALCATEL ONE TOUCH Fierce"
}
],
"dpi": [
240,
247.5
],
"bw": 3,
"ac": 1000
},
{
"type": "android",
"rules": [
{
"mdmh": "THL/*/thl 5000/*"
},
{
"ua": "thl 5000"
}
],
"dpi": [
480,
443.3
],
"bw": 3,
"ac": 1000
},
{
"type": "android",
"rules": [
{
"mdmh": "ZTE/*/ZTE Blade L2/*"
},
{
"ua": "ZTE Blade L2"
}
],
"dpi": 240,
"bw": 3,
"ac": 500
},
{
"type": "ios",
"rules": [
{
"res": [
640,
960
]
}
],
"dpi": [
325.1,
328.4
],
"bw": 4,
"ac": 1000
},
{
"type": "ios",
"rules": [
{
"res": [
640,
1136
]
}
],
"dpi": [
317.1,
320.2
],
"bw": 3,
"ac": 1000
},
{
"type": "ios",
"rules": [
{
"res": [
750,
1334
]
}
],
"dpi": 326.4,
"bw": 4,
"ac": 1000
},
{
"type": "ios",
"rules": [
{
"res": [
1242,
2208
]
}
],
"dpi": [
453.6,
458.4
],
"bw": 4,
"ac": 1000
},
{
"type": "ios",
"rules": [
{
"res": [
1125,
2001
]
}
],
"dpi": [
410.9,
415.4
],
"bw": 4,
"ac": 1000
}
]
}
},{}],57:[function(_dereq_,module,exports){
/*
* Copyright 2015 Google Inc. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Offline cache of the DPDB, to be used until we load the online one (and
// as a fallback in case we can't load the online one).
var DPDB_CACHE = _dereq_('./dpdb.json');
var Util = _dereq_('../util.js');
// Online DPDB URL.
var ONLINE_DPDB_URL =
'https://dpdb.webvr.rocks/dpdb.json';
/**
* Calculates device parameters based on the DPDB (Device Parameter Database).
* Initially, uses the cached DPDB values.
*
* If fetchOnline == true, then this object tries to fetch the online version
* of the DPDB and updates the device info if a better match is found.
* Calls the onDeviceParamsUpdated callback when there is an update to the
* device information.
*/
function Dpdb(fetchOnline, onDeviceParamsUpdated) {
// Start with the offline DPDB cache while we are loading the real one.
this.dpdb = DPDB_CACHE;
// Calculate device params based on the offline version of the DPDB.
this.recalculateDeviceParams_();
// XHR to fetch online DPDB file, if requested.
if (fetchOnline) {
// Set the callback.
this.onDeviceParamsUpdated = onDeviceParamsUpdated;
var xhr = new XMLHttpRequest();
var obj = this;
xhr.open('GET', ONLINE_DPDB_URL, true);
xhr.addEventListener('load', function() {
obj.loading = false;
if (xhr.status >= 200 && xhr.status <= 299) {
// Success.
obj.dpdb = JSON.parse(xhr.response);
obj.recalculateDeviceParams_();
} else {
// Error loading the DPDB.
console.error('Error loading online DPDB!');
}
});
xhr.send();
}
}
// Returns the current device parameters.
Dpdb.prototype.getDeviceParams = function() {
return this.deviceParams;
};
// Recalculates this device's parameters based on the DPDB.
Dpdb.prototype.recalculateDeviceParams_ = function() {
var newDeviceParams = this.calcDeviceParams_();
if (newDeviceParams) {
this.deviceParams = newDeviceParams;
// Invoke callback, if it is set.
if (this.onDeviceParamsUpdated) {
this.onDeviceParamsUpdated(this.deviceParams);
}
} else {
console.error('Failed to recalculate device parameters.');
}
};
// Returns a DeviceParams object that represents the best guess as to this
// device's parameters. Can return null if the device does not match any
// known devices.
Dpdb.prototype.calcDeviceParams_ = function() {
var db = this.dpdb; // shorthand
if (!db) {
console.error('DPDB not available.');
return null;
}
if (db.format != 1) {
console.error('DPDB has unexpected format version.');
return null;
}
if (!db.devices || !db.devices.length) {
console.error('DPDB does not have a devices section.');
return null;
}
// Get the actual user agent and screen dimensions in pixels.
var userAgent = navigator.userAgent || navigator.vendor || window.opera;
var width = Util.getScreenWidth();
var height = Util.getScreenHeight();
if (!db.devices) {
console.error('DPDB has no devices section.');
return null;
}
for (var i = 0; i < db.devices.length; i++) {
var device = db.devices[i];
if (!device.rules) {
console.warn('Device[' + i + '] has no rules section.');
continue;
}
if (device.type != 'ios' && device.type != 'android') {
console.warn('Device[' + i + '] has invalid type.');
continue;
}
// See if this device is of the appropriate type.
if (Util.isIOS() != (device.type == 'ios')) continue;
// See if this device matches any of the rules:
var matched = false;
for (var j = 0; j < device.rules.length; j++) {
var rule = device.rules[j];
if (this.matchRule_(rule, userAgent, width, height)) {
matched = true;
break;
}
}
if (!matched) continue;
// device.dpi might be an array of [ xdpi, ydpi] or just a scalar.
var xdpi = device.dpi[0] || device.dpi;
var ydpi = device.dpi[1] || device.dpi;
return new DeviceParams({ xdpi: xdpi, ydpi: ydpi, bevelMm: device.bw });
}
console.warn('No DPDB device match.');
return null;
};
Dpdb.prototype.matchRule_ = function(rule, ua, screenWidth, screenHeight) {
// We can only match 'ua' and 'res' rules, not other types like 'mdmh'
// (which are meant for native platforms).
if (!rule.ua && !rule.res) return false;
// If our user agent string doesn't contain the indicated user agent string,
// the match fails.
if (rule.ua && ua.indexOf(rule.ua) < 0) return false;
// If the rule specifies screen dimensions that don't correspond to ours,
// the match fails.
if (rule.res) {
if (!rule.res[0] || !rule.res[1]) return false;
var resX = rule.res[0];
var resY = rule.res[1];
// Compare min and max so as to make the order not matter, i.e., it should
// be true that 640x480 == 480x640.
if (Math.min(screenWidth, screenHeight) != Math.min(resX, resY) ||
(Math.max(screenWidth, screenHeight) != Math.max(resX, resY))) {
return false;
}
}
return true;
}
function DeviceParams(params) {
this.xdpi = params.xdpi;
this.ydpi = params.ydpi;
this.bevelMm = params.bevelMm;
}
module.exports = Dpdb;
},{"../util.js":68,"./dpdb.json":56}],58:[function(_dereq_,module,exports){
/*
* Copyright 2015 Google Inc. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var Util = _dereq_('./util.js');
var WebVRPolyfill = _dereq_('./webvr-polyfill.js').WebVRPolyfill;
// Initialize a WebVRConfig just in case.
window.WebVRConfig = Util.extend({
// Forces availability of VR mode, even for non-mobile devices.
FORCE_ENABLE_VR: false,
// Complementary filter coefficient. 0 for accelerometer, 1 for gyro.
K_FILTER: 0.98,
// How far into the future to predict during fast motion (in seconds).
PREDICTION_TIME_S: 0.040,
// Flag to enable touch panner. In case you have your own touch controls.
TOUCH_PANNER_DISABLED: true,
// Flag to disabled the UI in VR Mode.
CARDBOARD_UI_DISABLED: false, // Default: false
// Flag to disable the instructions to rotate your device.
ROTATE_INSTRUCTIONS_DISABLED: false, // Default: false.
// Enable yaw panning only, disabling roll and pitch. This can be useful
// for panoramas with nothing interesting above or below.
YAW_ONLY: false,
// To disable keyboard and mouse controls, if you want to use your own
// implementation.
MOUSE_KEYBOARD_CONTROLS_DISABLED: false,
// Prevent the polyfill from initializing immediately. Requires the app
// to call InitializeWebVRPolyfill() before it can be used.
DEFER_INITIALIZATION: false,
// Enable the deprecated version of the API (navigator.getVRDevices).
ENABLE_DEPRECATED_API: false,
// Scales the recommended buffer size reported by WebVR, which can improve
// performance.
// UPDATE(2016-05-03): Setting this to 0.5 by default since 1.0 does not
// perform well on many mobile devices.
BUFFER_SCALE: 0.5,
// Allow VRDisplay.submitFrame to change gl bindings, which is more
// efficient if the application code will re-bind its resources on the
// next frame anyway. This has been seen to cause rendering glitches with
// THREE.js.
// Dirty bindings include: gl.FRAMEBUFFER_BINDING, gl.CURRENT_PROGRAM,
// gl.ARRAY_BUFFER_BINDING, gl.ELEMENT_ARRAY_BUFFER_BINDING,
// and gl.TEXTURE_BINDING_2D for texture unit 0.
DIRTY_SUBMIT_FRAME_BINDINGS: false,
// When set to true, this will cause a polyfilled VRDisplay to always be
// appended to the list returned by navigator.getVRDisplays(), even if that
// list includes a native VRDisplay.
ALWAYS_APPEND_POLYFILL_DISPLAY: false,
// There are versions of Chrome (M58-M60?) where the native WebVR API exists,
// and instead of returning 0 VR displays when none are detected,
// `navigator.getVRDisplays()`'s promise never resolves. This results
// in the polyfill hanging and not being able to provide fallback
// displays, so set a timeout in milliseconds to stop waiting for a response
// and just use polyfilled displays.
// https://bugs.chromium.org/p/chromium/issues/detail?id=727969
GET_VR_DISPLAYS_TIMEOUT: 1000,
}, window.WebVRConfig);
if (!window.WebVRConfig.DEFER_INITIALIZATION) {
new WebVRPolyfill();
} else {
window.InitializeWebVRPolyfill = function() {
new WebVRPolyfill();
}
}
window.WebVRPolyfill = WebVRPolyfill;
},{"./util.js":68,"./webvr-polyfill.js":71}],59:[function(_dereq_,module,exports){
/*
* Copyright 2016 Google Inc. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var MathUtil = window.MathUtil || {};
MathUtil.degToRad = Math.PI / 180;
MathUtil.radToDeg = 180 / Math.PI;
// Some minimal math functionality borrowed from THREE.Math and stripped down
// for the purposes of this library.
MathUtil.Vector2 = function ( x, y ) {
this.x = x || 0;
this.y = y || 0;
};
MathUtil.Vector2.prototype = {
constructor: MathUtil.Vector2,
set: function ( x, y ) {
this.x = x;
this.y = y;
return this;
},
copy: function ( v ) {
this.x = v.x;
this.y = v.y;
return this;
},
subVectors: function ( a, b ) {
this.x = a.x - b.x;
this.y = a.y - b.y;
return this;
},
};
MathUtil.Vector3 = function ( x, y, z ) {
this.x = x || 0;
this.y = y || 0;
this.z = z || 0;
};
MathUtil.Vector3.prototype = {
constructor: MathUtil.Vector3,
set: function ( x, y, z ) {
this.x = x;
this.y = y;
this.z = z;
return this;
},
copy: function ( v ) {
this.x = v.x;
this.y = v.y;
this.z = v.z;
return this;
},
length: function () {
return Math.sqrt( this.x * this.x + this.y * this.y + this.z * this.z );
},
normalize: function () {
var scalar = this.length();
if ( scalar !== 0 ) {
var invScalar = 1 / scalar;
this.multiplyScalar(invScalar);
} else {
this.x = 0;
this.y = 0;
this.z = 0;
}
return this;
},
multiplyScalar: function ( scalar ) {
this.x *= scalar;
this.y *= scalar;
this.z *= scalar;
},
applyQuaternion: function ( q ) {
var x = this.x;
var y = this.y;
var z = this.z;
var qx = q.x;
var qy = q.y;
var qz = q.z;
var 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;
},
dot: function ( v ) {
return this.x * v.x + this.y * v.y + this.z * v.z;
},
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;
},
};
MathUtil.Quaternion = function ( x, y, z, w ) {
this.x = x || 0;
this.y = y || 0;
this.z = z || 0;
this.w = ( w !== undefined ) ? w : 1;
};
MathUtil.Quaternion.prototype = {
constructor: MathUtil.Quaternion,
set: function ( x, y, z, w ) {
this.x = x;
this.y = y;
this.z = z;
this.w = w;
return this;
},
copy: function ( quaternion ) {
this.x = quaternion.x;
this.y = quaternion.y;
this.z = quaternion.z;
this.w = quaternion.w;
return this;
},
setFromEulerXYZ: function( x, y, z ) {
var c1 = Math.cos( x / 2 );
var c2 = Math.cos( y / 2 );
var c3 = Math.cos( z / 2 );
var s1 = Math.sin( x / 2 );
var s2 = Math.sin( y / 2 );
var s3 = Math.sin( z / 2 );
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;
return this;
},
setFromEulerYXZ: function( x, y, z ) {
var c1 = Math.cos( x / 2 );
var c2 = Math.cos( y / 2 );
var c3 = Math.cos( z / 2 );
var s1 = Math.sin( x / 2 );
var s2 = Math.sin( y / 2 );
var s3 = Math.sin( z / 2 );
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;
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 );
return this;
},
multiply: function ( q ) {
return this.multiplyQuaternions( this, q );
},
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;
return this;
},
inverse: function () {
this.x *= -1;
this.y *= -1;
this.z *= -1;
this.normalize();
return this;
},
normalize: function () {
var l = Math.sqrt( this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w );
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;
}
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 halfTheta = Math.acos( cosHalfTheta );
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 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 );
return this;
},
setFromUnitVectors: function () {
// http://lolengine.net/blog/2014/02/24/quaternion-from-two-vectors-final
// assumes direction vectors vFrom and vTo are normalized
var v1, r;
var EPS = 0.000001;
return function ( vFrom, vTo ) {
if ( v1 === undefined ) v1 = new MathUtil.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;
this.normalize();
return this;
}
}(),
};
module.exports = MathUtil;
},{}],60:[function(_dereq_,module,exports){
/*
* Copyright 2016 Google Inc. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var VRDisplay = _dereq_('./base.js').VRDisplay;
var MathUtil = _dereq_('./math-util.js');
var Util = _dereq_('./util.js');
// How much to rotate per key stroke.
var KEY_SPEED = 0.15;
var KEY_ANIMATION_DURATION = 80;
// How much to rotate for mouse events.
var MOUSE_SPEED_X = 0.5;
var MOUSE_SPEED_Y = 0.3;
/**
* VRDisplay based on mouse and keyboard input. Designed for desktops/laptops
* where orientation events aren't supported. Cannot present.
*/
function MouseKeyboardVRDisplay() {
this.displayName = 'Mouse and Keyboard VRDisplay (webvr-polyfill)';
this.capabilities.hasOrientation = true;
// Attach to mouse and keyboard events.
window.addEventListener('keydown', this.onKeyDown_.bind(this));
window.addEventListener('mousemove', this.onMouseMove_.bind(this));
window.addEventListener('mousedown', this.onMouseDown_.bind(this));
window.addEventListener('mouseup', this.onMouseUp_.bind(this));
// "Private" members.
this.phi_ = 0;
this.theta_ = 0;
// Variables for keyboard-based rotation animation.
this.targetAngle_ = null;
this.angleAnimation_ = null;
// State variables for calculations.
this.orientation_ = new MathUtil.Quaternion();
// Variables for mouse-based rotation.
this.rotateStart_ = new MathUtil.Vector2();
this.rotateEnd_ = new MathUtil.Vector2();
this.rotateDelta_ = new MathUtil.Vector2();
this.isDragging_ = false;
this.orientationOut_ = new Float32Array(4);
}
MouseKeyboardVRDisplay.prototype = new VRDisplay();
MouseKeyboardVRDisplay.prototype.getImmediatePose = function() {
this.orientation_.setFromEulerYXZ(this.phi_, this.theta_, 0);
this.orientationOut_[0] = this.orientation_.x;
this.orientationOut_[1] = this.orientation_.y;
this.orientationOut_[2] = this.orientation_.z;
this.orientationOut_[3] = this.orientation_.w;
return {
position: null,
orientation: this.orientationOut_,
linearVelocity: null,
linearAcceleration: null,
angularVelocity: null,
angularAcceleration: null
};
};
MouseKeyboardVRDisplay.prototype.onKeyDown_ = function(e) {
// Track WASD and arrow keys.
if (e.keyCode == 38) { // Up key.
this.animatePhi_(this.phi_ + KEY_SPEED);
} else if (e.keyCode == 39) { // Right key.
this.animateTheta_(this.theta_ - KEY_SPEED);
} else if (e.keyCode == 40) { // Down key.
this.animatePhi_(this.phi_ - KEY_SPEED);
} else if (e.keyCode == 37) { // Left key.
this.animateTheta_(this.theta_ + KEY_SPEED);
}
};
MouseKeyboardVRDisplay.prototype.animateTheta_ = function(targetAngle) {
this.animateKeyTransitions_('theta_', targetAngle);
};
MouseKeyboardVRDisplay.prototype.animatePhi_ = function(targetAngle) {
// Prevent looking too far up or down.
targetAngle = Util.clamp(targetAngle, -Math.PI/2, Math.PI/2);
this.animateKeyTransitions_('phi_', targetAngle);
};
/**
* Start an animation to transition an angle from one value to another.
*/
MouseKeyboardVRDisplay.prototype.animateKeyTransitions_ = function(angleName, targetAngle) {
// If an animation is currently running, cancel it.
if (this.angleAnimation_) {
cancelAnimationFrame(this.angleAnimation_);
}
var startAngle = this[angleName];
var startTime = new Date();
// Set up an interval timer to perform the animation.
this.angleAnimation_ = requestAnimationFrame(function animate() {
// Once we're finished the animation, we're done.
var elapsed = new Date() - startTime;
if (elapsed >= KEY_ANIMATION_DURATION) {
this[angleName] = targetAngle;
cancelAnimationFrame(this.angleAnimation_);
return;
}
// loop with requestAnimationFrame
this.angleAnimation_ = requestAnimationFrame(animate.bind(this))
// Linearly interpolate the angle some amount.
var percent = elapsed / KEY_ANIMATION_DURATION;
this[angleName] = startAngle + (targetAngle - startAngle) * percent;
}.bind(this));
};
MouseKeyboardVRDisplay.prototype.onMouseDown_ = function(e) {
this.rotateStart_.set(e.clientX, e.clientY);
this.isDragging_ = true;
};
// Very similar to https://gist.github.com/mrflix/8351020
MouseKeyboardVRDisplay.prototype.onMouseMove_ = function(e) {
if (!this.isDragging_ && !this.isPointerLocked_()) {
return;
}
// Support pointer lock API.
if (this.isPointerLocked_()) {
var movementX = e.movementX || e.mozMovementX || 0;
var movementY = e.movementY || e.mozMovementY || 0;
this.rotateEnd_.set(this.rotateStart_.x - movementX, this.rotateStart_.y - movementY);
} else {
this.rotateEnd_.set(e.clientX, e.clientY);
}
// Calculate how much we moved in mouse space.
this.rotateDelta_.subVectors(this.rotateEnd_, this.rotateStart_);
this.rotateStart_.copy(this.rotateEnd_);
// Keep track of the cumulative euler angles.
this.phi_ += 2 * Math.PI * this.rotateDelta_.y / screen.height * MOUSE_SPEED_Y;
this.theta_ += 2 * Math.PI * this.rotateDelta_.x / screen.width * MOUSE_SPEED_X;
// Prevent looking too far up or down.
this.phi_ = Util.clamp(this.phi_, -Math.PI/2, Math.PI/2);
};
MouseKeyboardVRDisplay.prototype.onMouseUp_ = function(e) {
this.isDragging_ = false;
};
MouseKeyboardVRDisplay.prototype.isPointerLocked_ = function() {
var el = document.pointerLockElement || document.mozPointerLockElement ||
document.webkitPointerLockElement;
return el !== undefined;
};
MouseKeyboardVRDisplay.prototype.resetPose = function() {
this.phi_ = 0;
this.theta_ = 0;
};
module.exports = MouseKeyboardVRDisplay;
},{"./base.js":48,"./math-util.js":59,"./util.js":68}],61:[function(_dereq_,module,exports){
(function (global){
// This is the entry point if requiring/importing via node, or
// a build tool that uses package.json entry (like browserify, webpack).
// If running in node with a window mock available, globalize its members
// if needed. Otherwise, just continue to `./main`
if (typeof global !== 'undefined' && global.window) {
global.document = global.window.document;
global.navigator = global.window.navigator;
}
_dereq_('./main');
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
},{"./main":58}],62:[function(_dereq_,module,exports){
/*
* Copyright 2015 Google Inc. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var Util = _dereq_('./util.js');
function RotateInstructions() {
this.loadIcon_();
var overlay = document.createElement('div');
var s = overlay.style;
s.position = 'fixed';
s.top = 0;
s.right = 0;
s.bottom = 0;
s.left = 0;
s.backgroundColor = 'gray';
s.fontFamily = 'sans-serif';
// Force this to be above the fullscreen canvas, which is at zIndex: 999999.
s.zIndex = 1000000;
var img = document.createElement('img');
img.src = this.icon;
var s = img.style;
s.marginLeft = '25%';
s.marginTop = '25%';
s.width = '50%';
overlay.appendChild(img);
var text = document.createElement('div');
var s = text.style;
s.textAlign = 'center';
s.fontSize = '16px';
s.lineHeight = '24px';
s.margin = '24px 25%';
s.width = '50%';
text.innerHTML = 'Place your phone into your Cardboard viewer.';
overlay.appendChild(text);
var snackbar = document.createElement('div');
var s = snackbar.style;
s.backgroundColor = '#CFD8DC';
s.position = 'fixed';
s.bottom = 0;
s.width = '100%';
s.height = '48px';
s.padding = '14px 24px';
s.boxSizing = 'border-box';
s.color = '#656A6B';
overlay.appendChild(snackbar);
var snackbarText = document.createElement('div');
snackbarText.style.float = 'left';
snackbarText.innerHTML = 'No Cardboard viewer?';
var snackbarButton = document.createElement('a');
snackbarButton.href = 'https://www.google.com/get/cardboard/get-cardboard/';
snackbarButton.innerHTML = 'get one';
snackbarButton.target = '_blank';
var s = snackbarButton.style;
s.float = 'right';
s.fontWeight = 600;
s.textTransform = 'uppercase';
s.borderLeft = '1px solid gray';
s.paddingLeft = '24px';
s.textDecoration = 'none';
s.color = '#656A6B';
snackbar.appendChild(snackbarText);
snackbar.appendChild(snackbarButton);
this.overlay = overlay;
this.text = text;
this.hide();
}
RotateInstructions.prototype.show = function(parent) {
if (!parent && !this.overlay.parentElement) {
document.body.appendChild(this.overlay);
} else if (parent) {
if (this.overlay.parentElement && this.overlay.parentElement != parent)
this.overlay.parentElement.removeChild(this.overlay);
parent.appendChild(this.overlay);
}
this.overlay.style.display = 'block';
var img = this.overlay.querySelector('img');
var s = img.style;
if (Util.isLandscapeMode()) {
s.width = '20%';
s.marginLeft = '40%';
s.marginTop = '3%';
} else {
s.width = '50%';
s.marginLeft = '25%';
s.marginTop = '25%';
}
};
RotateInstructions.prototype.hide = function() {
this.overlay.style.display = 'none';
};
RotateInstructions.prototype.showTemporarily = function(ms, parent) {
this.show(parent);
this.timer = setTimeout(this.hide.bind(this), ms);
};
RotateInstructions.prototype.disableShowTemporarily = function() {
clearTimeout(this.timer);
};
RotateInstructions.prototype.update = function() {
this.disableShowTemporarily();
// In portrait VR mode, tell the user to rotate to landscape. Otherwise, hide
// the instructions.
if (!Util.isLandscapeMode() && Util.isMobile()) {
this.show();
} else {
this.hide();
}
};
RotateInstructions.prototype.loadIcon_ = function() {
// Encoded asset_src/rotate-instructions.svg
this.icon = Util.base64('image/svg+xml', 'PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+Cjxzdmcgd2lkdGg9IjE5OHB4IiBoZWlnaHQ9IjI0MHB4IiB2aWV3Qm94PSIwIDAgMTk4IDI0MCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4bWxuczpza2V0Y2g9Imh0dHA6Ly93d3cuYm9oZW1pYW5jb2RpbmcuY29tL3NrZXRjaC9ucyI+CiAgICA8IS0tIEdlbmVyYXRvcjogU2tldGNoIDMuMy4zICgxMjA4MSkgLSBodHRwOi8vd3d3LmJvaGVtaWFuY29kaW5nLmNvbS9za2V0Y2ggLS0+CiAgICA8dGl0bGU+dHJhbnNpdGlvbjwvdGl0bGU+CiAgICA8ZGVzYz5DcmVhdGVkIHdpdGggU2tldGNoLjwvZGVzYz4KICAgIDxkZWZzPjwvZGVmcz4KICAgIDxnIGlkPSJQYWdlLTEiIHN0cm9rZT0ibm9uZSIgc3Ryb2tlLXdpZHRoPSIxIiBmaWxsPSJub25lIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIHNrZXRjaDp0eXBlPSJNU1BhZ2UiPgogICAgICAgIDxnIGlkPSJ0cmFuc2l0aW9uIiBza2V0Y2g6dHlwZT0iTVNBcnRib2FyZEdyb3VwIj4KICAgICAgICAgICAgPGcgaWQ9IkltcG9ydGVkLUxheWVycy1Db3B5LTQtKy1JbXBvcnRlZC1MYXllcnMtQ29weS0rLUltcG9ydGVkLUxheWVycy1Db3B5LTItQ29weSIgc2tldGNoOnR5cGU9Ik1TTGF5ZXJHcm91cCI+CiAgICAgICAgICAgICAgICA8ZyBpZD0iSW1wb3J0ZWQtTGF5ZXJzLUNvcHktNCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMC4wMDAwMDAsIDEwNy4wMDAwMDApIiBza2V0Y2g6dHlwZT0iTVNTaGFwZUdyb3VwIj4KICAgICAgICAgICAgICAgICAgICA8cGF0aCBkPSJNMTQ5LjYyNSwyLjUyNyBDMTQ5LjYyNSwyLjUyNyAxNTUuODA1LDYuMDk2IDE1Ni4zNjIsNi40MTggTDE1Ni4zNjIsNy4zMDQgQzE1Ni4zNjIsNy40ODEgMTU2LjM3NSw3LjY2NCAxNTYuNCw3Ljg1MyBDMTU2LjQxLDcuOTM0IDE1Ni40Miw4LjAxNSAxNTYuNDI3LDguMDk1IEMxNTYuNTY3LDkuNTEgMTU3LjQwMSwxMS4wOTMgMTU4LjUzMiwxMi4wOTQgTDE2NC4yNTIsMTcuMTU2IEwxNjQuMzMzLDE3LjA2NiBDMTY0LjMzMywxNy4wNjYgMTY4LjcxNSwxNC41MzYgMTY5LjU2OCwxNC4wNDIgQzE3MS4wMjUsMTQuODgzIDE5NS41MzgsMjkuMDM1IDE5NS41MzgsMjkuMDM1IEwxOTUuNTM4LDgzLjAzNiBDMTk1LjUzOCw4My44MDcgMTk1LjE1Miw4NC4yNTMgMTk0LjU5LDg0LjI1MyBDMTk0LjM1Nyw4NC4yNTMgMTk0LjA5NSw4NC4xNzcgMTkzLjgxOCw4NC4wMTcgTDE2OS44NTEsNzAuMTc5IEwxNjkuODM3LDcwLjIwMyBMMTQyLjUxNSw4NS45NzggTDE0MS42NjUsODQuNjU1IEMxMzYuOTM0LDgzLjEyNiAxMzEuOTE3LDgxLjkxNSAxMjYuNzE0LDgxLjA0NSBDMTI2LjcwOSw4MS4wNiAxMjYuNzA3LDgxLjA2OSAxMjYuNzA3LDgxLjA2OSBMMTIxLjY0LDk4LjAzIEwxMTMuNzQ5LDEwMi41ODYgTDExMy43MTIsMTAyLjUyMyBMMTEzLjcxMiwxMzAuMTEzIEMxMTMuNzEyLDEzMC44ODUgMTEzLjMyNiwxMzEuMzMgMTEyLjc2NCwxMzEuMzMgQzExMi41MzIsMTMxLjMzIDExMi4yNjksMTMxLjI1NCAxMTEuOTkyLDEzMS4wOTQgTDY5LjUxOSwxMDYuNTcyIEM2OC41NjksMTA2LjAyMyA2Ny43OTksMTA0LjY5NSA2Ny43OTksMTAzLjYwNSBMNjcuNzk5LDEwMi41NyBMNjcuNzc4LDEwMi42MTcgQzY3LjI3LDEwMi4zOTMgNjYuNjQ4LDEwMi4yNDkgNjUuOTYyLDEwMi4yMTggQzY1Ljg3NSwxMDIuMjE0IDY1Ljc4OCwxMDIuMjEyIDY1LjcwMSwxMDIuMjEyIEM2NS42MDYsMTAyLjIxMiA2NS41MTEsMTAyLjIxNSA2NS40MTYsMTAyLjIxOSBDNjUuMTk1LDEwMi4yMjkgNjQuOTc0LDEwMi4yMzUgNjQuNzU0LDEwMi4yMzUgQzY0LjMzMSwxMDIuMjM1IDYzLjkxMSwxMDIuMjE2IDYzLjQ5OCwxMDIuMTc4IEM2MS44NDMsMTAyLjAyNSA2MC4yOTgsMTAxLjU3OCA1OS4wOTQsMTAwLjg4MiBMMTIuNTE4LDczLjk5MiBMMTIuNTIzLDc0LjAwNCBMMi4yNDUsNTUuMjU0IEMxLjI0NCw1My40MjcgMi4wMDQsNTEuMDM4IDMuOTQzLDQ5LjkxOCBMNTkuOTU0LDE3LjU3MyBDNjAuNjI2LDE3LjE4NSA2MS4zNSwxNy4wMDEgNjIuMDUzLDE3LjAwMSBDNjMuMzc5LDE3LjAwMSA2NC42MjUsMTcuNjYgNjUuMjgsMTguODU0IEw2NS4yODUsMTguODUxIEw2NS41MTIsMTkuMjY0IEw2NS41MDYsMTkuMjY4IEM2NS45MDksMjAuMDAzIDY2LjQwNSwyMC42OCA2Ni45ODMsMjEuMjg2IEw2Ny4yNiwyMS41NTYgQzY5LjE3NCwyMy40MDYgNzEuNzI4LDI0LjM1NyA3NC4zNzMsMjQuMzU3IEM3Ni4zMjIsMjQuMzU3IDc4LjMyMSwyMy44NCA4MC4xNDgsMjIuNzg1IEM4MC4xNjEsMjIuNzg1IDg3LjQ2NywxOC41NjYgODcuNDY3LDE4LjU2NiBDODguMTM5LDE4LjE3OCA4OC44NjMsMTcuOTk0IDg5LjU2NiwxNy45OTQgQzkwLjg5MiwxNy45OTQgOTIuMTM4LDE4LjY1MiA5Mi43OTIsMTkuODQ3IEw5Ni4wNDIsMjUuNzc1IEw5Ni4wNjQsMjUuNzU3IEwxMDIuODQ5LDI5LjY3NCBMMTAyLjc0NCwyOS40OTIgTDE0OS42MjUsMi41MjcgTTE0OS42MjUsMC44OTIgQzE0OS4zNDMsMC44OTIgMTQ5LjA2MiwwLjk2NSAxNDguODEsMS4xMSBMMTAyLjY0MSwyNy42NjYgTDk3LjIzMSwyNC41NDIgTDk0LjIyNiwxOS4wNjEgQzkzLjMxMywxNy4zOTQgOTEuNTI3LDE2LjM1OSA4OS41NjYsMTYuMzU4IEM4OC41NTUsMTYuMzU4IDg3LjU0NiwxNi42MzIgODYuNjQ5LDE3LjE1IEM4My44NzgsMTguNzUgNzkuNjg3LDIxLjE2OSA3OS4zNzQsMjEuMzQ1IEM3OS4zNTksMjEuMzUzIDc5LjM0NSwyMS4zNjEgNzkuMzMsMjEuMzY5IEM3Ny43OTgsMjIuMjU0IDc2LjA4NCwyMi43MjIgNzQuMzczLDIyLjcyMiBDNzIuMDgxLDIyLjcyMiA2OS45NTksMjEuODkgNjguMzk3LDIwLjM4IEw2OC4xNDUsMjAuMTM1IEM2Ny43MDYsMTkuNjcyIDY3LjMyMywxOS4xNTYgNjcuMDA2LDE4LjYwMSBDNjYuOTg4LDE4LjU1OSA2Ni45NjgsMTguNTE5IDY2Ljk0NiwxOC40NzkgTDY2LjcxOSwxOC4wNjUgQzY2LjY5LDE4LjAxMiA2Ni42NTgsMTcuOTYgNjYuNjI0LDE3LjkxMSBDNjUuNjg2LDE2LjMzNyA2My45NTEsMTUuMzY2IDYyLjA1MywxNS4zNjYgQzYxLjA0MiwxNS4zNjYgNjAuMDMzLDE1LjY0IDU5LjEzNiwxNi4xNTggTDMuMTI1LDQ4LjUwMiBDMC40MjYsNTAuMDYxIC0wLjYxMyw1My40NDIgMC44MTEsNTYuMDQgTDExLjA4OSw3NC43OSBDMTEuMjY2LDc1LjExMyAxMS41MzcsNzUuMzUzIDExLjg1LDc1LjQ5NCBMNTguMjc2LDEwMi4yOTggQzU5LjY3OSwxMDMuMTA4IDYxLjQzMywxMDMuNjMgNjMuMzQ4LDEwMy44MDYgQzYzLjgxMiwxMDMuODQ4IDY0LjI4NSwxMDMuODcgNjQuNzU0LDEwMy44NyBDNjUsMTAzLjg3IDY1LjI0OSwxMDMuODY0IDY1LjQ5NCwxMDMuODUyIEM2NS41NjMsMTAzLjg0OSA2NS42MzIsMTAzLjg0NyA2NS43MDEsMTAzLjg0NyBDNjUuNzY0LDEwMy44NDcgNjUuODI4LDEwMy44NDkgNjUuODksMTAzLjg1MiBDNjUuOTg2LDEwMy44NTYgNjYuMDgsMTAzLjg2MyA2Ni4xNzMsMTAzLjg3NCBDNjYuMjgyLDEwNS40NjcgNjcuMzMyLDEwNy4xOTcgNjguNzAyLDEwNy45ODggTDExMS4xNzQsMTMyLjUxIEMxMTEuNjk4LDEzMi44MTIgMTEyLjIzMiwxMzIuOTY1IDExMi43NjQsMTMyLjk2NSBDMTE0LjI2MSwxMzIuOTY1IDExNS4zNDcsMTMxLjc2NSAxMTUuMzQ3LDEzMC4xMTMgTDExNS4zNDcsMTAzLjU1MSBMMTIyLjQ1OCw5OS40NDYgQzEyMi44MTksOTkuMjM3IDEyMy4wODcsOTguODk4IDEyMy4yMDcsOTguNDk4IEwxMjcuODY1LDgyLjkwNSBDMTMyLjI3OSw4My43MDIgMTM2LjU1Nyw4NC43NTMgMTQwLjYwNyw4Ni4wMzMgTDE0MS4xNCw4Ni44NjIgQzE0MS40NTEsODcuMzQ2IDE0MS45NzcsODcuNjEzIDE0Mi41MTYsODcuNjEzIEMxNDIuNzk0LDg3LjYxMyAxNDMuMDc2LDg3LjU0MiAxNDMuMzMzLDg3LjM5MyBMMTY5Ljg2NSw3Mi4wNzYgTDE5Myw4NS40MzMgQzE5My41MjMsODUuNzM1IDE5NC4wNTgsODUuODg4IDE5NC41OSw4NS44ODggQzE5Ni4wODcsODUuODg4IDE5Ny4xNzMsODQuNjg5IDE5Ny4xNzMsODMuMDM2IEwxOTcuMTczLDI5LjAzNSBDMTk3LjE3MywyOC40NTEgMTk2Ljg2MSwyNy45MTEgMTk2LjM1NSwyNy42MTkgQzE5Ni4zNTUsMjcuNjE5IDE3MS44NDMsMTMuNDY3IDE3MC4zODUsMTIuNjI2IEMxNzAuMTMyLDEyLjQ4IDE2OS44NSwxMi40MDcgMTY5LjU2OCwxMi40MDcgQzE2OS4yODUsMTIuNDA3IDE2OS4wMDIsMTIuNDgxIDE2OC43NDksMTIuNjI3IEMxNjguMTQzLDEyLjk3OCAxNjUuNzU2LDE0LjM1NyAxNjQuNDI0LDE1LjEyNSBMMTU5LjYxNSwxMC44NyBDMTU4Ljc5NiwxMC4xNDUgMTU4LjE1NCw4LjkzNyAxNTguMDU0LDcuOTM0IEMxNTguMDQ1LDcuODM3IDE1OC4wMzQsNy43MzkgMTU4LjAyMSw3LjY0IEMxNTguMDA1LDcuNTIzIDE1Ny45OTgsNy40MSAxNTcuOTk4LDcuMzA0IEwxNTcuOTk4LDYuNDE4IEMxNTcuOTk4LDUuODM0IDE1Ny42ODYsNS4yOTUgMTU3LjE4MSw1LjAwMiBDMTU2LjYyNCw0LjY4IDE1MC40NDIsMS4xMTEgMTUwLjQ0MiwxLjExMSBDMTUwLjE4OSwwLjk2NSAxNDkuOTA3LDAuODkyIDE0OS42MjUsMC44OTIiIGlkPSJGaWxsLTEiIGZpbGw9IiM0NTVBNjQiPjwvcGF0aD4KICAgICAgICAgICAgICAgICAgICA8cGF0aCBkPSJNOTYuMDI3LDI1LjYzNiBMMTQyLjYwMyw1Mi41MjcgQzE0My44MDcsNTMuMjIyIDE0NC41ODIsNTQuMTE0IDE0NC44NDUsNTUuMDY4IEwxNDQuODM1LDU1LjA3NSBMNjMuNDYxLDEwMi4wNTcgTDYzLjQ2LDEwMi4wNTcgQzYxLjgwNiwxMDEuOTA1IDYwLjI2MSwxMDEuNDU3IDU5LjA1NywxMDAuNzYyIEwxMi40ODEsNzMuODcxIEw5Ni4wMjcsMjUuNjM2IiBpZD0iRmlsbC0yIiBmaWxsPSIjRkFGQUZBIj48L3BhdGg+CiAgICAgICAgICAgICAgICAgICAgPHBhdGggZD0iTTYzLjQ2MSwxMDIuMTc0IEM2My40NTMsMTAyLjE3NCA2My40NDYsMTAyLjE3NCA2My40MzksMTAyLjE3MiBDNjEuNzQ2LDEwMi4wMTYgNjAuMjExLDEwMS41NjMgNTguOTk4LDEwMC44NjMgTDEyLjQyMiw3My45NzMgQzEyLjM4Niw3My45NTIgMTIuMzY0LDczLjkxNCAxMi4zNjQsNzMuODcxIEMxMi4zNjQsNzMuODMgMTIuMzg2LDczLjc5MSAxMi40MjIsNzMuNzcgTDk1Ljk2OCwyNS41MzUgQzk2LjAwNCwyNS41MTQgOTYuMDQ5LDI1LjUxNCA5Ni4wODUsMjUuNTM1IEwxNDIuNjYxLDUyLjQyNiBDMTQzLjg4OCw1My4xMzQgMTQ0LjY4Miw1NC4wMzggMTQ0Ljk1Nyw1NS4wMzcgQzE0NC45Nyw1NS4wODMgMTQ0Ljk1Myw1NS4xMzMgMTQ0LjkxNSw1NS4xNjEgQzE0NC45MTEsNTUuMTY1IDE0NC44OTgsNTUuMTc0IDE0NC44OTQsNTUuMTc3IEw2My41MTksMTAyLjE1OCBDNjMuNTAxLDEwMi4xNjkgNjMuNDgxLDEwMi4xNzQgNjMuNDYxLDEwMi4xNzQgTDYzLjQ2MSwxMDIuMTc0IFogTTEyLjcxNCw3My44NzEgTDU5LjExNSwxMDAuNjYxIEM2MC4yOTMsMTAxLjM0MSA2MS43ODYsMTAxLjc4MiA2My40MzUsMTAxLjkzNyBMMTQ0LjcwNyw1NS4wMTUgQzE0NC40MjgsNTQuMTA4IDE0My42ODIsNTMuMjg1IDE0Mi41NDQsNTIuNjI4IEw5Ni4wMjcsMjUuNzcxIEwxMi43MTQsNzMuODcxIEwxMi43MTQsNzMuODcxIFoiIGlkPSJGaWxsLTMiIGZpbGw9IiM2MDdEOEIiPjwvcGF0aD4KICAgICAgICAgICAgICAgICAgICA8cGF0aCBkPSJNMTQ4LjMyNyw1OC40NzEgQzE0OC4xNDUsNTguNDggMTQ3Ljk2Miw1OC40OCAxNDcuNzgxLDU4LjQ3MiBDMTQ1Ljg4Nyw1OC4zODkgMTQ0LjQ3OSw1Ny40MzQgMTQ0LjYzNiw1Ni4zNCBDMTQ0LjY4OSw1NS45NjcgMTQ0LjY2NCw1NS41OTcgMTQ0LjU2NCw1NS4yMzUgTDYzLjQ2MSwxMDIuMDU3IEM2NC4wODksMTAyLjExNSA2NC43MzMsMTAyLjEzIDY1LjM3OSwxMDIuMDk5IEM2NS41NjEsMTAyLjA5IDY1Ljc0MywxMDIuMDkgNjUuOTI1LDEwMi4wOTggQzY3LjgxOSwxMDIuMTgxIDY5LjIyNywxMDMuMTM2IDY5LjA3LDEwNC4yMyBMMTQ4LjMyNyw1OC40NzEiIGlkPSJGaWxsLTQiIGZpbGw9IiNGRkZGRkYiPjwvcGF0aD4KICAgICAgICAgICAgICAgICAgICA8cGF0aCBkPSJNNjkuMDcsMTA0LjM0NyBDNjkuMDQ4LDEwNC4zNDcgNjkuMDI1LDEwNC4zNCA2OS4wMDUsMTA0LjMyNyBDNjguOTY4LDEwNC4zMDEgNjguOTQ4LDEwNC4yNTcgNjguOTU1LDEwNC4yMTMgQzY5LDEwMy44OTYgNjguODk4LDEwMy41NzYgNjguNjU4LDEwMy4yODggQzY4LjE1MywxMDIuNjc4IDY3LjEwMywxMDIuMjY2IDY1LjkyLDEwMi4yMTQgQzY1Ljc0MiwxMDIuMjA2IDY1LjU2MywxMDIuMjA3IDY1LjM4NSwxMDIuMjE1IEM2NC43NDIsMTAyLjI0NiA2NC4wODcsMTAyLjIzMiA2My40NSwxMDIuMTc0IEM2My4zOTksMTAyLjE2OSA2My4zNTgsMTAyLjEzMiA2My4zNDcsMTAyLjA4MiBDNjMuMzM2LDEwMi4wMzMgNjMuMzU4LDEwMS45ODEgNjMuNDAyLDEwMS45NTYgTDE0NC41MDYsNTUuMTM0IEMxNDQuNTM3LDU1LjExNiAxNDQuNTc1LDU1LjExMyAxNDQuNjA5LDU1LjEyNyBDMTQ0LjY0Miw1NS4xNDEgMTQ0LjY2OCw1NS4xNyAxNDQuNjc3LDU1LjIwNCBDMTQ0Ljc4MSw1NS41ODUgMTQ0LjgwNiw1NS45NzIgMTQ0Ljc1MSw1Ni4zNTcgQzE0NC43MDYsNTYuNjczIDE0NC44MDgsNTYuOTk0IDE0NS4wNDcsNTcuMjgyIEMxNDUuNTUzLDU3Ljg5MiAxNDYuNjAyLDU4LjMwMyAxNDcuNzg2LDU4LjM1NSBDMTQ3Ljk2NCw1OC4zNjMgMTQ4LjE0Myw1OC4zNjMgMTQ4LjMyMSw1OC4zNTQgQzE0OC4zNzcsNTguMzUyIDE0OC40MjQsNTguMzg3IDE0OC40MzksNTguNDM4IEMxNDguNDU0LDU4LjQ5IDE0OC40MzIsNTguNTQ1IDE0OC4zODUsNTguNTcyIEw2OS4xMjksMTA0LjMzMSBDNjkuMTExLDEwNC4zNDIgNjkuMDksMTA0LjM0NyA2OS4wNywxMDQuMzQ3IEw2OS4wNywxMDQuMzQ3IFogTTY1LjY2NSwxMDEuOTc1IEM2NS43NTQsMTAxLjk3NSA2NS44NDIsMTAxLjk3NyA2NS45MywxMDEuOTgxIEM2Ny4xOTYsMTAyLjAzNyA2OC4yODMsMTAyLjQ2OSA2OC44MzgsMTAzLjEzOSBDNjkuMDY1LDEwMy40MTMgNjkuMTg4LDEwMy43MTQgNjkuMTk4LDEwNC4wMjEgTDE0Ny44ODMsNTguNTkyIEMxNDcuODQ3LDU4LjU5MiAxNDcuODExLDU4LjU5MSAxNDcuNzc2LDU4LjU4OSBDMTQ2LjUwOSw1OC41MzMgMTQ1LjQyMiw1OC4xIDE0NC44NjcsNTcuNDMxIEMxNDQuNTg1LDU3LjA5MSAxNDQuNDY1LDU2LjcwNyAxNDQuNTIsNTYuMzI0IEMxNDQuNTYzLDU2LjAyMSAxNDQuNTUyLDU1LjcxNiAxNDQuNDg4LDU1LjQxNCBMNjMuODQ2LDEwMS45NyBDNjQuMzUzLDEwMi4wMDIgNjQuODY3LDEwMi4wMDYgNjUuMzc0LDEwMS45ODIgQzY1LjQ3MSwxMDEuOTc3IDY1LjU2OCwxMDEuOTc1IDY1LjY2NSwxMDEuOTc1IEw2NS42NjUsMTAxLjk3NSBaIiBpZD0iRmlsbC01IiBmaWxsPSIjNjA3RDhCIj48L3BhdGg+CiAgICAgICAgICAgICAgICAgICAgPHBhdGggZD0iTTIuMjA4LDU1LjEzNCBDMS4yMDcsNTMuMzA3IDEuOTY3LDUwLjkxNyAzLjkwNiw0OS43OTcgTDU5LjkxNywxNy40NTMgQzYxLjg1NiwxNi4zMzMgNjQuMjQxLDE2LjkwNyA2NS4yNDMsMTguNzM0IEw2NS40NzUsMTkuMTQ0IEM2NS44NzIsMTkuODgyIDY2LjM2OCwyMC41NiA2Ni45NDUsMjEuMTY1IEw2Ny4yMjMsMjEuNDM1IEM3MC41NDgsMjQuNjQ5IDc1LjgwNiwyNS4xNTEgODAuMTExLDIyLjY2NSBMODcuNDMsMTguNDQ1IEM4OS4zNywxNy4zMjYgOTEuNzU0LDE3Ljg5OSA5Mi43NTUsMTkuNzI3IEw5Ni4wMDUsMjUuNjU1IEwxMi40ODYsNzMuODg0IEwyLjIwOCw1NS4xMzQgWiIgaWQ9IkZpbGwtNiIgZmlsbD0iI0ZBRkFGQSI+PC9wYXRoPgogICAgICAgICAgICAgICAgICAgIDxwYXRoIGQ9Ik0xMi40ODYsNzQuMDAxIEMxMi40NzYsNzQuMDAxIDEyLjQ2NSw3My45OTkgMTIuNDU1LDczLjk5NiBDMTIuNDI0LDczLjk4OCAxMi4zOTksNzMuOTY3IDEyLjM4NCw3My45NCBMMi4xMDYsNTUuMTkgQzEuMDc1LDUzLjMxIDEuODU3LDUwLjg0NSAzLjg0OCw0OS42OTYgTDU5Ljg1OCwxNy4zNTIgQzYwLjUyNSwxNi45NjcgNjEuMjcxLDE2Ljc2NCA2Mi4wMTYsMTYuNzY0IEM2My40MzEsMTYuNzY0IDY0LjY2NiwxNy40NjYgNjUuMzI3LDE4LjY0NiBDNjUuMzM3LDE4LjY1NCA2NS4zNDUsMTguNjYzIDY1LjM1MSwxOC42NzQgTDY1LjU3OCwxOS4wODggQzY1LjU4NCwxOS4xIDY1LjU4OSwxOS4xMTIgNjUuNTkxLDE5LjEyNiBDNjUuOTg1LDE5LjgzOCA2Ni40NjksMjAuNDk3IDY3LjAzLDIxLjA4NSBMNjcuMzA1LDIxLjM1MSBDNjkuMTUxLDIzLjEzNyA3MS42NDksMjQuMTIgNzQuMzM2LDI0LjEyIEM3Ni4zMTMsMjQuMTIgNzguMjksMjMuNTgyIDgwLjA1MywyMi41NjMgQzgwLjA2NCwyMi41NTcgODAuMDc2LDIyLjU1MyA4MC4wODgsMjIuNTUgTDg3LjM3MiwxOC4zNDQgQzg4LjAzOCwxNy45NTkgODguNzg0LDE3Ljc1NiA4OS41MjksMTcuNzU2IEM5MC45NTYsMTcuNzU2IDkyLjIwMSwxOC40NzIgOTIuODU4LDE5LjY3IEw5Ni4xMDcsMjUuNTk5IEM5Ni4xMzgsMjUuNjU0IDk2LjExOCwyNS43MjQgOTYuMDYzLDI1Ljc1NiBMMTIuNTQ1LDczLjk4NSBDMTIuNTI2LDczLjk5NiAxMi41MDYsNzQuMDAxIDEyLjQ4Niw3NC4wMDEgTDEyLjQ4Niw3NC4wMDEgWiBNNjIuMDE2LDE2Ljk5NyBDNjEuMzEyLDE2Ljk5NyA2MC42MDYsMTcuMTkgNTkuOTc1LDE3LjU1NCBMMy45NjUsNDkuODk5IEMyLjA4Myw1MC45ODUgMS4zNDEsNTMuMzA4IDIuMzEsNTUuMDc4IEwxMi41MzEsNzMuNzIzIEw5NS44NDgsMjUuNjExIEw5Mi42NTMsMTkuNzgyIEM5Mi4wMzgsMTguNjYgOTAuODcsMTcuOTkgODkuNTI5LDE3Ljk5IEM4OC44MjUsMTcuOTkgODguMTE5LDE4LjE4MiA4Ny40ODksMTguNTQ3IEw4MC4xNzIsMjIuNzcyIEM4MC4xNjEsMjIuNzc4IDgwLjE0OSwyMi43ODIgODAuMTM3LDIyLjc4NSBDNzguMzQ2LDIzLjgxMSA3Ni4zNDEsMjQuMzU0IDc0LjMzNiwyNC4zNTQgQzcxLjU4OCwyNC4zNTQgNjkuMDMzLDIzLjM0NyA2Ny4xNDIsMjEuNTE5IEw2Ni44NjQsMjEuMjQ5IEM2Ni4yNzcsMjAuNjM0IDY1Ljc3NCwxOS45NDcgNjUuMzY3LDE5LjIwMyBDNjUuMzYsMTkuMTkyIDY1LjM1NiwxOS4xNzkgNjUuMzU0LDE5LjE2NiBMNjUuMTYzLDE4LjgxOSBDNjUuMTU0LDE4LjgxMSA2NS4xNDYsMTguODAxIDY1LjE0LDE4Ljc5IEM2NC41MjUsMTcuNjY3IDYzLjM1NywxNi45OTcgNjIuMDE2LDE2Ljk5NyBMNjIuMDE2LDE2Ljk5NyBaIiBpZD0iRmlsbC03IiBmaWxsPSIjNjA3RDhCIj48L3BhdGg+CiAgICAgICAgICAgICAgICAgICAgPHBhdGggZD0iTTQyLjQzNCw0OC44MDggTDQyLjQzNCw0OC44MDggQzM5LjkyNCw0OC44MDcgMzcuNzM3LDQ3LjU1IDM2LjU4Miw0NS40NDMgQzM0Ljc3MSw0Mi4xMzkgMzYuMTQ0LDM3LjgwOSAzOS42NDEsMzUuNzg5IEw1MS45MzIsMjguNjkxIEM1My4xMDMsMjguMDE1IDU0LjQxMywyNy42NTggNTUuNzIxLDI3LjY1OCBDNTguMjMxLDI3LjY1OCA2MC40MTgsMjguOTE2IDYxLjU3MywzMS4wMjMgQzYzLjM4NCwzNC4zMjcgNjIuMDEyLDM4LjY1NyA1OC41MTQsNDAuNjc3IEw0Ni4yMjMsNDcuNzc1IEM0NS4wNTMsNDguNDUgNDMuNzQyLDQ4LjgwOCA0Mi40MzQsNDguODA4IEw0Mi40MzQsNDguODA4IFogTTU1LjcyMSwyOC4xMjUgQzU0LjQ5NSwyOC4xMjUgNTMuMjY1LDI4LjQ2MSA1Mi4xNjYsMjkuMDk2IEwzOS44NzUsMzYuMTk0IEMzNi41OTYsMzguMDg3IDM1LjMwMiw0Mi4xMzYgMzYuOTkyLDQ1LjIxOCBDMzguMDYzLDQ3LjE3MyA0MC4wOTgsNDguMzQgNDIuNDM0LDQ4LjM0IEM0My42NjEsNDguMzQgNDQuODksNDguMDA1IDQ1Ljk5LDQ3LjM3IEw1OC4yODEsNDAuMjcyIEM2MS41NiwzOC4zNzkgNjIuODUzLDM0LjMzIDYxLjE2NCwzMS4yNDggQzYwLjA5MiwyOS4yOTMgNTguMDU4LDI4LjEyNSA1NS43MjEsMjguMTI1IEw1NS43MjEsMjguMTI1IFoiIGlkPSJGaWxsLTgiIGZpbGw9IiM2MDdEOEIiPjwvcGF0aD4KICAgICAgICAgICAgICAgICAgICA8cGF0aCBkPSJNMTQ5LjU4OCwyLjQwNyBDMTQ5LjU4OCwyLjQwNyAxNTUuNzY4LDUuOTc1IDE1Ni4zMjUsNi4yOTcgTDE1Ni4zMjUsNy4xODQgQzE1Ni4zMjUsNy4zNiAxNTYuMzM4LDcuNTQ0IDE1Ni4zNjIsNy43MzMgQzE1Ni4zNzMsNy44MTQgMTU2LjM4Miw3Ljg5NCAxNTYuMzksNy45NzUgQzE1Ni41Myw5LjM5IDE1Ny4zNjMsMTAuOTczIDE1OC40OTUsMTEuOTc0IEwxNjUuODkxLDE4LjUxOSBDMTY2LjA2OCwxOC42NzUgMTY2LjI0OSwxOC44MTQgMTY2LjQzMiwxOC45MzQgQzE2OC4wMTEsMTkuOTc0IDE2OS4zODIsMTkuNCAxNjkuNDk0LDE3LjY1MiBDMTY5LjU0MywxNi44NjggMTY5LjU1MSwxNi4wNTcgMTY5LjUxNywxNS4yMjMgTDE2OS41MTQsMTUuMDYzIEwxNjkuNTE0LDEzLjkxMiBDMTcwLjc4LDE0LjY0MiAxOTUuNTAxLDI4LjkxNSAxOTUuNTAxLDI4LjkxNSBMMTk1LjUwMSw4Mi45MTUgQzE5NS41MDEsODQuMDA1IDE5NC43MzEsODQuNDQ1IDE5My43ODEsODMuODk3IEwxNTEuMzA4LDU5LjM3NCBDMTUwLjM1OCw1OC44MjYgMTQ5LjU4OCw1Ny40OTcgMTQ5LjU4OCw1Ni40MDggTDE0OS41ODgsMjIuMzc1IiBpZD0iRmlsbC05IiBmaWxsPSIjRkFGQUZBIj48L3BhdGg+CiAgICAgICAgICAgICAgICAgICAgPHBhdGggZD0iTTE5NC41NTMsODQuMjUgQzE5NC4yOTYsODQuMjUgMTk0LjAxMyw4NC4xNjUgMTkzLjcyMiw4My45OTcgTDE1MS4yNSw1OS40NzYgQzE1MC4yNjksNTguOTA5IDE0OS40NzEsNTcuNTMzIDE0OS40NzEsNTYuNDA4IEwxNDkuNDcxLDIyLjM3NSBMMTQ5LjcwNSwyMi4zNzUgTDE0OS43MDUsNTYuNDA4IEMxNDkuNzA1LDU3LjQ1OSAxNTAuNDUsNTguNzQ0IDE1MS4zNjYsNTkuMjc0IEwxOTMuODM5LDgzLjc5NSBDMTk0LjI2Myw4NC4wNCAxOTQuNjU1LDg0LjA4MyAxOTQuOTQyLDgzLjkxNyBDMTk1LjIyNyw4My43NTMgMTk1LjM4NCw4My4zOTcgMTk1LjM4NCw4Mi45MTUgTDE5NS4zODQsMjguOTgyIEMxOTQuMTAyLDI4LjI0MiAxNzIuMTA0LDE1LjU0MiAxNjkuNjMxLDE0LjExNCBMMTY5LjYzNCwxNS4yMiBDMTY5LjY2OCwxNi4wNTIgMTY5LjY2LDE2Ljg3NCAxNjkuNjEsMTcuNjU5IEMxNjkuNTU2LDE4LjUwMyAxNjkuMjE0LDE5LjEyMyAxNjguNjQ3LDE5LjQwNSBDMTY4LjAyOCwxOS43MTQgMTY3LjE5NywxOS41NzggMTY2LjM2NywxOS4wMzIgQzE2Ni4xODEsMTguOTA5IDE2NS45OTUsMTguNzY2IDE2NS44MTQsMTguNjA2IEwxNTguNDE3LDEyLjA2MiBDMTU3LjI1OSwxMS4wMzYgMTU2LjQxOCw5LjQzNyAxNTYuMjc0LDcuOTg2IEMxNTYuMjY2LDcuOTA3IDE1Ni4yNTcsNy44MjcgMTU2LjI0Nyw3Ljc0OCBDMTU2LjIyMSw3LjU1NSAxNTYuMjA5LDcuMzY1IDE1Ni4yMDksNy4xODQgTDE1Ni4yMDksNi4zNjQgQzE1NS4zNzUsNS44ODMgMTQ5LjUyOSwyLjUwOCAxNDkuNTI5LDIuNTA4IEwxNDkuNjQ2LDIuMzA2IEMxNDkuNjQ2LDIuMzA2IDE1NS44MjcsNS44NzQgMTU2LjM4NCw2LjE5NiBMMTU2LjQ0Miw2LjIzIEwxNTYuNDQyLDcuMTg0IEMxNTYuNDQyLDcuMzU1IDE1Ni40NTQsNy41MzUgMTU2LjQ3OCw3LjcxNyBDMTU2LjQ4OSw3LjggMTU2LjQ5OSw3Ljg4MiAxNTYuNTA3LDcuOTYzIEMxNTYuNjQ1LDkuMzU4IDE1Ny40NTUsMTAuODk4IDE1OC41NzIsMTEuODg2IEwxNjUuOTY5LDE4LjQzMSBDMTY2LjE0MiwxOC41ODQgMTY2LjMxOSwxOC43MiAxNjYuNDk2LDE4LjgzNyBDMTY3LjI1NCwxOS4zMzYgMTY4LDE5LjQ2NyAxNjguNTQzLDE5LjE5NiBDMTY5LjAzMywxOC45NTMgMTY5LjMyOSwxOC40MDEgMTY5LjM3NywxNy42NDUgQzE2OS40MjcsMTYuODY3IDE2OS40MzQsMTYuMDU0IDE2OS40MDEsMTUuMjI4IEwxNjkuMzk3LDE1LjA2NSBMMTY5LjM5NywxMy43MSBMMTY5LjU3MiwxMy44MSBDMTcwLjgzOSwxNC41NDEgMTk1LjU1OSwyOC44MTQgMTk1LjU1OSwyOC44MTQgTDE5NS42MTgsMjguODQ3IEwxOTUuNjE4LDgyLjkxNSBDMTk1LjYxOCw4My40ODQgMTk1LjQyLDgzLjkxMSAxOTUuMDU5LDg0LjExOSBDMTk0LjkwOCw4NC4yMDYgMTk0LjczNyw4NC4yNSAxOTQuNTUzLDg0LjI1IiBpZD0iRmlsbC0xMCIgZmlsbD0iIzYwN0Q4QiI+PC9wYXRoPgogICAgICAgICAgICAgICAgICAgIDxwYXRoIGQ9Ik0xNDUuNjg1LDU2LjE2MSBMMTY5LjgsNzAuMDgzIEwxNDMuODIyLDg1LjA4MSBMMTQyLjM2LDg0Ljc3NCBDMTM1LjgyNiw4Mi42MDQgMTI4LjczMiw4MS4wNDYgMTIxLjM0MSw4MC4xNTggQzExNi45NzYsNzkuNjM0IDExMi42NzgsODEuMjU0IDExMS43NDMsODMuNzc4IEMxMTEuNTA2LDg0LjQxNCAxMTEuNTAzLDg1LjA3MSAxMTEuNzMyLDg1LjcwNiBDMTEzLjI3LDg5Ljk3MyAxMTUuOTY4LDk0LjA2OSAxMTkuNzI3LDk3Ljg0MSBMMTIwLjI1OSw5OC42ODYgQzEyMC4yNiw5OC42ODUgOTQuMjgyLDExMy42ODMgOTQuMjgyLDExMy42ODMgTDcwLjE2Nyw5OS43NjEgTDE0NS42ODUsNTYuMTYxIiBpZD0iRmlsbC0xMSIgZmlsbD0iI0ZGRkZGRiI+PC9wYXRoPgogICAgICAgICAgICAgICAgICAgIDxwYXRoIGQ9Ik05NC4yODIsMTEzLjgxOCBMOTQuMjIzLDExMy43ODUgTDY5LjkzMyw5OS43NjEgTDcwLjEwOCw5OS42NiBMMTQ1LjY4NSw1Ni4wMjYgTDE0NS43NDMsNTYuMDU5IEwxNzAuMDMzLDcwLjA4MyBMMTQzLjg0Miw4NS4yMDUgTDE0My43OTcsODUuMTk1IEMxNDMuNzcyLDg1LjE5IDE0Mi4zMzYsODQuODg4IDE0Mi4zMzYsODQuODg4IEMxMzUuNzg3LDgyLjcxNCAxMjguNzIzLDgxLjE2MyAxMjEuMzI3LDgwLjI3NCBDMTIwLjc4OCw4MC4yMDkgMTIwLjIzNiw4MC4xNzcgMTE5LjY4OSw4MC4xNzcgQzExNS45MzEsODAuMTc3IDExMi42MzUsODEuNzA4IDExMS44NTIsODMuODE5IEMxMTEuNjI0LDg0LjQzMiAxMTEuNjIxLDg1LjA1MyAxMTEuODQyLDg1LjY2NyBDMTEzLjM3Nyw4OS45MjUgMTE2LjA1OCw5My45OTMgMTE5LjgxLDk3Ljc1OCBMMTE5LjgyNiw5Ny43NzkgTDEyMC4zNTIsOTguNjE0IEMxMjAuMzU0LDk4LjYxNyAxMjAuMzU2LDk4LjYyIDEyMC4zNTgsOTguNjI0IEwxMjAuNDIyLDk4LjcyNiBMMTIwLjMxNyw5OC43ODcgQzEyMC4yNjQsOTguODE4IDk0LjU5OSwxMTMuNjM1IDk0LjM0LDExMy43ODUgTDk0LjI4MiwxMTMuODE4IEw5NC4yODIsMTEzLjgxOCBaIE03MC40MDEsOTkuNzYxIEw5NC4yODIsMTEzLjU0OSBMMTE5LjA4NCw5OS4yMjkgQzExOS42Myw5OC45MTQgMTE5LjkzLDk4Ljc0IDEyMC4xMDEsOTguNjU0IEwxMTkuNjM1LDk3LjkxNCBDMTE1Ljg2NCw5NC4xMjcgMTEzLjE2OCw5MC4wMzMgMTExLjYyMiw4NS43NDYgQzExMS4zODIsODUuMDc5IDExMS4zODYsODQuNDA0IDExMS42MzMsODMuNzM4IEMxMTIuNDQ4LDgxLjUzOSAxMTUuODM2LDc5Ljk0MyAxMTkuNjg5LDc5Ljk0MyBDMTIwLjI0Niw3OS45NDMgMTIwLjgwNiw3OS45NzYgMTIxLjM1NSw4MC4wNDIgQzEyOC43NjcsODAuOTMzIDEzNS44NDYsODIuNDg3IDE0Mi4zOTYsODQuNjYzIEMxNDMuMjMyLDg0LjgzOCAxNDMuNjExLDg0LjkxNyAxNDMuNzg2LDg0Ljk2NyBMMTY5LjU2Niw3MC4wODMgTDE0NS42ODUsNTYuMjk1IEw3MC40MDEsOTkuNzYxIEw3MC40MDEsOTkuNzYxIFoiIGlkPSJGaWxsLTEyIiBmaWxsPSIjNjA3RDhCIj48L3BhdGg+CiAgICAgICAgICAgICAgICAgICAgPHBhdGggZD0iTTE2Ny4yMywxOC45NzkgTDE2Ny4yMyw2OS44NSBMMTM5LjkwOSw4NS42MjMgTDEzMy40NDgsNzEuNDU2IEMxMzIuNTM4LDY5LjQ2IDEzMC4wMiw2OS43MTggMTI3LjgyNCw3Mi4wMyBDMTI2Ljc2OSw3My4xNCAxMjUuOTMxLDc0LjU4NSAxMjUuNDk0LDc2LjA0OCBMMTE5LjAzNCw5Ny42NzYgTDkxLjcxMiwxMTMuNDUgTDkxLjcxMiw2Mi41NzkgTDE2Ny4yMywxOC45NzkiIGlkPSJGaWxsLTEzIiBmaWxsPSIjRkZGRkZGIj48L3BhdGg+CiAgICAgICAgICAgICAgICAgICAgPHBhdGggZD0iTTkxLjcxMiwxMTMuNTY3IEM5MS42OTIsMTEzLjU2NyA5MS42NzIsMTEzLjU2MSA5MS42NTMsMTEzLjU1MSBDOTEuNjE4LDExMy41MyA5MS41OTUsMTEzLjQ5MiA5MS41OTUsMTEzLjQ1IEw5MS41OTUsNjIuNTc5IEM5MS41OTUsNjIuNTM3IDkxLjYxOCw2Mi40OTkgOTEuNjUzLDYyLjQ3OCBMMTY3LjE3MiwxOC44NzggQzE2Ny4yMDgsMTguODU3IDE2Ny4yNTIsMTguODU3IDE2Ny4yODgsMTguODc4IEMxNjcuMzI0LDE4Ljg5OSAxNjcuMzQ3LDE4LjkzNyAxNjcuMzQ3LDE4Ljk3OSBMMTY3LjM0Nyw2OS44NSBDMTY3LjM0Nyw2OS44OTEgMTY3LjMyNCw2OS45MyAxNjcuMjg4LDY5Ljk1IEwxMzkuOTY3LDg1LjcyNSBDMTM5LjkzOSw4NS43NDEgMTM5LjkwNSw4NS43NDUgMTM5Ljg3Myw4NS43MzUgQzEzOS44NDIsODUuNzI1IDEzOS44MTYsODUuNzAyIDEzOS44MDIsODUuNjcyIEwxMzMuMzQyLDcxLjUwNCBDMTMyLjk2Nyw3MC42ODIgMTMyLjI4LDcwLjIyOSAxMzEuNDA4LDcwLjIyOSBDMTMwLjMxOSw3MC4yMjkgMTI5LjA0NCw3MC45MTUgMTI3LjkwOCw3Mi4xMSBDMTI2Ljg3NCw3My4yIDEyNi4wMzQsNzQuNjQ3IDEyNS42MDYsNzYuMDgyIEwxMTkuMTQ2LDk3LjcwOSBDMTE5LjEzNyw5Ny43MzggMTE5LjExOCw5Ny43NjIgMTE5LjA5Miw5Ny43NzcgTDkxLjc3LDExMy41NTEgQzkxLjc1MiwxMTMuNTYxIDkxLjczMiwxMTMuNTY3IDkxLjcxMiwxMTMuNTY3IEw5MS43MTIsMTEzLjU2NyBaIE05MS44MjksNjIuNjQ3IEw5MS44MjksMTEzLjI0OCBMMTE4LjkzNSw5Ny41OTggTDEyNS4zODIsNzYuMDE1IEMxMjUuODI3LDc0LjUyNSAxMjYuNjY0LDczLjA4MSAxMjcuNzM5LDcxLjk1IEMxMjguOTE5LDcwLjcwOCAxMzAuMjU2LDY5Ljk5NiAxMzEuNDA4LDY5Ljk5NiBDMTMyLjM3Nyw2OS45OTYgMTMzLjEzOSw3MC40OTcgMTMzLjU1NCw3MS40MDcgTDEzOS45NjEsODUuNDU4IEwxNjcuMTEzLDY5Ljc4MiBMMTY3LjExMywxOS4xODEgTDkxLjgyOSw2Mi42NDcgTDkxLjgyOSw2Mi42NDcgWiIgaWQ9IkZpbGwtMTQiIGZpbGw9IiM2MDdEOEIiPjwvcGF0aD4KICAgICAgICAgICAgICAgICAgICA8cGF0aCBkPSJNMTY4LjU0MywxOS4yMTMgTDE2OC41NDMsNzAuMDgzIEwxNDEuMjIxLDg1Ljg1NyBMMTM0Ljc2MSw3MS42ODkgQzEzMy44NTEsNjkuNjk0IDEzMS4zMzMsNjkuOTUxIDEyOS4xMzcsNzIuMjYzIEMxMjguMDgyLDczLjM3NCAxMjcuMjQ0LDc0LjgxOSAxMjYuODA3LDc2LjI4MiBMMTIwLjM0Niw5Ny45MDkgTDkzLjAyNSwxMTMuNjgzIEw5My4wMjUsNjIuODEzIEwxNjguNTQzLDE5LjIxMyIgaWQ9IkZpbGwtMTUiIGZpbGw9IiNGRkZGRkYiPjwvcGF0aD4KICAgICAgICAgICAgICAgICAgICA8cGF0aCBkPSJNOTMuMDI1LDExMy44IEM5My4wMDUsMTEzLjggOTIuOTg0LDExMy43OTUgOTIuOTY2LDExMy43ODUgQzkyLjkzMSwxMTMuNzY0IDkyLjkwOCwxMTMuNzI1IDkyLjkwOCwxMTMuNjg0IEw5Mi45MDgsNjIuODEzIEM5Mi45MDgsNjIuNzcxIDkyLjkzMSw2Mi43MzMgOTIuOTY2LDYyLjcxMiBMMTY4LjQ4NCwxOS4xMTIgQzE2OC41MiwxOS4wOSAxNjguNTY1LDE5LjA5IDE2OC42MDEsMTkuMTEyIEMxNjguNjM3LDE5LjEzMiAxNjguNjYsMTkuMTcxIDE2OC42NiwxOS4yMTIgTDE2OC42Niw3MC4wODMgQzE2OC42Niw3MC4xMjUgMTY4LjYzNyw3MC4xNjQgMTY4LjYwMSw3MC4xODQgTDE0MS4yOCw4NS45NTggQzE0MS4yNTEsODUuOTc1IDE0MS4yMTcsODUuOTc5IDE0MS4xODYsODUuOTY4IEMxNDEuMTU0LDg1Ljk1OCAxNDEuMTI5LDg1LjkzNiAxNDEuMTE1LDg1LjkwNiBMMTM0LjY1NSw3MS43MzggQzEzNC4yOCw3MC45MTUgMTMzLjU5Myw3MC40NjMgMTMyLjcyLDcwLjQ2MyBDMTMxLjYzMiw3MC40NjMgMTMwLjM1Nyw3MS4xNDggMTI5LjIyMSw3Mi4zNDQgQzEyOC4xODYsNzMuNDMzIDEyNy4zNDcsNzQuODgxIDEyNi45MTksNzYuMzE1IEwxMjAuNDU4LDk3Ljk0MyBDMTIwLjQ1LDk3Ljk3MiAxMjAuNDMxLDk3Ljk5NiAxMjAuNDA1LDk4LjAxIEw5My4wODMsMTEzLjc4NSBDOTMuMDY1LDExMy43OTUgOTMuMDQ1LDExMy44IDkzLjAyNSwxMTMuOCBMOTMuMDI1LDExMy44IFogTTkzLjE0Miw2Mi44ODEgTDkzLjE0MiwxMTMuNDgxIEwxMjAuMjQ4LDk3LjgzMiBMMTI2LjY5NSw3Ni4yNDggQzEyNy4xNCw3NC43NTggMTI3Ljk3Nyw3My4zMTUgMTI5LjA1Miw3Mi4xODMgQzEzMC4yMzEsNzAuOTQyIDEzMS41NjgsNzAuMjI5IDEzMi43Miw3MC4yMjkgQzEzMy42ODksNzAuMjI5IDEzNC40NTIsNzAuNzMxIDEzNC44NjcsNzEuNjQxIEwxNDEuMjc0LDg1LjY5MiBMMTY4LjQyNiw3MC4wMTYgTDE2OC40MjYsMTkuNDE1IEw5My4xNDIsNjIuODgxIEw5My4xNDIsNjIuODgxIFoiIGlkPSJGaWxsLTE2IiBmaWxsPSIjNjA3RDhCIj48L3BhdGg+CiAgICAgICAgICAgICAgICAgICAgPHBhdGggZD0iTTE2OS44LDcwLjA4MyBMMTQyLjQ3OCw4NS44NTcgTDEzNi4wMTgsNzEuNjg5IEMxMzUuMTA4LDY5LjY5NCAxMzIuNTksNjkuOTUxIDEzMC4zOTMsNzIuMjYzIEMxMjkuMzM5LDczLjM3NCAxMjguNSw3NC44MTkgMTI4LjA2NCw3Ni4yODIgTDEyMS42MDMsOTcuOTA5IEw5NC4yODIsMTEzLjY4MyBMOTQuMjgyLDYyLjgxMyBMMTY5LjgsMTkuMjEzIEwxNjkuOCw3MC4wODMgWiIgaWQ9IkZpbGwtMTciIGZpbGw9IiNGQUZBRkEiPjwvcGF0aD4KICAgICAgICAgICAgICAgICAgICA8cGF0aCBkPSJNOTQuMjgyLDExMy45MTcgQzk0LjI0MSwxMTMuOTE3IDk0LjIwMSwxMTMuOTA3IDk0LjE2NSwxMTMuODg2IEM5NC4wOTMsMTEzLjg0NSA5NC4wNDgsMTEzLjc2NyA5NC4wNDgsMTEzLjY4NCBMOTQuMDQ4LDYyLjgxMyBDOTQuMDQ4LDYyLjczIDk0LjA5Myw2Mi42NTIgOTQuMTY1LDYyLjYxMSBMMTY5LjY4MywxOS4wMSBDMTY5Ljc1NSwxOC45NjkgMTY5Ljg0NCwxOC45NjkgMTY5LjkxNywxOS4wMSBDMTY5Ljk4OSwxOS4wNTIgMTcwLjAzMywxOS4xMjkgMTcwLjAzMywxOS4yMTIgTDE3MC4wMzMsNzAuMDgzIEMxNzAuMDMzLDcwLjE2NiAxNjkuOTg5LDcwLjI0NCAxNjkuOTE3LDcwLjI4NSBMMTQyLjU5NSw4Ni4wNiBDMTQyLjUzOCw4Ni4wOTIgMTQyLjQ2OSw4Ni4xIDE0Mi40MDcsODYuMDggQzE0Mi4zNDQsODYuMDYgMTQyLjI5Myw4Ni4wMTQgMTQyLjI2Niw4NS45NTQgTDEzNS44MDUsNzEuNzg2IEMxMzUuNDQ1LDcwLjk5NyAxMzQuODEzLDcwLjU4IDEzMy45NzcsNzAuNTggQzEzMi45MjEsNzAuNTggMTMxLjY3Niw3MS4yNTIgMTMwLjU2Miw3Mi40MjQgQzEyOS41NCw3My41MDEgMTI4LjcxMSw3NC45MzEgMTI4LjI4Nyw3Ni4zNDggTDEyMS44MjcsOTcuOTc2IEMxMjEuODEsOTguMDM0IDEyMS43NzEsOTguMDgyIDEyMS43Miw5OC4xMTIgTDk0LjM5OCwxMTMuODg2IEM5NC4zNjIsMTEzLjkwNyA5NC4zMjIsMTEzLjkxNyA5NC4yODIsMTEzLjkxNyBMOTQuMjgyLDExMy45MTcgWiBNOTQuNTE1LDYyLjk0OCBMOTQuNTE1LDExMy4yNzkgTDEyMS40MDYsOTcuNzU0IEwxMjcuODQsNzYuMjE1IEMxMjguMjksNzQuNzA4IDEyOS4xMzcsNzMuMjQ3IDEzMC4yMjQsNzIuMTAzIEMxMzEuNDI1LDcwLjgzOCAxMzIuNzkzLDcwLjExMiAxMzMuOTc3LDcwLjExMiBDMTM0Ljk5NSw3MC4xMTIgMTM1Ljc5NSw3MC42MzggMTM2LjIzLDcxLjU5MiBMMTQyLjU4NCw4NS41MjYgTDE2OS41NjYsNjkuOTQ4IEwxNjkuNTY2LDE5LjYxNyBMOTQuNTE1LDYyLjk0OCBMOTQuNTE1LDYyLjk0OCBaIiBpZD0iRmlsbC0xOCIgZmlsbD0iIzYwN0Q4QiI+PC9wYXRoPgogICAgICAgICAgICAgICAgICAgIDxwYXRoIGQ9Ik0xMDkuODk0LDkyLjk0MyBMMTA5Ljg5NCw5Mi45NDMgQzEwOC4xMiw5Mi45NDMgMTA2LjY1Myw5Mi4yMTggMTA1LjY1LDkwLjgyMyBDMTA1LjU4Myw5MC43MzEgMTA1LjU5Myw5MC42MSAxMDUuNjczLDkwLjUyOSBDMTA1Ljc1Myw5MC40NDggMTA1Ljg4LDkwLjQ0IDEwNS45NzQsOTAuNTA2IEMxMDYuNzU0LDkxLjA1MyAxMDcuNjc5LDkxLjMzMyAxMDguNzI0LDkxLjMzMyBDMTEwLjA0Nyw5MS4zMzMgMTExLjQ3OCw5MC44OTQgMTEyLjk4LDkwLjAyNyBDMTE4LjI5MSw4Ni45NiAxMjIuNjExLDc5LjUwOSAxMjIuNjExLDczLjQxNiBDMTIyLjYxMSw3MS40ODkgMTIyLjE2OSw2OS44NTYgMTIxLjMzMyw2OC42OTIgQzEyMS4yNjYsNjguNiAxMjEuMjc2LDY4LjQ3MyAxMjEuMzU2LDY4LjM5MiBDMTIxLjQzNiw2OC4zMTEgMTIxLjU2Myw2OC4yOTkgMTIxLjY1Niw2OC4zNjUgQzEyMy4zMjcsNjkuNTM3IDEyNC4yNDcsNzEuNzQ2IDEyNC4yNDcsNzQuNTg0IEMxMjQuMjQ3LDgwLjgyNiAxMTkuODIxLDg4LjQ0NyAxMTQuMzgyLDkxLjU4NyBDMTEyLjgwOCw5Mi40OTUgMTExLjI5OCw5Mi45NDMgMTA5Ljg5NCw5Mi45NDMgTDEwOS44OTQsOTIuOTQzIFogTTEwNi45MjUsOTEuNDAxIEMxMDcuNzM4LDkyLjA1MiAxMDguNzQ1LDkyLjI3OCAxMDkuODkzLDkyLjI3OCBMMTA5Ljg5NCw5Mi4yNzggQzExMS4yMTUsOTIuMjc4IDExMi42NDcsOTEuOTUxIDExNC4xNDgsOTEuMDg0IEMxMTkuNDU5LDg4LjAxNyAxMjMuNzgsODAuNjIxIDEyMy43OCw3NC41MjggQzEyMy43OCw3Mi41NDkgMTIzLjMxNyw3MC45MjkgMTIyLjQ1NCw2OS43NjcgQzEyMi44NjUsNzAuODAyIDEyMy4wNzksNzIuMDQyIDEyMy4wNzksNzMuNDAyIEMxMjMuMDc5LDc5LjY0NSAxMTguNjUzLDg3LjI4NSAxMTMuMjE0LDkwLjQyNSBDMTExLjY0LDkxLjMzNCAxMTAuMTMsOTEuNzQyIDEwOC43MjQsOTEuNzQyIEMxMDguMDgzLDkxLjc0MiAxMDcuNDgxLDkxLjU5MyAxMDYuOTI1LDkxLjQwMSBMMTA2LjkyNSw5MS40MDEgWiIgaWQ9IkZpbGwtMTkiIGZpbGw9IiM2MDdEOEIiPjwvcGF0aD4KICAgICAgICAgICAgICAgICAgICA8cGF0aCBkPSJNMTEzLjA5Nyw5MC4yMyBDMTE4LjQ4MSw4Ny4xMjIgMTIyLjg0NSw3OS41OTQgMTIyLjg0NSw3My40MTYgQzEyMi44NDUsNzEuMzY1IDEyMi4zNjIsNjkuNzI0IDEyMS41MjIsNjguNTU2IEMxMTkuNzM4LDY3LjMwNCAxMTcuMTQ4LDY3LjM2MiAxMTQuMjY1LDY5LjAyNiBDMTA4Ljg4MSw3Mi4xMzQgMTA0LjUxNyw3OS42NjIgMTA0LjUxNyw4NS44NCBDMTA0LjUxNyw4Ny44OTEgMTA1LDg5LjUzMiAxMDUuODQsOTAuNyBDMTA3LjYyNCw5MS45NTIgMTEwLjIxNCw5MS44OTQgMTEzLjA5Nyw5MC4yMyIgaWQ9IkZpbGwtMjAiIGZpbGw9IiNGQUZBRkEiPjwvcGF0aD4KICAgICAgICAgICAgICAgICAgICA8cGF0aCBkPSJNMTA4LjcyNCw5MS42MTQgTDEwOC43MjQsOTEuNjE0IEMxMDcuNTgyLDkxLjYxNCAxMDYuNTY2LDkxLjQwMSAxMDUuNzA1LDkwLjc5NyBDMTA1LjY4NCw5MC43ODMgMTA1LjY2NSw5MC44MTEgMTA1LjY1LDkwLjc5IEMxMDQuNzU2LDg5LjU0NiAxMDQuMjgzLDg3Ljg0MiAxMDQuMjgzLDg1LjgxNyBDMTA0LjI4Myw3OS41NzUgMTA4LjcwOSw3MS45NTMgMTE0LjE0OCw2OC44MTIgQzExNS43MjIsNjcuOTA0IDExNy4yMzIsNjcuNDQ5IDExOC42MzgsNjcuNDQ5IEMxMTkuNzgsNjcuNDQ5IDEyMC43OTYsNjcuNzU4IDEyMS42NTYsNjguMzYyIEMxMjEuNjc4LDY4LjM3NyAxMjEuNjk3LDY4LjM5NyAxMjEuNzEyLDY4LjQxOCBDMTIyLjYwNiw2OS42NjIgMTIzLjA3OSw3MS4zOSAxMjMuMDc5LDczLjQxNSBDMTIzLjA3OSw3OS42NTggMTE4LjY1Myw4Ny4xOTggMTEzLjIxNCw5MC4zMzggQzExMS42NCw5MS4yNDcgMTEwLjEzLDkxLjYxNCAxMDguNzI0LDkxLjYxNCBMMTA4LjcyNCw5MS42MTQgWiBNMTA2LjAwNiw5MC41MDUgQzEwNi43OCw5MS4wMzcgMTA3LjY5NCw5MS4yODEgMTA4LjcyNCw5MS4yODEgQzExMC4wNDcsOTEuMjgxIDExMS40NzgsOTAuODY4IDExMi45OCw5MC4wMDEgQzExOC4yOTEsODYuOTM1IDEyMi42MTEsNzkuNDk2IDEyMi42MTEsNzMuNDAzIEMxMjIuNjExLDcxLjQ5NCAxMjIuMTc3LDY5Ljg4IDEyMS4zNTYsNjguNzE4IEMxMjAuNTgyLDY4LjE4NSAxMTkuNjY4LDY3LjkxOSAxMTguNjM4LDY3LjkxOSBDMTE3LjMxNSw2Ny45MTkgMTE1Ljg4Myw2OC4zNiAxMTQuMzgyLDY5LjIyNyBDMTA5LjA3MSw3Mi4yOTMgMTA0Ljc1MSw3OS43MzMgMTA0Ljc1MSw4NS44MjYgQzEwNC43NTEsODcuNzM1IDEwNS4xODUsODkuMzQzIDEwNi4wMDYsOTAuNTA1IEwxMDYuMDA2LDkwLjUwNSBaIiBpZD0iRmlsbC0yMSIgZmlsbD0iIzYwN0Q4QiI+PC9wYXRoPgogICAgICAgICAgICAgICAgICAgIDxwYXRoIGQ9Ik0xNDkuMzE4LDcuMjYyIEwxMzkuMzM0LDE2LjE0IEwxNTUuMjI3LDI3LjE3MSBMMTYwLjgxNiwyMS4wNTkgTDE0OS4zMTgsNy4yNjIiIGlkPSJGaWxsLTIyIiBmaWxsPSIjRkFGQUZBIj48L3BhdGg+CiAgICAgICAgICAgICAgICAgICAgPHBhdGggZD0iTTE2OS42NzYsMTMuODQgTDE1OS45MjgsMTkuNDY3IEMxNTYuMjg2LDIxLjU3IDE1MC40LDIxLjU4IDE0Ni43ODEsMTkuNDkxIEMxNDMuMTYxLDE3LjQwMiAxNDMuMTgsMTQuMDAzIDE0Ni44MjIsMTEuOSBMMTU2LjMxNyw2LjI5MiBMMTQ5LjU4OCwyLjQwNyBMNjcuNzUyLDQ5LjQ3OCBMMTEzLjY3NSw3NS45OTIgTDExNi43NTYsNzQuMjEzIEMxMTcuMzg3LDczLjg0OCAxMTcuNjI1LDczLjMxNSAxMTcuMzc0LDcyLjgyMyBDMTE1LjAxNyw2OC4xOTEgMTE0Ljc4MSw2My4yNzcgMTE2LjY5MSw1OC41NjEgQzEyMi4zMjksNDQuNjQxIDE0MS4yLDMzLjc0NiAxNjUuMzA5LDMwLjQ5MSBDMTczLjQ3OCwyOS4zODggMTgxLjk4OSwyOS41MjQgMTkwLjAxMywzMC44ODUgQzE5MC44NjUsMzEuMDMgMTkxLjc4OSwzMC44OTMgMTkyLjQyLDMwLjUyOCBMMTk1LjUwMSwyOC43NSBMMTY5LjY3NiwxMy44NCIgaWQ9IkZpbGwtMjMiIGZpbGw9IiNGQUZBRkEiPjwvcGF0aD4KICAgICAgICAgICAgICAgICAgICA8cGF0aCBkPSJNMTEzLjY3NSw3Ni40NTkgQzExMy41OTQsNzYuNDU5IDExMy41MTQsNzYuNDM4IDExMy40NDIsNzYuMzk3IEw2Ny41MTgsNDkuODgyIEM2Ny4zNzQsNDkuNzk5IDY3LjI4NCw0OS42NDUgNjcuMjg1LDQ5LjQ3OCBDNjcuMjg1LDQ5LjMxMSA2Ny4zNzQsNDkuMTU3IDY3LjUxOSw0OS4wNzMgTDE0OS4zNTUsMi4wMDIgQzE0OS40OTksMS45MTkgMTQ5LjY3NywxLjkxOSAxNDkuODIxLDIuMDAyIEwxNTYuNTUsNS44ODcgQzE1Ni43NzQsNi4wMTcgMTU2Ljg1LDYuMzAyIDE1Ni43MjIsNi41MjYgQzE1Ni41OTIsNi43NDkgMTU2LjMwNyw2LjgyNiAxNTYuMDgzLDYuNjk2IEwxNDkuNTg3LDIuOTQ2IEw2OC42ODcsNDkuNDc5IEwxMTMuNjc1LDc1LjQ1MiBMMTE2LjUyMyw3My44MDggQzExNi43MTUsNzMuNjk3IDExNy4xNDMsNzMuMzk5IDExNi45NTgsNzMuMDM1IEMxMTQuNTQyLDY4LjI4NyAxMTQuMyw2My4yMjEgMTE2LjI1OCw1OC4zODUgQzExOS4wNjQsNTEuNDU4IDEyNS4xNDMsNDUuMTQzIDEzMy44NCw0MC4xMjIgQzE0Mi40OTcsMzUuMTI0IDE1My4zNTgsMzEuNjMzIDE2NS4yNDcsMzAuMDI4IEMxNzMuNDQ1LDI4LjkyMSAxODIuMDM3LDI5LjA1OCAxOTAuMDkxLDMwLjQyNSBDMTkwLjgzLDMwLjU1IDE5MS42NTIsMzAuNDMyIDE5Mi4xODYsMzAuMTI0IEwxOTQuNTY3LDI4Ljc1IEwxNjkuNDQyLDE0LjI0NCBDMTY5LjIxOSwxNC4xMTUgMTY5LjE0MiwxMy44MjkgMTY5LjI3MSwxMy42MDYgQzE2OS40LDEzLjM4MiAxNjkuNjg1LDEzLjMwNiAxNjkuOTA5LDEzLjQzNSBMMTk1LjczNCwyOC4zNDUgQzE5NS44NzksMjguNDI4IDE5NS45NjgsMjguNTgzIDE5NS45NjgsMjguNzUgQzE5NS45NjgsMjguOTE2IDE5NS44NzksMjkuMDcxIDE5NS43MzQsMjkuMTU0IEwxOTIuNjUzLDMwLjkzMyBDMTkxLjkzMiwzMS4zNSAxOTAuODksMzEuNTA4IDE4OS45MzUsMzEuMzQ2IEMxODEuOTcyLDI5Ljk5NSAxNzMuNDc4LDI5Ljg2IDE2NS4zNzIsMzAuOTU0IEMxNTMuNjAyLDMyLjU0MyAxNDIuODYsMzUuOTkzIDEzNC4zMDcsNDAuOTMxIEMxMjUuNzkzLDQ1Ljg0NyAxMTkuODUxLDUyLjAwNCAxMTcuMTI0LDU4LjczNiBDMTE1LjI3LDYzLjMxNCAxMTUuNTAxLDY4LjExMiAxMTcuNzksNzIuNjExIEMxMTguMTYsNzMuMzM2IDExNy44NDUsNzQuMTI0IDExNi45OSw3NC42MTcgTDExMy45MDksNzYuMzk3IEMxMTMuODM2LDc2LjQzOCAxMTMuNzU2LDc2LjQ1OSAxMTMuNjc1LDc2LjQ1OSIgaWQ9IkZpbGwtMjQiIGZpbGw9IiM0NTVBNjQiPjwvcGF0aD4KICAgICAgICAgICAgICAgICAgICA8cGF0aCBkPSJNMTUzLjMxNiwyMS4yNzkgQzE1MC45MDMsMjEuMjc5IDE0OC40OTUsMjAuNzUxIDE0Ni42NjQsMTkuNjkzIEMxNDQuODQ2LDE4LjY0NCAxNDMuODQ0LDE3LjIzMiAxNDMuODQ0LDE1LjcxOCBDMTQzLjg0NCwxNC4xOTEgMTQ0Ljg2LDEyLjc2MyAxNDYuNzA1LDExLjY5OCBMMTU2LjE5OCw2LjA5MSBDMTU2LjMwOSw2LjAyNSAxNTYuNDUyLDYuMDYyIDE1Ni41MTgsNi4xNzMgQzE1Ni41ODMsNi4yODQgMTU2LjU0Nyw2LjQyNyAxNTYuNDM2LDYuNDkzIEwxNDYuOTQsMTIuMTAyIEMxNDUuMjQ0LDEzLjA4MSAxNDQuMzEyLDE0LjM2NSAxNDQuMzEyLDE1LjcxOCBDMTQ0LjMxMiwxNy4wNTggMTQ1LjIzLDE4LjMyNiAxNDYuODk3LDE5LjI4OSBDMTUwLjQ0NiwyMS4zMzggMTU2LjI0LDIxLjMyNyAxNTkuODExLDE5LjI2NSBMMTY5LjU1OSwxMy42MzcgQzE2OS42NywxMy41NzMgMTY5LjgxMywxMy42MTEgMTY5Ljg3OCwxMy43MjMgQzE2OS45NDMsMTMuODM0IDE2OS45MDQsMTMuOTc3IDE2OS43OTMsMTQuMDQyIEwxNjAuMDQ1LDE5LjY3IEMxNTguMTg3LDIwLjc0MiAxNTUuNzQ5LDIxLjI3OSAxNTMuMzE2LDIxLjI3OSIgaWQ9IkZpbGwtMjUiIGZpbGw9IiM2MDdEOEIiPjwvcGF0aD4KICAgICAgICAgICAgICAgICAgICA8cGF0aCBkPSJNMTEzLjY3NSw3NS45OTIgTDY3Ljc2Miw0OS40ODQiIGlkPSJGaWxsLTI2IiBmaWxsPSIjNDU1QTY0Ij48L3BhdGg+CiAgICAgICAgICAgICAgICAgICAgPHBhdGggZD0iTTExMy42NzUsNzYuMzQyIEMxMTMuNjE1LDc2LjM0MiAxMTMuNTU1LDc2LjMyNyAxMTMuNSw3Ni4yOTUgTDY3LjU4Nyw0OS43ODcgQzY3LjQxOSw0OS42OSA2Ny4zNjIsNDkuNDc2IDY3LjQ1OSw0OS4zMDkgQzY3LjU1Niw0OS4xNDEgNjcuNzcsNDkuMDgzIDY3LjkzNyw0OS4xOCBMMTEzLjg1LDc1LjY4OCBDMTE0LjAxOCw3NS43ODUgMTE0LjA3NSw3NiAxMTMuOTc4LDc2LjE2NyBDMTEzLjkxNCw3Ni4yNzkgMTEzLjc5Niw3Ni4zNDIgMTEzLjY3NSw3Ni4zNDIiIGlkPSJGaWxsLTI3IiBmaWxsPSIjNDU1QTY0Ij48L3BhdGg+CiAgICAgICAgICAgICAgICAgICAgPHBhdGggZD0iTTY3Ljc2Miw0OS40ODQgTDY3Ljc2MiwxMDMuNDg1IEM2Ny43NjIsMTA0LjU3NSA2OC41MzIsMTA1LjkwMyA2OS40ODIsMTA2LjQ1MiBMMTExLjk1NSwxMzAuOTczIEMxMTIuOTA1LDEzMS41MjIgMTEzLjY3NSwxMzEuMDgzIDExMy42NzUsMTI5Ljk5MyBMMTEzLjY3NSw3NS45OTIiIGlkPSJGaWxsLTI4IiBmaWxsPSIjRkFGQUZBIj48L3BhdGg+CiAgICAgICAgICAgICAgICAgICAgPHBhdGggZD0iTTExMi43MjcsMTMxLjU2MSBDMTEyLjQzLDEzMS41NjEgMTEyLjEwNywxMzEuNDY2IDExMS43OCwxMzEuMjc2IEw2OS4zMDcsMTA2Ljc1NSBDNjguMjQ0LDEwNi4xNDIgNjcuNDEyLDEwNC43MDUgNjcuNDEyLDEwMy40ODUgTDY3LjQxMiw0OS40ODQgQzY3LjQxMiw0OS4yOSA2Ny41NjksNDkuMTM0IDY3Ljc2Miw0OS4xMzQgQzY3Ljk1Niw0OS4xMzQgNjguMTEzLDQ5LjI5IDY4LjExMyw0OS40ODQgTDY4LjExMywxMDMuNDg1IEM2OC4xMTMsMTA0LjQ0NSA2OC44MiwxMDUuNjY1IDY5LjY1NywxMDYuMTQ4IEwxMTIuMTMsMTMwLjY3IEMxMTIuNDc0LDEzMC44NjggMTEyLjc5MSwxMzAuOTEzIDExMywxMzAuNzkyIEMxMTMuMjA2LDEzMC42NzMgMTEzLjMyNSwxMzAuMzgxIDExMy4zMjUsMTI5Ljk5MyBMMTEzLjMyNSw3NS45OTIgQzExMy4zMjUsNzUuNzk4IDExMy40ODIsNzUuNjQxIDExMy42NzUsNzUuNjQxIEMxMTMuODY5LDc1LjY0MSAxMTQuMDI1LDc1Ljc5OCAxMTQuMDI1LDc1Ljk5MiBMMTE0LjAyNSwxMjkuOTkzIEMxMTQuMDI1LDEzMC42NDggMTEzLjc4NiwxMzEuMTQ3IDExMy4zNSwxMzEuMzk5IEMxMTMuMTYyLDEzMS41MDcgMTEyLjk1MiwxMzEuNTYxIDExMi43MjcsMTMxLjU2MSIgaWQ9IkZpbGwtMjkiIGZpbGw9IiM0NTVBNjQiPjwvcGF0aD4KICAgICAgICAgICAgICAgICAgICA8cGF0aCBkPSJNMTEyLjg2LDQwLjUxMiBDMTEyLjg2LDQwLjUxMiAxMTIuODYsNDAuNTEyIDExMi44NTksNDAuNTEyIEMxMTAuNTQxLDQwLjUxMiAxMDguMzYsMzkuOTkgMTA2LjcxNywzOS4wNDEgQzEwNS4wMTIsMzguMDU3IDEwNC4wNzQsMzYuNzI2IDEwNC4wNzQsMzUuMjkyIEMxMDQuMDc0LDMzLjg0NyAxMDUuMDI2LDMyLjUwMSAxMDYuNzU0LDMxLjUwNCBMMTE4Ljc5NSwyNC41NTEgQzEyMC40NjMsMjMuNTg5IDEyMi42NjksMjMuMDU4IDEyNS4wMDcsMjMuMDU4IEMxMjcuMzI1LDIzLjA1OCAxMjkuNTA2LDIzLjU4MSAxMzEuMTUsMjQuNTMgQzEzMi44NTQsMjUuNTE0IDEzMy43OTMsMjYuODQ1IDEzMy43OTMsMjguMjc4IEMxMzMuNzkzLDI5LjcyNCAxMzIuODQxLDMxLjA2OSAxMzEuMTEzLDMyLjA2NyBMMTE5LjA3MSwzOS4wMTkgQzExNy40MDMsMzkuOTgyIDExNS4xOTcsNDAuNTEyIDExMi44Niw0MC41MTIgTDExMi44Niw0MC41MTIgWiBNMTI1LjAwNywyMy43NTkgQzEyMi43OSwyMy43NTkgMTIwLjcwOSwyNC4yNTYgMTE5LjE0NiwyNS4xNTggTDEwNy4xMDQsMzIuMTEgQzEwNS42MDIsMzIuOTc4IDEwNC43NzQsMzQuMTA4IDEwNC43NzQsMzUuMjkyIEMxMDQuNzc0LDM2LjQ2NSAxMDUuNTg5LDM3LjU4MSAxMDcuMDY3LDM4LjQzNCBDMTA4LjYwNSwzOS4zMjMgMTEwLjY2MywzOS44MTIgMTEyLjg1OSwzOS44MTIgTDExMi44NiwzOS44MTIgQzExNS4wNzYsMzkuODEyIDExNy4xNTgsMzkuMzE1IDExOC43MjEsMzguNDEzIEwxMzAuNzYyLDMxLjQ2IEMxMzIuMjY0LDMwLjU5MyAxMzMuMDkyLDI5LjQ2MyAxMzMuMDkyLDI4LjI3OCBDMTMzLjA5MiwyNy4xMDYgMTMyLjI3OCwyNS45OSAxMzAuOCwyNS4xMzYgQzEyOS4yNjEsMjQuMjQ4IDEyNy4yMDQsMjMuNzU5IDEyNS4wMDcsMjMuNzU5IEwxMjUuMDA3LDIzLjc1OSBaIiBpZD0iRmlsbC0zMCIgZmlsbD0iIzYwN0Q4QiI+PC9wYXRoPgogICAgICAgICAgICAgICAgICAgIDxwYXRoIGQ9Ik0xNjUuNjMsMTYuMjE5IEwxNTkuODk2LDE5LjUzIEMxNTYuNzI5LDIxLjM1OCAxNTEuNjEsMjEuMzY3IDE0OC40NjMsMTkuNTUgQzE0NS4zMTYsMTcuNzMzIDE0NS4zMzIsMTQuNzc4IDE0OC40OTksMTIuOTQ5IEwxNTQuMjMzLDkuNjM5IEwxNjUuNjMsMTYuMjE5IiBpZD0iRmlsbC0zMSIgZmlsbD0iI0ZBRkFGQSI+PC9wYXRoPgogICAgICAgICAgICAgICAgICAgIDxwYXRoIGQ9Ik0xNTQuMjMzLDEwLjQ0OCBMMTY0LjIyOCwxNi4yMTkgTDE1OS41NDYsMTguOTIzIEMxNTguMTEyLDE5Ljc1IDE1Ni4xOTQsMjAuMjA2IDE1NC4xNDcsMjAuMjA2IEMxNTIuMTE4LDIwLjIwNiAxNTAuMjI0LDE5Ljc1NyAxNDguODE0LDE4Ljk0MyBDMTQ3LjUyNCwxOC4xOTkgMTQ2LjgxNCwxNy4yNDkgMTQ2LjgxNCwxNi4yNjkgQzE0Ni44MTQsMTUuMjc4IDE0Ny41MzcsMTQuMzE0IDE0OC44NSwxMy41NTYgTDE1NC4yMzMsMTAuNDQ4IE0xNTQuMjMzLDkuNjM5IEwxNDguNDk5LDEyLjk0OSBDMTQ1LjMzMiwxNC43NzggMTQ1LjMxNiwxNy43MzMgMTQ4LjQ2MywxOS41NSBDMTUwLjAzMSwyMC40NTUgMTUyLjA4NiwyMC45MDcgMTU0LjE0NywyMC45MDcgQzE1Ni4yMjQsMjAuOTA3IDE1OC4zMDYsMjAuNDQ3IDE1OS44OTYsMTkuNTMgTDE2NS42MywxNi4yMTkgTDE1NC4yMzMsOS42MzkiIGlkPSJGaWxsLTMyIiBmaWxsPSIjNjA3RDhCIj48L3BhdGg+CiAgICAgICAgICAgICAgICAgICAgPHBhdGggZD0iTTE0NS40NDUsNzIuNjY3IEwxNDUuNDQ1LDcyLjY2NyBDMTQzLjY3Miw3Mi42NjcgMTQyLjIwNCw3MS44MTcgMTQxLjIwMiw3MC40MjIgQzE0MS4xMzUsNzAuMzMgMTQxLjE0NSw3MC4xNDcgMTQxLjIyNSw3MC4wNjYgQzE0MS4zMDUsNjkuOTg1IDE0MS40MzIsNjkuOTQ2IDE0MS41MjUsNzAuMDExIEMxNDIuMzA2LDcwLjU1OSAxNDMuMjMxLDcwLjgyMyAxNDQuMjc2LDcwLjgyMiBDMTQ1LjU5OCw3MC44MjIgMTQ3LjAzLDcwLjM3NiAxNDguNTMyLDY5LjUwOSBDMTUzLjg0Miw2Ni40NDMgMTU4LjE2Myw1OC45ODcgMTU4LjE2Myw1Mi44OTQgQzE1OC4xNjMsNTAuOTY3IDE1Ny43MjEsNDkuMzMyIDE1Ni44ODQsNDguMTY4IEMxNTYuODE4LDQ4LjA3NiAxNTYuODI4LDQ3Ljk0OCAxNTYuOTA4LDQ3Ljg2NyBDMTU2Ljk4OCw0Ny43ODYgMTU3LjExNCw0Ny43NzQgMTU3LjIwOCw0Ny44NCBDMTU4Ljg3OCw0OS4wMTIgMTU5Ljc5OCw1MS4yMiAxNTkuNzk4LDU0LjA1OSBDMTU5Ljc5OCw2MC4zMDEgMTU1LjM3Myw2OC4wNDYgMTQ5LjkzMyw3MS4xODYgQzE0OC4zNiw3Mi4wOTQgMTQ2Ljg1LDcyLjY2NyAxNDUuNDQ1LDcyLjY2NyBMMTQ1LjQ0NSw3Mi42NjcgWiBNMTQyLjQ3Niw3MSBDMTQzLjI5LDcxLjY1MSAxNDQuMjk2LDcyLjAwMiAxNDUuNDQ1LDcyLjAwMiBDMTQ2Ljc2Nyw3Mi4wMDIgMTQ4LjE5OCw3MS41NSAxNDkuNyw3MC42ODIgQzE1NS4wMSw2Ny42MTcgMTU5LjMzMSw2MC4xNTkgMTU5LjMzMSw1NC4wNjUgQzE1OS4zMzEsNTIuMDg1IDE1OC44NjgsNTAuNDM1IDE1OC4wMDYsNDkuMjcyIEMxNTguNDE3LDUwLjMwNyAxNTguNjMsNTEuNTMyIDE1OC42Myw1Mi44OTIgQzE1OC42Myw1OS4xMzQgMTU0LjIwNSw2Ni43NjcgMTQ4Ljc2NSw2OS45MDcgQzE0Ny4xOTIsNzAuODE2IDE0NS42ODEsNzEuMjgzIDE0NC4yNzYsNzEuMjgzIEMxNDMuNjM0LDcxLjI4MyAxNDMuMDMzLDcxLjE5MiAxNDIuNDc2LDcxIEwxNDIuNDc2LDcxIFoiIGlkPSJGaWxsLTMzIiBmaWxsPSIjNjA3RDhCIj48L3BhdGg+CiAgICAgICAgICAgICAgICAgICAgPHBhdGggZD0iTTE0OC42NDgsNjkuNzA0IEMxNTQuMDMyLDY2LjU5NiAxNTguMzk2LDU5LjA2OCAxNTguMzk2LDUyLjg5MSBDMTU4LjM5Niw1MC44MzkgMTU3LjkxMyw0OS4xOTggMTU3LjA3NCw0OC4wMyBDMTU1LjI4OSw0Ni43NzggMTUyLjY5OSw0Ni44MzYgMTQ5LjgxNiw0OC41MDEgQzE0NC40MzMsNTEuNjA5IDE0MC4wNjgsNTkuMTM3IDE0MC4wNjgsNjUuMzE0IEMxNDAuMDY4LDY3LjM2NSAxNDAuNTUyLDY5LjAwNiAxNDEuMzkxLDcwLjE3NCBDMTQzLjE3Niw3MS40MjcgMTQ1Ljc2NSw3MS4zNjkgMTQ4LjY0OCw2OS43MDQiIGlkPSJGaWxsLTM0IiBmaWxsPSIjRkFGQUZBIj48L3BhdGg+CiAgICAgICAgICAgICAgICAgICAgPHBhdGggZD0iTTE0NC4yNzYsNzEuMjc2IEwxNDQuMjc2LDcxLjI3NiBDMTQzLjEzMyw3MS4yNzYgMTQyLjExOCw3MC45NjkgMTQxLjI1Nyw3MC4zNjUgQzE0MS4yMzYsNzAuMzUxIDE0MS4yMTcsNzAuMzMyIDE0MS4yMDIsNzAuMzExIEMxNDAuMzA3LDY5LjA2NyAxMzkuODM1LDY3LjMzOSAxMzkuODM1LDY1LjMxNCBDMTM5LjgzNSw1OS4wNzMgMTQ0LjI2LDUxLjQzOSAxNDkuNyw0OC4yOTggQzE1MS4yNzMsNDcuMzkgMTUyLjc4NCw0Ni45MjkgMTU0LjE4OSw0Ni45MjkgQzE1NS4zMzIsNDYuOTI5IDE1Ni4zNDcsNDcuMjM2IDE1Ny4yMDgsNDcuODM5IEMxNTcuMjI5LDQ3Ljg1NCAxNTcuMjQ4LDQ3Ljg3MyAxNTcuMjYzLDQ3Ljg5NCBDMTU4LjE1Nyw0OS4xMzggMTU4LjYzLDUwLjg2NSAxNTguNjMsNTIuODkxIEMxNTguNjMsNTkuMTMyIDE1NC4yMDUsNjYuNzY2IDE0OC43NjUsNjkuOTA3IEMxNDcuMTkyLDcwLjgxNSAxNDUuNjgxLDcxLjI3NiAxNDQuMjc2LDcxLjI3NiBMMTQ0LjI3Niw3MS4yNzYgWiBNMTQxLjU1OCw3MC4xMDQgQzE0Mi4zMzEsNzAuNjM3IDE0My4yNDUsNzEuMDA1IDE0NC4yNzYsNzEuMDA1IEMxNDUuNTk4LDcxLjAwNSAxNDcuMDMsNzAuNDY3IDE0OC41MzIsNjkuNiBDMTUzLjg0Miw2Ni41MzQgMTU4LjE2Myw1OS4wMzMgMTU4LjE2Myw1Mi45MzkgQzE1OC4xNjMsNTEuMDMxIDE1Ny43MjksNDkuMzg1IDE1Ni45MDcsNDguMjIzIEMxNTYuMTMzLDQ3LjY5MSAxNTUuMjE5LDQ3LjQwOSAxNTQuMTg5LDQ3LjQwOSBDMTUyLjg2Nyw0Ny40MDkgMTUxLjQzNSw0Ny44NDIgMTQ5LjkzMyw0OC43MDkgQzE0NC42MjMsNTEuNzc1IDE0MC4zMDIsNTkuMjczIDE0MC4zMDIsNjUuMzY2IEMxNDAuMzAyLDY3LjI3NiAxNDAuNzM2LDY4Ljk0MiAxNDEuNTU4LDcwLjEwNCBMMTQxLjU1OCw3MC4xMDQgWiIgaWQ9IkZpbGwtMzUiIGZpbGw9IiM2MDdEOEIiPjwvcGF0aD4KICAgICAgICAgICAgICAgICAgICA8cGF0aCBkPSJNMTUwLjcyLDY1LjM2MSBMMTUwLjM1Nyw2NS4wNjYgQzE1MS4xNDcsNjQuMDkyIDE1MS44NjksNjMuMDQgMTUyLjUwNSw2MS45MzggQzE1My4zMTMsNjAuNTM5IDE1My45NzgsNTkuMDY3IDE1NC40ODIsNTcuNTYzIEwxNTQuOTI1LDU3LjcxMiBDMTU0LjQxMiw1OS4yNDUgMTUzLjczMyw2MC43NDUgMTUyLjkxLDYyLjE3MiBDMTUyLjI2Miw2My4yOTUgMTUxLjUyNSw2NC4zNjggMTUwLjcyLDY1LjM2MSIgaWQ9IkZpbGwtMzYiIGZpbGw9IiM2MDdEOEIiPjwvcGF0aD4KICAgICAgICAgICAgICAgICAgICA8cGF0aCBkPSJNMTE1LjkxNyw4NC41MTQgTDExNS41NTQsODQuMjIgQzExNi4zNDQsODMuMjQ1IDExNy4wNjYsODIuMTk0IDExNy43MDIsODEuMDkyIEMxMTguNTEsNzkuNjkyIDExOS4xNzUsNzguMjIgMTE5LjY3OCw3Ni43MTcgTDEyMC4xMjEsNzYuODY1IEMxMTkuNjA4LDc4LjM5OCAxMTguOTMsNzkuODk5IDExOC4xMDYsODEuMzI2IEMxMTcuNDU4LDgyLjQ0OCAxMTYuNzIyLDgzLjUyMSAxMTUuOTE3LDg0LjUxNCIgaWQ9IkZpbGwtMzciIGZpbGw9IiM2MDdEOEIiPjwvcGF0aD4KICAgICAgICAgICAgICAgICAgICA8cGF0aCBkPSJNMTE0LDEzMC40NzYgTDExNCwxMzAuMDA4IEwxMTQsNzYuMDUyIEwxMTQsNzUuNTg0IEwxMTQsNzYuMDUyIEwxMTQsMTMwLjAwOCBMMTE0LDEzMC40NzYiIGlkPSJGaWxsLTM4IiBmaWxsPSIjNjA3RDhCIj48L3BhdGg+CiAgICAgICAgICAgICAgICA8L2c+CiAgICAgICAgICAgICAgICA8ZyBpZD0iSW1wb3J0ZWQtTGF5ZXJzLUNvcHkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDYyLjAwMDAwMCwgMC4wMDAwMDApIiBza2V0Y2g6dHlwZT0iTVNTaGFwZUdyb3VwIj4KICAgICAgICAgICAgICAgICAgICA8cGF0aCBkPSJNMTkuODIyLDM3LjQ3NCBDMTkuODM5LDM3LjMzOSAxOS43NDcsMzcuMTk0IDE5LjU1NSwzNy4wODIgQzE5LjIyOCwzNi44OTQgMTguNzI5LDM2Ljg3MiAxOC40NDYsMzcuMDM3IEwxMi40MzQsNDAuNTA4IEMxMi4zMDMsNDAuNTg0IDEyLjI0LDQwLjY4NiAxMi4yNDMsNDAuNzkzIEMxMi4yNDUsNDAuOTI1IDEyLjI0NSw0MS4yNTQgMTIuMjQ1LDQxLjM3MSBMMTIuMjQ1LDQxLjQxNCBMMTIuMjM4LDQxLjU0MiBDOC4xNDgsNDMuODg3IDUuNjQ3LDQ1LjMyMSA1LjY0Nyw0NS4zMjEgQzUuNjQ2LDQ1LjMyMSAzLjU3LDQ2LjM2NyAyLjg2LDUwLjUxMyBDMi44Niw1MC41MTMgMS45NDgsNTcuNDc0IDEuOTYyLDcwLjI1OCBDMS45NzcsODIuODI4IDIuNTY4LDg3LjMyOCAzLjEyOSw5MS42MDkgQzMuMzQ5LDkzLjI5MyA2LjEzLDkzLjczNCA2LjEzLDkzLjczNCBDNi40NjEsOTMuNzc0IDYuODI4LDkzLjcwNyA3LjIxLDkzLjQ4NiBMODIuNDgzLDQ5LjkzNSBDODQuMjkxLDQ4Ljg2NiA4NS4xNSw0Ni4yMTYgODUuNTM5LDQzLjY1MSBDODYuNzUyLDM1LjY2MSA4Ny4yMTQsMTAuNjczIDg1LjI2NCwzLjc3MyBDODUuMDY4LDMuMDggODQuNzU0LDIuNjkgODQuMzk2LDIuNDkxIEw4Mi4zMSwxLjcwMSBDODEuNTgzLDEuNzI5IDgwLjg5NCwyLjE2OCA4MC43NzYsMi4yMzYgQzgwLjYzNiwyLjMxNyA0MS44MDcsMjQuNTg1IDIwLjAzMiwzNy4wNzIgTDE5LjgyMiwzNy40NzQiIGlkPSJGaWxsLTEiIGZpbGw9IiNGRkZGRkYiPjwvcGF0aD4KICAgICAgICAgICAgICAgICAgICA8cGF0aCBkPSJNODIuMzExLDEuNzAxIEw4NC4zOTYsMi40OTEgQzg0Ljc1NCwyLjY5IDg1LjA2OCwzLjA4IDg1LjI2NCwzLjc3MyBDODcuMjEzLDEwLjY3MyA4Ni43NTEsMzUuNjYgODUuNTM5LDQzLjY1MSBDODUuMTQ5LDQ2LjIxNiA4NC4yOSw0OC44NjYgODIuNDgzLDQ5LjkzNSBMNy4yMSw5My40ODYgQzYuODk3LDkzLjY2NyA2LjU5NSw5My43NDQgNi4zMTQsOTMuNzQ0IEw2LjEzMSw5My43MzMgQzYuMTMxLDkzLjczNCAzLjM0OSw5My4yOTMgMy4xMjgsOTEuNjA5IEMyLjU2OCw4Ny4zMjcgMS45NzcsODIuODI4IDEuOTYzLDcwLjI1OCBDMS45NDgsNTcuNDc0IDIuODYsNTAuNTEzIDIuODYsNTAuNTEzIEMzLjU3LDQ2LjM2NyA1LjY0Nyw0NS4zMjEgNS42NDcsNDUuMzIxIEM1LjY0Nyw0NS4zMjEgOC4xNDgsNDMuODg3IDEyLjIzOCw0MS41NDIgTDEyLjI0NSw0MS40MTQgTDEyLjI0NSw0MS4zNzEgQzEyLjI0NSw0MS4yNTQgMTIuMjQ1LDQwLjkyNSAxMi4yNDMsNDAuNzkzIEMxMi4yNCw0MC42ODYgMTIuMzAyLDQwLjU4MyAxMi40MzQsNDAuNTA4IEwxOC40NDYsMzcuMDM2IEMxOC41NzQsMzYuOTYyIDE4Ljc0NiwzNi45MjYgMTguOTI3LDM2LjkyNiBDMTkuMTQ1LDM2LjkyNiAxOS4zNzYsMzYuOTc5IDE5LjU1NCwzNy4wODIgQzE5Ljc0NywzNy4xOTQgMTkuODM5LDM3LjM0IDE5LjgyMiwzNy40NzQgTDIwLjAzMywzNy4wNzIgQzQxLjgwNiwyNC41ODUgODAuNjM2LDIuMzE4IDgwLjc3NywyLjIzNiBDODAuODk0LDIuMTY4IDgxLjU4MywxLjcyOSA4Mi4zMTEsMS43MDEgTTgyLjMxMSwwLjcwNCBMODIuMjcyLDAuNzA1IEM4MS42NTQsMC43MjggODAuOTg5LDAuOTQ5IDgwLjI5OCwxLjM2MSBMODAuMjc3LDEuMzczIEM4MC4xMjksMS40NTggNTkuNzY4LDEzLjEzNSAxOS43NTgsMzYuMDc5IEMxOS41LDM1Ljk4MSAxOS4yMTQsMzUuOTI5IDE4LjkyNywzNS45MjkgQzE4LjU2MiwzNS45MjkgMTguMjIzLDM2LjAxMyAxNy45NDcsMzYuMTczIEwxMS45MzUsMzkuNjQ0IEMxMS40OTMsMzkuODk5IDExLjIzNiw0MC4zMzQgMTEuMjQ2LDQwLjgxIEwxMS4yNDcsNDAuOTYgTDUuMTY3LDQ0LjQ0NyBDNC43OTQsNDQuNjQ2IDIuNjI1LDQ1Ljk3OCAxLjg3Nyw1MC4zNDUgTDEuODcxLDUwLjM4NCBDMS44NjIsNTAuNDU0IDAuOTUxLDU3LjU1NyAwLjk2NSw3MC4yNTkgQzAuOTc5LDgyLjg3OSAxLjU2OCw4Ny4zNzUgMi4xMzcsOTEuNzI0IEwyLjEzOSw5MS43MzkgQzIuNDQ3LDk0LjA5NCA1LjYxNCw5NC42NjIgNS45NzUsOTQuNzE5IEw2LjAwOSw5NC43MjMgQzYuMTEsOTQuNzM2IDYuMjEzLDk0Ljc0MiA2LjMxNCw5NC43NDIgQzYuNzksOTQuNzQyIDcuMjYsOTQuNjEgNy43MSw5NC4zNSBMODIuOTgzLDUwLjc5OCBDODQuNzk0LDQ5LjcyNyA4NS45ODIsNDcuMzc1IDg2LjUyNSw0My44MDEgQzg3LjcxMSwzNS45ODcgODguMjU5LDEwLjcwNSA4Ni4yMjQsMy41MDIgQzg1Ljk3MSwyLjYwOSA4NS41MiwxLjk3NSA4NC44ODEsMS42MiBMODQuNzQ5LDEuNTU4IEw4Mi42NjQsMC43NjkgQzgyLjU1MSwwLjcyNSA4Mi40MzEsMC43MDQgODIuMzExLDAuNzA0IiBpZD0iRmlsbC0yIiBmaWxsPSIjNDU1QTY0Ij48L3BhdGg+CiAgICAgICAgICAgICAgICAgICAgPHBhdGggZD0iTTY2LjI2NywxMS41NjUgTDY3Ljc2MiwxMS45OTkgTDExLjQyMyw0NC4zMjUiIGlkPSJGaWxsLTMiIGZpbGw9IiNGRkZGRkYiPjwvcGF0aD4KICAgICAgICAgICAgICAgICAgICA8cGF0aCBkPSJNMTIuMjAyLDkwLjU0NSBDMTIuMDI5LDkwLjU0NSAxMS44NjIsOTAuNDU1IDExLjc2OSw5MC4yOTUgQzExLjYzMiw5MC4wNTcgMTEuNzEzLDg5Ljc1MiAxMS45NTIsODkuNjE0IEwzMC4zODksNzguOTY5IEMzMC42MjgsNzguODMxIDMwLjkzMyw3OC45MTMgMzEuMDcxLDc5LjE1MiBDMzEuMjA4LDc5LjM5IDMxLjEyNyw3OS42OTYgMzAuODg4LDc5LjgzMyBMMTIuNDUxLDkwLjQ3OCBMMTIuMjAyLDkwLjU0NSIgaWQ9IkZpbGwtNCIgZmlsbD0iIzYwN0Q4QiI+PC9wYXRoPgogICAgICAgICAgICAgICAgICAgIDxwYXRoIGQ9Ik0xMy43NjQsNDIuNjU0IEwxMy42NTYsNDIuNTkyIEwxMy43MDIsNDIuNDIxIEwxOC44MzcsMzkuNDU3IEwxOS4wMDcsMzkuNTAyIEwxOC45NjIsMzkuNjczIEwxMy44MjcsNDIuNjM3IEwxMy43NjQsNDIuNjU0IiBpZD0iRmlsbC01IiBmaWxsPSIjNjA3RDhCIj48L3BhdGg+CiAgICAgICAgICAgICAgICAgICAgPHBhdGggZD0iTTguNTIsOTAuMzc1IEw4LjUyLDQ2LjQyMSBMOC41ODMsNDYuMzg1IEw3NS44NCw3LjU1NCBMNzUuODQsNTEuNTA4IEw3NS43NzgsNTEuNTQ0IEw4LjUyLDkwLjM3NSBMOC41Miw5MC4zNzUgWiBNOC43Nyw0Ni41NjQgTDguNzcsODkuOTQ0IEw3NS41OTEsNTEuMzY1IEw3NS41OTEsNy45ODUgTDguNzcsNDYuNTY0IEw4Ljc3LDQ2LjU2NCBaIiBpZD0iRmlsbC02IiBmaWxsPSIjNjA3RDhCIj48L3BhdGg+CiAgICAgICAgICAgICAgICAgICAgPHBhdGggZD0iTTI0Ljk4Niw4My4xODIgQzI0Ljc1Niw4My4zMzEgMjQuMzc0LDgzLjU2NiAyNC4xMzcsODMuNzA1IEwxMi42MzIsOTAuNDA2IEMxMi4zOTUsOTAuNTQ1IDEyLjQyNiw5MC42NTggMTIuNyw5MC42NTggTDEzLjI2NSw5MC42NTggQzEzLjU0LDkwLjY1OCAxMy45NTgsOTAuNTQ1IDE0LjE5NSw5MC40MDYgTDI1LjcsODMuNzA1IEMyNS45MzcsODMuNTY2IDI2LjEyOCw4My40NTIgMjYuMTI1LDgzLjQ0OSBDMjYuMTIyLDgzLjQ0NyAyNi4xMTksODMuMjIgMjYuMTE5LDgyLjk0NiBDMjYuMTE5LDgyLjY3MiAyNS45MzEsODIuNTY5IDI1LjcwMSw4Mi43MTkgTDI0Ljk4Niw4My4xODIiIGlkPSJGaWxsLTciIGZpbGw9IiM2MDdEOEIiPjwvcGF0aD4KICAgICAgICAgICAgICAgICAgICA8cGF0aCBkPSJNMTMuMjY2LDkwLjc4MiBMMTIuNyw5MC43ODIgQzEyLjUsOTAuNzgyIDEyLjM4NCw5MC43MjYgMTIuMzU0LDkwLjYxNiBDMTIuMzI0LDkwLjUwNiAxMi4zOTcsOTAuMzk5IDEyLjU2OSw5MC4yOTkgTDI0LjA3NCw4My41OTcgQzI0LjMxLDgzLjQ1OSAyNC42ODksODMuMjI2IDI0LjkxOCw4My4wNzggTDI1LjYzMyw4Mi42MTQgQzI1LjcyMyw4Mi41NTUgMjUuODEzLDgyLjUyNSAyNS44OTksODIuNTI1IEMyNi4wNzEsODIuNTI1IDI2LjI0NCw4Mi42NTUgMjYuMjQ0LDgyLjk0NiBDMjYuMjQ0LDgzLjE2IDI2LjI0NSw4My4zMDkgMjYuMjQ3LDgzLjM4MyBMMjYuMjUzLDgzLjM4NyBMMjYuMjQ5LDgzLjQ1NiBDMjYuMjQ2LDgzLjUzMSAyNi4yNDYsODMuNTMxIDI1Ljc2Myw4My44MTIgTDE0LjI1OCw5MC41MTQgQzE0LDkwLjY2NSAxMy41NjQsOTAuNzgyIDEzLjI2Niw5MC43ODIgTDEzLjI2Niw5MC43ODIgWiBNMTIuNjY2LDkwLjUzMiBMMTIuNyw5MC41MzMgTDEzLjI2Niw5MC41MzMgQzEzLjUxOCw5MC41MzMgMTMuOTE1LDkwLjQyNSAxNC4xMzIsOTAuMjk5IEwyNS42MzcsODMuNTk3IEMyNS44MDUsODMuNDk5IDI1LjkzMSw4My40MjQgMjUuOTk4LDgzLjM4MyBDMjUuOTk0LDgzLjI5OSAyNS45OTQsODMuMTY1IDI1Ljk5NCw4Mi45NDYgTDI1Ljg5OSw4Mi43NzUgTDI1Ljc2OCw4Mi44MjQgTDI1LjA1NCw4My4yODcgQzI0LjgyMiw4My40MzcgMjQuNDM4LDgzLjY3MyAyNC4yLDgzLjgxMiBMMTIuNjk1LDkwLjUxNCBMMTIuNjY2LDkwLjUzMiBMMTIuNjY2LDkwLjUzMiBaIiBpZD0iRmlsbC04IiBmaWxsPSIjNjA3RDhCIj48L3BhdGg+CiAgICAgICAgICAgICAgICAgICAgPHBhdGggZD0iTTEzLjI2Niw4OS44NzEgTDEyLjcsODkuODcxIEMxMi41LDg5Ljg3MSAxMi4zODQsODkuODE1IDEyLjM1NCw4OS43MDUgQzEyLjMyNCw4OS41OTUgMTIuMzk3LDg5LjQ4OCAxMi41NjksODkuMzg4IEwyNC4wNzQsODIuNjg2IEMyNC4zMzIsODIuNTM1IDI0Ljc2OCw4Mi40MTggMjUuMDY3LDgyLjQxOCBMMjUuNjMyLDgyLjQxOCBDMjUuODMyLDgyLjQxOCAyNS45NDgsODIuNDc0IDI1Ljk3OCw4Mi41ODQgQzI2LjAwOCw4Mi42OTQgMjUuOTM1LDgyLjgwMSAyNS43NjMsODIuOTAxIEwxNC4yNTgsODkuNjAzIEMxNCw4OS43NTQgMTMuNTY0LDg5Ljg3MSAxMy4yNjYsODkuODcxIEwxMy4yNjYsODkuODcxIFogTTEyLjY2Niw4OS42MjEgTDEyLjcsODkuNjIyIEwxMy4yNjYsODkuNjIyIEMxMy41MTgsODkuNjIyIDEzLjkxNSw4OS41MTUgMTQuMTMyLDg5LjM4OCBMMjUuNjM3LDgyLjY4NiBMMjUuNjY3LDgyLjY2OCBMMjUuNjMyLDgyLjY2NyBMMjUuMDY3LDgyLjY2NyBDMjQuODE1LDgyLjY2NyAyNC40MTgsODIuNzc1IDI0LjIsODIuOTAxIEwxMi42OTUsODkuNjAzIEwxMi42NjYsODkuNjIxIEwxMi42NjYsODkuNjIxIFoiIGlkPSJGaWxsLTkiIGZpbGw9IiM2MDdEOEIiPjwvcGF0aD4KICAgICAgICAgICAgICAgICAgICA8cGF0aCBkPSJNMTIuMzcsOTAuODAxIEwxMi4zNyw4OS41NTQgTDEyLjM3LDkwLjgwMSIgaWQ9IkZpbGwtMTAiIGZpbGw9IiM2MDdEOEIiPjwvcGF0aD4KICAgICAgICAgICAgICAgICAgICA8cGF0aCBkPSJNNi4xMyw5My45MDEgQzUuMzc5LDkzLjgwOCA0LjgxNiw5My4xNjQgNC42OTEsOTIuNTI1IEMzLjg2LDg4LjI4NyAzLjU0LDgzLjc0MyAzLjUyNiw3MS4xNzMgQzMuNTExLDU4LjM4OSA0LjQyMyw1MS40MjggNC40MjMsNTEuNDI4IEM1LjEzNCw0Ny4yODIgNy4yMSw0Ni4yMzYgNy4yMSw0Ni4yMzYgQzcuMjEsNDYuMjM2IDgxLjY2NywzLjI1IDgyLjA2OSwzLjAxNyBDODIuMjkyLDIuODg4IDg0LjU1NiwxLjQzMyA4NS4yNjQsMy45NCBDODcuMjE0LDEwLjg0IDg2Ljc1MiwzNS44MjcgODUuNTM5LDQzLjgxOCBDODUuMTUsNDYuMzgzIDg0LjI5MSw0OS4wMzMgODIuNDgzLDUwLjEwMSBMNy4yMSw5My42NTMgQzYuODI4LDkzLjg3NCA2LjQ2MSw5My45NDEgNi4xMyw5My45MDEgQzYuMTMsOTMuOTAxIDMuMzQ5LDkzLjQ2IDMuMTI5LDkxLjc3NiBDMi41NjgsODcuNDk1IDEuOTc3LDgyLjk5NSAxLjk2Miw3MC40MjUgQzEuOTQ4LDU3LjY0MSAyLjg2LDUwLjY4IDIuODYsNTAuNjggQzMuNTcsNDYuNTM0IDUuNjQ3LDQ1LjQ4OSA1LjY0Nyw0NS40ODkgQzUuNjQ2LDQ1LjQ4OSA4LjA2NSw0NC4wOTIgMTIuMjQ1LDQxLjY3OSBMMTMuMTE2LDQxLjU2IEwxOS43MTUsMzcuNzMgTDE5Ljc2MSwzNy4yNjkgTDYuMTMsOTMuOTAxIiBpZD0iRmlsbC0xMSIgZmlsbD0iI0ZBRkFGQSI+PC9wYXRoPgogICAgICAgICAgICAgICAgICAgIDxwYXRoIGQ9Ik02LjMxNyw5NC4xNjEgTDYuMTAyLDk0LjE0OCBMNi4xMDEsOTQuMTQ4IEw1Ljg1Nyw5NC4xMDEgQzUuMTM4LDkzLjk0NSAzLjA4NSw5My4zNjUgMi44ODEsOTEuODA5IEMyLjMxMyw4Ny40NjkgMS43MjcsODIuOTk2IDEuNzEzLDcwLjQyNSBDMS42OTksNTcuNzcxIDIuNjA0LDUwLjcxOCAyLjYxMyw1MC42NDggQzMuMzM4LDQ2LjQxNyA1LjQ0NSw0NS4zMSA1LjUzNSw0NS4yNjYgTDEyLjE2Myw0MS40MzkgTDEzLjAzMyw0MS4zMiBMMTkuNDc5LDM3LjU3OCBMMTkuNTEzLDM3LjI0NCBDMTkuNTI2LDM3LjEwNyAxOS42NDcsMzcuMDA4IDE5Ljc4NiwzNy4wMjEgQzE5LjkyMiwzNy4wMzQgMjAuMDIzLDM3LjE1NiAyMC4wMDksMzcuMjkzIEwxOS45NSwzNy44ODIgTDEzLjE5OCw0MS44MDEgTDEyLjMyOCw0MS45MTkgTDUuNzcyLDQ1LjcwNCBDNS43NDEsNDUuNzIgMy43ODIsNDYuNzcyIDMuMTA2LDUwLjcyMiBDMy4wOTksNTAuNzgyIDIuMTk4LDU3LjgwOCAyLjIxMiw3MC40MjQgQzIuMjI2LDgyLjk2MyAyLjgwOSw4Ny40MiAzLjM3Myw5MS43MjkgQzMuNDY0LDkyLjQyIDQuMDYyLDkyLjg4MyA0LjY4Miw5My4xODEgQzQuNTY2LDkyLjk4NCA0LjQ4Niw5Mi43NzYgNC40NDYsOTIuNTcyIEMzLjY2NSw4OC41ODggMy4yOTEsODQuMzcgMy4yNzYsNzEuMTczIEMzLjI2Miw1OC41MiA0LjE2Nyw1MS40NjYgNC4xNzYsNTEuMzk2IEM0LjkwMSw0Ny4xNjUgNy4wMDgsNDYuMDU5IDcuMDk4LDQ2LjAxNCBDNy4wOTQsNDYuMDE1IDgxLjU0MiwzLjAzNCA4MS45NDQsMi44MDIgTDgxLjk3MiwyLjc4NSBDODIuODc2LDIuMjQ3IDgzLjY5MiwyLjA5NyA4NC4zMzIsMi4zNTIgQzg0Ljg4NywyLjU3MyA4NS4yODEsMy4wODUgODUuNTA0LDMuODcyIEM4Ny41MTgsMTEgODYuOTY0LDM2LjA5MSA4NS43ODUsNDMuODU1IEM4NS4yNzgsNDcuMTk2IDg0LjIxLDQ5LjM3IDgyLjYxLDUwLjMxNyBMNy4zMzUsOTMuODY5IEM2Ljk5OSw5NC4wNjMgNi42NTgsOTQuMTYxIDYuMzE3LDk0LjE2MSBMNi4zMTcsOTQuMTYxIFogTTYuMTcsOTMuNjU0IEM2LjQ2Myw5My42OSA2Ljc3NCw5My42MTcgNy4wODUsOTMuNDM3IEw4Mi4zNTgsNDkuODg2IEM4NC4xODEsNDguODA4IDg0Ljk2LDQ1Ljk3MSA4NS4yOTIsNDMuNzggQzg2LjQ2NiwzNi4wNDkgODcuMDIzLDExLjA4NSA4NS4wMjQsNC4wMDggQzg0Ljg0NiwzLjM3NyA4NC41NTEsMi45NzYgODQuMTQ4LDIuODE2IEM4My42NjQsMi42MjMgODIuOTgyLDIuNzY0IDgyLjIyNywzLjIxMyBMODIuMTkzLDMuMjM0IEM4MS43OTEsMy40NjYgNy4zMzUsNDYuNDUyIDcuMzM1LDQ2LjQ1MiBDNy4zMDQsNDYuNDY5IDUuMzQ2LDQ3LjUyMSA0LjY2OSw1MS40NzEgQzQuNjYyLDUxLjUzIDMuNzYxLDU4LjU1NiAzLjc3NSw3MS4xNzMgQzMuNzksODQuMzI4IDQuMTYxLDg4LjUyNCA0LjkzNiw5Mi40NzYgQzUuMDI2LDkyLjkzNyA1LjQxMiw5My40NTkgNS45NzMsOTMuNjE1IEM2LjA4Nyw5My42NCA2LjE1OCw5My42NTIgNi4xNjksOTMuNjU0IEw2LjE3LDkzLjY1NCBMNi4xNyw5My42NTQgWiIgaWQ9IkZpbGwtMTIiIGZpbGw9IiM0NTVBNjQiPjwvcGF0aD4KICAgICAgICAgICAgICAgICAgICA8cGF0aCBkPSJNNy4zMTcsNjguOTgyIEM3LjgwNiw2OC43MDEgOC4yMDIsNjguOTI2IDguMjAyLDY5LjQ4NyBDOC4yMDIsNzAuMDQ3IDcuODA2LDcwLjczIDcuMzE3LDcxLjAxMiBDNi44MjksNzEuMjk0IDYuNDMzLDcxLjA2OSA2LjQzMyw3MC41MDggQzYuNDMzLDY5Ljk0OCA2LjgyOSw2OS4yNjUgNy4zMTcsNjguOTgyIiBpZD0iRmlsbC0xMyIgZmlsbD0iI0ZGRkZGRiI+PC9wYXRoPgogICAgICAgICAgICAgICAgICAgIDxwYXRoIGQ9Ik02LjkyLDcxLjEzMyBDNi42MzEsNzEuMTMzIDYuNDMzLDcwLjkwNSA2LjQzMyw3MC41MDggQzYuNDMzLDY5Ljk0OCA2LjgyOSw2OS4yNjUgNy4zMTcsNjguOTgyIEM3LjQ2LDY4LjkgNy41OTUsNjguODYxIDcuNzE0LDY4Ljg2MSBDOC4wMDMsNjguODYxIDguMjAyLDY5LjA5IDguMjAyLDY5LjQ4NyBDOC4yMDIsNzAuMDQ3IDcuODA2LDcwLjczIDcuMzE3LDcxLjAxMiBDNy4xNzQsNzEuMDk0IDcuMDM5LDcxLjEzMyA2LjkyLDcxLjEzMyBNNy43MTQsNjguNjc0IEM3LjU1Nyw2OC42NzQgNy4zOTIsNjguNzIzIDcuMjI0LDY4LjgyMSBDNi42NzYsNjkuMTM4IDYuMjQ2LDY5Ljg3OSA2LjI0Niw3MC41MDggQzYuMjQ2LDcwLjk5NCA2LjUxNyw3MS4zMiA2LjkyLDcxLjMyIEM3LjA3OCw3MS4zMiA3LjI0Myw3MS4yNzEgNy40MTEsNzEuMTc0IEM3Ljk1OSw3MC44NTcgOC4zODksNzAuMTE3IDguMzg5LDY5LjQ4NyBDOC4zODksNjkuMDAxIDguMTE3LDY4LjY3NCA3LjcxNCw2OC42NzQiIGlkPSJGaWxsLTE0IiBmaWxsPSIjODA5N0EyIj48L3BhdGg+CiAgICAgICAgICAgICAgICAgICAgPHBhdGggZD0iTTYuOTIsNzAuOTQ3IEM2LjY0OSw3MC45NDcgNi42MjEsNzAuNjQgNi42MjEsNzAuNTA4IEM2LjYyMSw3MC4wMTcgNi45ODIsNjkuMzkyIDcuNDExLDY5LjE0NSBDNy41MjEsNjkuMDgyIDcuNjI1LDY5LjA0OSA3LjcxNCw2OS4wNDkgQzcuOTg2LDY5LjA0OSA4LjAxNSw2OS4zNTUgOC4wMTUsNjkuNDg3IEM4LjAxNSw2OS45NzggNy42NTIsNzAuNjAzIDcuMjI0LDcwLjg1MSBDNy4xMTUsNzAuOTE0IDcuMDEsNzAuOTQ3IDYuOTIsNzAuOTQ3IE03LjcxNCw2OC44NjEgQzcuNTk1LDY4Ljg2MSA3LjQ2LDY4LjkgNy4zMTcsNjguOTgyIEM2LjgyOSw2OS4yNjUgNi40MzMsNjkuOTQ4IDYuNDMzLDcwLjUwOCBDNi40MzMsNzAuOTA1IDYuNjMxLDcxLjEzMyA2LjkyLDcxLjEzMyBDNy4wMzksNzEuMTMzIDcuMTc0LDcxLjA5NCA3LjMxNyw3MS4wMTIgQzcuODA2LDcwLjczIDguMjAyLDcwLjA0NyA4LjIwMiw2OS40ODcgQzguMjAyLDY5LjA5IDguMDAzLDY4Ljg2MSA3LjcxNCw2OC44NjEiIGlkPSJGaWxsLTE1IiBmaWxsPSIjODA5N0EyIj48L3BhdGg+CiAgICAgICAgICAgICAgICAgICAgPHBhdGggZD0iTTcuNDQ0LDg1LjM1IEM3LjcwOCw4NS4xOTggNy45MjEsODUuMzE5IDcuOTIxLDg1LjYyMiBDNy45MjEsODUuOTI1IDcuNzA4LDg2LjI5MiA3LjQ0NCw4Ni40NDQgQzcuMTgxLDg2LjU5NyA2Ljk2Nyw4Ni40NzUgNi45NjcsODYuMTczIEM2Ljk2Nyw4NS44NzEgNy4xODEsODUuNTAyIDcuNDQ0LDg1LjM1IiBpZD0iRmlsbC0xNiIgZmlsbD0iI0ZGRkZGRiI+PC9wYXRoPgogICAgICAgICAgICAgICAgICAgIDxwYXRoIGQ9Ik03LjIzLDg2LjUxIEM3LjA3NCw4Ni41MSA2Ljk2Nyw4Ni4zODcgNi45NjcsODYuMTczIEM2Ljk2Nyw4NS44NzEgNy4xODEsODUuNTAyIDcuNDQ0LDg1LjM1IEM3LjUyMSw4NS4zMDUgNy41OTQsODUuMjg0IDcuNjU4LDg1LjI4NCBDNy44MTQsODUuMjg0IDcuOTIxLDg1LjQwOCA3LjkyMSw4NS42MjIgQzcuOTIxLDg1LjkyNSA3LjcwOCw4Ni4yOTIgNy40NDQsODYuNDQ0IEM3LjM2Nyw4Ni40ODkgNy4yOTQsODYuNTEgNy4yMyw4Ni41MSBNNy42NTgsODUuMDk4IEM3LjU1OCw4NS4wOTggNy40NTUsODUuMTI3IDcuMzUxLDg1LjE4OCBDNy4wMzEsODUuMzczIDYuNzgxLDg1LjgwNiA2Ljc4MSw4Ni4xNzMgQzYuNzgxLDg2LjQ4MiA2Ljk2Niw4Ni42OTcgNy4yMyw4Ni42OTcgQzcuMzMsODYuNjk3IDcuNDMzLDg2LjY2NiA3LjUzOCw4Ni42MDcgQzcuODU4LDg2LjQyMiA4LjEwOCw4NS45ODkgOC4xMDgsODUuNjIyIEM4LjEwOCw4NS4zMTMgNy45MjMsODUuMDk4IDcuNjU4LDg1LjA5OCIgaWQ9IkZpbGwtMTciIGZpbGw9IiM4MDk3QTIiPjwvcGF0aD4KICAgICAgICAgICAgICAgICAgICA8cGF0aCBkPSJNNy4yMyw4Ni4zMjIgTDcuMTU0LDg2LjE3MyBDNy4xNTQsODUuOTM4IDcuMzMzLDg1LjYyOSA3LjUzOCw4NS41MTIgTDcuNjU4LDg1LjQ3MSBMNy43MzQsODUuNjIyIEM3LjczNCw4NS44NTYgNy41NTUsODYuMTY0IDcuMzUxLDg2LjI4MiBMNy4yMyw4Ni4zMjIgTTcuNjU4LDg1LjI4NCBDNy41OTQsODUuMjg0IDcuNTIxLDg1LjMwNSA3LjQ0NCw4NS4zNSBDNy4xODEsODUuNTAyIDYuOTY3LDg1Ljg3MSA2Ljk2Nyw4Ni4xNzMgQzYuOTY3LDg2LjM4NyA3LjA3NCw4Ni41MSA3LjIzLDg2LjUxIEM3LjI5NCw4Ni41MSA3LjM2Nyw4Ni40ODkgNy40NDQsODYuNDQ0IEM3LjcwOCw4Ni4yOTIgNy45MjEsODUuOTI1IDcuOTIxLDg1LjYyMiBDNy45MjEsODUuNDA4IDcuODE0LDg1LjI4NCA3LjY1OCw4NS4yODQiIGlkPSJGaWxsLTE4IiBmaWxsPSIjODA5N0EyIj48L3BhdGg+CiAgICAgICAgICAgICAgICAgICAgPHBhdGggZD0iTTc3LjI3OCw3Ljc2OSBMNzcuMjc4LDUxLjQzNiBMMTAuMjA4LDkwLjE2IEwxMC4yMDgsNDYuNDkzIEw3Ny4yNzgsNy43NjkiIGlkPSJGaWxsLTE5IiBmaWxsPSIjNDU1QTY0Ij48L3BhdGg+CiAgICAgICAgICAgICAgICAgICAgPHBhdGggZD0iTTEwLjA4Myw5MC4zNzUgTDEwLjA4Myw0Ni40MjEgTDEwLjE0Niw0Ni4zODUgTDc3LjQwMyw3LjU1NCBMNzcuNDAzLDUxLjUwOCBMNzcuMzQxLDUxLjU0NCBMMTAuMDgzLDkwLjM3NSBMMTAuMDgzLDkwLjM3NSBaIE0xMC4zMzMsNDYuNTY0IEwxMC4zMzMsODkuOTQ0IEw3Ny4xNTQsNTEuMzY1IEw3Ny4xNTQsNy45ODUgTDEwLjMzMyw0Ni41NjQgTDEwLjMzMyw0Ni41NjQgWiIgaWQ9IkZpbGwtMjAiIGZpbGw9IiM2MDdEOEIiPjwvcGF0aD4KICAgICAgICAgICAgICAgIDwvZz4KICAgICAgICAgICAgICAgIDxwYXRoIGQ9Ik0xMjUuNzM3LDg4LjY0NyBMMTE4LjA5OCw5MS45ODEgTDExOC4wOTgsODQgTDEwNi42MzksODguNzEzIEwxMDYuNjM5LDk2Ljk4MiBMOTksMTAwLjMxNSBMMTEyLjM2OSwxMDMuOTYxIEwxMjUuNzM3LDg4LjY0NyIgaWQ9IkltcG9ydGVkLUxheWVycy1Db3B5LTIiIGZpbGw9IiM0NTVBNjQiIHNrZXRjaDp0eXBlPSJNU1NoYXBlR3JvdXAiPjwvcGF0aD4KICAgICAgICAgICAgPC9nPgogICAgICAgIDwvZz4KICAgIDwvZz4KPC9zdmc+');
};
module.exports = RotateInstructions;
},{"./util.js":68}],63:[function(_dereq_,module,exports){
/*
* Copyright 2015 Google Inc. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var SensorSample = _dereq_('./sensor-sample.js');
var MathUtil = _dereq_('../math-util.js');
var Util = _dereq_('../util.js');
/**
* An implementation of a simple complementary filter, which fuses gyroscope and
* accelerometer data from the 'devicemotion' event.
*
* Accelerometer data is very noisy, but stable over the long term.
* Gyroscope data is smooth, but tends to drift over the long term.
*
* This fusion is relatively simple:
* 1. Get orientation estimates from accelerometer by applying a low-pass filter
* on that data.
* 2. Get orientation estimates from gyroscope by integrating over time.
* 3. Combine the two estimates, weighing (1) in the long term, but (2) for the
* short term.
*/
function ComplementaryFilter(kFilter) {
this.kFilter = kFilter;
// Raw sensor measurements.
this.currentAccelMeasurement = new SensorSample();
this.currentGyroMeasurement = new SensorSample();
this.previousGyroMeasurement = new SensorSample();
// Set default look direction to be in the correct direction.
if (Util.isIOS()) {
this.filterQ = new MathUtil.Quaternion(-1, 0, 0, 1);
} else {
this.filterQ = new MathUtil.Quaternion(1, 0, 0, 1);
}
this.previousFilterQ = new MathUtil.Quaternion();
this.previousFilterQ.copy(this.filterQ);
// Orientation based on the accelerometer.
this.accelQ = new MathUtil.Quaternion();
// Whether or not the orientation has been initialized.
this.isOrientationInitialized = false;
// Running estimate of gravity based on the current orientation.
this.estimatedGravity = new MathUtil.Vector3();
// Measured gravity based on accelerometer.
this.measuredGravity = new MathUtil.Vector3();
// Debug only quaternion of gyro-based orientation.
this.gyroIntegralQ = new MathUtil.Quaternion();
}
ComplementaryFilter.prototype.addAccelMeasurement = function(vector, timestampS) {
this.currentAccelMeasurement.set(vector, timestampS);
};
ComplementaryFilter.prototype.addGyroMeasurement = function(vector, timestampS) {
this.currentGyroMeasurement.set(vector, timestampS);
var deltaT = timestampS - this.previousGyroMeasurement.timestampS;
if (Util.isTimestampDeltaValid(deltaT)) {
this.run_();
}
this.previousGyroMeasurement.copy(this.currentGyroMeasurement);
};
ComplementaryFilter.prototype.run_ = function() {
if (!this.isOrientationInitialized) {
this.accelQ = this.accelToQuaternion_(this.currentAccelMeasurement.sample);
this.previousFilterQ.copy(this.accelQ);
this.isOrientationInitialized = true;
return;
}
var deltaT = this.currentGyroMeasurement.timestampS -
this.previousGyroMeasurement.timestampS;
// Convert gyro rotation vector to a quaternion delta.
var gyroDeltaQ = this.gyroToQuaternionDelta_(this.currentGyroMeasurement.sample, deltaT);
this.gyroIntegralQ.multiply(gyroDeltaQ);
// filter_1 = K * (filter_0 + gyro * dT) + (1 - K) * accel.
this.filterQ.copy(this.previousFilterQ);
this.filterQ.multiply(gyroDeltaQ);
// Calculate the delta between the current estimated gravity and the real
// gravity vector from accelerometer.
var invFilterQ = new MathUtil.Quaternion();
invFilterQ.copy(this.filterQ);
invFilterQ.inverse();
this.estimatedGravity.set(0, 0, -1);
this.estimatedGravity.applyQuaternion(invFilterQ);
this.estimatedGravity.normalize();
this.measuredGravity.copy(this.currentAccelMeasurement.sample);
this.measuredGravity.normalize();
// Compare estimated gravity with measured gravity, get the delta quaternion
// between the two.
var deltaQ = new MathUtil.Quaternion();
deltaQ.setFromUnitVectors(this.estimatedGravity, this.measuredGravity);
deltaQ.inverse();
if (Util.isDebug()) {
console.log('Delta: %d deg, G_est: (%s, %s, %s), G_meas: (%s, %s, %s)',
MathUtil.radToDeg * Util.getQuaternionAngle(deltaQ),
(this.estimatedGravity.x).toFixed(1),
(this.estimatedGravity.y).toFixed(1),
(this.estimatedGravity.z).toFixed(1),
(this.measuredGravity.x).toFixed(1),
(this.measuredGravity.y).toFixed(1),
(this.measuredGravity.z).toFixed(1));
}
// Calculate the SLERP target: current orientation plus the measured-estimated
// quaternion delta.
var targetQ = new MathUtil.Quaternion();
targetQ.copy(this.filterQ);
targetQ.multiply(deltaQ);
// SLERP factor: 0 is pure gyro, 1 is pure accel.
this.filterQ.slerp(targetQ, 1 - this.kFilter);
this.previousFilterQ.copy(this.filterQ);
};
ComplementaryFilter.prototype.getOrientation = function() {
return this.filterQ;
};
ComplementaryFilter.prototype.accelToQuaternion_ = function(accel) {
var normAccel = new MathUtil.Vector3();
normAccel.copy(accel);
normAccel.normalize();
var quat = new MathUtil.Quaternion();
quat.setFromUnitVectors(new MathUtil.Vector3(0, 0, -1), normAccel);
quat.inverse();
return quat;
};
ComplementaryFilter.prototype.gyroToQuaternionDelta_ = function(gyro, dt) {
// Extract axis and angle from the gyroscope data.
var quat = new MathUtil.Quaternion();
var axis = new MathUtil.Vector3();
axis.copy(gyro);
axis.normalize();
quat.setFromAxisAngle(axis, gyro.length() * dt);
return quat;
};
module.exports = ComplementaryFilter;
},{"../math-util.js":59,"../util.js":68,"./sensor-sample.js":66}],64:[function(_dereq_,module,exports){
/*
* Copyright 2015 Google Inc. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var ComplementaryFilter = _dereq_('./complementary-filter.js');
var PosePredictor = _dereq_('./pose-predictor.js');
var TouchPanner = _dereq_('../touch-panner.js');
var MathUtil = _dereq_('../math-util.js');
var Util = _dereq_('../util.js');
/**
* The pose sensor, implemented using DeviceMotion APIs.
*/
function FusionPoseSensor() {
this.deviceId = 'webvr-polyfill:fused';
this.deviceName = 'VR Position Device (webvr-polyfill:fused)';
this.accelerometer = new MathUtil.Vector3();
this.gyroscope = new MathUtil.Vector3();
this.start();
this.filter = new ComplementaryFilter(window.WebVRConfig.K_FILTER);
this.posePredictor = new PosePredictor(window.WebVRConfig.PREDICTION_TIME_S);
this.touchPanner = new TouchPanner();
this.filterToWorldQ = new MathUtil.Quaternion();
// Set the filter to world transform, depending on OS.
if (Util.isIOS()) {
this.filterToWorldQ.setFromAxisAngle(new MathUtil.Vector3(1, 0, 0), Math.PI / 2);
} else {
this.filterToWorldQ.setFromAxisAngle(new MathUtil.Vector3(1, 0, 0), -Math.PI / 2);
}
this.inverseWorldToScreenQ = new MathUtil.Quaternion();
this.worldToScreenQ = new MathUtil.Quaternion();
this.originalPoseAdjustQ = new MathUtil.Quaternion();
this.originalPoseAdjustQ.setFromAxisAngle(new MathUtil.Vector3(0, 0, 1),
-window.orientation * Math.PI / 180);
this.setScreenTransform_();
// Adjust this filter for being in landscape mode.
if (Util.isLandscapeMode()) {
this.filterToWorldQ.multiply(this.inverseWorldToScreenQ);
}
// Keep track of a reset transform for resetSensor.
this.resetQ = new MathUtil.Quaternion();
this.isFirefoxAndroid = Util.isFirefoxAndroid();
this.isIOS = Util.isIOS();
this.orientationOut_ = new Float32Array(4);
}
FusionPoseSensor.prototype.getPosition = function() {
// This PoseSensor doesn't support position
return null;
};
FusionPoseSensor.prototype.getOrientation = function() {
// Convert from filter space to the the same system used by the
// deviceorientation event.
var orientation = this.filter.getOrientation();
// Predict orientation.
this.predictedQ = this.posePredictor.getPrediction(orientation, this.gyroscope, this.previousTimestampS);
// Convert to THREE coordinate system: -Z forward, Y up, X right.
var out = new MathUtil.Quaternion();
out.copy(this.filterToWorldQ);
out.multiply(this.resetQ);
if (!window.WebVRConfig.TOUCH_PANNER_DISABLED) {
out.multiply(this.touchPanner.getOrientation());
}
out.multiply(this.predictedQ);
out.multiply(this.worldToScreenQ);
// Handle the yaw-only case.
if (window.WebVRConfig.YAW_ONLY) {
// Make a quaternion that only turns around the Y-axis.
out.x = 0;
out.z = 0;
out.normalize();
}
this.orientationOut_[0] = out.x;
this.orientationOut_[1] = out.y;
this.orientationOut_[2] = out.z;
this.orientationOut_[3] = out.w;
return this.orientationOut_;
};
FusionPoseSensor.prototype.resetPose = function() {
// Reduce to inverted yaw-only.
this.resetQ.copy(this.filter.getOrientation());
this.resetQ.x = 0;
this.resetQ.y = 0;
this.resetQ.z *= -1;
this.resetQ.normalize();
// Take into account extra transformations in landscape mode.
if (Util.isLandscapeMode()) {
this.resetQ.multiply(this.inverseWorldToScreenQ);
}
// Take into account original pose.
this.resetQ.multiply(this.originalPoseAdjustQ);
if (!window.WebVRConfig.TOUCH_PANNER_DISABLED) {
this.touchPanner.resetSensor();
}
};
FusionPoseSensor.prototype.onDeviceMotion_ = function(deviceMotion) {
this.updateDeviceMotion_(deviceMotion);
};
FusionPoseSensor.prototype.updateDeviceMotion_ = function(deviceMotion) {
var accGravity = deviceMotion.accelerationIncludingGravity;
var rotRate = deviceMotion.rotationRate;
var timestampS = deviceMotion.timeStamp / 1000;
var deltaS = timestampS - this.previousTimestampS;
if (deltaS <= Util.MIN_TIMESTEP || deltaS > Util.MAX_TIMESTEP) {
console.warn('Invalid timestamps detected. Time step between successive ' +
'gyroscope sensor samples is very small or not monotonic');
this.previousTimestampS = timestampS;
return;
}
this.accelerometer.set(-accGravity.x, -accGravity.y, -accGravity.z);
this.gyroscope.set(rotRate.alpha, rotRate.beta, rotRate.gamma);
// With iOS and Firefox Android, rotationRate is reported in degrees,
// so we first convert to radians.
if (this.isIOS || this.isFirefoxAndroid) {
this.gyroscope.multiplyScalar(Math.PI / 180);
}
this.filter.addAccelMeasurement(this.accelerometer, timestampS);
this.filter.addGyroMeasurement(this.gyroscope, timestampS);
this.previousTimestampS = timestampS;
};
FusionPoseSensor.prototype.onOrientationChange_ = function(screenOrientation) {
this.setScreenTransform_();
};
/**
* This is only needed if we are in an cross origin iframe on iOS to work around
* this issue: https://bugs.webkit.org/show_bug.cgi?id=152299.
*/
FusionPoseSensor.prototype.onMessage_ = function(event) {
var message = event.data;
// If there's no message type, ignore it.
if (!message || !message.type) {
return;
}
// Ignore all messages that aren't devicemotion.
var type = message.type.toLowerCase();
if (type !== 'devicemotion') {
return;
}
// Update device motion.
this.updateDeviceMotion_(message.deviceMotionEvent);
};
FusionPoseSensor.prototype.setScreenTransform_ = function() {
this.worldToScreenQ.set(0, 0, 0, 1);
switch (window.orientation) {
case 0:
break;
case 90:
this.worldToScreenQ.setFromAxisAngle(new MathUtil.Vector3(0, 0, 1), -Math.PI / 2);
break;
case -90:
this.worldToScreenQ.setFromAxisAngle(new MathUtil.Vector3(0, 0, 1), Math.PI / 2);
break;
case 180:
// TODO.
break;
}
this.inverseWorldToScreenQ.copy(this.worldToScreenQ);
this.inverseWorldToScreenQ.inverse();
};
FusionPoseSensor.prototype.start = function() {
this.onDeviceMotionCallback_ = this.onDeviceMotion_.bind(this);
this.onOrientationChangeCallback_ = this.onOrientationChange_.bind(this);
this.onMessageCallback_ = this.onMessage_.bind(this);
// Only listen for postMessages if we're in an iOS and embedded inside a cross
// domain IFrame. In this case, the polyfill can still work if the containing
// page sends synthetic devicemotion events. For an example of this, see
// iframe-message-sender.js in VR View: https://goo.gl/XDtvFZ
if (Util.isIOS() && Util.isInsideCrossDomainIFrame()) {
window.addEventListener('message', this.onMessageCallback_);
}
window.addEventListener('orientationchange', this.onOrientationChangeCallback_);
window.addEventListener('devicemotion', this.onDeviceMotionCallback_);
};
FusionPoseSensor.prototype.stop = function() {
window.removeEventListener('devicemotion', this.onDeviceMotionCallback_);
window.removeEventListener('orientationchange', this.onOrientationChangeCallback_);
window.removeEventListener('message', this.onMessageCallback_);
};
module.exports = FusionPoseSensor;
},{"../math-util.js":59,"../touch-panner.js":67,"../util.js":68,"./complementary-filter.js":63,"./pose-predictor.js":65}],65:[function(_dereq_,module,exports){
/*
* Copyright 2015 Google Inc. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var MathUtil = _dereq_('../math-util');
var Util = _dereq_('../util');
/**
* Given an orientation and the gyroscope data, predicts the future orientation
* of the head. This makes rendering appear faster.
*
* Also see: http://msl.cs.uiuc.edu/~lavalle/papers/LavYerKatAnt14.pdf
*
* @param {Number} predictionTimeS time from head movement to the appearance of
* the corresponding image.
*/
function PosePredictor(predictionTimeS) {
this.predictionTimeS = predictionTimeS;
// The quaternion corresponding to the previous state.
this.previousQ = new MathUtil.Quaternion();
// Previous time a prediction occurred.
this.previousTimestampS = null;
// The delta quaternion that adjusts the current pose.
this.deltaQ = new MathUtil.Quaternion();
// The output quaternion.
this.outQ = new MathUtil.Quaternion();
}
PosePredictor.prototype.getPrediction = function(currentQ, gyro, timestampS) {
if (!this.previousTimestampS) {
this.previousQ.copy(currentQ);
this.previousTimestampS = timestampS;
return currentQ;
}
// Calculate axis and angle based on gyroscope rotation rate data.
var axis = new MathUtil.Vector3();
axis.copy(gyro);
axis.normalize();
var angularSpeed = gyro.length();
// If we're rotating slowly, don't do prediction.
if (angularSpeed < MathUtil.degToRad * 20) {
if (Util.isDebug()) {
console.log('Moving slowly, at %s deg/s: no prediction',
(MathUtil.radToDeg * angularSpeed).toFixed(1));
}
this.outQ.copy(currentQ);
this.previousQ.copy(currentQ);
return this.outQ;
}
// Get the predicted angle based on the time delta and latency.
var deltaT = timestampS - this.previousTimestampS;
var predictAngle = angularSpeed * this.predictionTimeS;
this.deltaQ.setFromAxisAngle(axis, predictAngle);
this.outQ.copy(this.previousQ);
this.outQ.multiply(this.deltaQ);
this.previousQ.copy(currentQ);
this.previousTimestampS = timestampS;
return this.outQ;
};
module.exports = PosePredictor;
},{"../math-util":59,"../util":68}],66:[function(_dereq_,module,exports){
function SensorSample(sample, timestampS) {
this.set(sample, timestampS);
};
SensorSample.prototype.set = function(sample, timestampS) {
this.sample = sample;
this.timestampS = timestampS;
};
SensorSample.prototype.copy = function(sensorSample) {
this.set(sensorSample.sample, sensorSample.timestampS);
};
module.exports = SensorSample;
},{}],67:[function(_dereq_,module,exports){
/*
* Copyright 2015 Google Inc. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var MathUtil = _dereq_('./math-util.js');
var Util = _dereq_('./util.js');
var ROTATE_SPEED = 0.5;
/**
* Provides a quaternion responsible for pre-panning the scene before further
* transformations due to device sensors.
*/
function TouchPanner() {
window.addEventListener('touchstart', this.onTouchStart_.bind(this));
window.addEventListener('touchmove', this.onTouchMove_.bind(this));
window.addEventListener('touchend', this.onTouchEnd_.bind(this));
this.isTouching = false;
this.rotateStart = new MathUtil.Vector2();
this.rotateEnd = new MathUtil.Vector2();
this.rotateDelta = new MathUtil.Vector2();
this.theta = 0;
this.orientation = new MathUtil.Quaternion();
}
TouchPanner.prototype.getOrientation = function() {
this.orientation.setFromEulerXYZ(0, 0, this.theta);
return this.orientation;
};
TouchPanner.prototype.resetSensor = function() {
this.theta = 0;
};
TouchPanner.prototype.onTouchStart_ = function(e) {
// Only respond if there is exactly one touch.
// Note that the Daydream controller passes in a `touchstart` event with
// no `touches` property, so we must check for that case too.
if (!e.touches || e.touches.length != 1) {
return;
}
this.rotateStart.set(e.touches[0].pageX, e.touches[0].pageY);
this.isTouching = true;
};
TouchPanner.prototype.onTouchMove_ = function(e) {
if (!this.isTouching) {
return;
}
this.rotateEnd.set(e.touches[0].pageX, e.touches[0].pageY);
this.rotateDelta.subVectors(this.rotateEnd, this.rotateStart);
this.rotateStart.copy(this.rotateEnd);
// On iOS, direction is inverted.
if (Util.isIOS()) {
this.rotateDelta.x *= -1;
}
var element = document.body;
this.theta += 2 * Math.PI * this.rotateDelta.x / element.clientWidth * ROTATE_SPEED;
};
TouchPanner.prototype.onTouchEnd_ = function(e) {
this.isTouching = false;
};
module.exports = TouchPanner;
},{"./math-util.js":59,"./util.js":68}],68:[function(_dereq_,module,exports){
/*
* Copyright 2015 Google Inc. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var Util = window.Util || {};
Util.MIN_TIMESTEP = 0.001;
Util.MAX_TIMESTEP = 1;
Util.base64 = function(mimeType, base64) {
return 'data:' + mimeType + ';base64,' + base64;
};
Util.clamp = function(value, min, max) {
return Math.min(Math.max(min, value), max);
};
Util.lerp = function(a, b, t) {
return a + ((b - a) * t);
};
/**
* Light polyfill for `Promise.race`. Returns
* a promise that resolves when the first promise
* provided resolves.
*
* @param {Array} promises
*/
Util.race = function(promises) {
if (Promise.race) {
return Promise.race(promises);
}
return new Promise(function (resolve, reject) {
for (var i = 0; i < promises.length; i++) {
promises[i].then(resolve, reject);
}
});
};
Util.isIOS = (function() {
var isIOS = /iPad|iPhone|iPod/.test(navigator.platform);
return function() {
return isIOS;
};
})();
Util.isWebViewAndroid = (function() {
var isWebViewAndroid = navigator.userAgent.indexOf('Version') !== -1 &&
navigator.userAgent.indexOf('Android') !== -1 &&
navigator.userAgent.indexOf('Chrome') !== -1;
return function() {
return isWebViewAndroid;
};
})();
Util.isSafari = (function() {
var isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
return function() {
return isSafari;
};
})();
Util.isFirefoxAndroid = (function() {
var isFirefoxAndroid = navigator.userAgent.indexOf('Firefox') !== -1 &&
navigator.userAgent.indexOf('Android') !== -1;
return function() {
return isFirefoxAndroid;
};
})();
Util.isLandscapeMode = function() {
return (window.orientation == 90 || window.orientation == -90);
};
// Helper method to validate the time steps of sensor timestamps.
Util.isTimestampDeltaValid = function(timestampDeltaS) {
if (isNaN(timestampDeltaS)) {
return false;
}
if (timestampDeltaS <= Util.MIN_TIMESTEP) {
return false;
}
if (timestampDeltaS > Util.MAX_TIMESTEP) {
return false;
}
return true;
};
Util.getScreenWidth = function() {
return Math.max(window.screen.width, window.screen.height) *
window.devicePixelRatio;
};
Util.getScreenHeight = function() {
return Math.min(window.screen.width, window.screen.height) *
window.devicePixelRatio;
};
Util.requestFullscreen = function(element) {
if (Util.isWebViewAndroid()) {
return false;
}
if (element.requestFullscreen) {
element.requestFullscreen();
} else if (element.webkitRequestFullscreen) {
element.webkitRequestFullscreen();
} else if (element.mozRequestFullScreen) {
element.mozRequestFullScreen();
} else if (element.msRequestFullscreen) {
element.msRequestFullscreen();
} else {
return false;
}
return true;
};
Util.exitFullscreen = function() {
if (document.exitFullscreen) {
document.exitFullscreen();
} else if (document.webkitExitFullscreen) {
document.webkitExitFullscreen();
} else if (document.mozCancelFullScreen) {
document.mozCancelFullScreen();
} else if (document.msExitFullscreen) {
document.msExitFullscreen();
} else {
return false;
}
return true;
};
Util.getFullscreenElement = function() {
return document.fullscreenElement ||
document.webkitFullscreenElement ||
document.mozFullScreenElement ||
document.msFullscreenElement;
};
Util.linkProgram = function(gl, vertexSource, fragmentSource, attribLocationMap) {
// No error checking for brevity.
var vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, vertexSource);
gl.compileShader(vertexShader);
var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, fragmentSource);
gl.compileShader(fragmentShader);
var program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
for (var attribName in attribLocationMap)
gl.bindAttribLocation(program, attribLocationMap[attribName], attribName);
gl.linkProgram(program);
gl.deleteShader(vertexShader);
gl.deleteShader(fragmentShader);
return program;
};
Util.getProgramUniforms = function(gl, program) {
var uniforms = {};
var uniformCount = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS);
var uniformName = '';
for (var i = 0; i < uniformCount; i++) {
var uniformInfo = gl.getActiveUniform(program, i);
uniformName = uniformInfo.name.replace('[0]', '');
uniforms[uniformName] = gl.getUniformLocation(program, uniformName);
}
return uniforms;
};
Util.orthoMatrix = function (out, left, right, bottom, top, near, far) {
var lr = 1 / (left - right),
bt = 1 / (bottom - top),
nf = 1 / (near - far);
out[0] = -2 * lr;
out[1] = 0;
out[2] = 0;
out[3] = 0;
out[4] = 0;
out[5] = -2 * bt;
out[6] = 0;
out[7] = 0;
out[8] = 0;
out[9] = 0;
out[10] = 2 * nf;
out[11] = 0;
out[12] = (left + right) * lr;
out[13] = (top + bottom) * bt;
out[14] = (far + near) * nf;
out[15] = 1;
return out;
};
Util.copyArray = function (source, dest) {
for (var i = 0, n = source.length; i < n; i++) {
dest[i] = source[i];
}
};
Util.isMobile = function() {
var check = false;
(function(a){if(/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(a)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0,4)))check = true})(navigator.userAgent||navigator.vendor||window.opera);
return check;
};
Util.extend = function(dest, src) {
for (var key in src) {
if (src.hasOwnProperty(key)) {
dest[key] = src[key];
}
}
return dest;
}
Util.safariCssSizeWorkaround = function(canvas) {
// TODO(smus): Remove this workaround when Safari for iOS is fixed.
// iOS only workaround (for https://bugs.webkit.org/show_bug.cgi?id=152556).
//
// "To the last I grapple with thee;
// from hell's heart I stab at thee;
// for hate's sake I spit my last breath at thee."
// -- Moby Dick, by Herman Melville
if (Util.isIOS()) {
var width = canvas.style.width;
var height = canvas.style.height;
canvas.style.width = (parseInt(width) + 1) + 'px';
canvas.style.height = (parseInt(height)) + 'px';
setTimeout(function() {
canvas.style.width = width;
canvas.style.height = height;
}, 100);
}
// Debug only.
window.Util = Util;
window.canvas = canvas;
};
Util.isDebug = function() {
return Util.getQueryParameter('debug');
};
Util.getQueryParameter = function(name) {
var name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
var regex = new RegExp("[\\?&]" + name + "=([^]*)"),
results = regex.exec(location.search);
return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
};
Util.frameDataFromPose = (function() {
var piOver180 = Math.PI / 180.0;
var rad45 = Math.PI * 0.25;
// Borrowed from glMatrix.
function mat4_perspectiveFromFieldOfView(out, fov, near, far) {
var upTan = Math.tan(fov ? (fov.upDegrees * piOver180) : rad45),
downTan = Math.tan(fov ? (fov.downDegrees * piOver180) : rad45),
leftTan = Math.tan(fov ? (fov.leftDegrees * piOver180) : rad45),
rightTan = Math.tan(fov ? (fov.rightDegrees * piOver180) : rad45),
xScale = 2.0 / (leftTan + rightTan),
yScale = 2.0 / (upTan + downTan);
out[0] = xScale;
out[1] = 0.0;
out[2] = 0.0;
out[3] = 0.0;
out[4] = 0.0;
out[5] = yScale;
out[6] = 0.0;
out[7] = 0.0;
out[8] = -((leftTan - rightTan) * xScale * 0.5);
out[9] = ((upTan - downTan) * yScale * 0.5);
out[10] = far / (near - far);
out[11] = -1.0;
out[12] = 0.0;
out[13] = 0.0;
out[14] = (far * near) / (near - far);
out[15] = 0.0;
return out;
}
function mat4_fromRotationTranslation(out, q, v) {
// Quaternion math
var x = q[0], y = q[1], z = q[2], w = q[3],
x2 = x + x,
y2 = y + y,
z2 = z + z,
xx = x * x2,
xy = x * y2,
xz = x * z2,
yy = y * y2,
yz = y * z2,
zz = z * z2,
wx = w * x2,
wy = w * y2,
wz = w * z2;
out[0] = 1 - (yy + zz);
out[1] = xy + wz;
out[2] = xz - wy;
out[3] = 0;
out[4] = xy - wz;
out[5] = 1 - (xx + zz);
out[6] = yz + wx;
out[7] = 0;
out[8] = xz + wy;
out[9] = yz - wx;
out[10] = 1 - (xx + yy);
out[11] = 0;
out[12] = v[0];
out[13] = v[1];
out[14] = v[2];
out[15] = 1;
return out;
};
function mat4_translate(out, a, v) {
var x = v[0], y = v[1], z = v[2],
a00, a01, a02, a03,
a10, a11, a12, a13,
a20, a21, a22, a23;
if (a === out) {
out[12] = a[0] * x + a[4] * y + a[8] * z + a[12];
out[13] = a[1] * x + a[5] * y + a[9] * z + a[13];
out[14] = a[2] * x + a[6] * y + a[10] * z + a[14];
out[15] = a[3] * x + a[7] * y + a[11] * z + a[15];
} else {
a00 = a[0]; a01 = a[1]; a02 = a[2]; a03 = a[3];
a10 = a[4]; a11 = a[5]; a12 = a[6]; a13 = a[7];
a20 = a[8]; a21 = a[9]; a22 = a[10]; a23 = a[11];
out[0] = a00; out[1] = a01; out[2] = a02; out[3] = a03;
out[4] = a10; out[5] = a11; out[6] = a12; out[7] = a13;
out[8] = a20; out[9] = a21; out[10] = a22; out[11] = a23;
out[12] = a00 * x + a10 * y + a20 * z + a[12];
out[13] = a01 * x + a11 * y + a21 * z + a[13];
out[14] = a02 * x + a12 * y + a22 * z + a[14];
out[15] = a03 * x + a13 * y + a23 * z + a[15];
}
return out;
};
function mat4_invert(out, a) {
var a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3],
a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7],
a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11],
a30 = a[12], a31 = a[13], a32 = a[14], a33 = a[15],
b00 = a00 * a11 - a01 * a10,
b01 = a00 * a12 - a02 * a10,
b02 = a00 * a13 - a03 * a10,
b03 = a01 * a12 - a02 * a11,
b04 = a01 * a13 - a03 * a11,
b05 = a02 * a13 - a03 * a12,
b06 = a20 * a31 - a21 * a30,
b07 = a20 * a32 - a22 * a30,
b08 = a20 * a33 - a23 * a30,
b09 = a21 * a32 - a22 * a31,
b10 = a21 * a33 - a23 * a31,
b11 = a22 * a33 - a23 * a32,
// Calculate the determinant
det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06;
if (!det) {
return null;
}
det = 1.0 / det;
out[0] = (a11 * b11 - a12 * b10 + a13 * b09) * det;
out[1] = (a02 * b10 - a01 * b11 - a03 * b09) * det;
out[2] = (a31 * b05 - a32 * b04 + a33 * b03) * det;
out[3] = (a22 * b04 - a21 * b05 - a23 * b03) * det;
out[4] = (a12 * b08 - a10 * b11 - a13 * b07) * det;
out[5] = (a00 * b11 - a02 * b08 + a03 * b07) * det;
out[6] = (a32 * b02 - a30 * b05 - a33 * b01) * det;
out[7] = (a20 * b05 - a22 * b02 + a23 * b01) * det;
out[8] = (a10 * b10 - a11 * b08 + a13 * b06) * det;
out[9] = (a01 * b08 - a00 * b10 - a03 * b06) * det;
out[10] = (a30 * b04 - a31 * b02 + a33 * b00) * det;
out[11] = (a21 * b02 - a20 * b04 - a23 * b00) * det;
out[12] = (a11 * b07 - a10 * b09 - a12 * b06) * det;
out[13] = (a00 * b09 - a01 * b07 + a02 * b06) * det;
out[14] = (a31 * b01 - a30 * b03 - a32 * b00) * det;
out[15] = (a20 * b03 - a21 * b01 + a22 * b00) * det;
return out;
};
var defaultOrientation = new Float32Array([0, 0, 0, 1]);
var defaultPosition = new Float32Array([0, 0, 0]);
function updateEyeMatrices(projection, view, pose, parameters, vrDisplay) {
mat4_perspectiveFromFieldOfView(projection, parameters ? parameters.fieldOfView : null, vrDisplay.depthNear, vrDisplay.depthFar);
var orientation = pose.orientation || defaultOrientation;
var position = pose.position || defaultPosition;
mat4_fromRotationTranslation(view, orientation, position);
if (parameters)
mat4_translate(view, view, parameters.offset);
mat4_invert(view, view);
}
return function(frameData, pose, vrDisplay) {
if (!frameData || !pose)
return false;
frameData.pose = pose;
frameData.timestamp = pose.timestamp;
updateEyeMatrices(
frameData.leftProjectionMatrix, frameData.leftViewMatrix,
pose, vrDisplay.getEyeParameters("left"), vrDisplay);
updateEyeMatrices(
frameData.rightProjectionMatrix, frameData.rightViewMatrix,
pose, vrDisplay.getEyeParameters("right"), vrDisplay);
return true;
};
})();
Util.isInsideCrossDomainIFrame = function() {
var isFramed = (window.self !== window.top);
var refDomain = Util.getDomainFromUrl(document.referrer);
var thisDomain = Util.getDomainFromUrl(window.location.href);
return isFramed && (refDomain !== thisDomain);
};
// From http://stackoverflow.com/a/23945027.
Util.getDomainFromUrl = function(url) {
var domain;
// Find & remove protocol (http, ftp, etc.) and get domain.
if (url.indexOf("://") > -1) {
domain = url.split('/')[2];
}
else {
domain = url.split('/')[0];
}
//find & remove port number
domain = domain.split(':')[0];
return domain;
}
module.exports = Util;
},{}],69:[function(_dereq_,module,exports){
/*
* Copyright 2015 Google Inc. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var DeviceInfo = _dereq_('./device-info.js');
var Util = _dereq_('./util.js');
var DEFAULT_VIEWER = 'CardboardV1';
var VIEWER_KEY = 'WEBVR_CARDBOARD_VIEWER';
var CLASS_NAME = 'webvr-polyfill-viewer-selector';
/**
* Creates a viewer selector with the options specified. Supports being shown
* and hidden. Generates events when viewer parameters change. Also supports
* saving the currently selected index in localStorage.
*/
function ViewerSelector() {
// Try to load the selected key from local storage.
try {
this.selectedKey = localStorage.getItem(VIEWER_KEY);
} catch (error) {
console.error('Failed to load viewer profile: %s', error);
}
//If none exists, or if localstorage is unavailable, use the default key.
if (!this.selectedKey) {
this.selectedKey = DEFAULT_VIEWER;
}
this.dialog = this.createDialog_(DeviceInfo.Viewers);
this.root = null;
this.onChangeCallbacks_ = [];
}
ViewerSelector.prototype.show = function(root) {
this.root = root;
root.appendChild(this.dialog);
// Ensure the currently selected item is checked.
var selected = this.dialog.querySelector('#' + this.selectedKey);
selected.checked = true;
// Show the UI.
this.dialog.style.display = 'block';
};
ViewerSelector.prototype.hide = function() {
if (this.root && this.root.contains(this.dialog)) {
this.root.removeChild(this.dialog);
}
this.dialog.style.display = 'none';
};
ViewerSelector.prototype.getCurrentViewer = function() {
return DeviceInfo.Viewers[this.selectedKey];
};
ViewerSelector.prototype.getSelectedKey_ = function() {
var input = this.dialog.querySelector('input[name=field]:checked');
if (input) {
return input.id;
}
return null;
};
ViewerSelector.prototype.onChange = function(cb) {
this.onChangeCallbacks_.push(cb);
};
ViewerSelector.prototype.fireOnChange_ = function(viewer) {
for (var i = 0; i < this.onChangeCallbacks_.length; i++) {
this.onChangeCallbacks_[i](viewer);
}
};
ViewerSelector.prototype.onSave_ = function() {
this.selectedKey = this.getSelectedKey_();
if (!this.selectedKey || !DeviceInfo.Viewers[this.selectedKey]) {
console.error('ViewerSelector.onSave_: this should never happen!');
return;
}
this.fireOnChange_(DeviceInfo.Viewers[this.selectedKey]);
// Attempt to save the viewer profile, but fails in private mode.
try {
localStorage.setItem(VIEWER_KEY, this.selectedKey);
} catch(error) {
console.error('Failed to save viewer profile: %s', error);
}
this.hide();
};
/**
* Creates the dialog.
*/
ViewerSelector.prototype.createDialog_ = function(options) {
var container = document.createElement('div');
container.classList.add(CLASS_NAME);
container.style.display = 'none';
// Create an overlay that dims the background, and which goes away when you
// tap it.
var overlay = document.createElement('div');
var s = overlay.style;
s.position = 'fixed';
s.left = 0;
s.top = 0;
s.width = '100%';
s.height = '100%';
s.background = 'rgba(0, 0, 0, 0.3)';
overlay.addEventListener('click', this.hide.bind(this));
var width = 280;
var dialog = document.createElement('div');
var s = dialog.style;
s.boxSizing = 'border-box';
s.position = 'fixed';
s.top = '24px';
s.left = '50%';
s.marginLeft = (-width/2) + 'px';
s.width = width + 'px';
s.padding = '24px';
s.overflow = 'hidden';
s.background = '#fafafa';
s.fontFamily = "'Roboto', sans-serif";
s.boxShadow = '0px 5px 20px #666';
dialog.appendChild(this.createH1_('Select your viewer'));
for (var id in options) {
dialog.appendChild(this.createChoice_(id, options[id].label));
}
dialog.appendChild(this.createButton_('Save', this.onSave_.bind(this)));
container.appendChild(overlay);
container.appendChild(dialog);
return container;
};
ViewerSelector.prototype.createH1_ = function(name) {
var h1 = document.createElement('h1');
var s = h1.style;
s.color = 'black';
s.fontSize = '20px';
s.fontWeight = 'bold';
s.marginTop = 0;
s.marginBottom = '24px';
h1.innerHTML = name;
return h1;
};
ViewerSelector.prototype.createChoice_ = function(id, name) {
/*
*/
var div = document.createElement('div');
div.style.marginTop = '8px';
div.style.color = 'black';
var input = document.createElement('input');
input.style.fontSize = '30px';
input.setAttribute('id', id);
input.setAttribute('type', 'radio');
input.setAttribute('value', id);
input.setAttribute('name', 'field');
var label = document.createElement('label');
label.style.marginLeft = '4px';
label.setAttribute('for', id);
label.innerHTML = name;
div.appendChild(input);
div.appendChild(label);
return div;
};
ViewerSelector.prototype.createButton_ = function(label, onclick) {
var button = document.createElement('button');
button.innerHTML = label;
var s = button.style;
s.float = 'right';
s.textTransform = 'uppercase';
s.color = '#1094f7';
s.fontSize = '14px';
s.letterSpacing = 0;
s.border = 0;
s.background = 'none';
s.marginTop = '16px';
button.addEventListener('click', onclick);
return button;
};
module.exports = ViewerSelector;
},{"./device-info.js":53,"./util.js":68}],70:[function(_dereq_,module,exports){
/*
* Copyright 2015 Google Inc. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var Util = _dereq_('./util.js');
/**
* Android and iOS compatible wakelock implementation.
*
* Refactored thanks to dkovalev@.
*/
function AndroidWakeLock() {
var video = document.createElement('video');
video.setAttribute('loop', '');
function addSourceToVideo(element, type, dataURI) {
var source = document.createElement('source');
source.src = dataURI;
source.type = 'video/' + type;
element.appendChild(source);
}
addSourceToVideo(video,'webm', Util.base64('video/webm', 'GkXfo0AgQoaBAUL3gQFC8oEEQvOBCEKCQAR3ZWJtQoeBAkKFgQIYU4BnQI0VSalmQCgq17FAAw9CQE2AQAZ3aGFtbXlXQUAGd2hhbW15RIlACECPQAAAAAAAFlSua0AxrkAu14EBY8WBAZyBACK1nEADdW5khkAFVl9WUDglhohAA1ZQOIOBAeBABrCBCLqBCB9DtnVAIueBAKNAHIEAAIAwAQCdASoIAAgAAUAmJaQAA3AA/vz0AAA='));
addSourceToVideo(video, 'mp4', Util.base64('video/mp4', 'AAAAHGZ0eXBpc29tAAACAGlzb21pc28ybXA0MQAAAAhmcmVlAAAAG21kYXQAAAGzABAHAAABthADAowdbb9/AAAC6W1vb3YAAABsbXZoZAAAAAB8JbCAfCWwgAAAA+gAAAAAAAEAAAEAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAIVdHJhawAAAFx0a2hkAAAAD3wlsIB8JbCAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAQAAAAAAIAAAACAAAAAABsW1kaWEAAAAgbWRoZAAAAAB8JbCAfCWwgAAAA+gAAAAAVcQAAAAAAC1oZGxyAAAAAAAAAAB2aWRlAAAAAAAAAAAAAAAAVmlkZW9IYW5kbGVyAAAAAVxtaW5mAAAAFHZtaGQAAAABAAAAAAAAAAAAAAAkZGluZgAAABxkcmVmAAAAAAAAAAEAAAAMdXJsIAAAAAEAAAEcc3RibAAAALhzdHNkAAAAAAAAAAEAAACobXA0dgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAIAAgASAAAAEgAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABj//wAAAFJlc2RzAAAAAANEAAEABDwgEQAAAAADDUAAAAAABS0AAAGwAQAAAbWJEwAAAQAAAAEgAMSNiB9FAEQBFGMAAAGyTGF2YzUyLjg3LjQGAQIAAAAYc3R0cwAAAAAAAAABAAAAAQAAAAAAAAAcc3RzYwAAAAAAAAABAAAAAQAAAAEAAAABAAAAFHN0c3oAAAAAAAAAEwAAAAEAAAAUc3RjbwAAAAAAAAABAAAALAAAAGB1ZHRhAAAAWG1ldGEAAAAAAAAAIWhkbHIAAAAAAAAAAG1kaXJhcHBsAAAAAAAAAAAAAAAAK2lsc3QAAAAjqXRvbwAAABtkYXRhAAAAAQAAAABMYXZmNTIuNzguMw=='));
this.request = function() {
if (video.paused) {
video.play();
}
};
this.release = function() {
video.pause();
};
}
function iOSWakeLock() {
var timer = null;
this.request = function() {
if (!timer) {
timer = setInterval(function() {
window.location = window.location;
setTimeout(window.stop, 0);
}, 30000);
}
}
this.release = function() {
if (timer) {
clearInterval(timer);
timer = null;
}
}
}
function getWakeLock() {
var userAgent = navigator.userAgent || navigator.vendor || window.opera;
if (userAgent.match(/iPhone/i) || userAgent.match(/iPod/i)) {
return iOSWakeLock;
} else {
return AndroidWakeLock;
}
}
module.exports = getWakeLock();
},{"./util.js":68}],71:[function(_dereq_,module,exports){
/*
* Copyright 2015 Google Inc. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var Util = _dereq_('./util.js');
var CardboardVRDisplay = _dereq_('./cardboard-vr-display.js');
var MouseKeyboardVRDisplay = _dereq_('./mouse-keyboard-vr-display.js');
// Uncomment to add positional tracking via webcam.
//var WebcamPositionSensorVRDevice = require('./webcam-position-sensor-vr-device.js');
var VRDisplay = _dereq_('./base.js').VRDisplay;
var VRFrameData = _dereq_('./base.js').VRFrameData;
var HMDVRDevice = _dereq_('./base.js').HMDVRDevice;
var PositionSensorVRDevice = _dereq_('./base.js').PositionSensorVRDevice;
var VRDisplayHMDDevice = _dereq_('./display-wrappers.js').VRDisplayHMDDevice;
var VRDisplayPositionSensorDevice = _dereq_('./display-wrappers.js').VRDisplayPositionSensorDevice;
var version = _dereq_('../package.json').version;
function WebVRPolyfill() {
this.displays = [];
this.devices = []; // For deprecated objects
this.devicesPopulated = false;
this.nativeWebVRAvailable = this.isWebVRAvailable();
this.nativeLegacyWebVRAvailable = this.isDeprecatedWebVRAvailable();
this.nativeGetVRDisplaysFunc = this.nativeWebVRAvailable ?
navigator.getVRDisplays :
null;
if (!this.nativeLegacyWebVRAvailable && !this.nativeWebVRAvailable) {
this.enablePolyfill();
if (window.WebVRConfig.ENABLE_DEPRECATED_API) {
this.enableDeprecatedPolyfill();
}
}
// Put a shim in place to update the API to 1.1 if needed.
InstallWebVRSpecShim();
}
WebVRPolyfill.prototype.isWebVRAvailable = function() {
return ('getVRDisplays' in navigator);
};
WebVRPolyfill.prototype.isDeprecatedWebVRAvailable = function() {
return ('getVRDevices' in navigator) || ('mozGetVRDevices' in navigator);
};
WebVRPolyfill.prototype.connectDisplay = function(vrDisplay) {
vrDisplay.fireVRDisplayConnect_();
this.displays.push(vrDisplay);
};
WebVRPolyfill.prototype.populateDevices = function() {
if (this.devicesPopulated) {
return;
}
// Initialize our virtual VR devices.
var vrDisplay = null;
// Add a Cardboard VRDisplay on compatible mobile devices
if (this.isCardboardCompatible()) {
vrDisplay = new CardboardVRDisplay();
this.connectDisplay(vrDisplay);
// For backwards compatibility
if (window.WebVRConfig.ENABLE_DEPRECATED_API) {
this.devices.push(new VRDisplayHMDDevice(vrDisplay));
this.devices.push(new VRDisplayPositionSensorDevice(vrDisplay));
}
}
// Add a Mouse and Keyboard driven VRDisplay for desktops/laptops
if (!this.isMobile() && !window.WebVRConfig.MOUSE_KEYBOARD_CONTROLS_DISABLED) {
vrDisplay = new MouseKeyboardVRDisplay();
this.connectDisplay(vrDisplay);
// For backwards compatibility
if (window.WebVRConfig.ENABLE_DEPRECATED_API) {
this.devices.push(new VRDisplayHMDDevice(vrDisplay));
this.devices.push(new VRDisplayPositionSensorDevice(vrDisplay));
}
}
// Uncomment to add positional tracking via webcam.
//if (!this.isMobile() && window.WebVRConfig.ENABLE_DEPRECATED_API) {
// positionDevice = new WebcamPositionSensorVRDevice();
// this.devices.push(positionDevice);
//}
this.devicesPopulated = true;
};
WebVRPolyfill.prototype.enablePolyfill = function() {
// Provide navigator.getVRDisplays.
navigator.getVRDisplays = this.getVRDisplays.bind(this);
// Polyfill native VRDisplay.getFrameData
if (this.nativeWebVRAvailable && window.VRFrameData) {
var NativeVRFrameData = window.VRFrameData;
var nativeFrameData = new window.VRFrameData();
var nativeGetFrameData = window.VRDisplay.prototype.getFrameData;
window.VRFrameData = VRFrameData;
window.VRDisplay.prototype.getFrameData = function(frameData) {
if (frameData instanceof NativeVRFrameData) {
nativeGetFrameData.call(this, frameData);
return;
}
/*
Copy frame data from the native object into the polyfilled object.
*/
nativeGetFrameData.call(this, nativeFrameData);
frameData.pose = nativeFrameData.pose;
Util.copyArray(nativeFrameData.leftProjectionMatrix, frameData.leftProjectionMatrix);
Util.copyArray(nativeFrameData.rightProjectionMatrix, frameData.rightProjectionMatrix);
Util.copyArray(nativeFrameData.leftViewMatrix, frameData.leftViewMatrix);
Util.copyArray(nativeFrameData.rightViewMatrix, frameData.rightViewMatrix);
//todo: copy
};
}
// Provide the `VRDisplay` object.
window.VRDisplay = VRDisplay;
// Provide the `navigator.vrEnabled` property.
if (navigator && typeof navigator.vrEnabled === 'undefined') {
var self = this;
Object.defineProperty(navigator, 'vrEnabled', {
get: function () {
return self.isCardboardCompatible() &&
(self.isFullScreenAvailable() || Util.isIOS());
}
});
}
if (!('VRFrameData' in window)) {
// Provide the VRFrameData object.
window.VRFrameData = VRFrameData;
}
};
WebVRPolyfill.prototype.enableDeprecatedPolyfill = function() {
// Provide navigator.getVRDevices.
navigator.getVRDevices = this.getVRDevices.bind(this);
// Provide the CardboardHMDVRDevice and PositionSensorVRDevice objects.
window.HMDVRDevice = HMDVRDevice;
window.PositionSensorVRDevice = PositionSensorVRDevice;
};
WebVRPolyfill.prototype.getVRDisplays = function() {
this.populateDevices();
var polyfillDisplays = this.displays;
if (!this.nativeWebVRAvailable) {
return Promise.resolve(polyfillDisplays);
}
// Set up a race condition if this browser has a bug where
// `navigator.getVRDisplays()` never resolves.
var timeoutId;
var vrDisplaysNative = this.nativeGetVRDisplaysFunc.call(navigator);
var timeoutPromise = new Promise(function(resolve) {
timeoutId = setTimeout(function() {
console.warn('Native WebVR implementation detected, but `getVRDisplays()` failed to resolve. Falling back to polyfill.');
resolve([]);
}, window.WebVRConfig.GET_VR_DISPLAYS_TIMEOUT);
});
return Util.race([
vrDisplaysNative,
timeoutPromise
]).then(function(nativeDisplays) {
clearTimeout(timeoutId);
if (window.WebVRConfig.ALWAYS_APPEND_POLYFILL_DISPLAY) {
return nativeDisplays.concat(polyfillDisplays);
} else {
return nativeDisplays.length > 0 ? nativeDisplays : polyfillDisplays;
}
});
};
WebVRPolyfill.prototype.getVRDevices = function() {
console.warn('getVRDevices is deprecated. Please update your code to use getVRDisplays instead.');
var self = this;
return new Promise(function(resolve, reject) {
try {
if (!self.devicesPopulated) {
if (self.nativeWebVRAvailable) {
return navigator.getVRDisplays(function(displays) {
for (var i = 0; i < displays.length; ++i) {
self.devices.push(new VRDisplayHMDDevice(displays[i]));
self.devices.push(new VRDisplayPositionSensorDevice(displays[i]));
}
self.devicesPopulated = true;
resolve(self.devices);
}, reject);
}
if (self.nativeLegacyWebVRAvailable) {
return (navigator.getVRDDevices || navigator.mozGetVRDevices)(function(devices) {
for (var i = 0; i < devices.length; ++i) {
if (devices[i] instanceof HMDVRDevice) {
self.devices.push(devices[i]);
}
if (devices[i] instanceof PositionSensorVRDevice) {
self.devices.push(devices[i]);
}
}
self.devicesPopulated = true;
resolve(self.devices);
}, reject);
}
}
self.populateDevices();
resolve(self.devices);
} catch (e) {
reject(e);
}
});
};
WebVRPolyfill.prototype.NativeVRFrameData = window.VRFrameData;
/**
* Determine if a device is mobile.
*/
WebVRPolyfill.prototype.isMobile = function() {
return /Android/i.test(navigator.userAgent) ||
/iPhone|iPad|iPod/i.test(navigator.userAgent);
};
WebVRPolyfill.prototype.isCardboardCompatible = function() {
// For now, support all iOS and Android devices.
// Also enable the WebVRConfig.FORCE_VR flag for debugging.
return this.isMobile() || window.WebVRConfig.FORCE_ENABLE_VR;
};
WebVRPolyfill.prototype.isFullScreenAvailable = function() {
return (document.fullscreenEnabled ||
document.mozFullScreenEnabled ||
document.webkitFullscreenEnabled ||
false);
};
// Installs a shim that updates a WebVR 1.0 spec implementation to WebVR 1.1
function InstallWebVRSpecShim() {
if ('VRDisplay' in window && !('VRFrameData' in window)) {
// Provide the VRFrameData object.
window.VRFrameData = VRFrameData;
// A lot of Chrome builds don't have depthNear and depthFar, even
// though they're in the WebVR 1.0 spec. Patch them in if they're not present.
if(!('depthNear' in window.VRDisplay.prototype)) {
window.VRDisplay.prototype.depthNear = 0.01;
}
if(!('depthFar' in window.VRDisplay.prototype)) {
window.VRDisplay.prototype.depthFar = 10000.0;
}
window.VRDisplay.prototype.getFrameData = function(frameData) {
return Util.frameDataFromPose(frameData, this.getPose(), this);
}
}
};
WebVRPolyfill.InstallWebVRSpecShim = InstallWebVRSpecShim;
WebVRPolyfill.version = version;
module.exports.WebVRPolyfill = WebVRPolyfill;
},{"../package.json":47,"./base.js":48,"./cardboard-vr-display.js":51,"./display-wrappers.js":54,"./mouse-keyboard-vr-display.js":60,"./util.js":68}],72:[function(_dereq_,module,exports){
var newline = /\n/
var newlineChar = '\n'
var whitespace = /\s/
module.exports = function(text, opt) {
var lines = module.exports.lines(text, opt)
return lines.map(function(line) {
return text.substring(line.start, line.end)
}).join('\n')
}
module.exports.lines = function wordwrap(text, opt) {
opt = opt||{}
//zero width results in nothing visible
if (opt.width === 0 && opt.mode !== 'nowrap')
return []
text = text||''
var width = typeof opt.width === 'number' ? opt.width : Number.MAX_VALUE
var start = Math.max(0, opt.start||0)
var end = typeof opt.end === 'number' ? opt.end : text.length
var mode = opt.mode
var measure = opt.measure || monospace
if (mode === 'pre')
return pre(measure, text, start, end, width)
else
return greedy(measure, text, start, end, width, mode)
}
function idxOf(text, chr, start, end) {
var idx = text.indexOf(chr, start)
if (idx === -1 || idx > end)
return end
return idx
}
function isWhitespace(chr) {
return whitespace.test(chr)
}
function pre(measure, text, start, end, width) {
var lines = []
var lineStart = start
for (var i=start; i start) {
if (isWhitespace(text.charAt(lineEnd)))
break
lineEnd--
}
if (lineEnd === start) {
if (nextStart > start + newlineChar.length) nextStart--
lineEnd = nextStart // If no characters to break, show all.
} else {
nextStart = lineEnd
//eat whitespace at end of line
while (lineEnd > start) {
if (!isWhitespace(text.charAt(lineEnd - newlineChar.length)))
break
lineEnd--
}
}
}
if (lineEnd >= start) {
var result = measure(text, start, lineEnd, testWidth)
lines.push(result)
}
start = nextStart
}
return lines
}
//determines the visible number of glyphs within a given width
function monospace(text, start, end, width) {
var glyphs = Math.min(width, end-start)
return {
start: start,
end: start+glyphs
}
}
},{}],73:[function(_dereq_,module,exports){
"use strict";
var window = _dereq_("global/window")
var isFunction = _dereq_("is-function")
var parseHeaders = _dereq_("parse-headers")
var xtend = _dereq_("xtend")
module.exports = createXHR
createXHR.XMLHttpRequest = window.XMLHttpRequest || noop
createXHR.XDomainRequest = "withCredentials" in (new createXHR.XMLHttpRequest()) ? createXHR.XMLHttpRequest : window.XDomainRequest
forEachArray(["get", "put", "post", "patch", "head", "delete"], function(method) {
createXHR[method === "delete" ? "del" : method] = function(uri, options, callback) {
options = initParams(uri, options, callback)
options.method = method.toUpperCase()
return _createXHR(options)
}
})
function forEachArray(array, iterator) {
for (var i = 0; i < array.length; i++) {
iterator(array[i])
}
}
function isEmpty(obj){
for(var i in obj){
if(obj.hasOwnProperty(i)) return false
}
return true
}
function initParams(uri, options, callback) {
var params = uri
if (isFunction(options)) {
callback = options
if (typeof uri === "string") {
params = {uri:uri}
}
} else {
params = xtend(options, {uri: uri})
}
params.callback = callback
return params
}
function createXHR(uri, options, callback) {
options = initParams(uri, options, callback)
return _createXHR(options)
}
function _createXHR(options) {
if(typeof options.callback === "undefined"){
throw new Error("callback argument missing")
}
var called = false
var callback = function cbOnce(err, response, body){
if(!called){
called = true
options.callback(err, response, body)
}
}
function readystatechange() {
if (xhr.readyState === 4) {
setTimeout(loadFunc, 0)
}
}
function getBody() {
// Chrome with requestType=blob throws errors arround when even testing access to responseText
var body = undefined
if (xhr.response) {
body = xhr.response
} else {
body = xhr.responseText || getXml(xhr)
}
if (isJson) {
try {
body = JSON.parse(body)
} catch (e) {}
}
return body
}
function errorFunc(evt) {
clearTimeout(timeoutTimer)
if(!(evt instanceof Error)){
evt = new Error("" + (evt || "Unknown XMLHttpRequest Error") )
}
evt.statusCode = 0
return callback(evt, failureResponse)
}
// will load the data & process the response in a special response object
function loadFunc() {
if (aborted) return
var status
clearTimeout(timeoutTimer)
if(options.useXDR && xhr.status===undefined) {
//IE8 CORS GET successful response doesn't have a status field, but body is fine
status = 200
} else {
status = (xhr.status === 1223 ? 204 : xhr.status)
}
var response = failureResponse
var err = null
if (status !== 0){
response = {
body: getBody(),
statusCode: status,
method: method,
headers: {},
url: uri,
rawRequest: xhr
}
if(xhr.getAllResponseHeaders){ //remember xhr can in fact be XDR for CORS in IE
response.headers = parseHeaders(xhr.getAllResponseHeaders())
}
} else {
err = new Error("Internal XMLHttpRequest Error")
}
return callback(err, response, response.body)
}
var xhr = options.xhr || null
if (!xhr) {
if (options.cors || options.useXDR) {
xhr = new createXHR.XDomainRequest()
}else{
xhr = new createXHR.XMLHttpRequest()
}
}
var key
var aborted
var uri = xhr.url = options.uri || options.url
var method = xhr.method = options.method || "GET"
var body = options.body || options.data
var headers = xhr.headers = options.headers || {}
var sync = !!options.sync
var isJson = false
var timeoutTimer
var failureResponse = {
body: undefined,
headers: {},
statusCode: 0,
method: method,
url: uri,
rawRequest: xhr
}
if ("json" in options && options.json !== false) {
isJson = true
headers["accept"] || headers["Accept"] || (headers["Accept"] = "application/json") //Don't override existing accept header declared by user
if (method !== "GET" && method !== "HEAD") {
headers["content-type"] || headers["Content-Type"] || (headers["Content-Type"] = "application/json") //Don't override existing accept header declared by user
body = JSON.stringify(options.json === true ? body : options.json)
}
}
xhr.onreadystatechange = readystatechange
xhr.onload = loadFunc
xhr.onerror = errorFunc
// IE9 must have onprogress be set to a unique function.
xhr.onprogress = function () {
// IE must die
}
xhr.onabort = function(){
aborted = true;
}
xhr.ontimeout = errorFunc
xhr.open(method, uri, !sync, options.username, options.password)
//has to be after open
if(!sync) {
xhr.withCredentials = !!options.withCredentials
}
// Cannot set timeout with sync request
// not setting timeout on the xhr object, because of old webkits etc. not handling that correctly
// both npm's request and jquery 1.x use this kind of timeout, so this is being consistent
if (!sync && options.timeout > 0 ) {
timeoutTimer = setTimeout(function(){
if (aborted) return
aborted = true//IE9 may still call readystatechange
xhr.abort("timeout")
var e = new Error("XMLHttpRequest timeout")
e.code = "ETIMEDOUT"
errorFunc(e)
}, options.timeout )
}
if (xhr.setRequestHeader) {
for(key in headers){
if(headers.hasOwnProperty(key)){
xhr.setRequestHeader(key, headers[key])
}
}
} else if (options.headers && !isEmpty(options.headers)) {
throw new Error("Headers cannot be set on an XDomainRequest object")
}
if ("responseType" in options) {
xhr.responseType = options.responseType
}
if ("beforeSend" in options &&
typeof options.beforeSend === "function"
) {
options.beforeSend(xhr)
}
// Microsoft Edge browser sends "undefined" when send is called with undefined value.
// XMLHttpRequest spec says to pass null as body to indicate no body
// See https://github.com/naugtur/xhr/issues/100.
xhr.send(body || null)
return xhr
}
function getXml(xhr) {
if (xhr.responseType === "document") {
return xhr.responseXML
}
var firefoxBugTakenEffect = xhr.responseXML && xhr.responseXML.documentElement.nodeName === "parsererror"
if (xhr.responseType === "" && !firefoxBugTakenEffect) {
return xhr.responseXML
}
return null
}
function noop() {}
},{"global/window":17,"is-function":21,"parse-headers":31,"xtend":75}],74:[function(_dereq_,module,exports){
module.exports = (function xmlparser() {
//common browsers
if (typeof self.DOMParser !== 'undefined') {
return function(str) {
var parser = new self.DOMParser()
return parser.parseFromString(str, 'application/xml')
}
}
//IE8 fallback
if (typeof self.ActiveXObject !== 'undefined'
&& new self.ActiveXObject('Microsoft.XMLDOM')) {
return function(str) {
var xmlDoc = new self.ActiveXObject("Microsoft.XMLDOM")
xmlDoc.async = "false"
xmlDoc.loadXML(str)
return xmlDoc
}
}
//last resort fallback
return function(str) {
var div = document.createElement('div')
div.innerHTML = str
return div
}
})()
},{}],75:[function(_dereq_,module,exports){
module.exports = extend
var hasOwnProperty = Object.prototype.hasOwnProperty;
function extend() {
var target = {}
for (var i = 0; i < arguments.length; i++) {
var source = arguments[i]
for (var key in source) {
if (hasOwnProperty.call(source, key)) {
target[key] = source[key]
}
}
}
return target
}
},{}],76:[function(_dereq_,module,exports){
module.exports={
"name": "aframe",
"version": "0.7.0",
"description": "A web framework for building virtual reality experiences.",
"homepage": "https://aframe.io/",
"main": "dist/aframe-master.js",
"scripts": {
"browserify": "browserify src/index.js -s 'AFRAME' -p browserify-derequire",
"build": "shx mkdir -p build/ && npm run browserify -- --debug -t [envify --INSPECTOR_VERSION dev] -o build/aframe.js",
"codecov": "codecov",
"dev": "npm run build && cross-env INSPECTOR_VERSION=dev node ./scripts/budo -t envify",
"dist": "node scripts/updateVersionLog.js && npm run dist:min && npm run dist:max",
"dist:max": "npm run browserify -s -- --debug | exorcist dist/aframe-v0.7.0.js.map > dist/aframe-v0.7.0.js",
"dist:min": "npm run browserify -s -- --debug -p [minifyify --map aframe-v0.7.0.min.js.map --output dist/aframe-v0.7.0.min.js.map] -o dist/aframe-v0.7.0.min.js",
"docs": "markserv --dir docs --port 9001",
"preghpages": "node ./scripts/preghpages.js",
"ghpages": "ghpages -p gh-pages/",
"lint": "semistandard -v | snazzy",
"lint:fix": "semistandard --fix",
"precommit": "npm run lint",
"prerelease": "node scripts/release.js 0.6.1 0.7.0",
"start": "npm run dev",
"test": "karma start ./tests/karma.conf.js",
"test:docs": "node scripts/docsLint.js",
"test:firefox": "npm test -- --browsers Firefox",
"test:chrome": "npm test -- --browsers Chrome",
"test:node": "mocha --ui tdd tests/node"
},
"repository": "aframevr/aframe",
"license": "MIT",
"dependencies": {
"@tweenjs/tween.js": "^16.8.0",
"browserify-css": "^0.8.2",
"debug": "ngokevin/debug#noTimestamp",
"deep-assign": "^2.0.0",
"document-register-element": "dmarcos/document-register-element#8ccc532b7",
"envify": "^3.4.1",
"load-bmfont": "^1.2.3",
"object-assign": "^4.0.1",
"present": "0.0.6",
"promise-polyfill": "^3.1.0",
"style-attr": "^1.0.2",
"three": "^0.87.0",
"three-bmfont-text": "^2.1.0",
"webvr-polyfill": "^0.9.36"
},
"devDependencies": {
"browserify": "^13.1.0",
"browserify-derequire": "^0.9.4",
"browserify-istanbul": "^2.0.0",
"budo": "^9.2.0",
"chai": "^3.5.0",
"chai-shallow-deep-equal": "^1.4.0",
"chalk": "^1.1.3",
"codecov": "^1.0.1",
"cross-env": "^5.0.1",
"exorcist": "^0.4.0",
"ghpages": "0.0.8",
"git-rev": "^0.2.1",
"glob": "^7.1.1",
"husky": "^0.11.7",
"istanbul": "^0.4.5",
"jsdom": "^9.11.0",
"karma": "1.4.1",
"karma-browserify": "^5.1.0",
"karma-chai-shallow-deep-equal": "0.0.4",
"karma-chrome-launcher": "^2.0.0",
"karma-coverage": "^1.1.1",
"karma-env-preprocessor": "^0.1.1",
"karma-firefox-launcher": "^1.0.0",
"karma-mocha": "^1.1.1",
"karma-mocha-reporter": "^2.1.0",
"karma-sinon-chai": "1.2.4",
"lolex": "^1.5.1",
"markserv": "0.0.20",
"minifyify": "^7.3.3",
"mocha": "^3.0.2",
"mozilla-download": "^1.1.1",
"replace-in-file": "^2.5.3",
"semistandard": "^9.0.0",
"shelljs": "^0.7.7",
"shx": "^0.2.2",
"sinon": "^1.17.5",
"sinon-chai": "2.8.0",
"snazzy": "^5.0.0",
"too-wordy": "ngokevin/too-wordy",
"uglifyjs": "^2.4.10",
"write-good": "^0.9.1"
},
"link": true,
"browserify": {
"transform": [
"browserify-css",
"envify"
]
},
"semistandard": {
"ignore": [
"build/**",
"dist/**",
"examples/**/shaders/*.js",
"**/vendor/**"
]
},
"keywords": [
"3d",
"aframe",
"cardboard",
"components",
"oculus",
"three",
"three.js",
"rift",
"vive",
"vr",
"web-components",
"webvr"
],
"browserify-css": {
"minify": true
},
"engines": {
"node": ">= 4.6.0",
"npm": "^2.15.9"
}
}
},{}],77:[function(_dereq_,module,exports){
var registerComponent = _dereq_('../core/component').registerComponent;
var THREE = _dereq_('../lib/three');
var utils = _dereq_('../utils/');
var bind = utils.bind;
var checkHasPositionalTracking = utils.device.checkHasPositionalTracking;
/**
* Camera component.
* Pairs along with camera system to handle tracking the active camera.
*/
module.exports.Component = registerComponent('camera', {
schema: {
active: {default: true},
far: {default: 10000},
fov: {default: 80, min: 0},
near: {default: 0.005, min: 0},
userHeight: {default: 0, min: 0},
zoom: {default: 1, min: 0}
},
/**
* Initialize three.js camera and add it to the entity.
* Add reference from scene to this entity as the camera.
*/
init: function () {
var camera;
var el = this.el;
var sceneEl = el.sceneEl;
this.savedPose = null;
// Create camera.
camera = this.camera = new THREE.PerspectiveCamera();
el.setObject3D('camera', camera);
// Add listeners to save and restore camera pose if headset is present.
this.onEnterVR = bind(this.onEnterVR, this);
this.onExitVR = bind(this.onExitVR, this);
sceneEl.addEventListener('enter-vr', this.onEnterVR);
sceneEl.addEventListener('exit-vr', this.onExitVR);
},
/**
* Update three.js camera.
*/
update: function (oldData) {
var el = this.el;
var data = this.data;
var camera = this.camera;
var system = this.system;
// Update height offset.
this.addHeightOffset(oldData.userHeight);
// Update properties.
camera.aspect = data.aspect || (window.innerWidth / window.innerHeight);
camera.far = data.far;
camera.fov = data.fov;
camera.near = data.near;
camera.zoom = data.zoom;
camera.updateProjectionMatrix();
// Active property did not change.
if (oldData && oldData.active === data.active) { return; }
// If `active` property changes, or first update, handle active camera with system.
if (data.active && system.activeCameraEl !== el) {
// Camera enabled. Set camera to this camera.
system.setActiveCamera(el);
} else if (!data.active && system.activeCameraEl === el) {
// Camera disabled. Set camera to another camera.
system.disableActiveCamera();
}
},
/**
* Remove camera on remove (callback).
*/
remove: function () {
var sceneEl = this.el.sceneEl;
this.el.removeObject3D('camera');
sceneEl.removeEventListener('enter-vr', this.onEnterVR);
sceneEl.removeEventListener('exit-vr', this.onExitVR);
},
/**
* Save pose and remove the offset.
*/
onEnterVR: function () {
this.saveCameraPose();
this.removeHeightOffset();
},
/**
* Restore the pose. Do not need to re-add the offset because it was saved on entering VR.
*/
onExitVR: function () {
this.restoreCameraPose();
},
/**
* Offsets the position of the camera to set a human scale perspective
* This offset is not necessary when using a headset because the SDK
* will return the real user's head height and position.
*/
addHeightOffset: function (oldOffset) {
var el = this.el;
var currentPosition;
var userHeightOffset = this.data.userHeight;
oldOffset = oldOffset || 0;
currentPosition = el.getAttribute('position') || {x: 0, y: 0, z: 0};
el.setAttribute('position', {
x: currentPosition.x,
y: currentPosition.y - oldOffset + userHeightOffset,
z: currentPosition.z
});
},
/**
* Remove the height offset (called when entering VR) since WebVR API gives absolute
* position.
*/
removeHeightOffset: function () {
var currentPosition;
var el = this.el;
var hasPositionalTracking;
var userHeightOffset = this.data.userHeight;
// Remove the offset if there is positional tracking when entering VR.
// Necessary for fullscreen mode with no headset.
// Checking this.hasPositionalTracking to make the value injectable for unit tests.
hasPositionalTracking = this.hasPositionalTracking !== undefined ? this.hasPositionalTracking : checkHasPositionalTracking();
if (!userHeightOffset || !hasPositionalTracking) { return; }
currentPosition = el.getAttribute('position') || {x: 0, y: 0, z: 0};
el.setAttribute('position', {
x: currentPosition.x,
y: currentPosition.y - userHeightOffset,
z: currentPosition.z
});
},
/**
* Save camera pose before entering VR to restore later if exiting.
*/
saveCameraPose: function () {
var el = this.el;
var hasPositionalTracking = this.hasPositionalTracking !== undefined ? this.hasPositionalTracking : checkHasPositionalTracking();
if (this.savedPose || !hasPositionalTracking) { return; }
this.savedPose = {
position: el.getAttribute('position'),
rotation: el.getAttribute('rotation')
};
},
/**
* Reset camera pose to before entering VR.
*/
restoreCameraPose: function () {
var el = this.el;
var savedPose = this.savedPose;
var hasPositionalTracking = this.hasPositionalTracking !== undefined ? this.hasPositionalTracking : checkHasPositionalTracking();
if (!savedPose || !hasPositionalTracking) { return; }
// Reset camera orientation.
el.setAttribute('position', savedPose.position);
el.setAttribute('rotation', savedPose.rotation);
this.savedPose = null;
}
});
},{"../core/component":125,"../lib/three":173,"../utils/":195}],78:[function(_dereq_,module,exports){
var registerComponent = _dereq_('../core/component').registerComponent;
var THREE = _dereq_('../lib/three');
module.exports.Component = registerComponent('collada-model', {
schema: {type: 'asset'},
init: function () {
this.model = null;
this.loader = new THREE.ColladaLoader();
this.loader.options.convertUpAxis = true;
},
update: function () {
var self = this;
var el = this.el;
var src = this.data;
if (!src) { return; }
this.remove();
this.loader.load(src, function (colladaModel) {
self.model = colladaModel.scene;
el.setObject3D('mesh', self.model);
el.emit('model-loaded', {format: 'collada', model: self.model});
});
},
remove: function () {
if (!this.model) { return; }
this.el.removeObject3D('mesh');
}
});
},{"../core/component":125,"../lib/three":173}],79:[function(_dereq_,module,exports){
/* global THREE */
var registerComponent = _dereq_('../core/component').registerComponent;
var utils = _dereq_('../utils/');
var bind = utils.bind;
var EVENTS = {
CLICK: 'click',
FUSING: 'fusing',
MOUSEENTER: 'mouseenter',
MOUSEDOWN: 'mousedown',
MOUSELEAVE: 'mouseleave',
MOUSEUP: 'mouseup'
};
var STATES = {
FUSING: 'cursor-fusing',
HOVERING: 'cursor-hovering',
HOVERED: 'cursor-hovered'
};
var CANVAS_EVENTS = {
DOWN: ['mousedown', 'touchstart'],
UP: ['mouseup', 'touchend']
};
/**
* Cursor component. Applies the raycaster component specifically for starting the raycaster
* from the camera and pointing from camera's facing direction, and then only returning the
* closest intersection. Cursor can be fine-tuned by setting raycaster properties.
*
* @member {object} fuseTimeout - Timeout to trigger fuse-click.
* @member {Element} cursorDownEl - Entity that was last mousedowned during current click.
* @member {object} intersection - Attributes of the current intersection event, including
* 3D- and 2D-space coordinates. See: http://threejs.org/docs/api/core/Raycaster.html
* @member {Element} intersectedEl - Currently-intersected entity. Used to keep track to
* emit events when unintersecting.
*/
module.exports.Component = registerComponent('cursor', {
dependencies: ['raycaster'],
schema: {
downEvents: {default: []},
fuse: {default: utils.device.isMobile()},
fuseTimeout: {default: 1500, min: 0},
upEvents: {default: []},
rayOrigin: {default: 'entity', oneOf: ['mouse', 'entity']}
},
init: function () {
var self = this;
this.fuseTimeout = undefined;
this.cursorDownEl = null;
this.intersection = null;
this.intersectedEl = null;
this.canvasBounds = document.body.getBoundingClientRect();
// Debounce.
this.updateCanvasBounds = utils.debounce(function updateCanvasBounds () {
self.canvasBounds = self.el.sceneEl.canvas.getBoundingClientRect();
}, 200);
// Bind methods.
this.onCursorDown = bind(this.onCursorDown, this);
this.onCursorUp = bind(this.onCursorUp, this);
this.onIntersection = bind(this.onIntersection, this);
this.onIntersectionCleared = bind(this.onIntersectionCleared, this);
this.onMouseMove = bind(this.onMouseMove, this);
},
update: function (oldData) {
if (this.data.rayOrigin === oldData.rayOrigin) { return; }
this.updateMouseEventListeners();
},
play: function () {
this.addEventListeners();
},
pause: function () {
this.removeEventListeners();
},
remove: function () {
var el = this.el;
el.removeState(STATES.HOVERING);
el.removeState(STATES.FUSING);
clearTimeout(this.fuseTimeout);
if (this.intersectedEl) { this.intersectedEl.removeState(STATES.HOVERED); }
this.removeEventListeners();
},
addEventListeners: function () {
var canvas;
var data = this.data;
var el = this.el;
var self = this;
function addCanvasListeners () {
canvas = el.sceneEl.canvas;
CANVAS_EVENTS.DOWN.forEach(function (downEvent) {
canvas.addEventListener(downEvent, self.onCursorDown);
});
CANVAS_EVENTS.UP.forEach(function (upEvent) {
canvas.addEventListener(upEvent, self.onCursorUp);
});
}
canvas = el.sceneEl.canvas;
if (canvas) {
addCanvasListeners();
} else {
el.sceneEl.addEventListener('render-target-loaded', addCanvasListeners);
}
data.downEvents.forEach(function (downEvent) {
el.addEventListener(downEvent, self.onCursorDown);
});
data.upEvents.forEach(function (upEvent) {
el.addEventListener(upEvent, self.onCursorUp);
});
el.addEventListener('raycaster-intersection', this.onIntersection);
el.addEventListener('raycaster-intersection-cleared', this.onIntersectionCleared);
window.addEventListener('resize', this.updateCanvasBounds);
},
removeEventListeners: function () {
var canvas;
var data = this.data;
var el = this.el;
var self = this;
canvas = el.sceneEl.canvas;
if (canvas) {
CANVAS_EVENTS.DOWN.forEach(function (downEvent) {
canvas.removeEventListener(downEvent, self.onCursorDown);
});
CANVAS_EVENTS.UP.forEach(function (upEvent) {
canvas.removeEventListener(upEvent, self.onCursorUp);
});
}
data.downEvents.forEach(function (downEvent) {
el.removeEventListener(downEvent, self.onCursorDown);
});
data.upEvents.forEach(function (upEvent) {
el.removeEventListener(upEvent, self.onCursorUp);
});
el.removeEventListener('raycaster-intersection', this.onIntersection);
el.removeEventListener('raycaster-intersection-cleared', this.onIntersectionCleared);
window.removeEventListener('mousemove', this.onMouseMove);
window.removeEventListener('resize', this.updateCanvasBounds);
},
updateMouseEventListeners: function () {
var el = this.el;
window.removeEventListener('mousemove', this.onMouseMove);
el.setAttribute('raycaster', 'useWorldCoordinates', false);
if (this.data.rayOrigin !== 'mouse') { return; }
window.addEventListener('mousemove', this.onMouseMove, false);
el.setAttribute('raycaster', 'useWorldCoordinates', true);
this.updateCanvasBounds();
},
onMouseMove: (function () {
var mouse = new THREE.Vector2();
var origin = new THREE.Vector3();
var direction = new THREE.Vector3();
var rayCasterConfig = {
origin: origin,
direction: direction
};
return function (evt) {
var camera = this.el.sceneEl.camera;
camera.parent.updateMatrixWorld();
camera.updateMatrixWorld();
// Calculate mouse position based on the canvas element
var bounds = this.canvasBounds;
var left = evt.clientX - bounds.left;
var top = evt.clientY - bounds.top;
mouse.x = (left / bounds.width) * 2 - 1;
mouse.y = -(top / bounds.height) * 2 + 1;
origin.setFromMatrixPosition(camera.matrixWorld);
direction.set(mouse.x, mouse.y, 0.5).unproject(camera).sub(origin).normalize();
this.el.setAttribute('raycaster', rayCasterConfig);
};
})(),
/**
* Trigger mousedown and keep track of the mousedowned entity.
*/
onCursorDown: function (evt) {
this.twoWayEmit(EVENTS.MOUSEDOWN);
this.cursorDownEl = this.intersectedEl;
},
/**
* Trigger mouseup if:
* - Not fusing (mobile has no mouse).
* - Currently intersecting an entity.
* - Currently-intersected entity is the same as the one when mousedown was triggered,
* in case user mousedowned one entity, dragged to another, and mouseupped.
*/
onCursorUp: function (evt) {
this.twoWayEmit(EVENTS.MOUSEUP);
// If intersected entity has changed since the cursorDown, still emit mouseUp on the
// previously cursorUp entity.
if (this.cursorDownEl && this.cursorDownEl !== this.intersectedEl) {
this.cursorDownEl.emit(EVENTS.MOUSEUP, {cursorEl: this.el, intersection: null});
}
if (!this.data.fuse && this.intersectedEl && this.cursorDownEl === this.intersectedEl) {
this.twoWayEmit(EVENTS.CLICK);
}
this.cursorDownEl = null;
},
/**
* Handle intersection.
*/
onIntersection: function (evt) {
var self = this;
var cursorEl = this.el;
var data = this.data;
var index;
var intersectedEl;
var intersection;
// Select closest object, excluding the cursor.
index = evt.detail.els[0] === cursorEl ? 1 : 0;
intersection = evt.detail.intersections[index];
intersectedEl = evt.detail.els[index];
// If cursor is the only intersected object, ignore the event.
if (!intersectedEl) { return; }
// Already intersecting this entity.
if (this.intersectedEl === intersectedEl) {
this.intersection = intersection;
return;
}
// Unset current intersection.
if (this.intersectedEl) { this.clearCurrentIntersection(); }
// Set new intersection.
this.intersection = intersection;
this.intersectedEl = intersectedEl;
// Hovering.
cursorEl.addState(STATES.HOVERING);
intersectedEl.addState(STATES.HOVERED);
self.twoWayEmit(EVENTS.MOUSEENTER);
// Begin fuse if necessary.
if (data.fuseTimeout === 0 || !data.fuse) { return; }
cursorEl.addState(STATES.FUSING);
this.twoWayEmit(EVENTS.FUSING);
this.fuseTimeout = setTimeout(function fuse () {
cursorEl.removeState(STATES.FUSING);
self.twoWayEmit(EVENTS.CLICK);
}, data.fuseTimeout);
},
/**
* Handle intersection cleared.
*/
onIntersectionCleared: function (evt) {
var cursorEl = this.el;
var intersectedEl = evt.detail.el;
// Ignore the cursor.
if (cursorEl === intersectedEl) { return; }
// Ignore if the event didn't occur on the current intersection.
if (intersectedEl !== this.intersectedEl) { return; }
this.clearCurrentIntersection();
},
clearCurrentIntersection: function () {
var cursorEl = this.el;
// No longer hovering (or fusing).
this.intersectedEl.removeState(STATES.HOVERED);
cursorEl.removeState(STATES.HOVERING);
cursorEl.removeState(STATES.FUSING);
this.twoWayEmit(EVENTS.MOUSELEAVE);
// Unset intersected entity (after emitting the event).
this.intersection = null;
this.intersectedEl = null;
// Clear fuseTimeout.
clearTimeout(this.fuseTimeout);
},
/**
* Helper to emit on both the cursor and the intersected entity (if exists).
*/
twoWayEmit: function (evtName) {
var el = this.el;
var intersectedEl = this.intersectedEl;
var intersection = this.intersection;
el.emit(evtName, {intersectedEl: intersectedEl, intersection: intersection});
if (!intersectedEl) { return; }
intersectedEl.emit(evtName, {cursorEl: el, intersection: intersection});
}
});
},{"../core/component":125,"../utils/":195}],80:[function(_dereq_,module,exports){
var registerComponent = _dereq_('../core/component').registerComponent;
var bind = _dereq_('../utils/bind');
var checkControllerPresentAndSetup = _dereq_('../utils/tracked-controls').checkControllerPresentAndSetup;
var emitIfAxesChanged = _dereq_('../utils/tracked-controls').emitIfAxesChanged;
var DAYDREAM_CONTROLLER_MODEL_BASE_URL = 'https://cdn.aframe.io/controllers/google/';
var DAYDREAM_CONTROLLER_MODEL_OBJ_URL = DAYDREAM_CONTROLLER_MODEL_BASE_URL + 'vr_controller_daydream.obj';
var DAYDREAM_CONTROLLER_MODEL_OBJ_MTL = DAYDREAM_CONTROLLER_MODEL_BASE_URL + 'vr_controller_daydream.mtl';
var GAMEPAD_ID_PREFIX = 'Daydream Controller';
/**
* Daydream controls.
*/
module.exports.Component = registerComponent('daydream-controls', {
schema: {
hand: {default: ''}, // Informs the degenerate arm model.
buttonColor: {type: 'color', default: '#000000'},
buttonTouchedColor: {type: 'color', default: '#777777'},
buttonHighlightColor: {type: 'color', default: '#FFFFFF'},
model: {default: true},
// Use -999 as sentinel value to auto-determine based on hand.
rotationOffset: {default: 0},
armModel: {default: true}
},
// buttonId
// 0 - trackpad
// 1 - menu (never dispatched on this layer)
// 2 - system (never dispatched on this layer)
mapping: {
axes: {trackpad: [0, 1]},
buttons: ['trackpad', 'menu', 'system']
},
bindMethods: function () {
this.onModelLoaded = bind(this.onModelLoaded, this);
this.onControllersUpdate = bind(this.onControllersUpdate, this);
this.checkIfControllerPresent = bind(this.checkIfControllerPresent, this);
this.removeControllersUpdateListener = bind(this.removeControllersUpdateListener, this);
this.onAxisMoved = bind(this.onAxisMoved, this);
this.onGamepadConnectionEvent = bind(this.onGamepadConnectionEvent, this);
},
init: function () {
var self = this;
this.animationActive = 'pointing';
this.onButtonChanged = bind(this.onButtonChanged, this);
this.onButtonDown = function (evt) { self.onButtonEvent(evt.detail.id, 'down'); };
this.onButtonUp = function (evt) { self.onButtonEvent(evt.detail.id, 'up'); };
this.onButtonTouchStart = function (evt) { self.onButtonEvent(evt.detail.id, 'touchstart'); };
this.onButtonTouchEnd = function (evt) { self.onButtonEvent(evt.detail.id, 'touchend'); };
this.onAxisMoved = bind(this.onAxisMoved, this);
this.controllerPresent = false;
this.everGotGamepadEvent = false;
this.lastControllerCheck = 0;
this.bindMethods();
this.checkControllerPresentAndSetup = checkControllerPresentAndSetup; // To allow mock.
this.emitIfAxesChanged = emitIfAxesChanged; // To allow mock.
},
addEventListeners: function () {
var el = this.el;
el.addEventListener('buttonchanged', this.onButtonChanged);
el.addEventListener('buttondown', this.onButtonDown);
el.addEventListener('buttonup', this.onButtonUp);
el.addEventListener('touchstart', this.onButtonTouchStart);
el.addEventListener('touchend', this.onButtonTouchEnd);
el.addEventListener('model-loaded', this.onModelLoaded);
el.addEventListener('axismove', this.onAxisMoved);
this.controllerEventsActive = true;
},
removeEventListeners: function () {
var el = this.el;
el.removeEventListener('buttonchanged', this.onButtonChanged);
el.removeEventListener('buttondown', this.onButtonDown);
el.removeEventListener('buttonup', this.onButtonUp);
el.removeEventListener('touchstart', this.onButtonTouchStart);
el.removeEventListener('touchend', this.onButtonTouchEnd);
el.removeEventListener('model-loaded', this.onModelLoaded);
el.removeEventListener('axismove', this.onAxisMoved);
this.controllerEventsActive = false;
},
checkIfControllerPresent: function () {
this.checkControllerPresentAndSetup(this, GAMEPAD_ID_PREFIX, {hand: this.data.hand});
},
onGamepadConnectionEvent: function (evt) {
this.everGotGamepadEvent = true;
// Due to an apparent bug in FF Nightly
// where only one gamepadconnected / disconnected event is fired,
// which makes it difficult to handle in individual controller entities,
// we no longer remove the controllersupdate listener as a result.
this.checkIfControllerPresent();
},
play: function () {
this.checkIfControllerPresent();
this.addControllersUpdateListener();
window.addEventListener('gamepadconnected', this.onGamepadConnectionEvent, false);
window.addEventListener('gamepaddisconnected', this.onGamepadConnectionEvent, false);
},
pause: function () {
this.removeEventListeners();
this.removeControllersUpdateListener();
window.removeEventListener('gamepadconnected', this.onGamepadConnectionEvent, false);
window.removeEventListener('gamepaddisconnected', this.onGamepadConnectionEvent, false);
},
injectTrackedControls: function () {
var el = this.el;
var data = this.data;
el.setAttribute('tracked-controls', {
armModel: data.armModel,
hand: data.hand,
idPrefix: GAMEPAD_ID_PREFIX,
rotationOffset: data.rotationOffset
});
if (!this.data.model) { return; }
this.el.setAttribute('obj-model', {
obj: DAYDREAM_CONTROLLER_MODEL_OBJ_URL,
mtl: DAYDREAM_CONTROLLER_MODEL_OBJ_MTL
});
},
addControllersUpdateListener: function () {
this.el.sceneEl.addEventListener('controllersupdated', this.onControllersUpdate, false);
},
removeControllersUpdateListener: function () {
this.el.sceneEl.removeEventListener('controllersupdated', this.onControllersUpdate, false);
},
onControllersUpdate: function () {
if (!this.everGotGamepadEvent) { this.checkIfControllerPresent(); }
},
onModelLoaded: function (evt) {
var controllerObject3D = evt.detail.model;
var buttonMeshes;
if (!this.data.model) { return; }
buttonMeshes = this.buttonMeshes = {};
buttonMeshes.menu = controllerObject3D.getObjectByName('AppButton_AppButton_Cylinder.004');
buttonMeshes.system = controllerObject3D.getObjectByName('HomeButton_HomeButton_Cylinder.005');
buttonMeshes.trackpad = controllerObject3D.getObjectByName('TouchPad_TouchPad_Cylinder.003');
// Offset pivot point.
controllerObject3D.position.set(0, 0, -0.04);
},
onAxisMoved: function (evt) {
this.emitIfAxesChanged(this, this.mapping.axes, evt);
},
onButtonChanged: function (evt) {
var button = this.mapping.buttons[evt.detail.id];
if (!button) return;
// Pass along changed event with button state, using button mapping for convenience.
this.el.emit(button + 'changed', evt.detail.state);
},
onButtonEvent: function (id, evtName) {
var buttonName = this.mapping.buttons[id];
var i;
if (Array.isArray(buttonName)) {
for (i = 0; i < buttonName.length; i++) {
this.el.emit(buttonName[i] + evtName);
}
} else {
this.el.emit(buttonName + evtName);
}
this.updateModel(buttonName, evtName);
},
updateModel: function (buttonName, evtName) {
var i;
if (!this.data.model) { return; }
if (Array.isArray(buttonName)) {
for (i = 0; i < buttonName.length; i++) {
this.updateButtonModel(buttonName[i], evtName);
}
} else {
this.updateButtonModel(buttonName, evtName);
}
},
updateButtonModel: function (buttonName, state) {
var buttonMeshes = this.buttonMeshes;
if (!buttonMeshes || !buttonMeshes[buttonName]) { return; }
var color;
switch (state) {
case 'down':
color = this.data.buttonHighlightColor;
break;
case 'touchstart':
color = this.data.buttonTouchedColor;
break;
default:
color = this.data.buttonColor;
}
buttonMeshes[buttonName].material.color.set(color);
}
});
},{"../core/component":125,"../utils/bind":189,"../utils/tracked-controls":199}],81:[function(_dereq_,module,exports){
var registerComponent = _dereq_('../core/component').registerComponent;
var bind = _dereq_('../utils/bind');
var checkControllerPresentAndSetup = _dereq_('../utils/tracked-controls').checkControllerPresentAndSetup;
var emitIfAxesChanged = _dereq_('../utils/tracked-controls').emitIfAxesChanged;
var GEARVR_CONTROLLER_MODEL_BASE_URL = 'https://cdn.aframe.io/controllers/samsung/';
var GEARVR_CONTROLLER_MODEL_OBJ_URL = GEARVR_CONTROLLER_MODEL_BASE_URL + 'gear_vr_controller.obj';
var GEARVR_CONTROLLER_MODEL_OBJ_MTL = GEARVR_CONTROLLER_MODEL_BASE_URL + 'gear_vr_controller.mtl';
var GAMEPAD_ID_PREFIX = 'Gear VR';
/**
* Vive Controls Component
* Interfaces with vive controllers and maps Gamepad events to
* common controller buttons: trackpad, trigger, grip, menu and system
* It loads a controller model and highlights the pressed buttons
*/
module.exports.Component = registerComponent('gearvr-controls', {
schema: {
hand: {default: ''}, // This informs the degenerate arm model.
buttonColor: {type: 'color', default: '#000000'},
buttonTouchedColor: {type: 'color', default: '#777777'},
buttonHighlightColor: {type: 'color', default: '#FFFFFF'},
model: {default: true},
rotationOffset: {default: 0},
armModel: {default: true}
},
// buttonId
// 0 - trackpad
// 1 - triggeri
mapping: {
axes: {trackpad: [0, 1]},
buttons: ['trackpad', 'trigger']
},
bindMethods: function () {
this.onModelLoaded = bind(this.onModelLoaded, this);
this.onControllersUpdate = bind(this.onControllersUpdate, this);
this.checkIfControllerPresent = bind(this.checkIfControllerPresent, this);
this.removeControllersUpdateListener = bind(this.removeControllersUpdateListener, this);
this.onAxisMoved = bind(this.onAxisMoved, this);
},
init: function () {
var self = this;
this.animationActive = 'pointing';
this.onButtonChanged = bind(this.onButtonChanged, this);
this.onButtonDown = function (evt) { self.onButtonEvent(evt.detail.id, 'down'); };
this.onButtonUp = function (evt) { self.onButtonEvent(evt.detail.id, 'up'); };
this.onButtonTouchStart = function (evt) { self.onButtonEvent(evt.detail.id, 'touchstart'); };
this.onButtonTouchEnd = function (evt) { self.onButtonEvent(evt.detail.id, 'touchend'); };
this.onAxisMoved = bind(this.onAxisMoved, this);
this.controllerPresent = false;
this.everGotGamepadEvent = false;
this.lastControllerCheck = 0;
this.bindMethods();
this.checkControllerPresentAndSetup = checkControllerPresentAndSetup; // To allow mock.
this.emitIfAxesChanged = emitIfAxesChanged; // To allow mock.
},
addEventListeners: function () {
var el = this.el;
el.addEventListener('buttonchanged', this.onButtonChanged);
el.addEventListener('buttondown', this.onButtonDown);
el.addEventListener('buttonup', this.onButtonUp);
el.addEventListener('touchstart', this.onButtonTouchStart);
el.addEventListener('touchend', this.onButtonTouchEnd);
el.addEventListener('model-loaded', this.onModelLoaded);
el.addEventListener('axismove', this.onAxisMoved);
this.controllerEventsActive = true;
this.addControllersUpdateListener();
},
removeEventListeners: function () {
var el = this.el;
el.removeEventListener('buttonchanged', this.onButtonChanged);
el.removeEventListener('buttondown', this.onButtonDown);
el.removeEventListener('buttonup', this.onButtonUp);
el.removeEventListener('touchstart', this.onButtonTouchStart);
el.removeEventListener('touchend', this.onButtonTouchEnd);
el.removeEventListener('model-loaded', this.onModelLoaded);
el.removeEventListener('axismove', this.onAxisMoved);
this.controllerEventsActive = false;
this.removeControllersUpdateListener();
},
checkIfControllerPresent: function () {
this.checkControllerPresentAndSetup(this, GAMEPAD_ID_PREFIX,
this.data.hand ? {hand: this.data.hand} : {});
},
play: function () {
this.checkIfControllerPresent();
this.addControllersUpdateListener();
},
pause: function () {
this.removeEventListeners();
this.removeControllersUpdateListener();
},
injectTrackedControls: function () {
var el = this.el;
var data = this.data;
el.setAttribute('tracked-controls', {
armModel: data.armModel,
idPrefix: GAMEPAD_ID_PREFIX,
rotationOffset: data.rotationOffset
});
if (!this.data.model) { return; }
this.el.setAttribute('obj-model', {
obj: GEARVR_CONTROLLER_MODEL_OBJ_URL,
mtl: GEARVR_CONTROLLER_MODEL_OBJ_MTL
});
},
addControllersUpdateListener: function () {
this.el.sceneEl.addEventListener('controllersupdated', this.onControllersUpdate, false);
},
removeControllersUpdateListener: function () {
this.el.sceneEl.removeEventListener('controllersupdated', this.onControllersUpdate, false);
},
onControllersUpdate: function () {
this.checkIfControllerPresent();
},
// No need for onButtonChanged, since Gear VR controller has no analog buttons.
onModelLoaded: function (evt) {
var controllerObject3D = evt.detail.model;
var buttonMeshes;
if (!this.data.model) { return; }
buttonMeshes = this.buttonMeshes = {};
buttonMeshes.trigger = controllerObject3D.getObjectByName('Trigger');
buttonMeshes.trackpad = controllerObject3D.getObjectByName('Touchpad');
},
onButtonChanged: function (evt) {
var button = this.mapping.buttons[evt.detail.id];
if (!button) return;
// Pass along changed event with button state, using button mapping for convenience.
this.el.emit(button + 'changed', evt.detail.state);
},
onButtonEvent: function (id, evtName) {
var buttonName = this.mapping.buttons[id];
var i;
if (Array.isArray(buttonName)) {
for (i = 0; i < buttonName.length; i++) {
this.el.emit(buttonName[i] + evtName);
}
} else {
this.el.emit(buttonName + evtName);
}
this.updateModel(buttonName, evtName);
},
onAxisMoved: function (evt) {
this.emitIfAxesChanged(this, this.mapping.axes, evt);
},
updateModel: function (buttonName, evtName) {
var i;
if (!this.data.model) { return; }
if (Array.isArray(buttonName)) {
for (i = 0; i < buttonName.length; i++) {
this.updateButtonModel(buttonName[i], evtName);
}
} else {
this.updateButtonModel(buttonName, evtName);
}
},
updateButtonModel: function (buttonName, state) {
var buttonMeshes = this.buttonMeshes;
if (!buttonMeshes || !buttonMeshes[buttonName]) { return; }
var color;
switch (state) {
case 'down':
color = this.data.buttonHighlightColor;
break;
case 'touchstart':
color = this.data.buttonTouchedColor;
break;
default:
color = this.data.buttonColor;
}
buttonMeshes[buttonName].material.color.set(color);
}
});
},{"../core/component":125,"../utils/bind":189,"../utils/tracked-controls":199}],82:[function(_dereq_,module,exports){
var debug = _dereq_('../utils/debug');
var geometries = _dereq_('../core/geometry').geometries;
var geometryNames = _dereq_('../core/geometry').geometryNames;
var registerComponent = _dereq_('../core/component').registerComponent;
var THREE = _dereq_('../lib/three');
var dummyGeometry = new THREE.Geometry();
var warn = debug('components:geometry:warn');
/**
* Geometry component. Combined with material component to make a mesh in 3D object.
* Extended with registered geometries.
*/
module.exports.Component = registerComponent('geometry', {
schema: {
buffer: {default: true},
mergeTo: {type: 'selector'},
primitive: {default: 'box', oneOf: geometryNames},
skipCache: {default: false}
},
init: function () {
this.geometry = null;
},
/**
* Talk to geometry system to get or create geometry.
*/
update: function (previousData) {
var data = this.data;
var mesh = this.el.getOrCreateObject3D('mesh', THREE.Mesh);
var system = this.system;
// Dispose old geometry if we created one.
if (this.geometry) {
system.unuseGeometry(previousData);
this.geometry = null;
}
// Create new geometry.
this.geometry = mesh.geometry = system.getOrCreateGeometry(data);
if (data.mergeTo) {
this.mergeTo(data.mergeTo);
}
},
/**
* Merge geometry to another entity's geometry.
* Remove the entity from the scene. Not a reversible operation.
*
* @param {Element} toEl - Entity where the geometry will be merged to.
*/
mergeTo: function (toEl) {
var el = this.el;
var mesh = el.getObject3D('mesh');
var toMesh;
if (!toEl || !toEl.isEntity) {
warn('There is not a valid entity to merge the geometry to');
return;
}
if (toEl === el) {
warn('Source and target geometries cannot be the same for merge');
return;
}
// Create mesh if entity does not have one.
toMesh = toEl.getObject3D('mesh');
if (!toMesh) {
toMesh = toEl.getOrCreateObject3D('mesh', THREE.Mesh);
toEl.setAttribute('material', el.getAttribute('material'));
return;
}
if (toMesh.geometry instanceof THREE.Geometry === false ||
mesh.geometry instanceof THREE.Geometry === false) {
warn('Geometry merge is only available for `THREE.Geometry` types. ' +
'Check that both of the merging geometry and the target geometry have `buffer` ' +
'set to false');
return;
}
if (this.data.skipCache === false) {
warn('Cached geometries are not allowed to merge. Set `skipCache` to true');
return;
}
mesh.parent.updateMatrixWorld();
toMesh.geometry.merge(mesh.geometry, mesh.matrixWorld);
el.emit('geometry-merged', {mergeTarget: toEl});
el.parentNode.removeChild(el);
},
/**
* Tell geometry system that entity is no longer using the geometry.
* Unset the geometry on the mesh
*/
remove: function () {
this.system.unuseGeometry(this.data);
this.el.getObject3D('mesh').geometry = dummyGeometry;
this.geometry = null;
},
/**
* Update geometry component schema based on geometry type.
*
* @param {object} data - New data passed by Component.
*/
updateSchema: function (data) {
var newGeometryType = data.primitive;
var currentGeometryType = this.data && this.data.primitive;
var schema = geometries[newGeometryType] && geometries[newGeometryType].schema;
// Geometry has no schema.
if (!schema) { throw new Error('Unknown geometry schema `' + newGeometryType + '`'); }
// Nothing has changed.
if (currentGeometryType && currentGeometryType === newGeometryType) { return; }
this.extendSchema(schema);
}
});
},{"../core/component":125,"../core/geometry":126,"../lib/three":173,"../utils/debug":191}],83:[function(_dereq_,module,exports){
var registerComponent = _dereq_('../core/component').registerComponent;
var THREE = _dereq_('../lib/three');
var utils = _dereq_('../utils/');
var warn = utils.debug('components:gltf-model:warn');
/**
* glTF model loader.
*/
module.exports.Component = registerComponent('gltf-model', {
schema: {type: 'model'},
init: function () {
this.model = null;
this.loader = new THREE.GLTFLoader();
},
update: function () {
var self = this;
var el = this.el;
var src = this.data;
if (!src) { return; }
this.remove();
this.loader.load(src, function gltfLoaded (gltfModel) {
self.model = gltfModel.scene || gltfModel.scenes[0];
self.model.animations = gltfModel.animations;
el.setObject3D('mesh', self.model);
el.emit('model-loaded', {format: 'gltf', model: self.model});
}, undefined /* onProgress */, function gltfFailed (error) {
var message = (error && error.message) ? error.message : 'Failed to load glTF model';
warn(message);
el.emit('model-error', {format: 'gltf', src: src});
});
},
remove: function () {
if (!this.model) { return; }
this.el.removeObject3D('mesh');
}
});
},{"../core/component":125,"../lib/three":173,"../utils/":195}],84:[function(_dereq_,module,exports){
/* global THREE */
var registerComponent = _dereq_('../core/component').registerComponent;
// Found at https://github.com/aframevr/assets.
var MODEL_URLS = {
left: 'https://cdn.aframe.io/controllers/oculus-hands/v2/leftHand.json',
right: 'https://cdn.aframe.io/controllers/oculus-hands/v2/rightHand.json'
};
// Poses.
var ANIMATIONS = {
open: 'Open',
// point: grip active, trackpad surface active, trigger inactive.
point: 'Point',
// pointThumb: grip active, trigger inactive, trackpad surface inactive.
pointThumb: 'Point + Thumb',
// fist: grip active, trigger active, trackpad surface active.
fist: 'Fist',
// hold: trigger active, grip inactive.
hold: 'Hold',
// thumbUp: grip active, trigger active, trackpad surface inactive.
thumbUp: 'Thumb Up'
};
// Map animation to public events for the API.
var EVENTS = {};
EVENTS[ANIMATIONS.fist] = 'grip';
EVENTS[ANIMATIONS.thumbUp] = 'pistol';
EVENTS[ANIMATIONS.point] = 'pointing';
EVENTS[ANIMATIONS.thumb] = 'thumb';
/**
* Hand controls component that abstracts 6DoF controls:
* oculus-touch-controls, vive-controls, windows-motion-controls.
*
* Originally meant to be a sample implementation of applications-specific controls that
* abstracts multiple types of controllers.
*
* Auto-detect appropriate controller.
* Handle common events coming from the detected vendor-specific controls.
* Translate button events to semantic hand-related event names:
* (gripclose, gripopen, thumbup, thumbdown, pointup, pointdown)
* Load hand model with gestures that are applied based on the button pressed.
*
* @property {string} Hand mapping (`left`, `right`).
*/
module.exports.Component = registerComponent('hand-controls', {
schema: {default: 'left'},
init: function () {
var self = this;
var el = this.el;
// Current pose.
this.gesture = ANIMATIONS.open;
// Active buttons populated by events provided by the attached controls.
this.pressedButtons = {};
this.touchedButtons = {};
this.loader = new THREE.ObjectLoader();
this.loader.setCrossOrigin('anonymous');
this.onGripDown = function () { self.handleButton('grip', 'down'); };
this.onGripUp = function () { self.handleButton('grip', 'up'); };
this.onTrackpadDown = function () { self.handleButton('trackpad', 'down'); };
this.onTrackpadUp = function () { self.handleButton('trackpad', 'up'); };
this.onTrackpadTouchStart = function () { self.handleButton('trackpad', 'touchstart'); };
this.onTrackpadTouchEnd = function () { self.handleButton('trackpad', 'touchend'); };
this.onTriggerDown = function () { self.handleButton('trigger', 'down'); };
this.onTriggerUp = function () { self.handleButton('trigger', 'up'); };
this.onTriggerTouchStart = function () { self.handleButton('trigger', 'touchstart'); };
this.onTriggerTouchEnd = function () { self.handleButton('trigger', 'touchend'); };
this.onGripTouchStart = function () { self.handleButton('grip', 'touchstart'); };
this.onGripTouchEnd = function () { self.handleButton('grip', 'touchend'); };
this.onThumbstickDown = function () { self.handleButton('thumbstick', 'down'); };
this.onThumbstickUp = function () { self.handleButton('thumbstick', 'up'); };
this.onAorXTouchStart = function () { self.handleButton('AorX', 'touchstart'); };
this.onAorXTouchEnd = function () { self.handleButton('AorX', 'touchend'); };
this.onBorYTouchStart = function () { self.handleButton('BorY', 'touchstart'); };
this.onBorYTouchEnd = function () { self.handleButton('BorY', 'touchend'); };
this.onSurfaceTouchStart = function () { self.handleButton('surface', 'touchstart'); };
this.onSurfaceTouchEnd = function () { self.handleButton('surface', 'touchend'); };
this.onControllerConnected = function () { self.setModelVisibility(true); };
this.onControllerDisconnected = function () { self.setModelVisibility(false); };
el.addEventListener('controllerconnected', this.onControllerConnected);
el.addEventListener('controllerdisconnected', this.onControllerDisconnected);
},
play: function () {
this.addEventListeners();
},
pause: function () {
this.removeEventListeners();
},
tick: function (time, delta) {
var mesh = this.el.getObject3D('mesh');
if (!mesh || !mesh.mixer) { return; }
mesh.mixer.update(delta / 1000);
},
addEventListeners: function () {
var el = this.el;
el.addEventListener('gripdown', this.onGripDown);
el.addEventListener('gripup', this.onGripUp);
el.addEventListener('trackpaddown', this.onTrackpadDown);
el.addEventListener('trackpadup', this.onTrackpadUp);
el.addEventListener('trackpadtouchstart', this.onTrackpadTouchStart);
el.addEventListener('trackpadtouchend', this.onTrackpadTouchEnd);
el.addEventListener('triggerdown', this.onTriggerDown);
el.addEventListener('triggerup', this.onTriggerUp);
el.addEventListener('triggertouchstart', this.onTriggerTouchStart);
el.addEventListener('triggertouchend', this.onTriggerTouchEnd);
el.addEventListener('griptouchstart', this.onGripTouchStart);
el.addEventListener('griptouchend', this.onGripTouchEnd);
el.addEventListener('thumbstickdown', this.onThumbstickDown);
el.addEventListener('thumbstickup', this.onThumbstickUp);
el.addEventListener('abuttontouchstart', this.onAorXTouchStart);
el.addEventListener('abuttontouchend', this.onAorXTouchEnd);
el.addEventListener('bbuttontouchstart', this.onBorYTouchStart);
el.addEventListener('bbuttontouchend', this.onBorYTouchEnd);
el.addEventListener('xbuttontouchstart', this.onAorXTouchStart);
el.addEventListener('xbuttontouchend', this.onAorXTouchEnd);
el.addEventListener('ybuttontouchstart', this.onBorYTouchStart);
el.addEventListener('ybuttontouchend', this.onBorYTouchEnd);
el.addEventListener('surfacetouchstart', this.onSurfaceTouchStart);
el.addEventListener('surfacetouchend', this.onSurfaceTouchEnd);
},
removeEventListeners: function () {
var el = this.el;
el.removeEventListener('gripdown', this.onGripDown);
el.removeEventListener('gripup', this.onGripUp);
el.removeEventListener('trackpaddown', this.onTrackpadDown);
el.removeEventListener('trackpadup', this.onTrackpadUp);
el.removeEventListener('trackpadtouchstart', this.onTrackpadTouchStart);
el.removeEventListener('trackpadtouchend', this.onTrackpadTouchEnd);
el.removeEventListener('triggerdown', this.onTriggerDown);
el.removeEventListener('triggerup', this.onTriggerUp);
el.removeEventListener('triggertouchstart', this.onTriggerTouchStart);
el.removeEventListener('triggertouchend', this.onTriggerTouchEnd);
el.removeEventListener('griptouchstart', this.onGripTouchStart);
el.removeEventListener('griptouchend', this.onGripTouchEnd);
el.removeEventListener('thumbstickdown', this.onThumbstickDown);
el.removeEventListener('thumbstickup', this.onThumbstickUp);
el.removeEventListener('abuttontouchstart', this.onAorXTouchStart);
el.removeEventListener('abuttontouchend', this.onAorXTouchEnd);
el.removeEventListener('bbuttontouchstart', this.onBorYTouchStart);
el.removeEventListener('bbuttontouchend', this.onBorYTouchEnd);
el.removeEventListener('xbuttontouchstart', this.onAorXTouchStart);
el.removeEventListener('xbuttontouchend', this.onAorXTouchEnd);
el.removeEventListener('ybuttontouchstart', this.onBorYTouchStart);
el.removeEventListener('ybuttontouchend', this.onBorYTouchEnd);
el.removeEventListener('surfacetouchstart', this.onSurfaceTouchStart);
el.removeEventListener('surfacetouchend', this.onSurfaceTouchEnd);
},
/**
* Update handler. More like the `init` handler since the only property is the hand, and
* that won't be changing much.
*/
update: function (previousHand) {
var controlConfiguration;
var el = this.el;
var hand = this.data;
// Get common configuration to abstract different vendor controls.
controlConfiguration = {
hand: hand,
model: false,
rotationOffset: hand === 'left' ? 90 : -90
};
// Set model.
if (hand !== previousHand) {
this.loader.load(MODEL_URLS[hand], function (scene) {
var mesh = scene.getObjectByName('Hand');
mesh.material.skinning = true;
mesh.mixer = new THREE.AnimationMixer(mesh);
el.setObject3D('mesh', mesh);
mesh.position.set(0, 0, 0);
mesh.rotation.set(0, 0, 0);
// hidden by default
mesh.visible = false;
el.setAttribute('vive-controls', controlConfiguration);
el.setAttribute('oculus-touch-controls', controlConfiguration);
el.setAttribute('windows-motion-controls', controlConfiguration);
});
}
},
remove: function () {
this.el.removeObject3D('mesh');
},
/**
* Play model animation, based on which button was pressed and which kind of event.
*
* 1. Process buttons.
* 2. Determine gesture (this.determineGesture()).
* 3. Animation gesture (this.animationGesture()).
* 4. Emit gesture events (this.emitGestureEvents()).
*
* @param {string} button - Name of the button.
* @param {string} evt - Type of event for the button (i.e., down/up/touchstart/touchend).
*/
handleButton: function (button, evt) {
var lastGesture;
var isPressed = evt === 'down';
var isTouched = evt === 'touchstart';
// Update objects.
if (evt.indexOf('touch') === 0) {
// Update touch object.
if (isTouched === this.touchedButtons[button]) { return; }
this.touchedButtons[button] = isTouched;
} else {
// Update button object.
if (isPressed === this.pressedButtons[button]) { return; }
this.pressedButtons[button] = isPressed;
}
// Determine the gesture.
lastGesture = this.gesture;
this.gesture = this.determineGesture();
// Same gesture.
if (this.gesture === lastGesture) { return; }
// Animate gesture.
this.animateGesture(this.gesture, lastGesture);
// Emit events.
this.emitGestureEvents(this.gesture, lastGesture);
},
/**
* Determine which pose hand should be in considering active and touched buttons.
*/
determineGesture: function () {
var gesture;
var isGripActive = this.pressedButtons['grip'];
var isSurfaceActive = this.pressedButtons['surface'] || this.touchedButtons['surface'];
var isTrackpadActive = this.pressedButtons['trackpad'] || this.touchedButtons['trackpad'];
var isTriggerActive = this.pressedButtons['trigger'] || this.touchedButtons['trigger'];
var isABXYActive = this.touchedButtons['AorX'] || this.touchedButtons['BorY'];
var isVive = isViveController(this.el.components['tracked-controls']);
// Works well with Oculus Touch and Windows Motion Controls, but Vive needs tweaks.
if (isGripActive) {
if (isVive) {
gesture = ANIMATIONS.fist;
} else
if (isSurfaceActive || isABXYActive || isTrackpadActive) {
gesture = isTriggerActive ? ANIMATIONS.fist : ANIMATIONS.point;
} else {
gesture = isTriggerActive ? ANIMATIONS.thumbUp : ANIMATIONS.pointThumb;
}
} else {
if (isTriggerActive) {
gesture = !isVive ? ANIMATIONS.hold : ANIMATIONS.fist;
} else if (isVive && isTrackpadActive) {
gesture = ANIMATIONS.point;
}
}
return gesture;
},
/**
* Play gesture animation.
*
* @param {string} gesture - Which pose to animate to. If absent, then animate to open.
* @param {string} lastGesture - Previous gesture, to reverse back to open if needed.
*/
animateGesture: function (gesture, lastGesture) {
if (gesture) {
this.playAnimation(gesture || ANIMATIONS.open, lastGesture, false);
return;
}
// If no gesture, then reverse the current gesture back to open pose.
this.playAnimation(lastGesture, lastGesture, true);
},
/**
* Emit `hand-controls`-specific events.
*/
emitGestureEvents: function (gesture, lastGesture) {
var el = this.el;
var eventName;
if (lastGesture === gesture) { return; }
// Emit event for lastGesture not inactive.
eventName = getGestureEventName(lastGesture, false);
if (eventName) { el.emit(eventName); }
// Emit event for current gesture now active.
eventName = getGestureEventName(gesture, true);
if (eventName) { el.emit(eventName); }
},
/**
* Play hand animation based on button state.
*
* @param {string} gesture - Name of the animation as specified by the model.
* @param {string} lastGesture - Previous pose.
* @param {boolean} reverse - Whether animation should play in reverse.
*/
playAnimation: function (gesture, lastGesture, reverse) {
var fromAction;
var mesh = this.el.getObject3D('mesh');
var toAction;
if (!mesh) { return; }
// Grab clip action.
toAction = mesh.mixer.clipAction(gesture);
toAction.clampWhenFinished = true;
toAction.loop = THREE.PingPong;
toAction.repetitions = 0;
toAction.timeScale = reverse ? -1 : 1;
toAction.weight = 1;
// No gesture to gesture or gesture to no gesture.
if (!lastGesture || gesture === lastGesture) {
// Stop all current animations.
mesh.mixer.stopAllAction();
// Play animation.
toAction.play();
return;
}
// Animate or crossfade from gesture to gesture.
fromAction = mesh.mixer.clipAction(lastGesture);
mesh.mixer.stopAllAction();
fromAction.weight = 0.15;
fromAction.play();
toAction.play();
fromAction.crossFadeTo(toAction, 0.15, true);
},
setModelVisibility: function (visible) {
var model = this.el.getObject3D('mesh');
if (!model) { return; }
model.visible = visible;
}
});
/**
* Suffix gestures based on toggle state (e.g., open/close, up/down, start/end).
*
* @param {string} gesture
* @param {boolean} active
*/
function getGestureEventName (gesture, active) {
var eventName;
if (!gesture) { return; }
eventName = EVENTS[gesture];
if (eventName === 'grip') {
return eventName + (active ? 'close' : 'open');
}
if (eventName === 'point' || eventName === 'thumb') {
return eventName + (active ? 'up' : 'down');
}
if (eventName === 'pointing' || eventName === 'pistol') {
return eventName + (active ? 'start' : 'end');
}
return;
}
function isViveController (trackedControls) {
var controllerId = trackedControls && trackedControls.controller &&
trackedControls.controller.id;
return controllerId && controllerId.indexOf('OpenVR ') === 0;
}
},{"../core/component":125}],85:[function(_dereq_,module,exports){
_dereq_('./camera');
_dereq_('./collada-model');
_dereq_('./cursor');
_dereq_('./daydream-controls');
_dereq_('./gearvr-controls');
_dereq_('./geometry');
_dereq_('./gltf-model');
_dereq_('./hand-controls');
_dereq_('./laser-controls');
_dereq_('./light');
_dereq_('./line');
_dereq_('./link');
_dereq_('./look-controls');
_dereq_('./material');
_dereq_('./obj-model');
_dereq_('./oculus-touch-controls');
_dereq_('./position');
_dereq_('./raycaster');
_dereq_('./rotation');
_dereq_('./scale');
_dereq_('./shadow');
_dereq_('./sound');
_dereq_('./text');
_dereq_('./tracked-controls');
_dereq_('./visible');
_dereq_('./vive-controls');
_dereq_('./wasd-controls');
_dereq_('./windows-motion-controls');
_dereq_('./scene/debug');
_dereq_('./scene/embedded');
_dereq_('./scene/inspector');
_dereq_('./scene/fog');
_dereq_('./scene/keyboard-shortcuts');
_dereq_('./scene/pool');
_dereq_('./scene/screenshot');
_dereq_('./scene/stats');
_dereq_('./scene/vr-mode-ui');
},{"./camera":77,"./collada-model":78,"./cursor":79,"./daydream-controls":80,"./gearvr-controls":81,"./geometry":82,"./gltf-model":83,"./hand-controls":84,"./laser-controls":86,"./light":87,"./line":88,"./link":89,"./look-controls":90,"./material":91,"./obj-model":92,"./oculus-touch-controls":93,"./position":94,"./raycaster":95,"./rotation":96,"./scale":97,"./scene/debug":98,"./scene/embedded":99,"./scene/fog":100,"./scene/inspector":101,"./scene/keyboard-shortcuts":102,"./scene/pool":103,"./scene/screenshot":104,"./scene/stats":105,"./scene/vr-mode-ui":106,"./shadow":107,"./sound":108,"./text":109,"./tracked-controls":110,"./visible":111,"./vive-controls":112,"./wasd-controls":113,"./windows-motion-controls":114}],86:[function(_dereq_,module,exports){
var registerComponent = _dereq_('../core/component').registerComponent;
var utils = _dereq_('../utils/');
registerComponent('laser-controls', {
schema: {
hand: {default: 'right'}
},
init: function () {
var config = this.config;
var data = this.data;
var el = this.el;
var self = this;
// Set all controller models.
el.setAttribute('daydream-controls', {hand: data.hand});
el.setAttribute('gearvr-controls', {hand: data.hand});
el.setAttribute('oculus-touch-controls', {hand: data.hand});
el.setAttribute('vive-controls', {hand: data.hand});
el.setAttribute('windows-motion-controls', {hand: data.hand});
// Wait for controller to connect, or have a valid pointing pose, before creating ray
el.addEventListener('controllerconnected', createRay);
el.addEventListener('controllerdisconnected', hideRay);
el.addEventListener('controllermodelready', function (evt) {
createRay(evt);
self.modelReady = true;
});
function createRay (evt) {
var controllerConfig = config[evt.detail.name];
if (!controllerConfig) { return; }
// Show the line unless a particular config opts to hide it, until a controllermodelready
// event comes through.
var raycasterConfig = utils.extend({
showLine: true
}, controllerConfig.raycaster || {});
// The controllermodelready event contains a rayOrigin that takes into account
// offsets specific to the loaded model.
if (evt.detail.rayOrigin) {
raycasterConfig.origin = evt.detail.rayOrigin.origin;
raycasterConfig.direction = evt.detail.rayOrigin.direction;
raycasterConfig.showLine = true;
}
// Only apply a default raycaster if it does not yet exist. This prevents it overwriting
// config applied from a controllermodelready event.
if (evt.detail.rayOrigin || !self.modelReady) {
el.setAttribute('raycaster', raycasterConfig);
} else {
el.setAttribute('raycaster', 'showLine', true);
}
el.setAttribute('cursor', utils.extend({
fuse: false
}, controllerConfig.cursor));
}
function hideRay () {
el.setAttribute('raycaster', 'showLine', false);
}
},
config: {
'daydream-controls': {
cursor: {downEvents: ['trackpaddown'], upEvents: ['trackpadup']}
},
'gearvr-controls': {
cursor: {downEvents: ['trackpaddown'], upEvents: ['trackpadup']},
raycaster: {origin: {x: 0, y: 0.0005, z: 0}}
},
'oculus-touch-controls': {
cursor: {downEvents: ['triggerdown'], upEvents: ['triggerup']},
raycaster: {origin: {x: 0.001, y: 0, z: 0.065}, direction: {x: 0, y: -0.8, z: -1}}
},
'vive-controls': {
cursor: {downEvents: ['triggerdown'], upEvents: ['triggerup']}
},
'windows-motion-controls': {
cursor: {downEvents: ['triggerdown'], upEvents: ['triggerup']},
raycaster: {showLine: false}
}
}
});
},{"../core/component":125,"../utils/":195}],87:[function(_dereq_,module,exports){
var bind = _dereq_('../utils/bind');
var diff = _dereq_('../utils').diff;
var debug = _dereq_('../utils/debug');
var registerComponent = _dereq_('../core/component').registerComponent;
var THREE = _dereq_('../lib/three');
var degToRad = THREE.Math.degToRad;
var warn = debug('components:light:warn');
/**
* Light component.
*/
module.exports.Component = registerComponent('light', {
schema: {
angle: {default: 60, if: {type: ['spot']}},
color: {type: 'color'},
groundColor: {type: 'color', if: {type: ['hemisphere']}},
decay: {default: 1, if: {type: ['point', 'spot']}},
distance: {default: 0.0, min: 0, if: {type: ['point', 'spot']}},
intensity: {default: 1.0, min: 0, if: {type: ['ambient', 'directional', 'hemisphere', 'point', 'spot']}},
penumbra: {default: 0, min: 0, max: 1, if: {type: ['spot']}},
type: {default: 'directional', oneOf: ['ambient', 'directional', 'hemisphere', 'point', 'spot']},
target: {type: 'selector', if: {type: ['spot', 'directional']}},
// Shadows.
castShadow: {default: false, if: {type: ['point', 'spot', 'directional']}},
shadowBias: {default: 0, if: {castShadow: true}},
shadowCameraFar: {default: 500, if: {castShadow: true}},
shadowCameraFov: {default: 90, if: {castShadow: true}},
shadowCameraNear: {default: 0.5, if: {castShadow: true}},
shadowCameraTop: {default: 5, if: {castShadow: true}},
shadowCameraRight: {default: 5, if: {castShadow: true}},
shadowCameraBottom: {default: -5, if: {castShadow: true}},
shadowCameraLeft: {default: -5, if: {castShadow: true}},
shadowCameraVisible: {default: false, if: {castShadow: true}},
shadowMapHeight: {default: 512, if: {castShadow: true}},
shadowMapWidth: {default: 512, if: {castShadow: true}}
},
/**
* Notifies scene a light has been added to remove default lighting.
*/
init: function () {
var el = this.el;
this.light = null;
this.defaultTarget = null;
this.system.registerLight(el);
},
/**
* (Re)create or update light.
*/
update: function (oldData) {
var data = this.data;
var diffData = diff(data, oldData);
var light = this.light;
var self = this;
// Existing light.
if (light && !('type' in diffData)) {
var shadowsLoaded = false;
// Light type has not changed. Update light.
Object.keys(diffData).forEach(function (key) {
var value = data[key];
switch (key) {
case 'color': {
light.color.set(value);
break;
}
case 'groundColor': {
light.groundColor.set(value);
break;
}
case 'angle': {
light.angle = degToRad(value);
break;
}
case 'target': {
// Reset target if selector is null.
if (value === null) {
if (data.type === 'spot' || data.type === 'directional') {
light.target = self.defaultTarget;
}
} else {
// Target specified, set target to entity's `object3D` when it is loaded.
if (value.hasLoaded) {
self.onSetTarget(value, light);
} else {
value.addEventListener('loaded', bind(self.onSetTarget, self, value, light));
}
}
break;
}
case 'castShadow':
case 'shadowBias':
case 'shadowCameraFar':
case 'shadowCameraFov':
case 'shadowCameraNear':
case 'shadowCameraTop':
case 'shadowCameraRight':
case 'shadowCameraBottom':
case 'shadowCameraLeft':
case 'shadowCameraVisible':
case 'shadowMapHeight':
case 'shadowMapWidth':
if (!shadowsLoaded) {
self.updateShadow();
shadowsLoaded = true;
}
break;
default: {
light[key] = value;
}
}
});
return;
}
// No light yet or light type has changed. Create and add light.
this.setLight(this.data);
this.updateShadow();
},
setLight: function (data) {
var el = this.el;
var newLight = this.getLight(data);
if (newLight) {
if (this.light) {
el.removeObject3D('light');
}
this.light = newLight;
this.light.el = el;
el.setObject3D('light', this.light);
// HACK solution for issue #1624
if (data.type === 'spot' || data.type === 'directional' || data.type === 'hemisphere') {
el.getObject3D('light').translateY(-1);
}
// set and position default lighttarget as a child to enable spotlight orientation
if (data.type === 'spot') {
el.setObject3D('light-target', this.defaultTarget);
el.getObject3D('light-target').position.set(0, 0, -1);
}
}
},
/**
* Updates shadow-related properties on the current light.
*/
updateShadow: function () {
var el = this.el;
var data = this.data;
var light = this.light;
light.castShadow = data.castShadow;
// Shadow camera helper.
var cameraHelper = el.getObject3D('cameraHelper');
if (data.shadowCameraVisible && !cameraHelper) {
el.setObject3D('cameraHelper', new THREE.CameraHelper(light.shadow.camera));
} else if (!data.shadowCameraVisible && cameraHelper) {
el.removeObject3D('cameraHelper');
}
if (!data.castShadow) { return light; }
// Shadow appearance.
light.shadow.bias = data.shadowBias;
light.shadow.mapSize.height = data.shadowMapHeight;
light.shadow.mapSize.width = data.shadowMapWidth;
// Shadow camera.
light.shadow.camera.near = data.shadowCameraNear;
light.shadow.camera.far = data.shadowCameraFar;
if (light.shadow.camera instanceof THREE.OrthographicCamera) {
light.shadow.camera.top = data.shadowCameraTop;
light.shadow.camera.right = data.shadowCameraRight;
light.shadow.camera.bottom = data.shadowCameraBottom;
light.shadow.camera.left = data.shadowCameraLeft;
} else {
light.shadow.camera.fov = data.shadowCameraFov;
}
light.shadow.camera.updateProjectionMatrix();
if (cameraHelper) { cameraHelper.update(); }
},
/**
* Creates a new three.js light object given data object defining the light.
*
* @param {object} data
*/
getLight: function (data) {
var angle = data.angle;
var color = new THREE.Color(data.color).getHex();
var decay = data.decay;
var distance = data.distance;
var groundColor = new THREE.Color(data.groundColor).getHex();
var intensity = data.intensity;
var type = data.type;
var target = data.target;
var light = null;
switch (type.toLowerCase()) {
case 'ambient': {
return new THREE.AmbientLight(color, intensity);
}
case 'directional': {
light = new THREE.DirectionalLight(color, intensity);
this.defaultTarget = light.target;
if (target) {
if (target.hasLoaded) {
this.onSetTarget(target, light);
} else {
target.addEventListener('loaded', bind(this.onSetTarget, this, target, light));
}
}
return light;
}
case 'hemisphere': {
return new THREE.HemisphereLight(color, groundColor, intensity);
}
case 'point': {
return new THREE.PointLight(color, intensity, distance, decay);
}
case 'spot': {
light = new THREE.SpotLight(color, intensity, distance, degToRad(angle), data.penumbra, decay);
this.defaultTarget = light.target;
if (target) {
if (target.hasLoaded) {
this.onSetTarget(target, light);
} else {
target.addEventListener('loaded', bind(this.onSetTarget, this, target, light));
}
}
return light;
}
default: {
warn('%s is not a valid light type. ' +
'Choose from ambient, directional, hemisphere, point, spot.', type);
}
}
},
onSetTarget: function (targetEl, light) {
light.target = targetEl.object3D;
},
/**
* Remove light on remove (callback).
*/
remove: function () {
var el = this.el;
el.removeObject3D('light');
if (el.getObject3D('cameraHelper')) {
el.removeObject3D('cameraHelper');
}
}
});
},{"../core/component":125,"../lib/three":173,"../utils":195,"../utils/bind":189,"../utils/debug":191}],88:[function(_dereq_,module,exports){
/* global THREE */
var registerComponent = _dereq_('../core/component').registerComponent;
module.exports.Component = registerComponent('line', {
schema: {
start: {type: 'vec3', default: {x: 0, y: 0, z: 0}},
end: {type: 'vec3', default: {x: 0, y: 0, z: 0}},
color: {type: 'color', default: '#74BEC1'},
opacity: {type: 'number', default: 1},
visible: {default: true}
},
multiple: true,
init: function () {
var data = this.data;
var geometry;
var material;
material = this.material = new THREE.LineBasicMaterial({
color: data.color,
opacity: data.opacity,
transparent: data.opacity < 1,
visible: data.visible
});
geometry = this.geometry = new THREE.BufferGeometry();
geometry.addAttribute('position', new THREE.BufferAttribute(new Float32Array(2 * 3), 3));
this.line = new THREE.Line(geometry, material);
this.el.setObject3D(this.attrName, this.line);
},
update: function (oldData) {
var data = this.data;
var geometry = this.geometry;
var geoNeedsUpdate = false;
var material = this.material;
var positionArray = geometry.attributes.position.array;
// Update geometry.
if (!isEqualVec3(data.start, oldData.start)) {
positionArray[0] = data.start.x;
positionArray[1] = data.start.y;
positionArray[2] = data.start.z;
geoNeedsUpdate = true;
}
if (!isEqualVec3(data.end, oldData.end)) {
positionArray[3] = data.end.x;
positionArray[4] = data.end.y;
positionArray[5] = data.end.z;
geoNeedsUpdate = true;
}
if (geoNeedsUpdate) {
geometry.attributes.position.needsUpdate = true;
geometry.computeBoundingSphere();
}
material.color.setStyle(data.color);
material.opacity = data.opacity;
material.transparent = data.opacity < 1;
material.visible = data.visible;
},
remove: function () {
this.el.removeObject3D('line', this.line);
}
});
function isEqualVec3 (a, b) {
if (!a || !b) { return false; }
return (a.x === b.x && a.y === b.y && a.z === b.z);
}
},{"../core/component":125}],89:[function(_dereq_,module,exports){
var registerComponent = _dereq_('../core/component').registerComponent;
var registerShader = _dereq_('../core/shader').registerShader;
var THREE = _dereq_('../lib/three');
/**
* Link component. Connect experiences and traverse between them in VR
*
* @member {object} hiddenEls - Stores the hidden elements during peek mode.
*/
module.exports.Component = registerComponent('link', {
schema: {
color: {default: 'white', type: 'color'},
highlighted: {default: false},
highlightedColor: {default: '#24CAFF', type: 'color'},
href: {default: ''},
image: {type: 'asset'},
on: {default: 'click'},
peekMode: {default: false},
title: {default: ''},
visualAspectEnabled: {default: true}
},
init: function () {
this.navigate = this.navigate.bind(this);
this.previousQuaternion = undefined;
// Store hidden elements during peek mode so we can show them again later.
this.hiddenEls = [];
this.initVisualAspect();
},
update: function (oldData) {
var data = this.data;
var el = this.el;
var strokeColor = data.highlighted ? data.highlightedColor : data.color;
el.setAttribute('material', 'strokeColor', strokeColor);
if (data.on !== oldData.on) { this.updateEventListener(); }
if (data.visualAspectEnabled && oldData.peekMode !== undefined && data.peekMode !== oldData.peekMode) {
this.updatePeekMode();
}
if (!data.image || oldData.image === data.image) { return; }
el.setAttribute('material', 'pano',
typeof data.image === 'string' ? data.image : data.image.src);
},
/*
* Hide / Show all elements and Hide / Show the full 360 preview
* of the linked page.
*/
updatePeekMode: function () {
var el = this.el;
var sphereEl = this.sphereEl;
if (this.data.peekMode) {
this.hideAll();
el.getObject3D('mesh').visible = false;
sphereEl.setAttribute('visible', true);
} else {
this.showAll();
el.getObject3D('mesh').visible = true;
sphereEl.setAttribute('visible', false);
}
},
play: function () {
this.updateEventListener();
},
pause: function () {
this.removeEventListener();
},
updateEventListener: function () {
var el = this.el;
if (!el.isPlaying) { return; }
this.removeEventListener();
el.addEventListener(this.data.on, this.navigate);
},
removeEventListener: function () {
var on = this.data.on;
if (!on) { return; }
this.el.removeEventListener(on, this.navigate);
},
initVisualAspect: function () {
var el = this.el;
var textEl;
var sphereEl;
var semiSphereEl;
if (!this.data.visualAspectEnabled) { return; }
textEl = this.textEl = this.textEl || document.createElement('a-entity');
sphereEl = this.sphereEl = this.sphereEl || document.createElement('a-entity');
semiSphereEl = this.semiSphereEl = this.semiSphereEl || document.createElement('a-entity');
// Set Portal
el.setAttribute('geometry', {primitive: 'circle', radius: 1.0, segments: 64});
el.setAttribute('material', {
shader: 'portal',
pano: this.data.image,
side: 'double'
});
// Set text that displays the link title / url
textEl.setAttribute('text', {
color: 'white',
align: 'center',
font: 'kelsonsans',
value: this.data.title || this.data.href,
width: 4
});
textEl.setAttribute('position', '0 1.5 0');
el.appendChild(textEl);
// Set the sphere that is rendered when the camera is close
// to the portal to allow the user peek inside
semiSphereEl.setAttribute('geometry', {
primitive: 'sphere',
radius: 1.0,
phiStart: 0,
segmentsWidth: 64,
segmentsHeight: 64,
phiLength: 180,
thetaStart: 0,
thetaLength: 360
});
semiSphereEl.setAttribute('material', {
shader: 'portal',
borderEnabled: 0.0,
pano: this.data.image,
side: 'back'
});
semiSphereEl.setAttribute('rotation', '0 180 0');
semiSphereEl.setAttribute('position', '0 0 0');
semiSphereEl.setAttribute('visible', false);
el.appendChild(semiSphereEl);
// Set the sphere that is rendered when the camera is close
// to the portal to allow the user peek inside
sphereEl.setAttribute('geometry', {
primitive: 'sphere',
radius: 10,
segmentsWidth: 64,
segmentsHeight: 64
});
sphereEl.setAttribute('material', {
shader: 'portal',
borderEnabled: 0.0,
pano: this.data.image,
side: 'back'
});
sphereEl.setAttribute('visible', false);
el.appendChild(sphereEl);
},
navigate: function () {
window.location = this.data.href;
},
/**
* The tick handles:
* 1. Swap the plane the represents the portal with a sphere with a hole when the camera is close
* so the user can peek inside the portal. The sphere is rendered on the oposite side of the portal
* from where the user enters.
* 2. It places the url / title above or inside the portal depending on the distance to the camera.
* 3. The portal faces the camera when it's far away from the user.
*
*/
tick: (function () {
var elWorldPosition = new THREE.Vector3();
var cameraWorldPosition = new THREE.Vector3();
var scale = new THREE.Vector3();
var quaternion = new THREE.Quaternion();
return function () {
if (!this.data.visualAspectEnabled) { return; }
var el = this.el;
var object3D = el.object3D;
var camera = el.sceneEl.camera;
var cameraPortalOrientation;
var distance;
var textEl = this.textEl;
// Update matrices
object3D.updateMatrixWorld();
camera.parent.updateMatrixWorld();
camera.updateMatrixWorld();
object3D.matrix.decompose(elWorldPosition, quaternion, scale);
elWorldPosition.setFromMatrixPosition(object3D.matrixWorld);
cameraWorldPosition.setFromMatrixPosition(camera.matrixWorld);
distance = elWorldPosition.distanceTo(cameraWorldPosition);
// Store original orientation to be restored when the portal
// stops facing the camera
this.previousQuaternion = this.previousQuaternion || quaternion.clone();
// If the portal is far away from the user the portal faces the camera
if (distance > 20) {
object3D.lookAt(cameraWorldPosition);
} else { // When the portal is close to the user (camera)
cameraPortalOrientation = this.calculateCameraPortalOrientation();
// If the user gets very close to the portal it is replaced
// by a holed sphere where she can peek inside
if (distance < 0.5) {
// Configure text size and sphere orientation depending
// the side the user approaches the portal
if (this.semiSphereEl.getAttribute('visible') === true) { return; }
textEl.setAttribute('text', 'width', 1.5);
if (cameraPortalOrientation <= 0.0) {
textEl.setAttribute('position', '0 0 0.75');
textEl.setAttribute('rotation', '0 180 0');
this.semiSphereEl.setAttribute('rotation', '0 0 0');
} else {
textEl.setAttribute('position', '0 0 -0.75');
textEl.setAttribute('rotation', '0 0 0');
this.semiSphereEl.setAttribute('rotation', '0 180 0');
}
el.getObject3D('mesh').visible = false;
this.semiSphereEl.setAttribute('visible', true);
this.peekCameraPortalOrientation = cameraPortalOrientation;
} else {
// Calculate wich side the camera is approaching the camera (back / front)
// Adjust text orientation based on camera position.
if (cameraPortalOrientation <= 0.0) {
textEl.setAttribute('rotation', '0 180 0');
} else {
textEl.setAttribute('rotation', '0 0 0');
}
textEl.setAttribute('text', 'width', 5);
textEl.setAttribute('position', '0 1.5 0');
el.getObject3D('mesh').visible = true;
this.semiSphereEl.setAttribute('visible', false);
this.peekCameraPortalOrientation = undefined;
}
if (this.previousQuaternion) {
object3D.quaternion.copy(this.previousQuaternion);
this.previousQuaternion = undefined;
}
}
};
})(),
hideAll: function () {
var el = this.el;
var hiddenEls = this.hiddenEls;
var self = this;
if (hiddenEls.length > 0) { return; }
el.sceneEl.object3D.traverse(function (object) {
if (object && object.el && object.el.hasAttribute('link-controls')) { return; }
if (!object.el || object === el.sceneEl.object3D || object.el === el || object.el === self.sphereEl ||
object.el === el.sceneEl.cameraEl || object.el.getAttribute('visible') === false || object.el === self.textEl || object.el === self.semiSphereEl) { return; }
object.el.setAttribute('visible', false);
hiddenEls.push(object.el);
});
},
showAll: function () {
this.hiddenEls.forEach(function (el) { el.setAttribute('visible', true); });
this.hiddenEls = [];
},
/**
* Calculate if the camera / user faces the front or back face of the portal
* @returns {number} > 0 if the camera faces the front of the portal < 0 if it faces the back.
*/
calculateCameraPortalOrientation: (function () {
var mat4 = new THREE.Matrix4();
var cameraPosition = new THREE.Vector3();
var portalNormal = new THREE.Vector3(0, 0, 1);
var portalPosition = new THREE.Vector3(0, 0, 0);
return function () {
var el = this.el;
var camera = el.sceneEl.camera;
// Reset tmp variables
cameraPosition.set(0, 0, 0);
portalNormal.set(0, 0, 1);
portalPosition.set(0, 0, 0);
// Apply portal orientation to the normal
el.object3D.matrixWorld.extractRotation(mat4);
portalNormal.applyMatrix4(mat4);
// Calculate portal world position
el.object3D.updateMatrixWorld();
el.object3D.localToWorld(portalPosition);
// Calculate camera world position
camera.parent.parent.updateMatrixWorld();
camera.parent.updateMatrixWorld();
camera.updateMatrixWorld();
camera.localToWorld(cameraPosition);
// Calculate vector from portal to camera
// (portal) -------> (camera)
cameraPosition.sub(portalPosition).normalize();
portalNormal.normalize();
// The side where the camera (user) approaches the portal
// is given by the sign of the dot product of the portal normal
// and the portal to camera vectors.
return Math.sign(portalNormal.dot(cameraPosition));
};
})(),
remove: function () {
this.removeEventListener();
}
});
/* eslint-disable */
registerShader('portal', {
schema: {
pano: {type: 'map', is: 'uniform'},
borderEnabled: {default: 1.0, type: 'int', is: 'uniform'},
strokeColor: {default: 'white', type: 'color', is: 'uniform'}
},
vertexShader: [
'vec3 portalPosition;',
'varying vec3 vWorldPosition;',
'varying float vDistanceToCenter;',
'varying float vDistance;',
'void main() {',
'vDistanceToCenter = clamp(length(position - vec3(0.0, 0.0, 0.0)), 0.0, 1.0);',
'portalPosition = (modelMatrix * vec4(0.0, 0.0, 0.0, 1.0)).xyz;',
'vDistance = length(portalPosition - cameraPosition);',
'vWorldPosition = (modelMatrix * vec4(position, 1.0)).xyz;',
'gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);',
'}'
].join('\n'),
fragmentShader: [
'#define RECIPROCAL_PI2 0.15915494',
'uniform sampler2D pano;',
'uniform vec3 strokeColor;',
'uniform float borderEnabled;',
'varying float vDistanceToCenter;',
'varying float vDistance;',
'varying vec3 vWorldPosition;',
'void main() {',
'vec3 direction = normalize(vWorldPosition - cameraPosition);',
'vec2 sampleUV;',
'float borderThickness = clamp(exp(-vDistance / 50.0), 0.6, 0.95);',
'sampleUV.y = saturate(direction.y * 0.5 + 0.5);',
'sampleUV.x = atan(direction.z, -direction.x) * -RECIPROCAL_PI2 + 0.5;',
'if (vDistanceToCenter > borderThickness && borderEnabled == 1.0) {',
'gl_FragColor = vec4(strokeColor, 1.0);',
'} else {',
'gl_FragColor = mix(texture2D(pano, sampleUV), vec4(0.93, 0.17, 0.36, 1.0), clamp(pow((vDistance / 15.0), 2.0), 0.0, 1.0));',
'}',
'}'
].join('\n')
});
/* eslint-enable */
},{"../core/component":125,"../core/shader":134,"../lib/three":173}],90:[function(_dereq_,module,exports){
var registerComponent = _dereq_('../core/component').registerComponent;
var THREE = _dereq_('../lib/three');
var DEFAULT_CAMERA_HEIGHT = _dereq_('../constants').DEFAULT_CAMERA_HEIGHT;
var bind = _dereq_('../utils/bind');
// To avoid recalculation at every mouse movement tick
var GRABBING_CLASS = 'a-grabbing';
var PI_2 = Math.PI / 2;
var radToDeg = THREE.Math.radToDeg;
/**
* look-controls. Update entity pose, factoring mouse, touch, and WebVR API data.
*/
module.exports.Component = registerComponent('look-controls', {
dependencies: ['position', 'rotation'],
schema: {
enabled: {default: true},
touchEnabled: {default: true},
hmdEnabled: {default: true},
reverseMouseDrag: {default: false},
standing: {default: true}
},
init: function () {
var sceneEl = this.el.sceneEl;
this.previousHMDPosition = new THREE.Vector3();
this.hmdQuaternion = new THREE.Quaternion();
this.hmdEuler = new THREE.Euler();
this.position = new THREE.Vector3();
this.rotation = {};
this.setupMouseControls();
this.setupHMDControls();
this.bindMethods();
// Reset previous HMD position when we exit VR.
sceneEl.addEventListener('exit-vr', this.onExitVR);
},
update: function (oldData) {
var data = this.data;
// Disable grab cursor classes if no longer enabled.
if (data.enabled !== oldData.enabled) {
this.updateGrabCursor(data.enabled);
}
// Reset pitch and yaw if disabling HMD.
if (oldData && !data.hmdEnabled && !oldData.hmdEnabled) {
this.pitchObject.rotation.set(0, 0, 0);
this.yawObject.rotation.set(0, 0, 0);
}
},
tick: function (t) {
var data = this.data;
if (!data.enabled) { return; }
this.controls.standing = data.standing;
this.controls.userHeight = this.getUserHeight();
this.controls.update();
this.updateOrientation();
this.updatePosition();
},
/**
* Return user height to use for standing poses, where a device doesn't provide an offset.
*/
getUserHeight: function () {
var el = this.el;
var userHeight = el.hasAttribute('camera') && el.getAttribute('camera').userHeight || DEFAULT_CAMERA_HEIGHT;
return userHeight;
},
play: function () {
this.addEventListeners();
},
pause: function () {
this.removeEventListeners();
},
remove: function () {
this.removeEventListeners();
},
bindMethods: function () {
this.onMouseDown = bind(this.onMouseDown, this);
this.onMouseMove = bind(this.onMouseMove, this);
this.onMouseUp = bind(this.onMouseUp, this);
this.onTouchStart = bind(this.onTouchStart, this);
this.onTouchMove = bind(this.onTouchMove, this);
this.onTouchEnd = bind(this.onTouchEnd, this);
this.onExitVR = bind(this.onExitVR, this);
},
/**
* Set up states and Object3Ds needed to store rotation data.
*/
setupMouseControls: function () {
this.mouseDown = false;
this.pitchObject = new THREE.Object3D();
this.yawObject = new THREE.Object3D();
this.yawObject.position.y = 10;
this.yawObject.add(this.pitchObject);
},
/**
* Set up VR controls that will copy data to the dolly.
*/
setupHMDControls: function () {
this.dolly = new THREE.Object3D();
this.euler = new THREE.Euler();
this.controls = new THREE.VRControls(this.dolly);
this.controls.userHeight = 0.0;
},
/**
* Add mouse and touch event listeners to canvas.
*/
addEventListeners: function () {
var sceneEl = this.el.sceneEl;
var canvasEl = sceneEl.canvas;
// Wait for canvas to load.
if (!canvasEl) {
sceneEl.addEventListener('render-target-loaded', bind(this.addEventListeners, this));
return;
}
// Mouse events.
canvasEl.addEventListener('mousedown', this.onMouseDown, false);
window.addEventListener('mousemove', this.onMouseMove, false);
window.addEventListener('mouseup', this.onMouseUp, false);
// Touch events.
canvasEl.addEventListener('touchstart', this.onTouchStart);
window.addEventListener('touchmove', this.onTouchMove);
window.addEventListener('touchend', this.onTouchEnd);
},
/**
* Remove mouse and touch event listeners from canvas.
*/
removeEventListeners: function () {
var sceneEl = this.el.sceneEl;
var canvasEl = sceneEl && sceneEl.canvas;
if (!canvasEl) { return; }
// Mouse events.
canvasEl.removeEventListener('mousedown', this.onMouseDown);
canvasEl.removeEventListener('mousemove', this.onMouseMove);
canvasEl.removeEventListener('mouseup', this.onMouseUp);
canvasEl.removeEventListener('mouseout', this.onMouseUp);
// Touch events.
canvasEl.removeEventListener('touchstart', this.onTouchStart);
canvasEl.removeEventListener('touchmove', this.onTouchMove);
canvasEl.removeEventListener('touchend', this.onTouchEnd);
},
/**
* Update orientation for mobile, mouse drag, and headset.
* Mouse-drag only enabled if HMD is not active.
*/
updateOrientation: function () {
var currentRotation;
var deltaRotation;
var hmdEuler = this.hmdEuler;
var hmdQuaternion = this.hmdQuaternion;
var pitchObject = this.pitchObject;
var yawObject = this.yawObject;
var sceneEl = this.el.sceneEl;
var rotation = this.rotation;
// Calculate HMD quaternion.
hmdQuaternion = hmdQuaternion.copy(this.dolly.quaternion);
hmdEuler.setFromQuaternion(hmdQuaternion, 'YXZ');
if (sceneEl.isMobile) {
// On mobile, do camera rotation with touch events and sensors.
rotation.x = radToDeg(hmdEuler.x) + radToDeg(pitchObject.rotation.x);
rotation.y = radToDeg(hmdEuler.y) + radToDeg(yawObject.rotation.y);
rotation.z = radToDeg(hmdEuler.z);
} else if (!sceneEl.is('vr-mode') || isNullVector(hmdEuler) || !this.data.hmdEnabled) {
// Mouse drag if WebVR not active (not connected, no incoming sensor data).
currentRotation = this.el.getAttribute('rotation');
deltaRotation = this.calculateDeltaRotation();
if (this.data.reverseMouseDrag) {
rotation.x = currentRotation.x - deltaRotation.x;
rotation.y = currentRotation.y - deltaRotation.y;
rotation.z = currentRotation.z;
} else {
rotation.x = currentRotation.x + deltaRotation.x;
rotation.y = currentRotation.y + deltaRotation.y;
rotation.z = currentRotation.z;
}
} else {
// Mouse rotation ignored with an active headset. Use headset rotation.
rotation.x = radToDeg(hmdEuler.x);
rotation.y = radToDeg(hmdEuler.y);
rotation.z = radToDeg(hmdEuler.z);
}
this.el.setAttribute('rotation', rotation);
},
/**
* Calculate delta rotation for mouse-drag and touch-drag.
*/
calculateDeltaRotation: function () {
var currentRotationX = radToDeg(this.pitchObject.rotation.x);
var currentRotationY = radToDeg(this.yawObject.rotation.y);
var deltaRotation;
deltaRotation = {
x: currentRotationX - (this.previousRotationX || 0),
y: currentRotationY - (this.previousRotationY || 0)
};
// Store current rotation for next tick.
this.previousRotationX = currentRotationX;
this.previousRotationY = currentRotationY;
return deltaRotation;
},
/**
* Handle positional tracking.
*/
updatePosition: function () {
var el = this.el;
var currentHMDPosition;
var currentPosition;
var position = this.position;
var previousHMDPosition = this.previousHMDPosition;
var sceneEl = this.el.sceneEl;
if (!sceneEl.is('vr-mode')) { return; }
// Calculate change in position.
currentHMDPosition = this.calculateHMDPosition();
currentPosition = el.getAttribute('position');
position.copy(currentPosition).sub(previousHMDPosition).add(currentHMDPosition);
el.setAttribute('position', position);
previousHMDPosition.copy(currentHMDPosition);
},
/**
* Get headset position from VRControls.
*/
calculateHMDPosition: (function () {
var position = new THREE.Vector3();
return function () {
this.dolly.updateMatrix();
position.setFromMatrixPosition(this.dolly.matrix);
return position;
};
})(),
/**
* Translate mouse drag into rotation.
*
* Dragging up and down rotates the camera around the X-axis (yaw).
* Dragging left and right rotates the camera around the Y-axis (pitch).
*/
onMouseMove: function (event) {
var pitchObject = this.pitchObject;
var yawObject = this.yawObject;
var previousMouseEvent = this.previousMouseEvent;
var movementX;
var movementY;
// Not dragging or not enabled.
if (!this.mouseDown || !this.data.enabled) { return; }
// Calculate delta.
movementX = event.movementX || event.mozMovementX;
movementY = event.movementY || event.mozMovementY;
if (movementX === undefined || movementY === undefined) {
movementX = event.screenX - previousMouseEvent.screenX;
movementY = event.screenY - previousMouseEvent.screenY;
}
this.previousMouseEvent = event;
// Calculate rotation.
yawObject.rotation.y -= movementX * 0.002;
pitchObject.rotation.x -= movementY * 0.002;
pitchObject.rotation.x = Math.max(-PI_2, Math.min(PI_2, pitchObject.rotation.x));
},
/**
* Register mouse down to detect mouse drag.
*/
onMouseDown: function (evt) {
if (!this.data.enabled) { return; }
// Handle only primary button.
if (evt.button !== 0) { return; }
this.mouseDown = true;
this.previousMouseEvent = evt;
document.body.classList.add(GRABBING_CLASS);
},
/**
* Register mouse up to detect release of mouse drag.
*/
onMouseUp: function () {
this.mouseDown = false;
document.body.classList.remove(GRABBING_CLASS);
},
/**
* Register touch down to detect touch drag.
*/
onTouchStart: function (evt) {
if (evt.touches.length !== 1 || !this.data.touchEnabled) { return; }
this.touchStart = {
x: evt.touches[0].pageX,
y: evt.touches[0].pageY
};
this.touchStarted = true;
},
/**
* Translate touch move to Y-axis rotation.
*/
onTouchMove: function (evt) {
var canvas = this.el.sceneEl.canvas;
var deltaY;
var yawObject = this.yawObject;
if (!this.touchStarted || !this.data.touchEnabled) { return; }
deltaY = 2 * Math.PI * (evt.touches[0].pageX - this.touchStart.x) / canvas.clientWidth;
// Limit touch orientaion to to yaw (y axis).
yawObject.rotation.y -= deltaY * 0.5;
this.touchStart = {
x: evt.touches[0].pageX,
y: evt.touches[0].pageY
};
},
/**
* Register touch end to detect release of touch drag.
*/
onTouchEnd: function () {
this.touchStarted = false;
},
onExitVR: function () {
this.previousHMDPosition.set(0, 0, 0);
},
/**
* Toggle the feature of showing/hiding the grab cursor.
*/
updateGrabCursor: function (enabled) {
var sceneEl = this.el.sceneEl;
function enableGrabCursor () { sceneEl.canvas.classList.add('a-grab-cursor'); }
function disableGrabCursor () { sceneEl.canvas.classList.remove('a-grab-cursor'); }
if (!sceneEl.canvas) {
if (enabled) {
sceneEl.addEventListener('render-target-loaded', enableGrabCursor);
} else {
sceneEl.addEventListener('render-target-loaded', disableGrabCursor);
}
return;
}
if (enabled) {
enableGrabCursor();
return;
}
disableGrabCursor();
}
});
function isNullVector (vector) {
return vector.x === 0 && vector.y === 0 && vector.z === 0;
}
},{"../constants":116,"../core/component":125,"../lib/three":173,"../utils/bind":189}],91:[function(_dereq_,module,exports){
/* global Promise */
var utils = _dereq_('../utils/');
var component = _dereq_('../core/component');
var THREE = _dereq_('../lib/three');
var shader = _dereq_('../core/shader');
var error = utils.debug('components:material:error');
var registerComponent = component.registerComponent;
var shaders = shader.shaders;
var shaderNames = shader.shaderNames;
/**
* Material component.
*
* @member {object} shader - Determines how material is shaded. Defaults to `standard`,
* three.js's implementation of PBR. Another standard shading model is `flat` which
* uses MeshBasicMaterial.
*/
module.exports.Component = registerComponent('material', {
schema: {
alphaTest: {default: 0.0, min: 0.0, max: 1.0},
depthTest: {default: true},
depthWrite: {default: true},
flatShading: {default: false},
npot: {default: false},
offset: {type: 'vec2', default: {x: 0, y: 0}},
opacity: {default: 1.0, min: 0.0, max: 1.0},
repeat: {type: 'vec2', default: {x: 1, y: 1}},
shader: {default: 'standard', oneOf: shaderNames},
side: {default: 'front', oneOf: ['front', 'back', 'double']},
transparent: {default: false},
vertexColors: {type: 'string', default: 'none', oneOf: ['face', 'vertex']},
visible: {default: true}
},
init: function () {
this.material = null;
},
/**
* Update or create material.
*
* @param {object|null} oldData
*/
update: function (oldData) {
var data = this.data;
if (!this.shader || data.shader !== oldData.shader) {
this.updateShader(data.shader);
}
this.shader.update(this.data);
this.updateMaterial(oldData);
},
updateSchema: function (data) {
var newShader = data.shader;
var currentShader = this.data && this.data.shader;
var shader = newShader || currentShader;
var schema = shaders[shader] && shaders[shader].schema;
if (!schema) { error('Unknown shader schema ' + shader); }
if (currentShader && newShader === currentShader) { return; }
this.extendSchema(schema);
this.updateBehavior();
},
updateBehavior: function () {
var schema = this.schema;
var self = this;
var sceneEl = this.el.sceneEl;
var tickProperties = {};
var tick = function (time, delta) {
Object.keys(tickProperties).forEach(function update (key) {
tickProperties[key] = time;
});
self.shader.update(tickProperties);
};
this.tick = undefined;
Object.keys(schema).forEach(function (key) {
if (schema[key].type === 'time') {
self.tick = tick;
tickProperties[key] = true;
}
});
if (!sceneEl) { return; }
if (!this.tick) {
sceneEl.removeBehavior(this);
} else {
sceneEl.addBehavior(this);
}
},
updateShader: function (shaderName) {
var data = this.data;
var Shader = shaders[shaderName] && shaders[shaderName].Shader;
var shaderInstance;
if (!Shader) { throw new Error('Unknown shader ' + shaderName); }
// Get material from A-Frame shader.
shaderInstance = this.shader = new Shader();
shaderInstance.el = this.el;
shaderInstance.init(data);
this.setMaterial(shaderInstance.material);
this.updateSchema(data);
},
/**
* Set and update base material properties.
* Set `needsUpdate` when needed.
*/
updateMaterial: function (oldData) {
var data = this.data;
var material = this.material;
// Base material properties.
material.alphaTest = data.alphaTest;
material.depthTest = data.depthTest !== false;
material.depthWrite = data.depthWrite !== false;
material.opacity = data.opacity;
material.flatShading = data.flatShading;
material.side = parseSide(data.side);
material.transparent = data.transparent !== false || data.opacity < 1.0;
material.vertexColors = parseVertexColors(data.vertexColors);
material.visible = data.visible;
// Check if material needs update.
if (Object.keys(oldData).length &&
(oldData.alphaTest !== data.alphaTest ||
oldData.side !== data.side ||
oldData.vertexColors !== data.vertexColors)) {
material.needsUpdate = true;
}
},
/**
* Remove material on remove (callback).
* Dispose of it from memory and unsubscribe from scene updates.
*/
remove: function () {
var defaultMaterial = new THREE.MeshBasicMaterial();
var material = this.material;
var object3D = this.el.getObject3D('mesh');
if (object3D) { object3D.material = defaultMaterial; }
disposeMaterial(material, this.system);
},
/**
* (Re)create new material. Has side-effects of setting `this.material` and updating
* material registration in scene.
*
* @param {object} data - Material component data.
* @param {object} type - Material type to create.
* @returns {object} Material.
*/
setMaterial: function (material) {
var mesh = this.el.getOrCreateObject3D('mesh', THREE.Mesh);
var system = this.system;
if (this.material) { disposeMaterial(this.material, system); }
this.material = mesh.material = material;
system.registerMaterial(material);
}
});
/**
* Return a three.js constant determining which material face sides to render
* based on the side parameter (passed as a component property).
*
* @param {string} [side=front] - `front`, `back`, or `double`.
* @returns {number} THREE.FrontSide, THREE.BackSide, or THREE.DoubleSide.
*/
function parseSide (side) {
switch (side) {
case 'back': {
return THREE.BackSide;
}
case 'double': {
return THREE.DoubleSide;
}
default: {
// Including case `front`.
return THREE.FrontSide;
}
}
}
/**
* Return a three.js constant determining vertex coloring.
*/
function parseVertexColors (coloring) {
switch (coloring) {
case 'face': {
return THREE.FaceColors;
}
case 'vertex': {
return THREE.VertexColors;
}
default: {
return THREE.NoColors;
}
}
}
/**
* Dispose of material from memory and unsubscribe material from scene updates like fog.
*/
function disposeMaterial (material, system) {
material.dispose();
system.unregisterMaterial(material);
}
},{"../core/component":125,"../core/shader":134,"../lib/three":173,"../utils/":195}],92:[function(_dereq_,module,exports){
var debug = _dereq_('../utils/debug');
var registerComponent = _dereq_('../core/component').registerComponent;
var THREE = _dereq_('../lib/three');
var warn = debug('components:obj-model:warn');
module.exports.Component = registerComponent('obj-model', {
schema: {
mtl: {type: 'model'},
obj: {type: 'model'}
},
init: function () {
this.model = null;
this.objLoader = new THREE.OBJLoader();
this.mtlLoader = new THREE.MTLLoader(this.objLoader.manager);
// Allow cross-origin images to be loaded.
this.mtlLoader.crossOrigin = '';
},
update: function () {
var data = this.data;
if (!data.obj) { return; }
this.remove();
this.loadObj(data.obj, data.mtl);
},
remove: function () {
if (!this.model) { return; }
this.el.removeObject3D('mesh');
},
loadObj: function (objUrl, mtlUrl) {
var self = this;
var el = this.el;
var mtlLoader = this.mtlLoader;
var objLoader = this.objLoader;
if (mtlUrl) {
// .OBJ with an .MTL.
if (el.hasAttribute('material')) {
warn('Material component properties are ignored when a .MTL is provided');
}
mtlLoader.setTexturePath(mtlUrl.substr(0, mtlUrl.lastIndexOf('/') + 1));
mtlLoader.load(mtlUrl, function (materials) {
materials.preload();
objLoader.setMaterials(materials);
objLoader.load(objUrl, function (objModel) {
self.model = objModel;
el.setObject3D('mesh', objModel);
el.emit('model-loaded', {format: 'obj', model: objModel});
});
});
return;
}
// .OBJ only.
objLoader.load(objUrl, function loadObjOnly (objModel) {
// Apply material.
var material = el.components.material;
if (material) {
objModel.traverse(function (child) {
if (child instanceof THREE.Mesh) {
child.material = material.material;
}
});
}
self.model = objModel;
el.setObject3D('mesh', objModel);
el.emit('model-loaded', {format: 'obj', model: objModel});
});
}
});
},{"../core/component":125,"../lib/three":173,"../utils/debug":191}],93:[function(_dereq_,module,exports){
var bind = _dereq_('../utils/bind');
var registerComponent = _dereq_('../core/component').registerComponent;
var controllerUtils = _dereq_('../utils/tracked-controls');
var TOUCH_CONTROLLER_MODEL_BASE_URL = 'https://cdn.aframe.io/controllers/oculus/oculus-touch-controller-';
var TOUCH_CONTROLLER_MODEL_OBJ_URL_L = TOUCH_CONTROLLER_MODEL_BASE_URL + 'left.obj';
var TOUCH_CONTROLLER_MODEL_OBJ_MTL_L = TOUCH_CONTROLLER_MODEL_BASE_URL + 'left.mtl';
var TOUCH_CONTROLLER_MODEL_OBJ_URL_R = TOUCH_CONTROLLER_MODEL_BASE_URL + 'right.obj';
var TOUCH_CONTROLLER_MODEL_OBJ_MTL_R = TOUCH_CONTROLLER_MODEL_BASE_URL + 'right.mtl';
var GAMEPAD_ID_PREFIX = 'Oculus Touch';
var PIVOT_OFFSET = {x: 0, y: -0.015, z: 0.04};
/**
* Oculus Touch controls component.
* Interface with Oculus Touch controllers and maps Gamepad events to
* common controller buttons: trackpad, trigger, grip, menu and system
* Load a controller model and highlights the pressed buttons
*/
module.exports.Component = registerComponent('oculus-touch-controls', {
schema: {
hand: {default: 'left'},
buttonColor: {type: 'color', default: '#999'}, // Off-white.
buttonTouchColor: {type: 'color', default: '#8AB'},
buttonHighlightColor: {type: 'color', default: '#2DF'}, // Light blue.
model: {default: true},
rotationOffset: {default: 0}
},
// buttonId
// 0 - thumbstick (which has separate axismove / thumbstickmoved events)
// 1 - trigger (with analog value, which goes up to 1)
// 2 - grip (with analog value, which goes up to 1)
// 3 - X (left) or A (right)
// 4 - Y (left) or B (right)
// 5 - surface (touch only)
mapping: {
left: {
axes: {thumbstick: [0, 1]},
buttons: ['thumbstick', 'trigger', 'grip', 'xbutton', 'ybutton', 'surface']
},
right: {
axes: {thumbstick: [0, 1]},
buttons: ['thumbstick', 'trigger', 'grip', 'abutton', 'bbutton', 'surface']
}
},
bindMethods: function () {
this.onModelLoaded = bind(this.onModelLoaded, this);
this.onControllersUpdate = bind(this.onControllersUpdate, this);
this.checkIfControllerPresent = bind(this.checkIfControllerPresent, this);
this.onAxisMoved = bind(this.onAxisMoved, this);
},
init: function () {
var self = this;
this.onButtonChanged = bind(this.onButtonChanged, this);
this.onButtonDown = function (evt) { self.onButtonEvent(evt.detail.id, 'down'); };
this.onButtonUp = function (evt) { self.onButtonEvent(evt.detail.id, 'up'); };
this.onButtonTouchStart = function (evt) { self.onButtonEvent(evt.detail.id, 'touchstart'); };
this.onButtonTouchEnd = function (evt) { self.onButtonEvent(evt.detail.id, 'touchend'); };
this.controllerPresent = false;
this.lastControllerCheck = 0;
this.previousButtonValues = {};
this.bindMethods();
// Allow mock.
this.emitIfAxesChanged = controllerUtils.emitIfAxesChanged;
this.checkControllerPresentAndSetup = controllerUtils.checkControllerPresentAndSetup;
},
addEventListeners: function () {
var el = this.el;
el.addEventListener('buttonchanged', this.onButtonChanged);
el.addEventListener('buttondown', this.onButtonDown);
el.addEventListener('buttonup', this.onButtonUp);
el.addEventListener('touchstart', this.onButtonTouchStart);
el.addEventListener('touchend', this.onButtonTouchEnd);
el.addEventListener('axismove', this.onAxisMoved);
el.addEventListener('model-loaded', this.onModelLoaded);
this.controllerEventsActive = true;
},
removeEventListeners: function () {
var el = this.el;
el.removeEventListener('buttonchanged', this.onButtonChanged);
el.removeEventListener('buttondown', this.onButtonDown);
el.removeEventListener('buttonup', this.onButtonUp);
el.removeEventListener('touchstart', this.onButtonTouchStart);
el.removeEventListener('touchend', this.onButtonTouchEnd);
el.removeEventListener('axismove', this.onAxisMoved);
el.removeEventListener('model-loaded', this.onModelLoaded);
this.controllerEventsActive = false;
},
checkIfControllerPresent: function () {
this.checkControllerPresentAndSetup(this, GAMEPAD_ID_PREFIX, {
hand: this.data.hand
});
},
play: function () {
this.checkIfControllerPresent();
this.addControllersUpdateListener();
},
pause: function () {
this.removeEventListeners();
this.removeControllersUpdateListener();
},
updateControllerModel: function () {
var objUrl, mtlUrl;
if (!this.data.model) { return; }
if (this.data.hand === 'right') {
objUrl = 'url(' + TOUCH_CONTROLLER_MODEL_OBJ_URL_R + ')';
mtlUrl = 'url(' + TOUCH_CONTROLLER_MODEL_OBJ_MTL_R + ')';
} else {
objUrl = 'url(' + TOUCH_CONTROLLER_MODEL_OBJ_URL_L + ')';
mtlUrl = 'url(' + TOUCH_CONTROLLER_MODEL_OBJ_MTL_L + ')';
}
this.el.setAttribute('obj-model', {obj: objUrl, mtl: mtlUrl});
},
injectTrackedControls: function () {
var data = this.data;
var offset = data.hand === 'right' ? -90 : 90;
this.el.setAttribute('tracked-controls', {
id: data.hand === 'right' ? 'Oculus Touch (Right)' : 'Oculus Touch (Left)',
controller: 0,
rotationOffset: data.rotationOffset !== -999 ? data.rotationOffset : offset
});
this.updateControllerModel();
},
addControllersUpdateListener: function () {
this.el.sceneEl.addEventListener('controllersupdated', this.onControllersUpdate, false);
},
removeControllersUpdateListener: function () {
this.el.sceneEl.removeEventListener('controllersupdated', this.onControllersUpdate, false);
},
onControllersUpdate: function () {
// Note that due to gamepadconnected event propagation issues, we don't rely on events.
this.checkIfControllerPresent();
},
onButtonChanged: function (evt) {
var button = this.mapping[this.data.hand].buttons[evt.detail.id];
var buttonMeshes = this.buttonMeshes;
var analogValue;
if (!button) { return; }
if (button === 'trigger' || button === 'grip') { analogValue = evt.detail.state.value; }
// Update trigger and/or grip meshes, if any.
if (buttonMeshes) {
if (button === 'trigger' && buttonMeshes.trigger) {
buttonMeshes.trigger.rotation.x = -analogValue * (Math.PI / 24);
}
if (button === 'grip' && buttonMeshes.grip) {
buttonMeshes.grip.rotation.y = (this.data.hand === 'left' ? -1 : 1) * analogValue * (Math.PI / 60);
}
}
// Pass along changed event with button state, using the buttom mapping for convenience.
this.el.emit(button + 'changed', evt.detail.state);
},
onModelLoaded: function (evt) {
var controllerObject3D = evt.detail.model;
var buttonMeshes;
if (!this.data.model) { return; }
var leftHand = this.data.hand === 'left';
buttonMeshes = this.buttonMeshes = {};
buttonMeshes.grip = controllerObject3D.getObjectByName(leftHand ? 'buttonHand_oculus-touch-controller-left.004' : 'buttonHand_oculus-touch-controller-right.005');
buttonMeshes.thumbstick = controllerObject3D.getObjectByName(leftHand ? 'stick_oculus-touch-controller-left.007' : 'stick_oculus-touch-controller-right.004');
buttonMeshes.trigger = controllerObject3D.getObjectByName(leftHand ? 'buttonTrigger_oculus-touch-controller-left.005' : 'buttonTrigger_oculus-touch-controller-right.006');
buttonMeshes.xbutton = controllerObject3D.getObjectByName('buttonX_oculus-touch-controller-left.002');
buttonMeshes.abutton = controllerObject3D.getObjectByName('buttonA_oculus-touch-controller-right.002');
buttonMeshes.ybutton = controllerObject3D.getObjectByName('buttonY_oculus-touch-controller-left.001');
buttonMeshes.bbutton = controllerObject3D.getObjectByName('buttonB_oculus-touch-controller-right.003');
// Offset pivot point
controllerObject3D.position = PIVOT_OFFSET;
},
onButtonEvent: function (id, evtName) {
var buttonName = this.mapping[this.data.hand].buttons[id];
var i;
if (Array.isArray(buttonName)) {
for (i = 0; i < buttonName.length; i++) {
this.el.emit(buttonName[i] + evtName);
}
} else {
this.el.emit(buttonName + evtName);
}
this.updateModel(buttonName, evtName);
},
onAxisMoved: function (evt) {
this.emitIfAxesChanged(this, this.mapping[this.data.hand].axes, evt);
},
updateModel: function (buttonName, evtName) {
var i;
if (Array.isArray(buttonName)) {
for (i = 0; i < buttonName.length; i++) {
this.updateButtonModel(buttonName[i], evtName);
}
} else {
this.updateButtonModel(buttonName, evtName);
}
},
updateButtonModel: function (buttonName, state) {
var color = (state === 'up' || state === 'touchend') ? this.data.buttonColor : state === 'touchstart' ? this.data.buttonTouchColor : this.data.buttonHighlightColor;
var buttonMeshes = this.buttonMeshes;
if (!this.data.model) { return; }
if (buttonMeshes && buttonMeshes[buttonName]) {
buttonMeshes[buttonName].material.color.set(color);
}
}
});
},{"../core/component":125,"../utils/bind":189,"../utils/tracked-controls":199}],94:[function(_dereq_,module,exports){
var registerComponent = _dereq_('../core/component').registerComponent;
module.exports.Component = registerComponent('position', {
schema: {type: 'vec3'},
update: function () {
var object3D = this.el.object3D;
var data = this.data;
object3D.position.set(data.x, data.y, data.z);
}
});
},{"../core/component":125}],95:[function(_dereq_,module,exports){
var registerComponent = _dereq_('../core/component').registerComponent;
var THREE = _dereq_('../lib/three');
var utils = _dereq_('../utils/');
var bind = utils.bind;
var dummyVec = new THREE.Vector3();
/**
* Raycaster component.
*
* Pass options to three.js Raycaster including which objects to test.
* Poll for intersections.
* Emit event on origin entity and on target entity on intersect.
*
* @member {array} intersectedEls - List of currently intersected entities.
* @member {array} objects - Cached list of meshes to intersect.
* @member {number} prevCheckTime - Previous time intersection was checked. To help interval.
* @member {object} raycaster - three.js Raycaster.
*/
module.exports.Component = registerComponent('raycaster', {
schema: {
direction: {type: 'vec3', default: {x: 0, y: 0, z: -1}},
far: {default: 1000},
interval: {default: 100},
near: {default: 0},
objects: {default: ''},
origin: {type: 'vec3'},
recursive: {default: true},
showLine: {default: false},
useWorldCoordinates: {default: false}
},
init: function () {
// Calculate unit vector for line direction. Can be multiplied via scalar to performantly
// adjust line length.
this.lineData = {};
this.lineEndVec3 = new THREE.Vector3();
this.unitLineEndVec3 = new THREE.Vector3();
this.intersectedEls = [];
this.objects = null;
this.prevCheckTime = undefined;
this.prevIntersectedEls = [];
this.raycaster = new THREE.Raycaster();
this.updateOriginDirection();
this.refreshObjects = bind(this.refreshObjects, this);
this.refreshOnceChildLoaded = bind(this.refreshOnceChildLoaded, this);
},
/**
* Create or update raycaster object.
*/
update: function (oldData) {
var data = this.data;
var el = this.el;
var raycaster = this.raycaster;
// Set raycaster properties.
raycaster.far = data.far;
raycaster.near = data.near;
// Draw line.
if (data.showLine &&
(data.far !== oldData.far || data.origin !== oldData.origin ||
data.direction !== oldData.direction || data.showLine !== oldData.showLine)) {
this.unitLineEndVec3.copy(data.origin).add(data.direction).normalize();
this.drawLine();
}
if (!data.showLine && oldData.showLine) {
el.removeAttribute('line');
}
this.refreshObjects();
},
play: function () {
this.el.sceneEl.addEventListener('loaded', this.refreshObjects);
this.el.sceneEl.addEventListener('child-attached', this.refreshOnceChildLoaded);
this.el.sceneEl.addEventListener('child-detached', this.refreshObjects);
},
pause: function () {
this.el.sceneEl.removeEventListener('loaded', this.refreshObjects);
this.el.sceneEl.removeEventListener('child-attached', this.refreshOnceChildLoaded);
this.el.sceneEl.removeEventListener('child-detached', this.refreshObjects);
},
remove: function () {
if (this.data.showLine) {
this.el.removeAttribute('line');
}
},
/**
* Update list of objects to test for intersection once child is loaded.
*/
refreshOnceChildLoaded: function (evt) {
var self = this;
var childEl = evt.detail.el;
if (!childEl) { return; }
if (childEl.hasLoaded) {
this.refreshObjects();
} else {
childEl.addEventListener('loaded', function nowRefresh (evt) {
childEl.removeEventListener('loaded', nowRefresh);
self.refreshObjects();
});
}
},
/**
* Update list of objects to test for intersection.
*/
refreshObjects: function () {
var children;
var data = this.data;
var i;
var objects;
// Target entities.
var targetEls = data.objects ? this.el.sceneEl.querySelectorAll(data.objects) : null;
// Push meshes onto list of objects to intersect.
if (targetEls) {
objects = [];
for (i = 0; i < targetEls.length; i++) {
objects.push(targetEls[i].object3D);
}
} else {
// If objects not defined, intersect with everything.
objects = this.el.sceneEl.object3D.children;
}
this.objects = [];
for (i = 0; i < objects.length; i++) {
// A-Frame wraps everything in THREE.Group. Grab the children.
children = objects[i].children;
// Add the object3D children for non-recursive raycasting.
// If no children, refresh after entity loaded.
if (children) { this.objects.push.apply(this.objects, children); }
}
},
/**
* Check for intersections and cleared intersections on an interval.
*/
tick: (function () {
var intersections = [];
return function (time) {
var el = this.el;
var data = this.data;
var i;
var intersectedEls = this.intersectedEls;
var intersection;
var lineLength;
var prevCheckTime = this.prevCheckTime;
var prevIntersectedEls = this.prevIntersectedEls;
var rawIntersections;
// Only check for intersection if interval time has passed.
if (prevCheckTime && (time - prevCheckTime < data.interval)) { return; }
// Update check time.
this.prevCheckTime = time;
// Store old previously intersected entities.
copyArray(this.prevIntersectedEls, this.intersectedEls);
// Raycast.
this.updateOriginDirection();
rawIntersections = this.raycaster.intersectObjects(this.objects, data.recursive);
// Only keep intersections against objects that have a reference to an entity.
intersections.length = 0;
for (i = 0; i < rawIntersections.length; i++) {
intersection = rawIntersections[i];
// Don't intersect with own line.
if (data.showLine && intersection.object === el.getObject3D('line')) {
continue;
}
if (intersection.object.el) {
intersections.push(intersection);
}
}
// Update intersectedEls.
intersectedEls.length = intersections.length;
for (i = 0; i < intersections.length; i++) {
intersectedEls[i] = intersections[i].object.el;
}
// Emit intersected on intersected entity per intersected entity.
for (i = 0; i < intersections.length; i++) {
intersections[i].object.el.emit('raycaster-intersected', {
el: el,
intersection: intersections[i]
});
}
// Emit all intersections at once on raycasting entity.
if (intersections.length) {
el.emit('raycaster-intersection', {
els: intersectedEls,
intersections: intersections
});
}
// Emit intersection cleared on both entities per formerly intersected entity.
for (i = 0; i < prevIntersectedEls.length; i++) {
if (intersectedEls.indexOf(prevIntersectedEls[i]) !== -1) { return; }
el.emit('raycaster-intersection-cleared', {el: prevIntersectedEls[i]});
prevIntersectedEls[i].emit('raycaster-intersected-cleared', {el: el});
}
// Update line length.
if (data.showLine) {
if (intersections.length) {
if (intersections[0].object.el === el && intersections[1]) {
lineLength = intersections[1].distance;
} else {
lineLength = intersections[0].distance;
}
}
this.drawLine(lineLength);
}
};
})(),
/**
* Update origin and direction of raycaster using entity transforms and supplied origin or
* direction offsets.
*/
updateOriginDirection: (function () {
var direction = new THREE.Vector3();
var quaternion = new THREE.Quaternion();
var originVec3 = new THREE.Vector3();
// Closure to make quaternion/vector3 objects private.
return function updateOriginDirection () {
var el = this.el;
var data = this.data;
if (data.useWorldCoordinates) {
this.raycaster.set(data.origin, data.direction);
return;
}
// Grab the position and rotation.
el.object3D.updateMatrixWorld();
el.object3D.matrixWorld.decompose(originVec3, quaternion, dummyVec);
// If non-zero origin, translate the origin into world space.
if (data.origin.x !== 0 || data.origin.y !== 0 || data.origin.z !== 0) {
originVec3 = el.object3D.localToWorld(originVec3.copy(data.origin));
}
// three.js raycaster direction is relative to 0, 0, 0 NOT the origin / offset we
// provide. Apply the offset to the direction, then rotation from the object,
// and normalize.
direction.copy(data.direction).add(data.origin).applyQuaternion(quaternion).normalize();
// Apply offset and direction, in world coordinates.
this.raycaster.set(originVec3, direction);
};
})(),
/**
* Create or update line to give raycaster visual representation.
* Customize the line through through line component.
* We draw the line in the raycaster component to customize the line to the
* raycaster's origin, direction, and far.
*
* Unlike the raycaster, we create the line as a child of the object. The line will
* be affected by the transforms of the objects, so we don't have to calculate transforms
* like we do with the raycaster.
*
* @param {number} length - Length of line. Pass in to shorten the line to the intersection
* point. If not provided, length will default to the max length, `raycaster.far`.
*/
drawLine: (function (length) {
var lineEndVec3 = new THREE.Vector3();
var lineData = {};
return function (length) {
var data = this.data;
var el = this.el;
// Treat Infinity as 1000m for the line.
if (length === undefined) {
length = data.far === Infinity ? 1000 : data.far;
}
// Update the length of the line if given. `unitLineEndVec3` is the direction
// given by data.direction, then we apply a scalar to give it a length.
lineData.start = data.origin;
lineData.end = lineEndVec3.copy(this.unitLineEndVec3).multiplyScalar(length);
el.setAttribute('line', lineData);
};
})()
});
/**
* Copy contents of one array to another without allocating new array.
*/
function copyArray (a, b) {
var i;
a.length = b.length;
for (i = 0; i < b.length; i++) {
a[i] = b[i];
}
}
},{"../core/component":125,"../lib/three":173,"../utils/":195}],96:[function(_dereq_,module,exports){
var degToRad = _dereq_('../lib/three').Math.degToRad;
var registerComponent = _dereq_('../core/component').registerComponent;
module.exports.Component = registerComponent('rotation', {
schema: {type: 'vec3'},
/**
* Updates object3D rotation.
*/
update: function () {
var data = this.data;
var object3D = this.el.object3D;
object3D.rotation.set(degToRad(data.x), degToRad(data.y), degToRad(data.z));
object3D.rotation.order = 'YXZ';
}
});
},{"../core/component":125,"../lib/three":173}],97:[function(_dereq_,module,exports){
var registerComponent = _dereq_('../core/component').registerComponent;
// Avoids triggering a zero-determinant which makes object3D matrix non-invertible.
var zeroScale = 0.00001;
module.exports.Component = registerComponent('scale', {
schema: {
type: 'vec3',
default: {x: 1, y: 1, z: 1}
},
update: function () {
var data = this.data;
var object3D = this.el.object3D;
var x = data.x === 0 ? zeroScale : data.x;
var y = data.y === 0 ? zeroScale : data.y;
var z = data.z === 0 ? zeroScale : data.z;
object3D.scale.set(x, y, z);
}
});
},{"../core/component":125}],98:[function(_dereq_,module,exports){
var register = _dereq_('../../core/component').registerComponent;
module.exports.Component = register('debug', {
schema: {default: true}
});
},{"../../core/component":125}],99:[function(_dereq_,module,exports){
var registerComponent = _dereq_('../../core/component').registerComponent;
/**
* Component to embed an a-frame scene within the layout of a 2D page.
*/
module.exports.Component = registerComponent('embedded', {
dependencies: ['vr-mode-ui'],
schema: {default: true},
update: function () {
var sceneEl = this.el;
var enterVREl = sceneEl.querySelector('.a-enter-vr');
if (this.data === true) {
if (enterVREl) { enterVREl.classList.add('embedded'); }
sceneEl.removeFullScreenStyles();
} else {
if (enterVREl) { enterVREl.classList.remove('embedded'); }
sceneEl.addFullScreenStyles();
}
}
});
},{"../../core/component":125}],100:[function(_dereq_,module,exports){
var register = _dereq_('../../core/component').registerComponent;
var THREE = _dereq_('../../lib/three');
var debug = _dereq_('../../utils/debug');
var warn = debug('components:fog:warn');
/**
* Fog component.
* Applies only to the scene entity.
*/
module.exports.Component = register('fog', {
schema: {
color: {type: 'color', default: '#000'},
density: {default: 0.00025},
far: {default: 1000, min: 0},
near: {default: 1, min: 0},
type: {default: 'linear', oneOf: ['linear', 'exponential']}
},
update: function () {
var data = this.data;
var el = this.el;
var fog = this.el.object3D.fog;
if (!el.isScene) {
warn('Fog component can only be applied to ');
return;
}
// (Re)create fog if fog doesn't exist or fog type changed.
if (!fog || data.type !== fog.name) {
el.object3D.fog = getFog(data);
el.systems.material.updateMaterials();
return;
}
// Fog data changed. Update fog.
Object.keys(this.schema).forEach(function (key) {
var value = data[key];
if (key === 'color') { value = new THREE.Color(value); }
fog[key] = value;
});
},
/**
* Remove fog on remove (callback).
*/
remove: function () {
var fog = this.el.object3D.fog;
if (!fog) { return; }
fog.far = 0;
fog.near = 0.1;
}
});
/**
* Creates a fog object. Sets fog.name to be able to detect fog type changes.
*
* @param {object} data - Fog data.
* @returns {object} fog
*/
function getFog (data) {
var fog;
if (data.type === 'exponential') {
fog = new THREE.FogExp2(data.color, data.density);
} else {
fog = new THREE.Fog(data.color, data.near, data.far);
}
fog.name = data.type;
return fog;
}
},{"../../core/component":125,"../../lib/three":173,"../../utils/debug":191}],101:[function(_dereq_,module,exports){
(function (process){
/* global AFRAME */
var AFRAME_INJECTED = _dereq_('../../constants').AFRAME_INJECTED;
var bind = _dereq_('../../utils/bind');
var pkg = _dereq_('../../../package');
var registerComponent = _dereq_('../../core/component').registerComponent;
/**
* 0.4.2 to 0.4.x
* Will need to update this when A-Frame goes to 1.x.x.
*/
function getFuzzyPatchVersion (version) {
var split = version.split('.');
split[2] = 'x';
return split.join('.');
}
var INSPECTOR_DEV_URL = 'https://aframe.io/aframe-inspector/dist/aframe-inspector.js';
var INSPECTOR_RELEASE_URL = 'https://unpkg.com/aframe-inspector@' + getFuzzyPatchVersion(pkg.version) + '/dist/aframe-inspector.min.js';
var INSPECTOR_URL = process.env.INSPECTOR_VERSION === 'dev' ? INSPECTOR_DEV_URL : INSPECTOR_RELEASE_URL;
var LOADING_MESSAGE = 'Loading Inspector';
var LOADING_ERROR_MESSAGE = 'Error loading Inspector';
module.exports.Component = registerComponent('inspector', {
schema: {
url: {default: INSPECTOR_URL}
},
init: function () {
this.onKeydown = bind(this.onKeydown, this);
this.onMessage = bind(this.onMessage, this);
this.initOverlay();
window.addEventListener('keydown', this.onKeydown);
window.addEventListener('message', this.onMessage);
},
initOverlay: function () {
var dotsHTML = '...';
this.loadingMessageEl = document.createElement('div');
this.loadingMessageEl.classList.add('a-inspector-loader');
this.loadingMessageEl.innerHTML = LOADING_MESSAGE + dotsHTML;
},
remove: function () {
this.removeEventListeners();
},
/**
* + + i keyboard shortcut.
*/
onKeydown: function (evt) {
var shortcutPressed = evt.keyCode === 73 && evt.ctrlKey && evt.altKey;
if (!this.data || !shortcutPressed) { return; }
this.injectInspector();
},
showLoader: function () {
document.body.appendChild(this.loadingMessageEl);
},
hideLoader: function () {
document.body.removeChild(this.loadingMessageEl);
},
/**
* postMessage. aframe.io uses this to create a button on examples to open Inspector.
*/
onMessage: function (evt) {
if (evt.data === 'INJECT_AFRAME_INSPECTOR') { this.injectInspector(); }
},
injectInspector: function () {
var self = this;
var script;
if (AFRAME.INSPECTOR || AFRAME.inspectorInjected) { return; }
this.showLoader();
// Inject.
script = document.createElement('script');
script.src = this.data.url;
script.setAttribute('data-name', 'aframe-inspector');
script.setAttribute(AFRAME_INJECTED, '');
script.onload = function () {
AFRAME.INSPECTOR.open();
self.hideLoader();
self.removeEventListeners();
};
script.onerror = function () {
self.loadingMessageEl.innerHTML = LOADING_ERROR_MESSAGE;
};
document.head.appendChild(script);
AFRAME.inspectorInjected = true;
},
removeEventListeners: function () {
window.removeEventListener('keydown', this.onKeydown);
window.removeEventListener('message', this.onMessage);
}
});
}).call(this,_dereq_('_process'))
},{"../../../package":76,"../../constants":116,"../../core/component":125,"../../utils/bind":189,"_process":6}],102:[function(_dereq_,module,exports){
var registerComponent = _dereq_('../../core/component').registerComponent;
var shouldCaptureKeyEvent = _dereq_('../../utils/').shouldCaptureKeyEvent;
module.exports.Component = registerComponent('keyboard-shortcuts', {
schema: {
enterVR: {default: true},
exitVR: {default: true}
},
init: function () {
var self = this;
var scene = this.el;
this.listener = window.addEventListener('keyup', function (event) {
if (!shouldCaptureKeyEvent(event)) { return; }
if (self.enterVREnabled && event.keyCode === 70) { // f.
scene.enterVR();
}
if (self.enterVREnabled && event.keyCode === 27) { // escape.
scene.exitVR();
}
}, false);
},
update: function (oldData) {
var data = this.data;
this.enterVREnabled = data.enterVR;
},
remove: function () {
window.removeEventListener('keyup', this.listener);
}
});
},{"../../core/component":125,"../../utils/":195}],103:[function(_dereq_,module,exports){
var debug = _dereq_('../../utils/debug');
var registerComponent = _dereq_('../../core/component').registerComponent;
var warn = debug('components:pool:warn');
/**
* Pool component.
* A pool of entities that will be reused.
* It avoids creating and destroying the same kind of entities
* in dynamic scenes. It will help reduce GC pauses. Useful for example
* in a game where you want to reuse enemies entities.
*
* @member {array} availableEls - Available entities in the pool.
* @member {array} usedEls - Entities of the pool in use.
*
*/
module.exports.Component = registerComponent('pool', {
schema: {
mixin: {default: ''},
size: {default: 0},
dynamic: {default: false}
},
multiple: true,
initPool: function () {
var i;
var mixin = this.data.mixin;
if (!mixin) { return; }
this.availableEls = [];
this.usedEls = [];
for (i = 0; i < this.data.size; ++i) {
this.createEntity();
}
},
update: function (oldData) {
var data = this.data;
if (oldData.mixin !== data.mixin || oldData.size !== data.size) {
this.initPool();
}
},
/**
* Add a new entity to the list of available entities.
*/
createEntity: function () {
var el = document.createElement('a-entity');
el.play = this.wrapPlay(el.play);
el.setAttribute('mixin', this.data.mixin);
el.setAttribute('visible', false);
this.el.appendChild(el);
this.availableEls.push(el);
},
/**
* Play wrapper for pooled entities. When pausing and playing
* a scene we don't want to play the entities that are not in use
*/
wrapPlay: function (playMethod) {
var usedEls = this.usedEls;
return function () {
if (usedEls.indexOf(this) === -1) { return; }
playMethod.call(this);
};
},
/**
* Used to request one of the available entities of the pool
*/
requestEntity: function () {
var el;
if (this.availableEls.length === 0) {
if (this.data.dynamic === false) {
warn('Requested entity from empty pool ' + this.name);
return;
} else {
warn('Requested entity from empty pool. This pool is dynamic' +
'and will resize automatically. You might want to increase its initial size' + this.name);
}
this.createEntity();
}
el = this.availableEls.shift();
this.usedEls.push(el);
el.setAttribute('visible', true);
return el;
},
/**
* Used to return a used entity to the pool
*/
returnEntity: function (el) {
var index = this.usedEls.indexOf(el);
if (index === -1) {
warn('The returned entity was not previously pooled from ' + this.name);
return;
}
this.usedEls.splice(index, 1);
this.availableEls.push(el);
el.setAttribute('visible', false);
el.pause();
}
});
},{"../../core/component":125,"../../utils/debug":191}],104:[function(_dereq_,module,exports){
/* global ImageData, URL */
var registerComponent = _dereq_('../../core/component').registerComponent;
var THREE = _dereq_('../../lib/three');
var VERTEX_SHADER = [
'attribute vec3 position;',
'attribute vec2 uv;',
'uniform mat4 projectionMatrix;',
'uniform mat4 modelViewMatrix;',
'varying vec2 vUv;',
'void main() {',
' vUv = vec2( 1.- uv.x, uv.y );',
' gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );',
'}'
].join('\n');
var FRAGMENT_SHADER = [
'precision mediump float;',
'uniform samplerCube map;',
'varying vec2 vUv;',
'#define M_PI 3.141592653589793238462643383279',
'void main() {',
' vec2 uv = vUv;',
' float longitude = uv.x * 2. * M_PI - M_PI + M_PI / 2.;',
' float latitude = uv.y * M_PI;',
' vec3 dir = vec3(',
' - sin( longitude ) * sin( latitude ),',
' cos( latitude ),',
' - cos( longitude ) * sin( latitude )',
' );',
' normalize( dir );',
' gl_FragColor = vec4( textureCube( map, dir ).rgb, 1.0 );',
'}'
].join('\n');
/**
* Component to take screenshots of the scene using a keboard shortcut (alt+s).
* It can be configured to either take 360° captures (`equirectangular`)
* or regular screenshots (`projection`)
*
* This is based on https://github.com/spite/THREE.CubemapToEquirectangular
* To capture an equirectangular projection of the scene a THREE.CubeCamera is used
* The cube map produced by the CubeCamera is projected on a quad and then rendered to
* WebGLRenderTarget with an ortographic camera.
*/
module.exports.Component = registerComponent('screenshot', {
schema: {
width: {default: 4096},
height: {default: 2048}
},
init: function () {
var el = this.el;
var self = this;
if (el.renderer) {
setup();
} else {
el.addEventListener('render-target-loaded', setup);
}
function setup () {
var gl = el.renderer.getContext();
self.cubeMapSize = gl.getParameter(gl.MAX_CUBE_MAP_TEXTURE_SIZE);
self.material = new THREE.RawShaderMaterial({
uniforms: {map: {type: 't', value: null}},
vertexShader: VERTEX_SHADER,
fragmentShader: FRAGMENT_SHADER,
side: THREE.DoubleSide
});
self.quad = new THREE.Mesh(
new THREE.PlaneBufferGeometry(1, 1),
self.material
);
self.quad.visible = false;
self.camera = new THREE.OrthographicCamera(-1 / 2, 1 / 2, 1 / 2, -1 / 2, -10000, 10000);
self.canvas = document.createElement('canvas');
self.ctx = self.canvas.getContext('2d');
if (el.camera) { el.camera.add(self.quad); }
self.onKeyDown = self.onKeyDown.bind(self);
self.onCameraActive = self.onCameraActive.bind(self);
el.addEventListener('camera-set-active', self.onCameraActive);
}
},
getRenderTarget: function (width, height) {
return new THREE.WebGLRenderTarget(width, height, {
minFilter: THREE.LinearFilter,
magFilter: THREE.LinearFilter,
wrapS: THREE.ClampToEdgeWrapping,
wrapT: THREE.ClampToEdgeWrapping,
format: THREE.RGBAFormat,
type: THREE.UnsignedByteType
});
},
resize: function (width, height) {
// Resize quad.
this.quad.scale.set(width, height, 1);
// Resize camera.
this.camera.left = -1 * width / 2;
this.camera.right = width / 2;
this.camera.top = height / 2;
this.camera.bottom = -1 * height / 2;
this.camera.updateProjectionMatrix();
// Resize canvas.
this.canvas.width = width;
this.canvas.height = height;
},
play: function () {
window.addEventListener('keydown', this.onKeyDown);
},
onCameraActive: function (evt) {
var cameraParent = this.quad.parent;
if (cameraParent) { cameraParent.remove(this.quad); }
evt.detail.cameraEl.getObject3D('camera').add(this.quad);
},
/**
* + + s = Regular screenshot.
* + + + s = Equirectangular screenshot.
*/
onKeyDown: function (evt) {
var shortcutPressed = evt.keyCode === 83 && evt.ctrlKey && evt.altKey;
if (!this.data || !shortcutPressed) { return; }
var projection = evt.shiftKey ? 'equirectangular' : 'perspective';
this.capture(projection);
},
/**
* Capture a screenshot of the scene.
*
* @param {string} projection - Screenshot projection (equirectangular or perspective).
*/
setCapture: function (projection) {
var el = this.el;
var size;
var camera;
var cubeCamera;
// Configure camera.
if (projection === 'perspective') {
// Quad is only used in equirectangular mode. Hide it in this case.
this.quad.visible = false;
// Use scene camera.
camera = el.camera;
size = {width: this.data.width, height: this.data.height};
} else {
// Use ortho camera.
camera = this.camera;
// Copy position and rotation of scene camera into the ortho one.
camera.position.copy(el.camera.getWorldPosition());
camera.rotation.copy(el.camera.getWorldRotation());
// Create cube camera and copy position from scene camera.
cubeCamera = new THREE.CubeCamera(el.camera.near, el.camera.far,
Math.min(this.cubeMapSize, 2048));
cubeCamera.position.copy(el.camera.getWorldPosition());
cubeCamera.rotation.copy(el.camera.getWorldRotation());
// Render scene with cube camera.
cubeCamera.updateCubeMap(el.renderer, el.object3D);
this.quad.material.uniforms.map.value = cubeCamera.renderTarget.texture;
size = {width: this.data.width, height: this.data.height};
// Use quad to project image taken by the cube camera.
this.quad.visible = true;
}
return {
camera: camera,
size: size,
projection: projection
};
},
/**
* Maintained for backwards compatibility.
*/
capture: function (projection) {
var params = this.setCapture(projection);
this.renderCapture(params.camera, params.size, params.projection);
// Trigger file download.
this.saveCapture();
},
/**
* Return canvas instead of triggering download (e.g., for uploading blob to server).
*/
getCanvas: function (projection) {
var params = this.setCapture(projection);
this.renderCapture(params.camera, params.size, params.projection);
return this.canvas;
},
renderCapture: function (camera, size, projection) {
var autoClear = this.el.renderer.autoClear;
var el = this.el;
var imageData;
var output;
var pixels;
var renderer = this.el.renderer;
// Create rendering target and buffer to store the read pixels.
output = this.getRenderTarget(size.width, size.height);
pixels = new Uint8Array(4 * size.width * size.height);
// Resize quad, camera, and canvas.
this.resize(size.width, size.height);
// Render scene to render target.
renderer.autoClear = true;
renderer.render(el.object3D, camera, output, true);
renderer.autoClear = autoClear;
// Read image pizels back.
renderer.readRenderTargetPixels(output, 0, 0, size.width, size.height, pixels);
if (projection === 'perspective') {
pixels = this.flipPixelsVertically(pixels, size.width, size.height);
}
imageData = new ImageData(new Uint8ClampedArray(pixels), size.width, size.height);
// Hide quad after projecting the image.
this.quad.visible = false;
// Copy pixels into canvas.
this.ctx.putImageData(imageData, 0, 0);
},
flipPixelsVertically: function (pixels, width, height) {
var flippedPixels = pixels.slice(0);
for (var x = 0; x < width; ++x) {
for (var y = 0; y < height; ++y) {
flippedPixels[x * 4 + y * width * 4] = pixels[x * 4 + (height - y) * width * 4];
flippedPixels[x * 4 + 1 + y * width * 4] = pixels[x * 4 + 1 + (height - y) * width * 4];
flippedPixels[x * 4 + 2 + y * width * 4] = pixels[x * 4 + 2 + (height - y) * width * 4];
flippedPixels[x * 4 + 3 + y * width * 4] = pixels[x * 4 + 3 + (height - y) * width * 4];
}
}
return flippedPixels;
},
/**
* Download capture to file.
*/
saveCapture: function () {
this.canvas.toBlob(function (blob) {
var fileName = 'screenshot-' + document.title.toLowerCase() + '-' + Date.now() + '.png';
var linkEl = document.createElement('a');
var url = URL.createObjectURL(blob);
linkEl.href = url;
linkEl.setAttribute('download', fileName);
linkEl.innerHTML = 'downloading...';
linkEl.style.display = 'none';
document.body.appendChild(linkEl);
setTimeout(function () {
linkEl.click();
document.body.removeChild(linkEl);
}, 1);
}, 'image/png');
}
});
},{"../../core/component":125,"../../lib/three":173}],105:[function(_dereq_,module,exports){
var registerComponent = _dereq_('../../core/component').registerComponent;
var RStats = _dereq_('../../../vendor/rStats');
var utils = _dereq_('../../utils');
_dereq_('../../../vendor/rStats.extras');
_dereq_('../../lib/rStatsAframe');
var AFrameStats = window.aframeStats;
var bind = utils.bind;
var HIDDEN_CLASS = 'a-hidden';
var ThreeStats = window.threeStats;
/**
* Stats appended to document.body by RStats.
*/
module.exports.Component = registerComponent('stats', {
schema: {default: true},
init: function () {
var scene = this.el;
if (utils.getUrlParameter('stats') === 'false') { return; }
this.stats = createStats(scene);
this.statsEl = document.querySelector('.rs-base');
this.hideBound = bind(this.hide, this);
this.showBound = bind(this.show, this);
scene.addEventListener('enter-vr', this.hideBound);
scene.addEventListener('exit-vr', this.showBound);
},
update: function () {
if (!this.stats) { return; }
return (!this.data) ? this.hide() : this.show();
},
remove: function () {
this.el.removeEventListener('enter-vr', this.hideBound);
this.el.removeEventListener('exit-vr', this.showBound);
if (!this.statsEl) { return; } // Scene detached.
this.statsEl.parentNode.removeChild(this.statsEl);
},
tick: function () {
var stats = this.stats;
if (!stats) { return; }
stats('rAF').tick();
stats('FPS').frame();
stats().update();
},
hide: function () {
this.statsEl.classList.add(HIDDEN_CLASS);
},
show: function () {
this.statsEl.classList.remove(HIDDEN_CLASS);
}
});
function createStats (scene) {
var threeStats = new ThreeStats(scene.renderer);
var aframeStats = new AFrameStats(scene);
var plugins = scene.isMobile ? [] : [threeStats, aframeStats];
return new RStats({
css: [], // Our stylesheet is injected from `src/index.js`.
values: {
fps: {caption: 'fps', below: 30}
},
groups: [
{caption: 'Framerate', values: ['fps', 'raf']}
],
plugins: plugins
});
}
},{"../../../vendor/rStats":203,"../../../vendor/rStats.extras":202,"../../core/component":125,"../../lib/rStatsAframe":172,"../../utils":195}],106:[function(_dereq_,module,exports){
var registerComponent = _dereq_('../../core/component').registerComponent;
var constants = _dereq_('../../constants/');
var utils = _dereq_('../../utils/');
var bind = utils.bind;
var ENTER_VR_CLASS = 'a-enter-vr';
var ENTER_VR_BTN_CLASS = 'a-enter-vr-button';
var HIDDEN_CLASS = 'a-hidden';
var ORIENTATION_MODAL_CLASS = 'a-orientation-modal';
/**
* UI for entering VR mode.
*/
module.exports.Component = registerComponent('vr-mode-ui', {
dependencies: ['canvas'],
schema: {
enabled: {default: true}
},
init: function () {
var self = this;
var sceneEl = this.el;
if (utils.getUrlParameter('ui') === 'false') { return; }
this.enterVR = bind(sceneEl.enterVR, sceneEl);
this.exitVR = bind(sceneEl.exitVR, sceneEl);
this.insideLoader = false;
this.enterVREl = null;
this.orientationModalEl = null;
// Hide/show VR UI when entering/exiting VR mode.
sceneEl.addEventListener('enter-vr', bind(this.updateEnterVRInterface, this));
sceneEl.addEventListener('exit-vr', bind(this.updateEnterVRInterface, this));
window.addEventListener('message', function (event) {
if (event.data.type === 'loaderReady') {
self.insideLoader = true;
self.remove();
}
});
// Modal that tells the user to change orientation if in portrait.
window.addEventListener('orientationchange',
bind(this.toggleOrientationModalIfNeeded, this));
},
update: function () {
var sceneEl = this.el;
if (!this.data.enabled || this.insideLoader || utils.getUrlParameter('ui') === 'false') {
return this.remove();
}
if (this.enterVREl || this.orientationModalEl) { return; }
// Add UI if enabled and not already present.
this.enterVREl = createEnterVRButton(this.enterVR);
sceneEl.appendChild(this.enterVREl);
this.orientationModalEl = createOrientationModal(this.exitVR);
sceneEl.appendChild(this.orientationModalEl);
this.updateEnterVRInterface();
},
remove: function () {
[this.enterVREl, this.orientationModalEl].forEach(function (uiElement) {
if (uiElement) {
uiElement.parentNode.removeChild(uiElement);
}
});
},
updateEnterVRInterface: function () {
this.toggleEnterVRButtonIfNeeded();
this.toggleOrientationModalIfNeeded();
},
toggleEnterVRButtonIfNeeded: function () {
var sceneEl = this.el;
if (!this.enterVREl) { return; }
if (sceneEl.is('vr-mode')) {
this.enterVREl.classList.add(HIDDEN_CLASS);
} else {
this.enterVREl.classList.remove(HIDDEN_CLASS);
}
},
toggleOrientationModalIfNeeded: function () {
var sceneEl = this.el;
var orientationModalEl = this.orientationModalEl;
if (!orientationModalEl || !sceneEl.isMobile) { return; }
if (!utils.device.isLandscape() && sceneEl.is('vr-mode')) {
// Show if in VR mode on portrait.
orientationModalEl.classList.remove(HIDDEN_CLASS);
} else {
orientationModalEl.classList.add(HIDDEN_CLASS);
}
}
});
/**
* Creates a button that when clicked will enter into stereo-rendering mode for VR.
*
* Structure:
*
* @param {function} enterVRHandler
* @returns {Element} Wrapper
.
*/
function createEnterVRButton (enterVRHandler) {
var vrButton;
var wrapper;
// Create elements.
wrapper = document.createElement('div');
wrapper.classList.add(ENTER_VR_CLASS);
wrapper.setAttribute(constants.AFRAME_INJECTED, '');
vrButton = document.createElement('button');
vrButton.className = ENTER_VR_BTN_CLASS;
vrButton.setAttribute('title', 'Enter VR mode with a headset or fullscreen mode on a desktop. Visit https://webvr.rocks or https://webvr.info for more information.');
vrButton.setAttribute(constants.AFRAME_INJECTED, '');
// Insert elements.
wrapper.appendChild(vrButton);
vrButton.addEventListener('click', function (evt) {
enterVRHandler();
});
return wrapper;
}
/**
* Create a modal that tells mobile users to orient the phone to landscape.
* Add a close button that if clicked, exits VR and closes the modal.
*/
function createOrientationModal (exitVRHandler) {
var modal = document.createElement('div');
modal.className = ORIENTATION_MODAL_CLASS;
modal.classList.add(HIDDEN_CLASS);
modal.setAttribute(constants.AFRAME_INJECTED, '');
var exit = document.createElement('button');
exit.setAttribute(constants.AFRAME_INJECTED, '');
exit.innerHTML = 'Exit VR';
// Exit VR on close.
exit.addEventListener('click', exitVRHandler);
modal.appendChild(exit);
return modal;
}
},{"../../constants/":116,"../../core/component":125,"../../utils/":195}],107:[function(_dereq_,module,exports){
var component = _dereq_('../core/component');
var THREE = _dereq_('../lib/three');
var bind = _dereq_('../utils/bind');
var registerComponent = component.registerComponent;
/**
* Shadow component.
*
* When applied to an entity, that entity's geometry and any descendants will cast or receive
* shadows as specified by the `cast` and `receive` properties.
*/
module.exports.Component = registerComponent('shadow', {
schema: {
cast: {default: true},
receive: {default: true}
},
init: function () {
this.onMeshChanged = bind(this.update, this);
this.el.addEventListener('object3dset', this.onMeshChanged);
this.system.setShadowMapEnabled(true);
},
update: function () {
var data = this.data;
this.updateDescendants(data.cast, data.receive);
},
remove: function () {
var el = this.el;
el.removeEventListener('object3dset', this.onMeshChanged);
this.updateDescendants(false, false);
},
updateDescendants: function (cast, receive) {
var sceneEl = this.el.sceneEl;
this.el.object3D.traverse(function (node) {
if (!(node instanceof THREE.Mesh)) { return; }
node.castShadow = cast;
node.receiveShadow = receive;
// If scene has already rendered, materials must be updated.
if (sceneEl.hasLoaded && node.material) {
var materials = node.material.materials || [node.material];
for (var i = 0; i < materials.length; i++) {
materials[i].needsUpdate = true;
}
}
});
}
});
},{"../core/component":125,"../lib/three":173,"../utils/bind":189}],108:[function(_dereq_,module,exports){
var registerComponent = _dereq_('../core/component').registerComponent;
var debug = _dereq_('../utils/debug');
var bind = _dereq_('../utils/bind');
var THREE = _dereq_('../lib/three');
var warn = debug('components:sound:warn');
/**
* Sound component.
*/
module.exports.Component = registerComponent('sound', {
schema: {
autoplay: {default: false},
distanceModel: {default: 'inverse',
oneOf: ['linear', 'inverse', 'exponential']},
loop: {default: false},
maxDistance: {default: 10000},
on: {default: ''},
poolSize: {default: 1},
positional: {default: true},
refDistance: {default: 1},
rolloffFactor: {default: 1},
src: {type: 'audio'},
volume: {default: 1}
},
multiple: true,
init: function () {
this.listener = null;
this.audioLoader = new THREE.AudioLoader();
this.pool = new THREE.Group();
this.loaded = false;
this.mustPlay = false;
this.playSound = bind(this.playSound, this);
},
update: function (oldData) {
var data = this.data;
var srcChanged = data.src !== oldData.src;
// Create new sound if not yet created or changing `src`.
if (srcChanged) {
if (!data.src) {
warn('Audio source was not specified with `src`');
return;
}
this.setupSound();
}
this.pool.children.forEach(function (sound) {
if (data.positional) {
sound.setDistanceModel(data.distanceModel);
sound.setMaxDistance(data.maxDistance);
sound.setRefDistance(data.refDistance);
sound.setRolloffFactor(data.rolloffFactor);
}
sound.setLoop(data.loop);
sound.setVolume(data.volume);
sound.isPaused = false;
});
if (data.on !== oldData.on) {
this.updateEventListener(oldData.on);
}
// All sound values set. Load in `src`.
if (srcChanged) {
var self = this;
this.loaded = false;
this.audioLoader.load(data.src, function (buffer) {
self.pool.children.forEach(function (sound) {
sound.setBuffer(buffer);
});
self.loaded = true;
// Remove this key from cache, otherwise we can't play it again
THREE.Cache.remove(data.src);
if (self.data.autoplay || self.mustPlay) { self.playSound(); }
self.el.emit('sound-loaded');
});
}
},
pause: function () {
this.stopSound();
this.removeEventListener();
},
play: function () {
if (this.data.autoplay) { this.playSound(); }
this.updateEventListener();
},
remove: function () {
this.removeEventListener();
this.el.removeObject3D(this.attrName);
try {
this.pool.children.forEach(function (sound) {
sound.disconnect();
});
} catch (e) {
// disconnect() will throw if it was never connected initially.
warn('Audio source not properly disconnected');
}
},
/**
* Update listener attached to the user defined on event.
*/
updateEventListener: function (oldEvt) {
var el = this.el;
if (oldEvt) { el.removeEventListener(oldEvt, this.playSound); }
el.addEventListener(this.data.on, this.playSound);
},
removeEventListener: function () {
this.el.removeEventListener(this.data.on, this.playSound);
},
/**
* Removes current sound object, creates new sound object, adds to entity.
*
* @returns {object} sound
*/
setupSound: function () {
var el = this.el;
var sceneEl = el.sceneEl;
if (this.pool.children.length > 0) {
this.stopSound();
el.removeObject3D('sound');
}
// Only want one AudioListener. Cache it on the scene.
var listener = this.listener = sceneEl.audioListener || new THREE.AudioListener();
sceneEl.audioListener = listener;
if (sceneEl.camera) {
sceneEl.camera.add(listener);
}
// Wait for camera if necessary.
sceneEl.addEventListener('camera-set-active', function (evt) {
evt.detail.cameraEl.getObject3D('camera').add(listener);
});
// Create [poolSize] audio instances and attach them to pool
this.pool = new THREE.Group();
for (var i = 0; i < this.data.poolSize; i++) {
var sound = this.data.positional ? new THREE.PositionalAudio(listener) : new THREE.Audio(listener);
this.pool.add(sound);
}
el.setObject3D(this.attrName, this.pool);
this.pool.children.forEach(function (sound) {
sound.onEnded = function () {
sound.isPlaying = false;
el.emit('sound-ended', {index: i});
};
});
},
/**
* Pause all the sounds in the pool.
*/
pauseSound: function () {
this.isPlaying = false;
this.pool.children.forEach(function (sound) {
if (!sound.source || !sound.source.buffer || !sound.isPlaying || sound.isPaused) { return; }
sound.isPaused = true;
sound.pause();
});
},
/**
* Look for an unused sound in the pool and play it if found.
*/
playSound: function () {
if (!this.loaded) {
warn('Sound not loaded yet. It will be played once it finished loading');
this.mustPlay = true;
return;
}
var found = false;
this.isPlaying = true;
this.pool.children.forEach(function (sound) {
if (!sound.isPlaying && sound.buffer && !found) {
sound.play();
sound.isPaused = false;
found = true;
return;
}
});
if (!found) {
warn('All the sounds are playing. If you need to play more sounds simultaneously ' +
'consider increasing the size of pool with the `poolSize` attribute.', this.el);
return;
}
this.mustPlay = false;
},
/**
* Stop all the sounds in the pool.
*/
stopSound: function () {
this.isPlaying = false;
this.pool.children.forEach(function (sound) {
if (!sound.source || !sound.source.buffer) { return; }
sound.stop();
});
}
});
},{"../core/component":125,"../lib/three":173,"../utils/bind":189,"../utils/debug":191}],109:[function(_dereq_,module,exports){
var createTextGeometry = _dereq_('three-bmfont-text');
var loadBMFont = _dereq_('load-bmfont');
var path = _dereq_('path');
var registerComponent = _dereq_('../core/component').registerComponent;
var coreShader = _dereq_('../core/shader');
var THREE = _dereq_('../lib/three');
var utils = _dereq_('../utils/');
var error = utils.debug('components:text:error');
var shaders = coreShader.shaders;
var warn = utils.debug('components:text:warn');
// 1 to match other A-Frame default widths.
var DEFAULT_WIDTH = 1;
// @bryik set anisotropy to 16. Improves look of large amounts of text when viewed from angle.
var MAX_ANISOTROPY = 16;
var FONT_BASE_URL = 'https://cdn.aframe.io/fonts/';
var FONTS = {
aileronsemibold: FONT_BASE_URL + 'Aileron-Semibold.fnt',
dejavu: FONT_BASE_URL + 'DejaVu-sdf.fnt',
exo2bold: FONT_BASE_URL + 'Exo2Bold.fnt',
exo2semibold: FONT_BASE_URL + 'Exo2SemiBold.fnt',
kelsonsans: FONT_BASE_URL + 'KelsonSans.fnt',
monoid: FONT_BASE_URL + 'Monoid.fnt',
mozillavr: FONT_BASE_URL + 'mozillavr.fnt',
roboto: FONT_BASE_URL + 'Roboto-msdf.json',
sourcecodepro: FONT_BASE_URL + 'SourceCodePro.fnt'
};
var MSDF_FONTS = ['roboto'];
var DEFAULT_FONT = 'roboto';
module.exports.FONTS = FONTS;
var cache = new PromiseCache();
var fontWidthFactors = {};
/**
* SDF-based text component.
* Based on https://github.com/Jam3/three-bmfont-text.
*
* All the stock fonts are for the `sdf` registered shader, an improved version of jam3's
* original `sdf` shader.
*/
module.exports.Component = registerComponent('text', {
multiple: true,
schema: {
align: {type: 'string', default: 'left', oneOf: ['left', 'right', 'center']},
alphaTest: {default: 0.5},
// `anchor` defaults to center to match geometries.
anchor: {default: 'center', oneOf: ['left', 'right', 'center', 'align']},
baseline: {default: 'center', oneOf: ['top', 'center', 'bottom']},
color: {type: 'color', default: '#FFF'},
font: {type: 'string', default: DEFAULT_FONT},
// `fontImage` defaults to the font name as a .png (e.g., mozillavr.fnt -> mozillavr.png).
fontImage: {type: 'string'},
// `height` has no default, will be populated at layout.
height: {type: 'number'},
letterSpacing: {type: 'number', default: 0},
// `lineHeight` defaults to font's `lineHeight` value.
lineHeight: {type: 'number'},
opacity: {type: 'number', default: 1.0},
shader: {default: 'sdf', oneOf: shaders},
side: {default: 'front', oneOf: ['front', 'back', 'double']},
tabSize: {default: 4},
transparent: {default: true},
value: {type: 'string'},
whiteSpace: {default: 'normal', oneOf: ['normal', 'pre', 'nowrap']},
// `width` defaults to geometry width if present, else `DEFAULT_WIDTH`.
width: {type: 'number'},
// `wrapCount` units are about one default font character. Wrap roughly at this number.
wrapCount: {type: 'number', default: 40},
// `wrapPixels` will wrap using bmfont pixel units (e.g., dejavu's is 32 pixels).
wrapPixels: {type: 'number'},
// `yOffset` to adjust generated fonts from tools that may have incorrect metrics.
yOffset: {type: 'number', default: 0},
// `zOffset` will provide a small z offset to avoid z-fighting.
zOffset: {type: 'number', default: 0.001}
},
init: function () {
this.texture = new THREE.Texture();
this.texture.anisotropy = MAX_ANISOTROPY;
this.geometry = createTextGeometry();
this.createOrUpdateMaterial();
this.mesh = new THREE.Mesh(this.geometry, this.material);
this.el.setObject3D(this.attrName, this.mesh);
},
update: function (oldData) {
var data = coerceData(this.data);
var font = this.currentFont;
// Update material.
this.createOrUpdateMaterial();
// New font. `updateFont` will later change data and layout.
if (oldData.font !== data.font) {
this.updateFont();
return;
}
// Update geometry and layout.
if (font) {
this.updateGeometry(this.geometry, data, font);
this.updateLayout(data);
}
},
/**
* Clean up geometry, material, texture, mesh, objects.
*/
remove: function () {
this.geometry.dispose();
this.geometry = null;
this.el.removeObject3D(this.attrName);
this.material.dispose();
this.material = null;
this.texture.dispose();
this.texture = null;
if (this.shaderObject) {
delete this.shaderObject;
}
},
/**
* Update the shader of the material.
*/
createOrUpdateMaterial: function () {
var data = this.data;
var hasChangedShader;
var material = this.material;
var NewShader;
var shaderData;
var shaderName;
// Infer shader if using a stock font (or from `-msdf` filename convention).
shaderName = data.shader;
if (MSDF_FONTS.indexOf(data.font) !== -1 || data.font.indexOf('-msdf.') >= 0) {
shaderName = 'msdf';
} else if (data.font in FONTS && MSDF_FONTS.indexOf(data.font) === -1) {
shaderName = 'sdf';
}
hasChangedShader = (this.shaderObject && this.shaderObject.name) !== shaderName;
shaderData = {
alphaTest: data.alphaTest,
color: data.color,
map: this.texture,
opacity: data.opacity,
side: parseSide(data.side),
transparent: data.transparent
};
// Shader has not changed, do an update.
if (!hasChangedShader) {
// Update shader material.
this.shaderObject.update(shaderData);
// Apparently, was not set on `init` nor `update`.
material.transparent = shaderData.transparent;
updateBaseMaterial(material, shaderData);
return;
}
// Shader has changed. Create a shader material.
NewShader = createShader(this.el, shaderName, shaderData);
this.material = NewShader.material;
this.shaderObject = NewShader.shader;
// Set new shader material.
updateBaseMaterial(this.material, shaderData);
if (this.mesh) { this.mesh.material = this.material; }
},
/**
* Load font for geometry, load font image for material, and apply.
*/
updateFont: function () {
var data = this.data;
var el = this.el;
var fontSrc;
var geometry = this.geometry;
var self = this;
if (!data.font) { warn('No font specified. Using the default font.'); }
// Make invisible during font swap.
this.mesh.visible = false;
// Look up font URL to use, and perform cached load.
fontSrc = this.lookupFont(data.font || DEFAULT_FONT) || data.font;
cache.get(fontSrc, function doLoadFont () {
return loadFont(fontSrc, data.yOffset);
}).then(function setFont (font) {
var coercedData;
var fontImgSrc;
if (font.pages.length !== 1) {
throw new Error('Currently only single-page bitmap fonts are supported.');
}
if (!fontWidthFactors[fontSrc]) {
font.widthFactor = fontWidthFactors[font] = computeFontWidthFactor(font);
}
// Update geometry given font metrics.
coercedData = coerceData(data);
self.updateGeometry(geometry, self.data, font);
// Set font and update layout.
self.currentFont = font;
self.updateLayout(coercedData);
// Look up font image URL to use, and perform cached load.
fontImgSrc = data.fontImage || fontSrc.replace(/(\.fnt)|(\.json)/, '.png') ||
path.dirname(data.font) + '/' + font.pages[0];
cache.get(fontImgSrc, function () {
return loadTexture(fontImgSrc);
}).then(function (image) {
// Make mesh visible and apply font image as texture.
self.mesh.visible = true;
self.texture.image = image;
self.texture.needsUpdate = true;
el.emit('textfontset', {font: data.font, fontObj: font});
}).catch(function (err) {
error(err);
throw err;
});
}).catch(function (err) {
error(err);
throw err;
});
},
/**
* Update layout with anchor, alignment, baseline, and considering any meshes.
*/
updateLayout: function (data) {
var anchor;
var baseline;
var el = this.el;
var geometry = this.geometry;
var geometryComponent = el.getAttribute('geometry');
var height;
var layout = geometry.layout;
var mesh = this.mesh;
var textRenderWidth;
var textScale;
var width;
var x;
var y;
// Determine width to use (defined width, geometry's width, or default width).
geometryComponent = el.getAttribute('geometry');
width = data.width || (geometryComponent && geometryComponent.width) || DEFAULT_WIDTH;
// Determine wrap pixel count. Either specified or by experimental fudge factor.
// Note that experimental factor will never be correct for variable width fonts.
textRenderWidth = computeWidth(data.wrapPixels, data.wrapCount,
this.currentFont.widthFactor);
textScale = width / textRenderWidth;
// Determine height to use.
height = textScale * (layout.height + layout.descender);
// Update geometry dimensions to match text layout if width and height are set to 0.
// For example, scales a plane to fit text.
if (geometryComponent) {
if (!geometryComponent.width) { el.setAttribute('geometry', 'width', width); }
if (!geometryComponent.height) { el.setAttribute('geometry', 'height', height); }
}
// Calculate X position to anchor text left, center, or right.
anchor = data.anchor === 'align' ? data.align : data.anchor;
if (anchor === 'left') {
x = 0;
} else if (anchor === 'right') {
x = -1 * layout.width;
} else if (anchor === 'center') {
x = -1 * layout.width / 2;
} else {
throw new TypeError('Invalid text.anchor property value', anchor);
}
// Calculate Y position to anchor text top, center, or bottom.
baseline = data.baseline;
if (baseline === 'bottom') {
y = 0;
} else if (baseline === 'top') {
y = -1 * layout.height + layout.ascender;
} else if (baseline === 'center') {
y = -1 * layout.height / 2;
} else {
throw new TypeError('Invalid text.baseline property value', baseline);
}
// Position and scale mesh to apply layout.
mesh.position.x = x * textScale;
mesh.position.y = y * textScale;
// Place text slightly in front to avoid Z-fighting.
mesh.position.z = data.zOffset;
mesh.scale.set(textScale, -1 * textScale, textScale);
this.geometry.computeBoundingSphere();
},
/**
* Grab font from the constant.
* Set as a method for test stubbing purposes.
*/
lookupFont: function (key) {
return FONTS[key];
},
/**
* Update the text geometry using `three-bmfont-text.update`.
*/
updateGeometry: function (geometry, data, font) {
geometry.update(utils.extend({}, data, {
font: font,
width: computeWidth(data.wrapPixels, data.wrapCount, font.widthFactor),
text: data.value.toString().replace(/\\n/g, '\n').replace(/\\t/g, '\t'),
lineHeight: data.lineHeight || font.common.lineHeight
}));
}
});
function parseSide (side) {
switch (side) {
case 'back': {
return THREE.BackSide;
}
case 'double': {
return THREE.DoubleSide;
}
default: {
return THREE.FrontSide;
}
}
}
/**
* Coerce some data to numbers.
* as they will be passed directly into text creation and update
*/
function coerceData (data) {
data = utils.clone(data);
if (data.lineHeight !== undefined) {
data.lineHeight = parseFloat(data.lineHeight);
if (!isFinite(data.lineHeight)) { data.lineHeight = undefined; }
}
if (data.width !== undefined) {
data.width = parseFloat(data.width);
if (!isFinite(data.width)) { data.width = undefined; }
}
return data;
}
/**
* @returns {Promise}
*/
function loadFont (src, yOffset) {
return new Promise(function (resolve, reject) {
loadBMFont(src, function (err, font) {
if (err) {
error('Error loading font', src);
reject(err);
return;
}
// Fix negative Y offsets for Roboto MSDF font from tool. Experimentally determined.
if (src.indexOf('/Roboto-msdf.json') >= 0) { yOffset = 30; }
if (yOffset) { font.chars.map(function doOffset (ch) { ch.yoffset += yOffset; }); }
resolve(font);
});
});
}
/**
* @returns {Promise}
*/
function loadTexture (src) {
return new Promise(function (resolve, reject) {
new THREE.ImageLoader().load(src, function (image) {
resolve(image);
}, undefined, function () {
error('Error loading font image', src);
reject(null);
});
});
}
function createShader (el, shaderName, data) {
var shader;
var shaderObject;
// Set up Shader.
shaderObject = new shaders[shaderName].Shader();
shaderObject.el = el;
shaderObject.init(data);
shaderObject.update(data);
// Get material.
shader = shaderObject.material;
// Apparently, was not set on `init` nor `update`.
shader.transparent = data.transparent;
return {
material: shader,
shader: shaderObject
};
}
/**
* @todo Add more supported material properties (e.g., `visible`).
*/
function updateBaseMaterial (material, data) {
material.side = data.side;
}
/**
* Determine wrap pixel count. Either specified or by experimental fudge factor.
* Note that experimental factor will never be correct for variable width fonts.
*/
function computeWidth (wrapPixels, wrapCount, widthFactor) {
return wrapPixels || ((0.5 + wrapCount) * widthFactor);
}
/**
* Compute default font width factor to use.
*/
function computeFontWidthFactor (font) {
var sum = 0;
var digitsum = 0;
var digits = 0;
font.chars.map(function (ch) {
sum += ch.xadvance;
if (ch.id >= 48 && ch.id <= 57) {
digits++;
digitsum += ch.xadvance;
}
});
return digits ? digitsum / digits : sum / font.chars.length;
}
/**
* Get or create a promise given a key and promise generator.
* @todo Move to a utility and use in other parts of A-Frame.
*/
function PromiseCache () {
var cache = this.cache = {};
this.get = function (key, promiseGenerator) {
if (key in cache) {
return cache[key];
}
cache[key] = promiseGenerator();
return cache[key];
};
}
},{"../core/component":125,"../core/shader":134,"../lib/three":173,"../utils/":195,"load-bmfont":24,"path":32,"three-bmfont-text":37}],110:[function(_dereq_,module,exports){
var registerComponent = _dereq_('../core/component').registerComponent;
var controllerUtils = _dereq_('../utils/tracked-controls');
var THREE = _dereq_('../lib/three');
var DEFAULT_CAMERA_HEIGHT = _dereq_('../constants').DEFAULT_CAMERA_HEIGHT;
var DEFAULT_HANDEDNESS = _dereq_('../constants').DEFAULT_HANDEDNESS;
// Vector from eyes to elbow (divided by user height).
var EYES_TO_ELBOW = {x: 0.175, y: -0.3, z: -0.03};
// Vector from eyes to elbow (divided by user height).
var FOREARM = {x: 0, y: 0, z: -0.175};
/**
* Tracked controls component.
* Wrap the gamepad API for pose and button states.
* Select the appropriate controller and apply pose to the entity.
* Observe button states and emit appropriate events.
*
* @property {number} controller - Index of controller in array returned by Gamepad API. Only used if hand property is not set.
* @property {string} id - Selected controller among those returned by Gamepad API.
* @property {number} hand - If multiple controllers found with id, choose the one with the given value for hand. If set, we ignore 'controller' property
*/
module.exports.Component = registerComponent('tracked-controls', {
schema: {
controller: {default: 0},
id: {type: 'string', default: ''},
hand: {type: 'string', default: ''},
idPrefix: {type: 'string', default: ''},
rotationOffset: {default: 0},
// Arm model parameters when not 6DoF.
armModel: {default: true},
headElement: {type: 'selector'}
},
init: function () {
this.axis = [0, 0, 0];
this.buttonStates = {};
this.targetControllerNumber = this.data.controller;
this.dolly = new THREE.Object3D();
this.controllerEuler = new THREE.Euler();
this.controllerEuler.order = 'YXZ';
this.controllerPosition = new THREE.Vector3();
this.controllerQuaternion = new THREE.Quaternion();
this.deltaControllerPosition = new THREE.Vector3();
this.position = new THREE.Vector3();
this.rotation = {};
this.standingMatrix = new THREE.Matrix4();
this.previousControllerPosition = new THREE.Vector3();
this.updateGamepad();
},
tick: function (time, delta) {
var mesh = this.el.getObject3D('mesh');
// Update mesh animations.
if (mesh && mesh.update) { mesh.update(delta / 1000); }
this.updateGamepad();
this.updatePose();
this.updateButtons();
},
/**
* Return default user height to use for non-6DOF arm model.
*/
defaultUserHeight: function () {
return DEFAULT_CAMERA_HEIGHT;
},
/**
* Return head element to use for non-6DOF arm model.
*/
getHeadElement: function () {
return this.data.headElement || this.el.sceneEl.camera.el;
},
/**
* Handle update controller match criteria (such as `id`, `idPrefix`, `hand`, `controller`)
*/
updateGamepad: function () {
var data = this.data;
var controller = controllerUtils.findMatchingController(
this.system.controllers,
data.id,
data.idPrefix,
data.hand,
data.controller
);
this.controller = controller;
},
applyArmModel: function (controllerPosition) {
// Use controllerPosition and deltaControllerPosition to avoid creating variables.
var controller = this.controller;
var controllerEuler = this.controllerEuler;
var controllerQuaternion = this.controllerQuaternion;
var deltaControllerPosition = this.deltaControllerPosition;
var hand;
var headCamera;
var headEl;
var headObject3D;
var pose;
var userHeight;
headEl = this.getHeadElement();
headObject3D = headEl.object3D;
headCamera = headEl.components.camera;
userHeight = (headCamera ? headCamera.data.userHeight : 0) || this.defaultUserHeight();
pose = controller.pose;
hand = (controller ? controller.hand : undefined) || DEFAULT_HANDEDNESS;
// Use camera position as head position.
controllerPosition.copy(headObject3D.position);
// Set offset for degenerate "arm model" to elbow.
deltaControllerPosition.set(
EYES_TO_ELBOW.x * (hand === 'left' ? -1 : hand === 'right' ? 1 : 0),
EYES_TO_ELBOW.y, // Lower than our eyes.
EYES_TO_ELBOW.z); // Slightly out in front.
// Scale offset by user height.
deltaControllerPosition.multiplyScalar(userHeight);
// Apply camera Y rotation (not X or Z, so you can look down at your hand).
deltaControllerPosition.applyAxisAngle(headObject3D.up, headObject3D.rotation.y);
// Apply rotated offset to position.
controllerPosition.add(deltaControllerPosition);
// Set offset for degenerate "arm model" forearm. Forearm sticking out from elbow.
deltaControllerPosition.set(FOREARM.x, FOREARM.y, FOREARM.z);
// Scale offset by user height.
deltaControllerPosition.multiplyScalar(userHeight);
// Apply controller X/Y rotation (tilting up/down/left/right is usually moving the arm).
if (pose.orientation) {
controllerQuaternion.fromArray(pose.orientation);
} else {
controllerQuaternion.copy(headObject3D.quaternion);
}
controllerEuler.setFromQuaternion(controllerQuaternion);
controllerEuler.set(controllerEuler.x, controllerEuler.y, 0);
deltaControllerPosition.applyEuler(controllerEuler);
// Apply rotated offset to position.
controllerPosition.add(deltaControllerPosition);
},
/**
* Read pose from controller (from Gamepad API), apply transforms, apply to entity.
*/
updatePose: function () {
var controller = this.controller;
var controllerEuler = this.controllerEuler;
var controllerPosition = this.controllerPosition;
var elPosition;
var previousControllerPosition = this.previousControllerPosition;
var dolly = this.dolly;
var el = this.el;
var pose;
var standingMatrix = this.standingMatrix;
var vrDisplay = this.system.vrDisplay;
var headEl = this.getHeadElement();
var headCamera = headEl.components.camera;
var userHeight = (headCamera ? headCamera.data.userHeight : 0) || this.defaultUserHeight();
if (!controller) { return; }
// Compose pose from Gamepad.
pose = controller.pose;
if (pose.orientation !== null) {
dolly.quaternion.fromArray(pose.orientation);
}
// controller position or arm model
if (pose.position !== null) {
dolly.position.fromArray(pose.position);
} else {
// Controller not 6DOF, apply arm model.
if (this.data.armModel) { this.applyArmModel(dolly.position); }
}
// Apply transforms, if 6DOF and in VR.
if (pose.position != null && vrDisplay) {
if (vrDisplay.stageParameters) {
standingMatrix.fromArray(vrDisplay.stageParameters.sittingToStandingTransform);
dolly.matrix.compose(dolly.position, dolly.quaternion, dolly.scale);
dolly.matrix.multiplyMatrices(standingMatrix, dolly.matrix);
} else {
// Apply default camera height
dolly.position.y += userHeight;
dolly.matrix.compose(dolly.position, dolly.quaternion, dolly.scale);
}
} else {
dolly.matrix.compose(dolly.position, dolly.quaternion, dolly.scale);
}
// Decompose.
controllerEuler.setFromRotationMatrix(dolly.matrix);
controllerPosition.setFromMatrixPosition(dolly.matrix);
// Apply rotation.
this.rotation.x = THREE.Math.radToDeg(controllerEuler.x);
this.rotation.y = THREE.Math.radToDeg(controllerEuler.y);
this.rotation.z = THREE.Math.radToDeg(controllerEuler.z) + this.data.rotationOffset;
el.setAttribute('rotation', this.rotation);
// Apply position.
elPosition = el.getAttribute('position');
this.position.copy(elPosition).sub(previousControllerPosition).add(controllerPosition);
el.setAttribute('position', this.position);
previousControllerPosition.copy(controllerPosition);
},
/**
* Handle button changes including axes, presses, touches, values.
*/
updateButtons: function () {
var buttonState;
var controller = this.controller;
var id;
if (!controller) { return; }
// Check every button.
for (id = 0; id < controller.buttons.length; ++id) {
// Initialize button state.
if (!this.buttonStates[id]) {
this.buttonStates[id] = {pressed: false, touched: false, value: 0};
}
buttonState = controller.buttons[id];
this.handleButton(id, buttonState);
}
// Check axes.
this.handleAxes();
},
/**
* Handle presses and touches for a single button.
*
* @param {number} id - Index of button in Gamepad button array.
* @param {number} buttonState - Value of button state from 0 to 1.
* @returns {boolean} Whether button has changed in any way.
*/
handleButton: function (id, buttonState) {
var changed = this.handlePress(id, buttonState) ||
this.handleTouch(id, buttonState) ||
this.handleValue(id, buttonState);
if (!changed) { return false; }
this.el.emit('buttonchanged', {id: id, state: buttonState});
return true;
},
/**
* An axis is an array of values from -1 (up, left) to 1 (down, right).
* Compare each component of the axis to the previous value to determine change.
*
* @returns {boolean} Whether axes changed.
*/
handleAxes: function () {
var changed = false;
var controllerAxes = this.controller.axes;
var i;
var previousAxis = this.axis;
var changedAxes = [];
// Check if axis changed.
for (i = 0; i < controllerAxes.length; ++i) {
changedAxes.push(previousAxis[i] !== controllerAxes[i]);
if (changedAxes[i]) { changed = true; }
}
if (!changed) { return false; }
this.axis = controllerAxes.slice();
this.el.emit('axismove', {axis: this.axis, changed: changedAxes});
return true;
},
/**
* Determine whether a button press has occured and emit events as appropriate.
*
* @param {string} id - ID of the button to check.
* @param {object} buttonState - State of the button to check.
* @returns {boolean} Whether button press state changed.
*/
handlePress: function (id, buttonState) {
var evtName;
var previousButtonState = this.buttonStates[id];
// Not changed.
if (buttonState.pressed === previousButtonState.pressed) { return false; }
evtName = buttonState.pressed ? 'down' : 'up';
this.el.emit('button' + evtName, {id: id, state: buttonState});
previousButtonState.pressed = buttonState.pressed;
return true;
},
/**
* Determine whether a button touch has occured and emit events as appropriate.
*
* @param {string} id - ID of the button to check.
* @param {object} buttonState - State of the button to check.
* @returns {boolean} Whether button touch state changed.
*/
handleTouch: function (id, buttonState) {
var evtName;
var previousButtonState = this.buttonStates[id];
// Not changed.
if (buttonState.touched === previousButtonState.touched) { return false; }
evtName = buttonState.touched ? 'start' : 'end';
// Due to unfortunate name collision, add empty touches array to avoid Daydream error.
this.el.emit('touch' + evtName, {id: id, state: buttonState}, true, {touches: []});
previousButtonState.touched = buttonState.touched;
return true;
},
/**
* Determine whether a button value has changed.
*
* @param {string} id - Id of the button to check.
* @param {object} buttonState - State of the button to check.
* @returns {boolean} Whether button value changed.
*/
handleValue: function (id, buttonState) {
var previousButtonState = this.buttonStates[id];
// Not changed.
if (buttonState.value === previousButtonState.value) { return false; }
previousButtonState.value = buttonState.value;
return true;
}
});
},{"../constants":116,"../core/component":125,"../lib/three":173,"../utils/tracked-controls":199}],111:[function(_dereq_,module,exports){
var registerComponent = _dereq_('../core/component').registerComponent;
/**
* Visibility component.
*/
module.exports.Component = registerComponent('visible', {
schema: {default: true},
update: function () {
this.el.object3D.visible = this.data;
}
});
},{"../core/component":125}],112:[function(_dereq_,module,exports){
var registerComponent = _dereq_('../core/component').registerComponent;
var utils = _dereq_('../utils/');
var bind = utils.bind;
var checkControllerPresentAndSetup = utils.trackedControls.checkControllerPresentAndSetup;
var emitIfAxesChanged = utils.trackedControls.emitIfAxesChanged;
var VIVE_CONTROLLER_MODEL_OBJ_URL = 'https://cdn.aframe.io/controllers/vive/vr_controller_vive.obj';
var VIVE_CONTROLLER_MODEL_OBJ_MTL = 'https://cdn.aframe.io/controllers/vive/vr_controller_vive.mtl';
var GAMEPAD_ID_PREFIX = 'OpenVR ';
/**
* Vive controls.
* Interface with Vive controllers and map Gamepad events to controller buttons:
* trackpad, trigger, grip, menu, system
* Load a controller model and highlight the pressed buttons.
*/
module.exports.Component = registerComponent('vive-controls', {
schema: {
hand: {default: 'left'},
buttonColor: {type: 'color', default: '#FAFAFA'}, // Off-white.
buttonHighlightColor: {type: 'color', default: '#22D1EE'}, // Light blue.
model: {default: true},
rotationOffset: {default: 0}
},
/**
* Button IDs:
* 0 - trackpad
* 1 - trigger (intensity value from 0.5 to 1)
* 2 - grip
* 3 - menu (dispatch but better for menu options)
* 4 - system (never dispatched on this layer)
*/
mapping: {
axes: {trackpad: [0, 1]},
buttons: ['trackpad', 'trigger', 'grip', 'menu', 'system']
},
init: function () {
var self = this;
this.animationActive = 'pointing';
this.checkControllerPresentAndSetup = checkControllerPresentAndSetup; // To allow mock.
this.controllerPresent = false;
this.emitIfAxesChanged = emitIfAxesChanged; // To allow mock.
this.lastControllerCheck = 0;
this.onButtonChanged = bind(this.onButtonChanged, this);
this.onButtonDown = function (evt) { self.onButtonEvent(evt.detail.id, 'down'); };
this.onButtonUp = function (evt) { self.onButtonEvent(evt.detail.id, 'up'); };
this.onButtonTouchEnd = function (evt) { self.onButtonEvent(evt.detail.id, 'touchend'); };
this.onButtonTouchStart = function (evt) { self.onButtonEvent(evt.detail.id, 'touchstart'); };
this.onAxisMoved = bind(this.onAxisMoved, this);
this.previousButtonValues = {};
this.bindMethods();
},
play: function () {
this.checkIfControllerPresent();
this.addControllersUpdateListener();
},
pause: function () {
this.removeEventListeners();
this.removeControllersUpdateListener();
},
bindMethods: function () {
this.onModelLoaded = bind(this.onModelLoaded, this);
this.onControllersUpdate = bind(this.onControllersUpdate, this);
this.checkIfControllerPresent = bind(this.checkIfControllerPresent, this);
this.removeControllersUpdateListener = bind(this.removeControllersUpdateListener, this);
this.onAxisMoved = bind(this.onAxisMoved, this);
},
addEventListeners: function () {
var el = this.el;
el.addEventListener('buttonchanged', this.onButtonChanged);
el.addEventListener('buttondown', this.onButtonDown);
el.addEventListener('buttonup', this.onButtonUp);
el.addEventListener('touchend', this.onButtonTouchEnd);
el.addEventListener('touchstart', this.onButtonTouchStart);
el.addEventListener('model-loaded', this.onModelLoaded);
el.addEventListener('axismove', this.onAxisMoved);
this.controllerEventsActive = true;
},
removeEventListeners: function () {
var el = this.el;
el.removeEventListener('buttonchanged', this.onButtonChanged);
el.removeEventListener('buttondown', this.onButtonDown);
el.removeEventListener('buttonup', this.onButtonUp);
el.removeEventListener('touchend', this.onButtonTouchEnd);
el.removeEventListener('touchstart', this.onButtonTouchStart);
el.removeEventListener('model-loaded', this.onModelLoaded);
el.removeEventListener('axismove', this.onAxisMoved);
this.controllerEventsActive = false;
},
/**
* Once OpenVR returns correct hand data in supporting browsers, we can use hand property.
* var isPresent = this.checkControllerPresentAndSetup(this.el.sceneEl, GAMEPAD_ID_PREFIX,
{ hand: data.hand });
* Until then, use hardcoded index.
*/
checkIfControllerPresent: function () {
var data = this.data;
var controllerIndex = data.hand === 'right' ? 0 : data.hand === 'left' ? 1 : 2;
this.checkControllerPresentAndSetup(this, GAMEPAD_ID_PREFIX, {index: controllerIndex});
},
injectTrackedControls: function () {
var el = this.el;
var data = this.data;
// If we have an OpenVR Gamepad, use the fixed mapping.
el.setAttribute('tracked-controls', {
idPrefix: GAMEPAD_ID_PREFIX,
// Hand IDs: 0 = right, 1 = left, 2 = anything else.
controller: data.hand === 'right' ? 0 : data.hand === 'left' ? 1 : 2,
rotationOffset: data.rotationOffset
});
// Load model.
if (!this.data.model) { return; }
this.el.setAttribute('obj-model', {
obj: VIVE_CONTROLLER_MODEL_OBJ_URL,
mtl: VIVE_CONTROLLER_MODEL_OBJ_MTL
});
},
addControllersUpdateListener: function () {
this.el.sceneEl.addEventListener('controllersupdated', this.onControllersUpdate, false);
},
removeControllersUpdateListener: function () {
this.el.sceneEl.removeEventListener('controllersupdated', this.onControllersUpdate, false);
},
onControllersUpdate: function () {
this.checkIfControllerPresent();
},
/**
* Rotate the trigger button based on how hard the trigger is pressed.
*/
onButtonChanged: function (evt) {
var button = this.mapping.buttons[evt.detail.id];
var buttonMeshes = this.buttonMeshes;
var analogValue;
if (!button) { return; }
if (button === 'trigger') {
analogValue = evt.detail.state.value;
// Update trigger rotation depending on button value.
if (buttonMeshes && buttonMeshes.trigger) {
buttonMeshes.trigger.rotation.x = -analogValue * (Math.PI / 12);
}
}
// Pass along changed event with button state, using button mapping for convenience.
this.el.emit(button + 'changed', evt.detail.state);
},
onModelLoaded: function (evt) {
var buttonMeshes;
var controllerObject3D = evt.detail.model;
var self = this;
if (!this.data.model) { return; }
// Store button meshes object to be able to change their colors.
buttonMeshes = this.buttonMeshes = {};
buttonMeshes.grip = {
left: controllerObject3D.getObjectByName('leftgrip'),
right: controllerObject3D.getObjectByName('rightgrip')
};
buttonMeshes.menu = controllerObject3D.getObjectByName('menubutton');
buttonMeshes.system = controllerObject3D.getObjectByName('systembutton');
buttonMeshes.trackpad = controllerObject3D.getObjectByName('touchpad');
buttonMeshes.trigger = controllerObject3D.getObjectByName('trigger');
// Set default colors.
Object.keys(buttonMeshes).forEach(function (buttonName) {
self.setButtonColor(buttonName, self.data.buttonColor);
});
// Offset pivot point.
controllerObject3D.position.set(0, -0.015, 0.04);
},
onAxisMoved: function (evt) {
this.emitIfAxesChanged(this, this.mapping.axes, evt);
},
onButtonEvent: function (id, evtName) {
var buttonName = this.mapping.buttons[id];
var color;
var i;
var isTouch = evtName.indexOf('touch') !== -1;
// Emit events.
if (Array.isArray(buttonName)) {
for (i = 0; i < buttonName.length; i++) {
this.el.emit(buttonName[i] + evtName);
}
} else {
this.el.emit(buttonName + evtName);
}
if (!this.data.model) { return; }
// Don't change color for trackpad touch.
if (isTouch) { return; }
// Update colors.
color = evtName === 'up' ? this.data.buttonColor : this.data.buttonHighlightColor;
if (Array.isArray(buttonName)) {
for (i = 0; i < buttonName.length; i++) {
this.setButtonColor(buttonName[i], color);
}
} else {
this.setButtonColor(buttonName, color);
}
},
setButtonColor: function (buttonName, color) {
var buttonMeshes = this.buttonMeshes;
if (!buttonMeshes) { return; }
// Need to do both left and right sides for grip.
if (buttonName === 'grip') {
buttonMeshes.grip.left.material.color.set(color);
buttonMeshes.grip.right.material.color.set(color);
return;
}
buttonMeshes[buttonName].material.color.set(color);
}
});
},{"../core/component":125,"../utils/":195}],113:[function(_dereq_,module,exports){
var KEYCODE_TO_CODE = _dereq_('../constants').keyboardevent.KEYCODE_TO_CODE;
var registerComponent = _dereq_('../core/component').registerComponent;
var THREE = _dereq_('../lib/three');
var utils = _dereq_('../utils/');
var bind = utils.bind;
var shouldCaptureKeyEvent = utils.shouldCaptureKeyEvent;
var CLAMP_VELOCITY = 0.00001;
var MAX_DELTA = 0.2;
var KEYS = [
'KeyW', 'KeyA', 'KeyS', 'KeyD',
'ArrowUp', 'ArrowLeft', 'ArrowRight', 'ArrowDown'
];
/**
* WASD component to control entities using WASD keys.
*/
module.exports.Component = registerComponent('wasd-controls', {
schema: {
acceleration: {default: 65},
adAxis: {default: 'x', oneOf: ['x', 'y', 'z']},
adEnabled: {default: true},
adInverted: {default: false},
easing: {default: 20},
enabled: {default: true},
fly: {default: false},
wsAxis: {default: 'z', oneOf: ['x', 'y', 'z']},
wsEnabled: {default: true},
wsInverted: {default: false}
},
init: function () {
// To keep track of the pressed keys.
this.keys = {};
this.position = {};
this.velocity = new THREE.Vector3();
// Bind methods and add event listeners.
this.onBlur = bind(this.onBlur, this);
this.onFocus = bind(this.onFocus, this);
this.onKeyDown = bind(this.onKeyDown, this);
this.onKeyUp = bind(this.onKeyUp, this);
this.onVisibilityChange = bind(this.onVisibilityChange, this);
this.attachVisibilityEventListeners();
},
tick: function (time, delta) {
var currentPosition;
var data = this.data;
var el = this.el;
var movementVector;
var position = this.position;
var velocity = this.velocity;
if (!velocity[data.adAxis] && !velocity[data.wsAxis] &&
isEmptyObject(this.keys)) { return; }
// Update velocity.
delta = delta / 1000;
this.updateVelocity(delta);
if (!velocity[data.adAxis] && !velocity[data.wsAxis]) { return; }
// Get movement vector and translate position.
currentPosition = el.getAttribute('position');
movementVector = this.getMovementVector(delta);
position.x = currentPosition.x + movementVector.x;
position.y = currentPosition.y + movementVector.y;
position.z = currentPosition.z + movementVector.z;
el.setAttribute('position', position);
},
remove: function () {
this.removeKeyEventListeners();
this.removeVisibilityEventListeners();
},
play: function () {
this.attachKeyEventListeners();
},
pause: function () {
this.keys = {};
this.removeKeyEventListeners();
},
updateVelocity: function (delta) {
var acceleration;
var adAxis;
var adSign;
var data = this.data;
var keys = this.keys;
var velocity = this.velocity;
var wsAxis;
var wsSign;
adAxis = data.adAxis;
wsAxis = data.wsAxis;
// If FPS too low, reset velocity.
if (delta > MAX_DELTA) {
velocity[adAxis] = 0;
velocity[wsAxis] = 0;
return;
}
// Decay velocity.
if (velocity[adAxis] !== 0) {
velocity[adAxis] -= velocity[adAxis] * data.easing * delta;
}
if (velocity[wsAxis] !== 0) {
velocity[wsAxis] -= velocity[wsAxis] * data.easing * delta;
}
// Clamp velocity easing.
if (Math.abs(velocity[adAxis]) < CLAMP_VELOCITY) { velocity[adAxis] = 0; }
if (Math.abs(velocity[wsAxis]) < CLAMP_VELOCITY) { velocity[wsAxis] = 0; }
if (!data.enabled) { return; }
// Update velocity using keys pressed.
acceleration = data.acceleration;
if (data.adEnabled) {
adSign = data.adInverted ? -1 : 1;
if (keys.KeyA || keys.ArrowLeft) { velocity[adAxis] -= adSign * acceleration * delta; }
if (keys.KeyD || keys.ArrowRight) { velocity[adAxis] += adSign * acceleration * delta; }
}
if (data.wsEnabled) {
wsSign = data.wsInverted ? -1 : 1;
if (keys.KeyW || keys.ArrowUp) { velocity[wsAxis] -= wsSign * acceleration * delta; }
if (keys.KeyS || keys.ArrowDown) { velocity[wsAxis] += wsSign * acceleration * delta; }
}
},
getMovementVector: (function () {
var directionVector = new THREE.Vector3(0, 0, 0);
var rotationEuler = new THREE.Euler(0, 0, 0, 'YXZ');
return function (delta) {
var rotation = this.el.getAttribute('rotation');
var velocity = this.velocity;
var xRotation;
directionVector.copy(velocity);
directionVector.multiplyScalar(delta);
// Absolute.
if (!rotation) { return directionVector; }
xRotation = this.data.fly ? rotation.x : 0;
// Transform direction relative to heading.
rotationEuler.set(THREE.Math.degToRad(xRotation), THREE.Math.degToRad(rotation.y), 0);
directionVector.applyEuler(rotationEuler);
return directionVector;
};
})(),
attachVisibilityEventListeners: function () {
window.addEventListener('blur', this.onBlur);
window.addEventListener('focus', this.onFocus);
document.addEventListener('visibilitychange', this.onVisibilityChange);
},
removeVisibilityEventListeners: function () {
window.removeEventListener('blur', this.onBlur);
window.removeEventListener('focus', this.onFocus);
document.removeEventListener('visibilitychange', this.onVisibilityChange);
},
attachKeyEventListeners: function () {
window.addEventListener('keydown', this.onKeyDown);
window.addEventListener('keyup', this.onKeyUp);
},
removeKeyEventListeners: function () {
window.removeEventListener('keydown', this.onKeyDown);
window.removeEventListener('keyup', this.onKeyUp);
},
onBlur: function () {
this.pause();
},
onFocus: function () {
this.play();
},
onVisibilityChange: function () {
if (document.hidden) {
this.onBlur();
} else {
this.onFocus();
}
},
onKeyDown: function (event) {
var code;
if (!shouldCaptureKeyEvent(event)) { return; }
code = event.code || KEYCODE_TO_CODE[event.keyCode];
if (KEYS.indexOf(code) !== -1) { this.keys[code] = true; }
},
onKeyUp: function (event) {
var code;
code = event.code || KEYCODE_TO_CODE[event.keyCode];
delete this.keys[code];
}
});
function isEmptyObject (keys) {
var key;
for (key in keys) { return false; }
return true;
}
},{"../constants":116,"../core/component":125,"../lib/three":173,"../utils/":195}],114:[function(_dereq_,module,exports){
/* global THREE */
var bind = _dereq_('../utils/bind');
var registerComponent = _dereq_('../core/component').registerComponent;
var controllerUtils = _dereq_('../utils/tracked-controls');
var utils = _dereq_('../utils/');
var debug = utils.debug('components:windows-motion-controls:debug');
var warn = utils.debug('components:windows-motion-controls:warn');
var DEFAULT_HANDEDNESS = _dereq_('../constants').DEFAULT_HANDEDNESS;
var MODEL_BASE_URL = 'https://cdn.aframe.io/controllers/microsoft/';
var MODEL_FILENAMES = { left: 'left.glb', right: 'right.glb', default: 'universal.glb' };
var GAMEPAD_ID_PREFIX = 'Spatial Controller (Spatial Interaction Source) ';
var GAMEPAD_ID_PATTERN = /([0-9a-zA-Z]+-[0-9a-zA-Z]+)$/;
/**
* Windows Motion Controller Controls Component
* Interfaces with Windows Motion Controller controllers and maps Gamepad events to
* common controller buttons: trackpad, trigger, grip, menu and system
* It loads a controller model and transforms the pressed buttons
*/
module.exports.Component = registerComponent('windows-motion-controls', {
schema: {
hand: {default: DEFAULT_HANDEDNESS},
// It is possible to have multiple pairs of controllers attached (a pair has both left and right).
// Set this to 1 to use a controller from the second pair, 2 from the third pair, etc.
pair: {default: 0},
// If true, loads the controller glTF asset.
model: {default: true},
// If true, will hide the model from the scene if no matching gamepad (based on ID & hand) is connected.
hideDisconnected: {default: true}
},
mapping: {
// A-Frame specific semantic axis names
axes: {'thumbstick': [0, 1], 'trackpad': [2, 3]},
// A-Frame specific semantic button names
buttons: ['thumbstick', 'trigger', 'grip', 'menu', 'trackpad'],
// A mapping of the semantic name to node name in the glTF model file,
// that should be transformed by axis value.
// This array mirrors the browser Gamepad.axes array, such that
// the mesh corresponding to axis 0 is in this array index 0.
axisMeshNames: [
'THUMBSTICK_X',
'THUMBSTICK_Y',
'TOUCHPAD_TOUCH_X',
'TOUCHPAD_TOUCH_Y'
],
// A mapping of the semantic name to button node name in the glTF model file,
// that should be transformed by button value.
buttonMeshNames: {
'trigger': 'SELECT',
'menu': 'MENU',
'grip': 'GRASP',
'thumbstick': 'THUMBSTICK_PRESS',
'trackpad': 'TOUCHPAD_PRESS'
},
pointingPoseMeshName: 'POINTING_POSE'
},
bindMethods: function () {
this.onModelError = bind(this.onModelError, this);
this.onModelLoaded = bind(this.onModelLoaded, this);
this.onControllersUpdate = bind(this.onControllersUpdate, this);
this.checkIfControllerPresent = bind(this.checkIfControllerPresent, this);
this.onAxisMoved = bind(this.onAxisMoved, this);
},
init: function () {
var self = this;
var el = this.el;
this.onButtonChanged = bind(this.onButtonChanged, this);
this.onButtonDown = function (evt) { self.onButtonEvent(evt, 'down'); };
this.onButtonUp = function (evt) { self.onButtonEvent(evt, 'up'); };
this.onButtonTouchStart = function (evt) { self.onButtonEvent(evt, 'touchstart'); };
this.onButtonTouchEnd = function (evt) { self.onButtonEvent(evt, 'touchend'); };
this.onControllerConnected = function () { self.setModelVisibility(true); };
this.onControllerDisconnected = function () { self.setModelVisibility(false); };
this.controllerPresent = false;
this.lastControllerCheck = 0;
this.previousButtonValues = {};
this.bindMethods();
// Cache for submeshes that we have looked up by name.
this.loadedMeshInfo = {
buttonMeshes: null,
axisMeshes: null
};
// Pointing poses
this.rayOrigin = {
origin: new THREE.Vector3(),
direction: new THREE.Vector3(0, 0, -1),
createdFromMesh: false
};
// Stored on object to allow for mocking in tests
this.emitIfAxesChanged = controllerUtils.emitIfAxesChanged;
this.checkControllerPresentAndSetup = controllerUtils.checkControllerPresentAndSetup;
el.addEventListener('controllerconnected', this.onControllerConnected);
el.addEventListener('controllerdisconnected', this.onControllerDisconnected);
},
addEventListeners: function () {
var el = this.el;
el.addEventListener('buttonchanged', this.onButtonChanged);
el.addEventListener('buttondown', this.onButtonDown);
el.addEventListener('buttonup', this.onButtonUp);
el.addEventListener('touchstart', this.onButtonTouchStart);
el.addEventListener('touchend', this.onButtonTouchEnd);
el.addEventListener('axismove', this.onAxisMoved);
el.addEventListener('model-error', this.onModelError);
el.addEventListener('model-loaded', this.onModelLoaded);
this.controllerEventsActive = true;
},
removeEventListeners: function () {
var el = this.el;
el.removeEventListener('buttonchanged', this.onButtonChanged);
el.removeEventListener('buttondown', this.onButtonDown);
el.removeEventListener('buttonup', this.onButtonUp);
el.removeEventListener('touchstart', this.onButtonTouchStart);
el.removeEventListener('touchend', this.onButtonTouchEnd);
el.removeEventListener('axismove', this.onAxisMoved);
el.removeEventListener('model-error', this.onModelError);
el.removeEventListener('model-loaded', this.onModelLoaded);
this.controllerEventsActive = false;
},
checkIfControllerPresent: function () {
this.checkControllerPresentAndSetup(this, GAMEPAD_ID_PREFIX, {
hand: this.data.hand,
index: this.data.pair
});
},
play: function () {
this.checkIfControllerPresent();
this.addControllersUpdateListener();
},
pause: function () {
this.removeEventListeners();
this.removeControllersUpdateListener();
},
updateControllerModel: function () {
// If we do not want to load a model, or, have already loaded the model, emit the controllermodelready event.
if (!this.data.model || this.rayOrigin.createdFromMesh) {
this.modelReady();
return;
}
var sourceUrl = this.createControllerModelUrl();
this.loadModel(sourceUrl);
},
/**
* Helper function that constructs a URL from the controller ID suffix, for future proofed
* art assets.
*/
createControllerModelUrl: function (forceDefault) {
// Determine the device specific folder based on the ID suffix
var trackedControlsComponent = this.el.components['tracked-controls'];
var controller = trackedControlsComponent ? trackedControlsComponent.controller : null;
var device = 'default';
var hand = this.data.hand;
var filename;
if (controller) {
// Read hand directly from the controller, rather than this.data, as in the case that the controller
// is unhanded this.data will still have 'left' or 'right' (depending on what the user inserted in to the scene).
// In this case, we want to load the universal model, so need to get the '' from the controller.
hand = controller.hand;
if (!forceDefault) {
var match = controller.id.match(GAMEPAD_ID_PATTERN);
device = ((match && match[0]) || device);
}
}
// Hand
filename = MODEL_FILENAMES[hand] || MODEL_FILENAMES.default;
// Final url
return MODEL_BASE_URL + device + '/' + filename;
},
injectTrackedControls: function () {
var data = this.data;
this.el.setAttribute('tracked-controls', {
idPrefix: GAMEPAD_ID_PREFIX,
controller: data.pair,
hand: data.hand,
armModel: false
});
this.updateControllerModel();
},
addControllersUpdateListener: function () {
this.el.sceneEl.addEventListener('controllersupdated', this.onControllersUpdate, false);
},
removeControllersUpdateListener: function () {
this.el.sceneEl.removeEventListener('controllersupdated', this.onControllersUpdate, false);
},
onControllersUpdate: function () {
this.checkIfControllerPresent();
},
onModelError: function (evt) {
var defaultUrl = this.createControllerModelUrl(true);
if (evt.detail.src !== defaultUrl) {
warn('Failed to load controller model for device, attempting to load default.');
this.loadModel(defaultUrl);
} else {
warn('Failed to load default controller model.');
}
},
loadModel: function (url) {
// The model is loaded by the gltf-model compoent when this attribute is initially set,
// removed and re-loaded if the given url changes.
this.el.setAttribute('gltf-model', 'url(' + url + ')');
},
onModelLoaded: function (evt) {
var rootNode = this.controllerModel = evt.detail.model;
var loadedMeshInfo = this.loadedMeshInfo;
var i;
var meshName;
var mesh;
var meshInfo;
debug('Processing model');
// Reset the caches
loadedMeshInfo.buttonMeshes = {};
loadedMeshInfo.axisMeshes = {};
// Cache our meshes so we aren't traversing the hierarchy per frame
if (rootNode) {
// Button Meshes
for (i = 0; i < this.mapping.buttons.length; i++) {
meshName = this.mapping.buttonMeshNames[this.mapping.buttons[i]];
if (!meshName) {
debug('Skipping unknown button at index: ' + i + ' with mapped name: ' + this.mapping.buttons[i]);
continue;
}
mesh = rootNode.getObjectByName(meshName);
if (!mesh) {
warn('Missing button mesh with name: ' + meshName);
continue;
}
meshInfo = {
index: i,
value: getImmediateChildByName(mesh, 'VALUE'),
pressed: getImmediateChildByName(mesh, 'PRESSED'),
unpressed: getImmediateChildByName(mesh, 'UNPRESSED')
};
if (meshInfo.value && meshInfo.pressed && meshInfo.unpressed) {
loadedMeshInfo.buttonMeshes[this.mapping.buttons[i]] = meshInfo;
} else {
// If we didn't find the mesh, it simply means this button won't have transforms applied as mapped button value changes.
warn('Missing button submesh under mesh with name: ' + meshName +
'(VALUE: ' + !!meshInfo.value +
', PRESSED: ' + !!meshInfo.pressed +
', UNPRESSED:' + !!meshInfo.unpressed +
')');
}
}
// Axis Meshes
for (i = 0; i < this.mapping.axisMeshNames.length; i++) {
meshName = this.mapping.axisMeshNames[i];
if (!meshName) {
debug('Skipping unknown axis at index: ' + i);
continue;
}
mesh = rootNode.getObjectByName(meshName);
if (!mesh) {
warn('Missing axis mesh with name: ' + meshName);
continue;
}
meshInfo = {
index: i,
value: getImmediateChildByName(mesh, 'VALUE'),
min: getImmediateChildByName(mesh, 'MIN'),
max: getImmediateChildByName(mesh, 'MAX')
};
if (meshInfo.value && meshInfo.min && meshInfo.max) {
loadedMeshInfo.axisMeshes[i] = meshInfo;
} else {
// If we didn't find the mesh, it simply means this axis won't have transforms applied as mapped axis values change.
warn('Missing axis submesh under mesh with name: ' + meshName +
'(VALUE: ' + !!meshInfo.value +
', MIN: ' + !!meshInfo.min +
', MAX:' + !!meshInfo.max +
')');
}
}
this.calculateRayOriginFromMesh(rootNode);
// Determine if the model has to be visible or not.
this.setModelVisibility();
}
debug('Model load complete.');
// Look through only immediate children. This will return null if no mesh exists with the given name.
function getImmediateChildByName (object3d, value) {
for (var i = 0, l = object3d.children.length; i < l; i++) {
var obj = object3d.children[i];
if (obj && obj['name'] === value) {
return obj;
}
}
return undefined;
}
},
calculateRayOriginFromMesh: (function () {
var quaternion = new THREE.Quaternion();
return function (rootNode) {
var mesh;
// Calculate the pointer pose (used for rays), by applying the world transform of th POINTER_POSE node
// in the glTF (assumes that root node is at world origin)
this.rayOrigin.origin.set(0, 0, 0);
this.rayOrigin.direction.set(0, 0, -1);
this.rayOrigin.createdFromMesh = true;
// Try to read Pointing pose from the source model
mesh = rootNode.getObjectByName(this.mapping.pointingPoseMeshName);
if (mesh) {
var parent = rootNode.parent;
// We need to read pose transforms accumulated from the root of the glTF, not the scene.
if (parent) {
rootNode.parent = null;
rootNode.updateMatrixWorld(true);
rootNode.parent = parent;
}
mesh.getWorldPosition(this.rayOrigin.origin);
mesh.getWorldQuaternion(quaternion);
this.rayOrigin.direction.applyQuaternion(quaternion);
// Recalculate the world matrices now that the rootNode is re-attached to the parent.
if (parent) {
rootNode.updateMatrixWorld(true);
}
} else {
debug('Mesh does not contain pointing origin data, defaulting to none.');
}
// Emit event stating that our pointing ray is now accurate.
this.modelReady();
};
})(),
lerpAxisTransform: (function () {
var quaternion = new THREE.Quaternion();
return function (axis, axisValue) {
var axisMeshInfo = this.loadedMeshInfo.axisMeshes[axis];
if (!axisMeshInfo) return;
var min = axisMeshInfo.min;
var max = axisMeshInfo.max;
var target = axisMeshInfo.value;
// Convert from gamepad value range (-1 to +1) to lerp range (0 to 1)
var lerpValue = axisValue * 0.5 + 0.5;
target.setRotationFromQuaternion(quaternion.copy(min.quaternion).slerp(max.quaternion, lerpValue));
target.position.lerpVectors(min.position, max.position, lerpValue);
};
})(),
lerpButtonTransform: (function () {
var quaternion = new THREE.Quaternion();
return function (buttonName, buttonValue) {
var buttonMeshInfo = this.loadedMeshInfo.buttonMeshes[buttonName];
if (!buttonMeshInfo) return;
var min = buttonMeshInfo.unpressed;
var max = buttonMeshInfo.pressed;
var target = buttonMeshInfo.value;
target.setRotationFromQuaternion(quaternion.copy(min.quaternion).slerp(max.quaternion, buttonValue));
target.position.lerpVectors(min.position, max.position, buttonValue);
};
})(),
modelReady: function () {
this.el.emit('controllermodelready', {
name: 'windows-motion-controls',
model: this.data.model,
rayOrigin: this.rayOrigin
});
},
onButtonChanged: function (evt) {
var buttonName = this.mapping.buttons[evt.detail.id];
if (buttonName) {
// Update the button mesh transform
if (this.loadedMeshInfo && this.loadedMeshInfo.buttonMeshes) {
this.lerpButtonTransform(buttonName, evt.detail.state.value);
}
// Only emit events for buttons that we know how to map from index to name
this.el.emit(buttonName + 'changed', evt.detail.state);
}
},
onButtonEvent: function (evt, evtName) {
var buttonName = this.mapping.buttons[evt.detail.id];
debug('onButtonEvent(' + evt.detail.id + ', ' + evtName + ')');
if (buttonName) {
// Only emit events for buttons that we know how to map from index to name
this.el.emit(buttonName + evtName);
}
},
onAxisMoved: function (evt) {
var numAxes = this.mapping.axisMeshNames.length;
// Only attempt to update meshes if we have valid data.
if (this.loadedMeshInfo && this.loadedMeshInfo.axisMeshes) {
for (var axis = 0; axis < numAxes; axis++) {
// Update the button mesh transform
this.lerpAxisTransform(axis, evt.detail.axis[axis] || 0.0);
}
}
this.emitIfAxesChanged(this, this.mapping.axes, evt);
},
setModelVisibility: function (visible) {
var model = this.el.getObject3D('mesh');
visible = visible !== undefined ? visible : this.modelVisible;
this.modelVisible = visible;
if (!model) { return; }
model.visible = visible;
}
});
},{"../constants":116,"../core/component":125,"../utils/":195,"../utils/bind":189,"../utils/tracked-controls":199}],115:[function(_dereq_,module,exports){
/**
* Animation configuration options for TWEEN.js animations.
* Used by ``.
*/
var TWEEN = _dereq_('@tweenjs/tween.js');
var DIRECTIONS = {
alternate: 'alternate',
alternateReverse: 'alternate-reverse',
normal: 'normal',
reverse: 'reverse'
};
var EASING_FUNCTIONS = {
'linear': TWEEN.Easing.Linear.None,
'ease': TWEEN.Easing.Cubic.InOut,
'ease-in': TWEEN.Easing.Cubic.In,
'ease-out': TWEEN.Easing.Cubic.Out,
'ease-in-out': TWEEN.Easing.Cubic.InOut,
'ease-cubic': TWEEN.Easing.Cubic.In,
'ease-in-cubic': TWEEN.Easing.Cubic.In,
'ease-out-cubic': TWEEN.Easing.Cubic.Out,
'ease-in-out-cubic': TWEEN.Easing.Cubic.InOut,
'ease-quad': TWEEN.Easing.Quadratic.InOut,
'ease-in-quad': TWEEN.Easing.Quadratic.In,
'ease-out-quad': TWEEN.Easing.Quadratic.Out,
'ease-in-out-quad': TWEEN.Easing.Quadratic.InOut,
'ease-quart': TWEEN.Easing.Quartic.InOut,
'ease-in-quart': TWEEN.Easing.Quartic.In,
'ease-out-quart': TWEEN.Easing.Quartic.Out,
'ease-in-out-quart': TWEEN.Easing.Quartic.InOut,
'ease-quint': TWEEN.Easing.Quintic.InOut,
'ease-in-quint': TWEEN.Easing.Quintic.In,
'ease-out-quint': TWEEN.Easing.Quintic.Out,
'ease-in-out-quint': TWEEN.Easing.Quintic.InOut,
'ease-sine': TWEEN.Easing.Sinusoidal.InOut,
'ease-in-sine': TWEEN.Easing.Sinusoidal.In,
'ease-out-sine': TWEEN.Easing.Sinusoidal.Out,
'ease-in-out-sine': TWEEN.Easing.Sinusoidal.InOut,
'ease-expo': TWEEN.Easing.Exponential.InOut,
'ease-in-expo': TWEEN.Easing.Exponential.In,
'ease-out-expo': TWEEN.Easing.Exponential.Out,
'ease-in-out-expo': TWEEN.Easing.Exponential.InOut,
'ease-circ': TWEEN.Easing.Circular.InOut,
'ease-in-circ': TWEEN.Easing.Circular.In,
'ease-out-circ': TWEEN.Easing.Circular.Out,
'ease-in-out-circ': TWEEN.Easing.Circular.InOut,
'ease-elastic': TWEEN.Easing.Elastic.InOut,
'ease-in-elastic': TWEEN.Easing.Elastic.In,
'ease-out-elastic': TWEEN.Easing.Elastic.Out,
'ease-in-out-elastic': TWEEN.Easing.Elastic.InOut,
'ease-back': TWEEN.Easing.Back.InOut,
'ease-in-back': TWEEN.Easing.Back.In,
'ease-out-back': TWEEN.Easing.Back.Out,
'ease-in-out-back': TWEEN.Easing.Back.InOut,
'ease-bounce': TWEEN.Easing.Bounce.InOut,
'ease-in-bounce': TWEEN.Easing.Bounce.In,
'ease-out-bounce': TWEEN.Easing.Bounce.Out,
'ease-in-out-bounce': TWEEN.Easing.Bounce.InOut
};
var FILLS = {
backwards: 'backwards',
both: 'both',
forwards: 'forwards',
none: 'none'
};
var REPEATS = {
indefinite: 'indefinite'
};
var DEFAULTS = {
attribute: 'rotation',
begin: '',
end: '',
delay: 0,
dur: 1000,
easing: 'ease',
direction: DIRECTIONS.normal,
fill: FILLS.forwards,
from: undefined,
repeat: 0,
to: undefined
};
module.exports.defaults = DEFAULTS;
module.exports.directions = DIRECTIONS;
module.exports.easingFunctions = EASING_FUNCTIONS;
module.exports.fills = FILLS;
module.exports.repeats = REPEATS;
},{"@tweenjs/tween.js":1}],116:[function(_dereq_,module,exports){
module.exports = {
AFRAME_INJECTED: 'aframe-injected',
DEFAULT_CAMERA_HEIGHT: 1.6,
DEFAULT_HANDEDNESS: 'right',
animation: _dereq_('./animation'),
keyboardevent: _dereq_('./keyboardevent')
};
},{"./animation":115,"./keyboardevent":117}],117:[function(_dereq_,module,exports){
module.exports = {
// Tiny KeyboardEvent.code polyfill.
KEYCODE_TO_CODE: {
'38': 'ArrowUp',
'37': 'ArrowLeft',
'40': 'ArrowDown',
'39': 'ArrowRight',
'87': 'KeyW',
'65': 'KeyA',
'83': 'KeyS',
'68': 'KeyD'
}
};
},{}],118:[function(_dereq_,module,exports){
var ANode = _dereq_('./a-node');
var animationConstants = _dereq_('../constants/animation');
var coordinates = _dereq_('../utils/').coordinates;
var parseProperty = _dereq_('./schema').parseProperty;
var registerElement = _dereq_('./a-register-element').registerElement;
var TWEEN = _dereq_('@tweenjs/tween.js');
var THREE = _dereq_('../lib/three');
var utils = _dereq_('../utils/');
var bind = utils.bind;
var getComponentProperty = utils.entity.getComponentProperty;
var DEFAULTS = animationConstants.defaults;
var DIRECTIONS = animationConstants.directions;
var EASING_FUNCTIONS = animationConstants.easingFunctions;
var FILLS = animationConstants.fills;
var REPEATS = animationConstants.repeats;
var isCoordinates = coordinates.isCoordinates;
/**
* Animation element that applies Tween animation to parent element (entity).
* Takes after the Web Animations spec.
*
* @member {number} count - Decrementing counter for how many cycles of animations left to
* run.
* @member {Element} el - Entity which the animation is modifying.
* @member initialValue - Value before animation started. Used to restore state.
* @member {bool} isRunning - Whether animation is currently running.
* @member {function} partialSetAttribute -
* setAttribute function that is agnostic to whether we are setting an attribute value
* or a component property value. The el and the attribute names are bundled with
* the function.
* @member {object} tween - tween.js object.
*/
module.exports.AAnimation = registerElement('a-animation', {
prototype: Object.create(ANode.prototype, {
createdCallback: {
value: function () {
this.bindMethods();
this.isRunning = false;
this.partialSetAttribute = function () { /* no-op */ };
this.tween = null;
}
},
attachedCallback: {
value: function () {
this.el = this.parentNode;
this.handleMixinUpdate();
this.update();
this.load();
}
},
attributeChangedCallback: {
value: function (attr, oldVal, newVal) {
if (!this.hasLoaded || !this.isRunning) { return; }
this.stop();
this.handleMixinUpdate();
this.update();
}
},
detachedCallback: {
value: function () {
if (!this.isRunning) { return; }
this.stop();
}
},
/**
* Builds a Tween object to handle animations.
* Uses tween.js's from, to, delay, easing, repeat, onUpdate, and onComplete.
* Note: tween.js takes objects for its `from` and `to` values.
*
* @returns {object}
*/
getTween: {
value: function () {
var self = this;
var data = self.data;
var el = self.el;
var animationValues;
var attribute = data.attribute;
var delay = parseInt(data.delay, 10);
var currentValue = getComponentProperty(el, attribute);
var direction = self.getDirection(data.direction);
var easing = EASING_FUNCTIONS[data.easing];
var fill = data.fill;
var from;
var repeat = data.repeat === REPEATS.indefinite ? Infinity : 0;
var to;
var toTemp;
var yoyo = false;
animationValues = getAnimationValues(el, attribute, data.from || self.initialValue, data.to, currentValue);
from = animationValues.from;
to = animationValues.to;
self.partialSetAttribute = animationValues.partialSetAttribute;
if (self.count === undefined) {
self.count = repeat === Infinity ? 0 : parseInt(data.repeat, 10);
}
if (isNaN(delay)) { delay = 0; }
// Store initial state.
self.initialValue = self.initialValue || cloneValue(currentValue);
// Handle indefinite + forwards + alternate yoyo edge-case (#405).
if (repeat === Infinity && fill === FILLS.forwards &&
[DIRECTIONS.alternate,
DIRECTIONS.alternateReverse].indexOf(data.direction) !== -1) {
yoyo = true;
}
// If reversing, swap from and to.
if (direction === DIRECTIONS.reverse) {
toTemp = to;
to = cloneValue(from);
from = cloneValue(toTemp);
}
// If fill is backwards or both, start animation at the specified from.
if ([FILLS.backwards, FILLS.both].indexOf(fill) !== -1) {
self.partialSetAttribute(from);
}
// Create Tween.
return new TWEEN.Tween(cloneValue(from))
.to(to, data.dur)
.delay(delay)
.easing(easing)
.repeat(repeat)
.yoyo(yoyo)
.onUpdate(function () {
self.partialSetAttribute(this);
})
.onComplete(bind(self.onCompleted, self));
}
},
/**
* Animation parameters changed. Stop current animation, get a new one, and start it.
*/
update: {
value: function () {
var data = this.data;
// Terminology warning if infinite used instead of indefinite
if (data.repeat === 'infinite') {
console.warn("Using 'infinite' as 'repeat' value is invalid. Use 'indefinite' instead.");
}
// Deprecation warning for begin when used as a delay.
if (data.begin !== '' && !isNaN(data.begin)) {
console.warn("Using 'begin' to specify a delay is deprecated. Use 'delay' instead.");
data.delay = data.begin;
data.begin = '';
}
var begin = data.begin;
var end = data.end;
// Cancel previous event listeners
if (this.evt) { this.removeEventListeners(this.evt); }
// Store new event name.
this.evt = {begin: begin, end: end};
// Add new event listeners
this.addEventListeners(this.evt);
// If `begin` is not defined, start the animation right away.
if (begin === '') {
this.stop();
this.start();
}
},
writable: window.debug
},
/**
* Callback for when a cycle of an animation is complete. Handles when to completely
* finish the animation.
*
* If `repeat` is set to a value, this method is called after each repeat. Repeats are
* handled by ending the current animation and creating a new one with `count` updated.
* Note that this method is *not* called if repeat is set to `indefinite`.
*/
onCompleted: {
value: function () {
var data = this.data;
this.isRunning = false;
if ([FILLS.backwards, FILLS.none].indexOf(data.fill) !== -1) {
this.partialSetAttribute(this.initialValue);
}
if (this.count === 0) {
this.count = undefined;
this.emit('animationend');
return;
}
this.isRunning = false;
this.count--;
this.start();
}
},
start: {
value: function () {
var self = this;
// Postpone animation start until the entity has loaded
if (!this.el.hasLoaded) {
this.el.addEventListener('loaded', function () { self.start(); });
return;
}
if (this.isRunning || !this.el.isPlaying) { return; }
this.tween = this.getTween();
this.isRunning = true;
this.tween.start();
this.emit('animationstart');
},
writable: true
},
stop: {
value: function () {
var tween = this.tween;
if (!tween) { return; }
tween.stop();
this.isRunning = false;
if ([FILLS.backwards, FILLS.none].indexOf(this.data.fill) !== -1) {
this.partialSetAttribute(this.initialValue);
}
this.emit('animationstop');
},
writable: true
},
/**
* Handle alternating directions. Given the current direction, calculate the next one,
* and store the current one.
*
* @param {string} direction
* @returns {string} Direction that the next individual cycle of the animation will go
* towards.
*/
getDirection: {
value: function (direction) {
if (direction === DIRECTIONS.alternate) {
this.prevDirection =
this.prevDirection === DIRECTIONS.normal ? DIRECTIONS.reverse : DIRECTIONS.normal;
return this.prevDirection;
}
if (direction === DIRECTIONS.alternateReverse) {
this.prevDirection =
this.prevDirection === DIRECTIONS.reverse ? DIRECTIONS.normal : DIRECTIONS.reverse;
return this.prevDirection;
}
return direction;
}
},
/**
* Preemptive binding to attach/detach event listeners (see `update`).
*/
bindMethods: {
value: function () {
this.start = bind(this.start, this);
this.stop = bind(this.stop, this);
this.onStateAdded = bind(this.onStateAdded, this);
this.onStateRemoved = bind(this.onStateRemoved, this);
}
},
addEventListeners: {
value: function (evts) {
var el = this.el;
var self = this;
utils.splitString(evts.begin).forEach(function (evt) {
el.addEventListener(evt, self.start);
});
utils.splitString(evts.end).forEach(function (evt) {
el.addEventListener(evt, self.stop);
});
// If "begin" is an event name, wait. If it is not defined, start.
if (evts.begin === '') { el.addEventListener('play', this.start); }
el.addEventListener('pause', this.stop);
el.addEventListener('stateadded', this.onStateAdded);
el.addEventListener('stateremoved', this.onStateRemoved);
}
},
removeEventListeners: {
value: function (evts) {
var el = this.el;
var start = this.start;
var stop = this.stop;
utils.splitString(evts.begin).forEach(function (evt) {
el.removeEventListener(evt, start);
});
utils.splitString(evts.end).forEach(function (evt) {
el.removeEventListener(evt, stop);
});
el.removeEventListener('stateadded', this.onStateAdded);
el.removeEventListener('stateremoved', this.onStateRemoved);
}
},
onStateAdded: {
value: function (evt) {
if (evt.detail.state === this.data.begin) { this.start(); }
},
writable: true
},
onStateRemoved: {
value: function (evt) {
if (evt.detail.state === this.data.begin) { this.stop(); }
},
writable: true
},
/**
* Applies animation data from a mixin element.
* Works the same as component mixins but reimplemented because animations
* aren't components.
*/
handleMixinUpdate: {
value: function () {
var data = {};
var elData;
var mixinData;
var mixinEl;
// Get mixin data.
mixinEl = document.querySelector('#' + this.getAttribute('mixin'));
mixinData = mixinEl ? utils.getElData(mixinEl, DEFAULTS) : {};
elData = utils.getElData(this, DEFAULTS);
utils.extend(data, DEFAULTS, mixinData, elData);
this.data = data;
}
}
})
});
function cloneValue (val) {
return utils.extend({}, val);
}
/**
* Deduces different animation values based on whether we are:
* - animating an inner attribute of a component.
* - animating a coordinate component.
* - animating a boolean.
* - animating a number.
*
* @param {Element} el
* @param {string} attribute - Tells what to animate based on whether it is dot-separated.
* @param {string} dataFrom - Data `from` value.
* @param {string} dataTo - Data `to` value.
* @param currentValue
* @returns {object}
* Object with keys [from, to, partialSetAttribute].
* `from` and `to`
* Objects where key is attribute being animated and value is value.
* `partialSetAttribute`
* Closured-function that tells tween how to update the component.
*/
function getAnimationValues (el, attribute, dataFrom, dataTo, currentValue) {
var attributeSplit = attribute.split('.');
var schema;
var component;
var componentPropName;
var componentName;
var from = {};
var partialSetAttribute;
var to = {};
if (attributeSplit.length === 2) {
if (isColor()) {
getForColorComponent();
} else {
getForComponentAttribute();
}
} else if (dataTo && isCoordinates(dataTo)) {
getForCoordinateComponent();
} else if (['true', 'false'].indexOf(dataTo) !== -1) {
getForBoolean();
} else if (isNaN(dataTo)) {
getForColorComponent();
} else {
getForNumber();
}
return {
from: from,
partialSetAttribute: partialSetAttribute,
to: to
};
/**
* Match the schema type to color
* @return {bool} if the schema is of type color
*/
function isColor () {
var componentName = attributeSplit[0];
var propertyName = attributeSplit[1];
var component = el.components[componentName];
var schema = component && component.schema;
return schema && schema[propertyName] && schema[propertyName].type === 'color';
}
/**
* Animating a component that has multiple attributes (e.g., geometry.width).
*/
function getForComponentAttribute () {
componentName = attributeSplit[0];
componentPropName = attributeSplit[1];
component = el.components[componentName];
if (!component) {
el.setAttribute(componentName, '');
component = el.components[componentName];
}
schema = component.schema;
if (dataFrom === undefined) { // dataFrom can be 0.
from[attribute] = getComponentProperty(el, attribute);
} else {
from[attribute] = dataFrom;
}
from[attribute] = parseProperty(from[attribute], schema[componentPropName]);
to[attribute] = parseProperty(dataTo, schema[componentPropName]);
partialSetAttribute = function (value) {
if (!(attribute in value)) { return; }
el.setAttribute(componentName, componentPropName, value[attribute]);
};
}
/**
* Animating a component that is an XYZ coordinate (e.g., position).
* Will be tweening {x, y, z} all at once.
*/
function getForCoordinateComponent () {
from = dataFrom ? coordinates.parse(dataFrom) : currentValue;
to = coordinates.parse(dataTo);
partialSetAttribute = function (value) {
el.setAttribute(attribute, value);
};
}
/**
* Animation a boolean (e.g., visible).
* Have to convert from boolean to an integer (0 is false, > 0 is true) for tween.
*/
function getForBoolean () {
if (dataFrom === undefined) {
from[attribute] = false;
} else {
from[attribute] = strToBool(dataFrom);
}
from[attribute] = boolToNum(from[attribute]);
to[attribute] = boolToNum(strToBool(dataTo));
partialSetAttribute = function (value) {
el.setAttribute(attribute, !!value[attribute]);
};
}
/**
* Animating a color component
* Will convert a hex value to a THREE.Color
* Then converts to hex for the setAttribute
*/
function getForColorComponent () {
from = new THREE.Color(dataFrom || el.getAttribute(attribute));
to = new THREE.Color(dataTo);
partialSetAttribute = function (value) {
if (attributeSplit.length > 1) {
el.setAttribute(attributeSplit[0], attributeSplit[1], rgbVectorToHex(value));
}
el.setAttribute(attribute, rgbVectorToHex(value));
};
}
/**
* Animating a numbered attribute (e.g., opacity).
*/
function getForNumber () {
if (dataFrom === undefined) { // dataFrom can be 0.
from[attribute] = parseFloat(el.getAttribute(attribute));
} else {
from[attribute] = parseFloat(dataFrom);
}
to[attribute] = parseFloat(dataTo);
partialSetAttribute = function (value) {
el.setAttribute(attribute, value[attribute]);
};
}
}
module.exports.getAnimationValues = getAnimationValues;
/**
* Converts string to bool.
*
* @param {string} str - `true` or `false`.
* @returns {bool}
*/
function strToBool (str) {
if (str === 'true') { return true; }
return false;
}
/**
* Converts boolean to number.
*
* @param {bool}
* @returns {number}
*/
function boolToNum (bool) {
return bool ? 1 : 0;
}
/**
* Converts a number 0-255 to hex
* @param {number} color number 0 - 255
* @returns {string} hex value of number bassed
*/
function componentToHex (color) {
var hex = color.toString(16);
return hex.length === 1 ? '0' + hex : hex;
}
/**
* Clamps a number to 0-1
* Then converts that number to 0-255
* @param {number} color number 0 - 1
* @returns {number} color number 0 - 255
*/
function convertToIntegerColor (color) {
return Math.floor(Math.min(Math.abs(color), 1) * 255);
}
/**
* Converts a rgb object into a hex string
* @param {object} color { r: 1, g: 1, b: 1 }
* @returns {string} hex value #ffffff
*/
function rgbVectorToHex (color) {
return '#' + ['r', 'g', 'b'].map(function (prop) {
return componentToHex(convertToIntegerColor(color[prop]));
}).join('');
}
},{"../constants/animation":115,"../lib/three":173,"../utils/":195,"./a-node":123,"./a-register-element":124,"./schema":133,"@tweenjs/tween.js":1}],119:[function(_dereq_,module,exports){
var ANode = _dereq_('./a-node');
var bind = _dereq_('../utils/bind');
var debug = _dereq_('../utils/debug');
var registerElement = _dereq_('./a-register-element').registerElement;
var THREE = _dereq_('../lib/three');
var fileLoader = new THREE.FileLoader();
var warn = debug('core:a-assets:warn');
/**
* Asset management system. Handles blocking on asset loading.
*/
module.exports = registerElement('a-assets', {
prototype: Object.create(ANode.prototype, {
createdCallback: {
value: function () {
this.isAssets = true;
this.fileLoader = fileLoader;
this.timeout = null;
}
},
attachedCallback: {
value: function () {
var self = this;
var i;
var loaded = [];
var mediaEl;
var mediaEls;
var imgEl;
var imgEls;
var timeout;
if (!this.parentNode.isScene) {
throw new Error(' must be a child of a .');
}
// Wait for s.
imgEls = this.querySelectorAll('img');
for (i = 0; i < imgEls.length; i++) {
imgEl = fixUpMediaElement(imgEls[i]);
loaded.push(new Promise(function (resolve, reject) {
// Set in cache because we won't be needing to call three.js loader if we have.
// a loaded media element.
THREE.Cache.files[imgEls[i].getAttribute('src')] = imgEl;
imgEl.onload = resolve;
imgEl.onerror = reject;
}));
}
// Wait for