├── .gitignore ├── 086-005a.py ├── README.md ├── crosshairs.png ├── dependencies ├── DDSLoader.js ├── MTLLoader.js ├── OBJLoader.js ├── OrbitControls.js ├── compatibility.js ├── jquery-3.1.0.min.js ├── jquery-ui.min.css ├── jquery-ui.min.js ├── jsfeat-min.js ├── jsfeat.js ├── socket.io-1.3.5.js ├── three.js ├── three.min.js └── vr │ ├── VRControls.js │ ├── VREffect.js │ ├── ViveController.js │ └── WebVR.js ├── index.html ├── js ├── controls.js ├── fitness.js ├── main.js ├── optimize.js └── webcam.js ├── node ├── nodeServer.js ├── stepper_angles.py └── test.py ├── projectionAR.png └── style.css /.gitignore: -------------------------------------------------------------------------------- 1 | data/ 2 | node/node_modules 3 | node_modules 4 | 5 | motionPlannerDebugFile.txt 6 | virtualmachineids.vmp 7 | 8 | *.pyc 9 | -------------------------------------------------------------------------------- /086-005a.py: -------------------------------------------------------------------------------- 1 | from pygestalt import nodes 2 | from pygestalt import utilities 3 | from pygestalt.utilities import notice 4 | from pygestalt import functions as functions 5 | from pygestalt import packets as packets 6 | from pygestalt import core 7 | import time 8 | import math 9 | 10 | class virtualNode(nodes.baseStandardGestaltNode): 11 | def init(self, **kwargs): 12 | pass 13 | 14 | def initParameters(self): 15 | #ADC parameters 16 | self.ADCRefVoltage = 5.0 #voltage divider to ref pin is 5V -> 10K -> REF -> 5K -> GND 17 | self.motorSenseResistor = 0.1 #ohms 18 | 19 | #stepping parameters 20 | self.maxSteps = 255 #one byte 21 | self.clockFrequency = 18432000.0 #Hz 22 | self.timeBaseTicks = 921.0 #clock cycles, this works out to around 20KHz 23 | self.timeBasePeriod = self.timeBaseTicks / self.clockFrequency #seconds 24 | self.uStepsPerStep = 1048576 #uSteps 25 | 26 | #axes parameters 27 | self.numberOfAxes = 1 #three axis driver 28 | 29 | def initFunctions(self): 30 | pass 31 | 32 | def initPackets(self): 33 | self.getReferenceVoltageResponsePacket = packets.packet(template = [packets.pInteger('voltage',2)]) 34 | 35 | self.spinRequestPacket = packets.packet(template = [packets.pInteger('majorSteps',1), 36 | packets.pInteger('directions',1), 37 | packets.pInteger('steps', 1), 38 | packets.pInteger('accel',1), 39 | packets.pInteger('accelSteps',1), 40 | packets.pInteger('decelSteps',1), 41 | packets.pInteger('sync', 1)]) #if >0, indicates that move is synchronous 42 | 43 | self.spinStatusPacket = packets.packet(template = [packets.pInteger('statusCode',1), 44 | packets.pInteger('currentKey',1), 45 | packets.pInteger('stepsRemaining',1), 46 | packets.pInteger('readPosition',1), 47 | packets.pInteger('writePosition',1)]) 48 | 49 | self.setVelocityPacket = packets.packet(template = [packets.pInteger('velocity',2)]) 50 | 51 | 52 | def initPorts(self): 53 | #get reference voltage 54 | self.bindPort(port = 20, outboundFunction = self.getReferenceVoltageRequest, inboundPacket = self.getReferenceVoltageResponsePacket) 55 | 56 | #enable drivers 57 | self.bindPort(port = 21, outboundFunction = self.enableRequest) 58 | 59 | #disable drivers 60 | self.bindPort(port = 22, outboundFunction = self.disableRequest) 61 | 62 | #move 63 | self.bindPort(port = 23, outboundFunction = self.spinRequest, outboundPacket = self.spinRequestPacket, 64 | inboundPacket = self.spinStatusPacket) 65 | 66 | #set velocity 67 | self.bindPort(port = 24, outboundFunction = self.setVelocityRequest, outboundPacket = self.setVelocityPacket) 68 | 69 | #spin status 70 | self.bindPort(port = 26, outboundFunction = self.spinStatusRequest, inboundPacket = self.spinStatusPacket) 71 | 72 | #sync 73 | self.bindPort(port = 30, outboundFunction = self.syncRequest) 74 | 75 | 76 | #----- API FUNCTIONS -------------------- 77 | def setMotorCurrent(self, setCurrent): 78 | if setCurrent < 0 or setCurrent > 2.0: 79 | notice(self, "Motor current must be between 0 and 2.0 amps. " + str(setCurrent) + " was requested.") 80 | setCurrent = round(float(setCurrent), 1) 81 | while True: 82 | motorVoltage = self.getReferenceVoltage() 83 | motorCurrent = round(motorVoltage /(8.0*self.motorSenseResistor),2) #amps 84 | if round(motorCurrent,1) > setCurrent: 85 | notice(self, "Motor current: " + str(motorCurrent) + "A / " + "Desired: " + str(setCurrent) + "A. Turn trimmer CW.") 86 | elif round(motorCurrent,1) < setCurrent: 87 | notice(self, "Motor current: " + str(motorCurrent) + "A / " + "Desired: " + str(setCurrent) + "A. Turn trimmer CCW.") 88 | else: 89 | notice(self, "Motor current set to: " + str(motorCurrent) + "A") 90 | break; 91 | time.sleep(0.5) 92 | 93 | 94 | def getReferenceVoltage(self): 95 | ADCReading = self.getReferenceVoltageRequest() 96 | if ADCReading: 97 | return self.ADCRefVoltage * ADCReading / 1024.0 98 | else: 99 | return False 100 | 101 | def enableMotorsRequest(self): 102 | return self.enableRequest() 103 | 104 | def disableMotorsRequest(self): 105 | return self.disableRequest() 106 | 107 | #----- SERVICE ROUTINES ----------------- 108 | class enableRequest(functions.serviceRoutine): 109 | class actionObject(core.actionObject): 110 | def init(self): 111 | self.setPacket({}) 112 | self.commitAndRelease() 113 | self.waitForChannelAccess() 114 | if self.transmitPersistent(): 115 | notice(self.virtualNode, "Stepper motor enabled.") 116 | return True 117 | else: 118 | notice(self.virtualNode, "Stepper motor could not be enabled.") 119 | return False 120 | 121 | class disableRequest(functions.serviceRoutine): 122 | class actionObject(core.actionObject): 123 | def init(self): 124 | self.setPacket({}) 125 | self.commitAndRelease() 126 | self.waitForChannelAccess() 127 | if self.transmitPersistent(): 128 | notice(self.virtualNode, "Stepper motor disabled.") 129 | return True 130 | else: 131 | notice(self.virtualNode, "Stepper motor could not be disabled.") 132 | return False 133 | 134 | class getReferenceVoltageRequest(functions.serviceRoutine): 135 | class actionObject(core.actionObject): 136 | def init(self): 137 | self.setPacket({}) 138 | self.commitAndRelease() 139 | self.waitForChannelAccess() 140 | if self.transmitPersistent(): return self.getPacket()['voltage'] 141 | else: return False 142 | 143 | class spinStatusRequest(functions.serviceRoutine): 144 | class actionObject(core.actionObject): 145 | def init(self): 146 | self.setPacket({}) 147 | self.commitAndRelease() 148 | self.waitForChannelAccess() 149 | if self.transmitPersistent(): 150 | return self.getPacket() 151 | else: 152 | notice(self.virtualNode, 'unable to get status from node.') 153 | 154 | class setVelocityRequest(functions.serviceRoutine): 155 | class actionObject(core.actionObject): 156 | def init(self, velocity): 157 | #velocity is in steps/sec 158 | velocity = int(round((velocity * self.virtualNode.uStepsPerStep * self.virtualNode.timeBasePeriod)/16.0,0)) #convert into 16*uSteps/timebase 159 | print velocity 160 | self.setPacket({'velocity': velocity}) 161 | self.commitAndRelease() 162 | self.waitForChannelAccess() 163 | if self.transmitPersistent(): 164 | return True 165 | else: 166 | notice(self.virtualNode, 'unable to set velocity.') 167 | return False 168 | 169 | class spinRequest(functions.serviceRoutine): 170 | class actionObject(core.actionObject): 171 | def init(self, axesSteps, accelSteps = 0, decelSteps = 0, accelRate = 0, external = False, sync = False, majorSteps = None): 172 | # axesSteps: a list containing the number of steps which each axis of the node should take in synchrony. 173 | # accelSteps: number of virtual major axis steps during which acceleration should occur. 174 | # deccelSteps: number of virtual major axis steps during which deceleration should occur. 175 | # accelRate: accel/decel rate in steps/sec^2 176 | # external: indicates whether the actionObject will be commited to its interface externally. 177 | # When False, the actionObject will self commit and release. 178 | # When True, the actionObject will prepare a packet but will need to be commited and release externally. 179 | # sync: indicates that this actionObject will be synchronized externally and that parameters might change. This will prevent it from spawning. 180 | # majorSteps: this parameter is only called internally when a request calls for too many steps for one actionObject and the actionObject needs to spawn. 181 | if type(axesSteps) != list and type(axesSteps) != tuple: axesSteps = [axesSteps] 182 | self.axesSteps = [int(axis) for axis in axesSteps] #list of steps to take in each axis e.g. [x, y, z]. May be provided as a tuple. 183 | self.accelSteps = accelSteps 184 | self.decelSteps = decelSteps 185 | self.accelRate = accelRate 186 | self.external = external 187 | self.sync = sync 188 | self.actionSequence = False #start out with no action sequence 189 | self.sequenceMajorSteps = None #if this actionObject becomes an actionSequence parent, this will store the list of major axes 190 | 191 | #calculate virtual major axis 192 | if majorSteps: #if provided externally, use that. 193 | self.majorSteps = majorSteps 194 | else: #generate majorSteps from provided axes. 195 | self.majorSteps = max([abs(axis) for axis in self.axesSteps]) 196 | 197 | if self.majorSteps > self.virtualNode.maxSteps: #check if step request exceeds maximum length 198 | if not sync: #no other anticipated recalculations, so go ahead and generate actionSequence 199 | #need to generate an actionSequence with multiple new actionObjects 200 | self.actionSequence = self.actionSequenceGen() 201 | return self.actionSequence 202 | else: #the majorSteps has not yet been synchronized, so cannot generate an action sequence yet. 203 | return self 204 | 205 | else: #step request is executable by this action object. Either initial request met this criteria, or this actionObject was initialized by actionSequence(). 206 | #calculate directions 207 | directions = [1 if axis>0 else 0 for axis in self.axesSteps] 208 | directionMultiplexer = [2**b for b in range(self.virtualNode.numberOfAxes-1, -1, -1)] #generates [2^n, 2^n-1,...,2^0] 209 | self.directionByte = sum(map(lambda x,y: x*y, directionMultiplexer, directions)) #directionByte is each term of directionMultiplexer multiplied by the directions and then summed. 210 | 211 | if external or sync: #actionObject is being managed by an external function and shouldn't auto-commit 212 | return self 213 | else: 214 | #set packet 215 | accelRate = self.tbAccelRate(self.accelRate) #convert accel rate into timebase 216 | self.setPacket({'majorSteps':self.majorSteps, 'directions':self.directionByte, 'steps':abs(self.axesSteps[0]), 'accel':accelRate, 217 | 'accelSteps':accelSteps, 'decelSteps':decelSteps}) 218 | 219 | self.commitAndRelease() 220 | self.waitForChannelAccess() 221 | moveQueued = False 222 | while not moveQueued: 223 | if self.transmitPersistent(): 224 | responsePacket = self.getPacket() 225 | moveQueued = bool(responsePacket['statusCode']) #False if move was not queued 226 | time.sleep(0.02) 227 | else: 228 | notice(self.virtualNode, "got no response to spin request") 229 | return False 230 | return responsePacket 231 | 232 | def syncPush(self): 233 | '''Stores this actionObject's sync tokens to the provided syncToken.''' 234 | if self.sync: 235 | self.sync.push('majorSteps', self.majorSteps) 236 | 237 | def syncPull(self): 238 | '''Actually does the synchronization based on the tokens stored in self.sync.''' 239 | if self.sync: 240 | self.majorSteps = int(max(self.sync.pull('majorSteps'))) 241 | if self.majorSteps > self.virtualNode.maxSteps: 242 | self.actionSequence = self.actionSequenceGen() 243 | return self.actionSequence 244 | else: 245 | return self 246 | 247 | 248 | def channelAccess(self): 249 | '''This gets called when channel access is granted. 250 | To prevent double-transmission, only transmit if external is True.''' 251 | if self.external: 252 | #set packet 253 | accelRate = self.tbAccelRate(self.accelRate) #convert accel rate into timebase 254 | self.setPacket({'majorSteps':self.majorSteps, 'directions':self.directionByte, 'steps':abs(self.axesSteps[0]), 'accel':accelRate, 255 | 'accelSteps':self.accelSteps, 'decelSteps':self.decelSteps, 'sync':int(bool(self.sync))}) 256 | #since this node was instantiated under external control, it did not auto-transmit. 257 | moveQueued = False 258 | while not moveQueued: 259 | if self.transmitPersistent(): 260 | responsePacket = self.getPacket() 261 | print responsePacket 262 | moveQueued = bool(responsePacket['statusCode']) #False if move was not queued, meaning buffer is full 263 | if not moveQueued: time.sleep(0.02) #wait before retransmitting 264 | else: 265 | notice(self.virtualNode, "got no response to spin request") 266 | 267 | 268 | def splitNumber(self, value, segments): 269 | '''Returns a list of integers (length segments) whose sum is value. 270 | 271 | This algorithm should produce similar results to the bresenham algorithm without needing to iterate.''' 272 | #e.g. splitNumber(800, 7) 273 | integers = [int(value/segments) for i in range(segments)] #[114, 114, 114, 114, 114, 114, 114] 274 | remainder = value - sum(integers) #2 275 | distributedRemainder = remainder/float(segments) #0.285... 276 | remainderSequence = [0] + [distributedRemainder*i - round(distributedRemainder*i,0) for i in range(1, 1+segments)] #[0.285, -0.428, -0.142, 0.142, 0.428, -0.285, 0.0] 277 | extraSteps = map(lambda x,y: 1 if x>y else 0, remainderSequence[:-1], remainderSequence[1:]) #[0, 1, 0, 0, 0, 1, 0] passes negative steps 278 | return map(lambda x,y: x+y, integers, extraSteps) #[114, 115, 114, 114, 114, 115, 114] 279 | 280 | def fillFront(self, fillNumber, binSizes): 281 | '''Fills a series of bins whose size are specified by fillList, starting with the first bin.''' 282 | filledBins = [0 for bin in binSizes] 283 | for binIndex, binSize in enumerate(binSizes): 284 | if fillNumber > binSize: 285 | filledBins[binIndex] = binSize 286 | fillNumber -= binSize 287 | else: 288 | filledBins[binIndex] = fillNumber 289 | fillNumber = 0 290 | return filledBins 291 | 292 | def fillBack(self, fillNumber, binSizes): 293 | '''Fills a series of bins whose size are specified by fillList, starting with the last bin.''' 294 | reversedBinSizes = list(binSizes) #makes a copy 295 | reversedBinSizes.reverse() 296 | filledBins = self.fillFront(fillNumber, reversedBinSizes) 297 | filledBins.reverse() 298 | return filledBins 299 | 300 | 301 | def actionSequenceGen(self): #need to generate an action sequence to accommodate the request 302 | '''Returns an actionSequence which contains a sequential set of actionObjects.''' 303 | segments = int(math.ceil(self.majorSteps/float(self.virtualNode.maxSteps))) #number of actionObjects needed to address requested number of steps 304 | axesSteps = zip(*[self.splitNumber(axisSteps, segments) for axisSteps in self.axesSteps]) #a list of axesSteps lists which are divided into the necessary number of segments 305 | majorSteps = self.splitNumber(self.majorSteps, segments) #a list of majorSteps which are divided into the necessary number of segments 306 | self.sequenceMajorSteps = majorSteps #store at the actionObject level for when parameters need to be updated later. 307 | accelSteps = self.fillFront(self.accelSteps, majorSteps) #a list of accelSteps which fills bins of maximum size majorSteps starting from the front 308 | decelSteps = self.fillBack(self.decelSteps, majorSteps) #a list of decelSteps which fills bins of maximum size majorSteps starting from the back 309 | accelRate = [self.accelRate for segment in range(segments)] 310 | external = [self.external for segment in range(segments)] 311 | sync = [self.sync for segment in range(segments)] 312 | 313 | return self.__actionSequence__(axesSteps, accelSteps, decelSteps, accelRate, external, sync, majorSteps) 314 | 315 | def update(self, accelSteps = 0, decelSteps = 0, accelRate = 0): 316 | '''Updates the acceleration and deceleration parameters for the spin actionObject.''' 317 | 318 | if self.actionSequence: #this actionObject is the parent of an actionSequence. Update the whole sequence 319 | accelSteps = self.fillFront(accelSteps, self.sequenceMajorSteps) 320 | decelSteps = self.fillBack(decelSteps, self.sequenceMajorSteps) 321 | accelRates = [accelRate for majorSteps in self.sequenceMajorSteps] 322 | 323 | for actionObject, args in zip(self.actionSequence.actionObjects, zip(accelSteps, decelSteps, accelRates)): 324 | actionObject.update(*args) 325 | else: 326 | self.accelSteps = accelSteps 327 | self.decelSteps = decelSteps 328 | self.accelRate = accelRate 329 | 330 | 331 | def tbAccelRate(self, accelRate): 332 | '''Converts acceleration from steps/sec^2 to uSteps/timeBase^2.''' 333 | return int(round(accelRate * self.virtualNode.uStepsPerStep * self.virtualNode.timeBasePeriod * self.virtualNode.timeBasePeriod,0)) #uSteps/timePeriod^2 334 | 335 | class syncRequest(functions.serviceRoutine): 336 | class actionObject(core.actionObject): 337 | def init(self): 338 | self.mode = 'multicast' 339 | def channelAccess(self): 340 | self.transmit() 341 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Projection Mapping Alignment 2 | 3 | 4 | 5 | 6 | A WebGL app that breaks out many of the threejs camera parameters to fine tune projection mapping alignment to a physical model. Sends G-code to a rotary stage (via a node server) to rotate the physical and virtual models together. I began to also incorporate some closed loop optimization into the alignment process via a webcam - though that work was never quite finished. Part of a collaboration with Jeff Koons studio. Live demo here. -------------------------------------------------------------------------------- /crosshairs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amandaghassaei/ProjectionMappingAlignment/1e617c63ff7527b509eee8a75d2ec52e67865cae/crosshairs.png -------------------------------------------------------------------------------- /dependencies/DDSLoader.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @author mrdoob / http://mrdoob.com/ 3 | */ 4 | 5 | THREE.DDSLoader = function () { 6 | 7 | this._parser = THREE.DDSLoader.parse; 8 | 9 | }; 10 | 11 | THREE.DDSLoader.prototype = Object.create( THREE.CompressedTextureLoader.prototype ); 12 | THREE.DDSLoader.prototype.constructor = THREE.DDSLoader; 13 | 14 | THREE.DDSLoader.parse = function ( buffer, loadMipmaps ) { 15 | 16 | var dds = { mipmaps: [], width: 0, height: 0, format: null, mipmapCount: 1 }; 17 | 18 | // Adapted from @toji's DDS utils 19 | // https://github.com/toji/webgl-texture-utils/blob/master/texture-util/dds.js 20 | 21 | // All values and structures referenced from: 22 | // http://msdn.microsoft.com/en-us/library/bb943991.aspx/ 23 | 24 | var DDS_MAGIC = 0x20534444; 25 | 26 | var DDSD_CAPS = 0x1, 27 | DDSD_HEIGHT = 0x2, 28 | DDSD_WIDTH = 0x4, 29 | DDSD_PITCH = 0x8, 30 | DDSD_PIXELFORMAT = 0x1000, 31 | DDSD_MIPMAPCOUNT = 0x20000, 32 | DDSD_LINEARSIZE = 0x80000, 33 | DDSD_DEPTH = 0x800000; 34 | 35 | var DDSCAPS_COMPLEX = 0x8, 36 | DDSCAPS_MIPMAP = 0x400000, 37 | DDSCAPS_TEXTURE = 0x1000; 38 | 39 | var DDSCAPS2_CUBEMAP = 0x200, 40 | DDSCAPS2_CUBEMAP_POSITIVEX = 0x400, 41 | DDSCAPS2_CUBEMAP_NEGATIVEX = 0x800, 42 | DDSCAPS2_CUBEMAP_POSITIVEY = 0x1000, 43 | DDSCAPS2_CUBEMAP_NEGATIVEY = 0x2000, 44 | DDSCAPS2_CUBEMAP_POSITIVEZ = 0x4000, 45 | DDSCAPS2_CUBEMAP_NEGATIVEZ = 0x8000, 46 | DDSCAPS2_VOLUME = 0x200000; 47 | 48 | var DDPF_ALPHAPIXELS = 0x1, 49 | DDPF_ALPHA = 0x2, 50 | DDPF_FOURCC = 0x4, 51 | DDPF_RGB = 0x40, 52 | DDPF_YUV = 0x200, 53 | DDPF_LUMINANCE = 0x20000; 54 | 55 | function fourCCToInt32( value ) { 56 | 57 | return value.charCodeAt( 0 ) + 58 | ( value.charCodeAt( 1 ) << 8 ) + 59 | ( value.charCodeAt( 2 ) << 16 ) + 60 | ( value.charCodeAt( 3 ) << 24 ); 61 | 62 | } 63 | 64 | function int32ToFourCC( value ) { 65 | 66 | return String.fromCharCode( 67 | value & 0xff, 68 | ( value >> 8 ) & 0xff, 69 | ( value >> 16 ) & 0xff, 70 | ( value >> 24 ) & 0xff 71 | ); 72 | 73 | } 74 | 75 | function loadARGBMip( buffer, dataOffset, width, height ) { 76 | 77 | var dataLength = width * height * 4; 78 | var srcBuffer = new Uint8Array( buffer, dataOffset, dataLength ); 79 | var byteArray = new Uint8Array( dataLength ); 80 | var dst = 0; 81 | var src = 0; 82 | for ( var y = 0; y < height; y ++ ) { 83 | 84 | for ( var x = 0; x < width; x ++ ) { 85 | 86 | var b = srcBuffer[ src ]; src ++; 87 | var g = srcBuffer[ src ]; src ++; 88 | var r = srcBuffer[ src ]; src ++; 89 | var a = srcBuffer[ src ]; src ++; 90 | byteArray[ dst ] = r; dst ++; //r 91 | byteArray[ dst ] = g; dst ++; //g 92 | byteArray[ dst ] = b; dst ++; //b 93 | byteArray[ dst ] = a; dst ++; //a 94 | 95 | } 96 | 97 | } 98 | return byteArray; 99 | 100 | } 101 | 102 | var FOURCC_DXT1 = fourCCToInt32( "DXT1" ); 103 | var FOURCC_DXT3 = fourCCToInt32( "DXT3" ); 104 | var FOURCC_DXT5 = fourCCToInt32( "DXT5" ); 105 | var FOURCC_ETC1 = fourCCToInt32( "ETC1" ); 106 | 107 | var headerLengthInt = 31; // The header length in 32 bit ints 108 | 109 | // Offsets into the header array 110 | 111 | var off_magic = 0; 112 | 113 | var off_size = 1; 114 | var off_flags = 2; 115 | var off_height = 3; 116 | var off_width = 4; 117 | 118 | var off_mipmapCount = 7; 119 | 120 | var off_pfFlags = 20; 121 | var off_pfFourCC = 21; 122 | var off_RGBBitCount = 22; 123 | var off_RBitMask = 23; 124 | var off_GBitMask = 24; 125 | var off_BBitMask = 25; 126 | var off_ABitMask = 26; 127 | 128 | var off_caps = 27; 129 | var off_caps2 = 28; 130 | var off_caps3 = 29; 131 | var off_caps4 = 30; 132 | 133 | // Parse header 134 | 135 | var header = new Int32Array( buffer, 0, headerLengthInt ); 136 | 137 | if ( header[ off_magic ] !== DDS_MAGIC ) { 138 | 139 | console.error( 'THREE.DDSLoader.parse: Invalid magic number in DDS header.' ); 140 | return dds; 141 | 142 | } 143 | 144 | if ( ! header[ off_pfFlags ] & DDPF_FOURCC ) { 145 | 146 | console.error( 'THREE.DDSLoader.parse: Unsupported format, must contain a FourCC code.' ); 147 | return dds; 148 | 149 | } 150 | 151 | var blockBytes; 152 | 153 | var fourCC = header[ off_pfFourCC ]; 154 | 155 | var isRGBAUncompressed = false; 156 | 157 | switch ( fourCC ) { 158 | 159 | case FOURCC_DXT1: 160 | 161 | blockBytes = 8; 162 | dds.format = THREE.RGB_S3TC_DXT1_Format; 163 | break; 164 | 165 | case FOURCC_DXT3: 166 | 167 | blockBytes = 16; 168 | dds.format = THREE.RGBA_S3TC_DXT3_Format; 169 | break; 170 | 171 | case FOURCC_DXT5: 172 | 173 | blockBytes = 16; 174 | dds.format = THREE.RGBA_S3TC_DXT5_Format; 175 | break; 176 | 177 | case FOURCC_ETC1: 178 | 179 | blockBytes = 8; 180 | dds.format = THREE.RGB_ETC1_Format; 181 | break; 182 | 183 | default: 184 | 185 | if ( header[ off_RGBBitCount ] === 32 186 | && header[ off_RBitMask ] & 0xff0000 187 | && header[ off_GBitMask ] & 0xff00 188 | && header[ off_BBitMask ] & 0xff 189 | && header[ off_ABitMask ] & 0xff000000 ) { 190 | 191 | isRGBAUncompressed = true; 192 | blockBytes = 64; 193 | dds.format = THREE.RGBAFormat; 194 | 195 | } else { 196 | 197 | console.error( 'THREE.DDSLoader.parse: Unsupported FourCC code ', int32ToFourCC( fourCC ) ); 198 | return dds; 199 | 200 | } 201 | } 202 | 203 | dds.mipmapCount = 1; 204 | 205 | if ( header[ off_flags ] & DDSD_MIPMAPCOUNT && loadMipmaps !== false ) { 206 | 207 | dds.mipmapCount = Math.max( 1, header[ off_mipmapCount ] ); 208 | 209 | } 210 | 211 | var caps2 = header[ off_caps2 ]; 212 | dds.isCubemap = caps2 & DDSCAPS2_CUBEMAP ? true : false; 213 | if ( dds.isCubemap && ( 214 | ! ( caps2 & DDSCAPS2_CUBEMAP_POSITIVEX ) || 215 | ! ( caps2 & DDSCAPS2_CUBEMAP_NEGATIVEX ) || 216 | ! ( caps2 & DDSCAPS2_CUBEMAP_POSITIVEY ) || 217 | ! ( caps2 & DDSCAPS2_CUBEMAP_NEGATIVEY ) || 218 | ! ( caps2 & DDSCAPS2_CUBEMAP_POSITIVEZ ) || 219 | ! ( caps2 & DDSCAPS2_CUBEMAP_NEGATIVEZ ) 220 | ) ) { 221 | 222 | console.error( 'THREE.DDSLoader.parse: Incomplete cubemap faces' ); 223 | return dds; 224 | 225 | } 226 | 227 | dds.width = header[ off_width ]; 228 | dds.height = header[ off_height ]; 229 | 230 | var dataOffset = header[ off_size ] + 4; 231 | 232 | // Extract mipmaps buffers 233 | 234 | var faces = dds.isCubemap ? 6 : 1; 235 | 236 | for ( var face = 0; face < faces; face ++ ) { 237 | 238 | var width = dds.width; 239 | var height = dds.height; 240 | 241 | for ( var i = 0; i < dds.mipmapCount; i ++ ) { 242 | 243 | if ( isRGBAUncompressed ) { 244 | 245 | var byteArray = loadARGBMip( buffer, dataOffset, width, height ); 246 | var dataLength = byteArray.length; 247 | 248 | } else { 249 | 250 | var dataLength = Math.max( 4, width ) / 4 * Math.max( 4, height ) / 4 * blockBytes; 251 | var byteArray = new Uint8Array( buffer, dataOffset, dataLength ); 252 | 253 | } 254 | 255 | var mipmap = { "data": byteArray, "width": width, "height": height }; 256 | dds.mipmaps.push( mipmap ); 257 | 258 | dataOffset += dataLength; 259 | 260 | width = Math.max( width >> 1, 1 ); 261 | height = Math.max( height >> 1, 1 ); 262 | 263 | } 264 | 265 | } 266 | 267 | return dds; 268 | 269 | }; 270 | -------------------------------------------------------------------------------- /dependencies/MTLLoader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Loads a Wavefront .mtl file specifying materials 3 | * 4 | * @author angelxuanchang 5 | */ 6 | 7 | THREE.MTLLoader = function( manager ) { 8 | 9 | this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager; 10 | 11 | }; 12 | 13 | THREE.MTLLoader.prototype = { 14 | 15 | constructor: THREE.MTLLoader, 16 | 17 | load: function ( url, onLoad, onProgress, onError ) { 18 | 19 | var scope = this; 20 | 21 | var loader = new THREE.XHRLoader( this.manager ); 22 | loader.setPath( this.path ); 23 | loader.load( url, function ( text ) { 24 | 25 | onLoad( scope.parse( text ) ); 26 | 27 | }, onProgress, onError ); 28 | 29 | }, 30 | 31 | setPath: function ( value ) { 32 | 33 | this.path = value; 34 | 35 | }, 36 | 37 | setBaseUrl: function( value ) { 38 | 39 | // TODO: Merge with setPath()? Or rename to setTexturePath? 40 | 41 | this.baseUrl = value; 42 | 43 | }, 44 | 45 | setCrossOrigin: function ( value ) { 46 | 47 | this.crossOrigin = value; 48 | 49 | }, 50 | 51 | setMaterialOptions: function ( value ) { 52 | 53 | this.materialOptions = value; 54 | 55 | }, 56 | 57 | /** 58 | * Parses loaded MTL file 59 | * @param text - Content of MTL file 60 | * @return {THREE.MTLLoader.MaterialCreator} 61 | */ 62 | parse: function ( text ) { 63 | 64 | var lines = text.split( "\n" ); 65 | var info = {}; 66 | var delimiter_pattern = /\s+/; 67 | var materialsInfo = {}; 68 | 69 | for ( var i = 0; i < lines.length; i ++ ) { 70 | 71 | var line = lines[ i ]; 72 | line = line.trim(); 73 | 74 | if ( line.length === 0 || line.charAt( 0 ) === '#' ) { 75 | 76 | // Blank line or comment ignore 77 | continue; 78 | 79 | } 80 | 81 | var pos = line.indexOf( ' ' ); 82 | 83 | var key = ( pos >= 0 ) ? line.substring( 0, pos ) : line; 84 | key = key.toLowerCase(); 85 | 86 | var value = ( pos >= 0 ) ? line.substring( pos + 1 ) : ""; 87 | value = value.trim(); 88 | 89 | if ( key === "newmtl" ) { 90 | 91 | // New material 92 | 93 | info = { name: value }; 94 | materialsInfo[ value ] = info; 95 | 96 | } else if ( info ) { 97 | 98 | if ( key === "ka" || key === "kd" || key === "ks" ) { 99 | 100 | var ss = value.split( delimiter_pattern, 3 ); 101 | info[ key ] = [ parseFloat( ss[ 0 ] ), parseFloat( ss[ 1 ] ), parseFloat( ss[ 2 ] ) ]; 102 | 103 | } else { 104 | 105 | info[ key ] = value; 106 | 107 | } 108 | 109 | } 110 | 111 | } 112 | 113 | var materialCreator = new THREE.MTLLoader.MaterialCreator( this.baseUrl, this.materialOptions ); 114 | materialCreator.setCrossOrigin( this.crossOrigin ); 115 | materialCreator.setManager( this.manager ); 116 | materialCreator.setMaterials( materialsInfo ); 117 | return materialCreator; 118 | 119 | } 120 | 121 | }; 122 | 123 | /** 124 | * Create a new THREE-MTLLoader.MaterialCreator 125 | * @param baseUrl - Url relative to which textures are loaded 126 | * @param options - Set of options on how to construct the materials 127 | * side: Which side to apply the material 128 | * THREE.FrontSide (default), THREE.BackSide, THREE.DoubleSide 129 | * wrap: What type of wrapping to apply for textures 130 | * THREE.RepeatWrapping (default), THREE.ClampToEdgeWrapping, THREE.MirroredRepeatWrapping 131 | * normalizeRGB: RGBs need to be normalized to 0-1 from 0-255 132 | * Default: false, assumed to be already normalized 133 | * ignoreZeroRGBs: Ignore values of RGBs (Ka,Kd,Ks) that are all 0's 134 | * Default: false 135 | * @constructor 136 | */ 137 | 138 | THREE.MTLLoader.MaterialCreator = function( baseUrl, options ) { 139 | 140 | this.baseUrl = baseUrl; 141 | this.options = options; 142 | this.materialsInfo = {}; 143 | this.materials = {}; 144 | this.materialsArray = []; 145 | this.nameLookup = {}; 146 | 147 | this.side = ( this.options && this.options.side ) ? this.options.side : THREE.FrontSide; 148 | this.wrap = ( this.options && this.options.wrap ) ? this.options.wrap : THREE.RepeatWrapping; 149 | 150 | }; 151 | 152 | THREE.MTLLoader.MaterialCreator.prototype = { 153 | 154 | constructor: THREE.MTLLoader.MaterialCreator, 155 | 156 | setCrossOrigin: function ( value ) { 157 | 158 | this.crossOrigin = value; 159 | 160 | }, 161 | 162 | setManager: function ( value ) { 163 | 164 | this.manager = value; 165 | 166 | }, 167 | 168 | setMaterials: function( materialsInfo ) { 169 | 170 | this.materialsInfo = this.convert( materialsInfo ); 171 | this.materials = {}; 172 | this.materialsArray = []; 173 | this.nameLookup = {}; 174 | 175 | }, 176 | 177 | convert: function( materialsInfo ) { 178 | 179 | if ( ! this.options ) return materialsInfo; 180 | 181 | var converted = {}; 182 | 183 | for ( var mn in materialsInfo ) { 184 | 185 | // Convert materials info into normalized form based on options 186 | 187 | var mat = materialsInfo[ mn ]; 188 | 189 | var covmat = {}; 190 | 191 | converted[ mn ] = covmat; 192 | 193 | for ( var prop in mat ) { 194 | 195 | var save = true; 196 | var value = mat[ prop ]; 197 | var lprop = prop.toLowerCase(); 198 | 199 | switch ( lprop ) { 200 | 201 | case 'kd': 202 | case 'ka': 203 | case 'ks': 204 | 205 | // Diffuse color (color under white light) using RGB values 206 | 207 | if ( this.options && this.options.normalizeRGB ) { 208 | 209 | value = [ value[ 0 ] / 255, value[ 1 ] / 255, value[ 2 ] / 255 ]; 210 | 211 | } 212 | 213 | if ( this.options && this.options.ignoreZeroRGBs ) { 214 | 215 | if ( value[ 0 ] === 0 && value[ 1 ] === 0 && value[ 1 ] === 0 ) { 216 | 217 | // ignore 218 | 219 | save = false; 220 | 221 | } 222 | 223 | } 224 | 225 | break; 226 | 227 | default: 228 | 229 | break; 230 | } 231 | 232 | if ( save ) { 233 | 234 | covmat[ lprop ] = value; 235 | 236 | } 237 | 238 | } 239 | 240 | } 241 | 242 | return converted; 243 | 244 | }, 245 | 246 | preload: function () { 247 | 248 | for ( var mn in this.materialsInfo ) { 249 | 250 | this.create( mn ); 251 | 252 | } 253 | 254 | }, 255 | 256 | getIndex: function( materialName ) { 257 | 258 | return this.nameLookup[ materialName ]; 259 | 260 | }, 261 | 262 | getAsArray: function() { 263 | 264 | var index = 0; 265 | 266 | for ( var mn in this.materialsInfo ) { 267 | 268 | this.materialsArray[ index ] = this.create( mn ); 269 | this.nameLookup[ mn ] = index; 270 | index ++; 271 | 272 | } 273 | 274 | return this.materialsArray; 275 | 276 | }, 277 | 278 | create: function ( materialName ) { 279 | 280 | if ( this.materials[ materialName ] === undefined ) { 281 | 282 | this.createMaterial_( materialName ); 283 | 284 | } 285 | 286 | return this.materials[ materialName ]; 287 | 288 | }, 289 | 290 | createMaterial_: function ( materialName ) { 291 | 292 | // Create material 293 | 294 | var mat = this.materialsInfo[ materialName ]; 295 | var params = { 296 | 297 | name: materialName, 298 | side: this.side 299 | 300 | }; 301 | 302 | for ( var prop in mat ) { 303 | 304 | var value = mat[ prop ]; 305 | 306 | if ( value === '' ) continue; 307 | 308 | switch ( prop.toLowerCase() ) { 309 | 310 | // Ns is material specular exponent 311 | 312 | case 'kd': 313 | 314 | // Diffuse color (color under white light) using RGB values 315 | 316 | params[ 'color' ] = new THREE.Color().fromArray( value ); 317 | 318 | break; 319 | 320 | case 'ks': 321 | 322 | // Specular color (color when light is reflected from shiny surface) using RGB values 323 | params[ 'specular' ] = new THREE.Color().fromArray( value ); 324 | 325 | break; 326 | 327 | case 'map_kd': 328 | 329 | // Diffuse texture map 330 | 331 | params[ 'map' ] = this.loadTexture( this.baseUrl + value ); 332 | params[ 'map' ].wrapS = this.wrap; 333 | params[ 'map' ].wrapT = this.wrap; 334 | 335 | break; 336 | 337 | case 'ns': 338 | 339 | // The specular exponent (defines the focus of the specular highlight) 340 | // A high exponent results in a tight, concentrated highlight. Ns values normally range from 0 to 1000. 341 | 342 | params[ 'shininess' ] = parseFloat( value ); 343 | 344 | break; 345 | 346 | case 'd': 347 | 348 | if ( value < 1 ) { 349 | 350 | params[ 'opacity' ] = value; 351 | params[ 'transparent' ] = true; 352 | 353 | } 354 | 355 | break; 356 | 357 | case 'Tr': 358 | 359 | if ( value > 0 ) { 360 | 361 | params[ 'opacity' ] = 1 - value; 362 | params[ 'transparent' ] = true; 363 | 364 | } 365 | 366 | break; 367 | 368 | case 'map_bump': 369 | case 'bump': 370 | 371 | // Bump texture map 372 | 373 | if ( params[ 'bumpMap' ] ) break; // Avoid loading twice. 374 | 375 | params[ 'bumpMap' ] = this.loadTexture( this.baseUrl + value ); 376 | params[ 'bumpMap' ].wrapS = this.wrap; 377 | params[ 'bumpMap' ].wrapT = this.wrap; 378 | 379 | break; 380 | 381 | default: 382 | break; 383 | 384 | } 385 | 386 | } 387 | 388 | this.materials[ materialName ] = new THREE.MeshPhongMaterial( params ); 389 | return this.materials[ materialName ]; 390 | 391 | }, 392 | 393 | 394 | loadTexture: function ( url, mapping, onLoad, onProgress, onError ) { 395 | 396 | var texture; 397 | var loader = THREE.Loader.Handlers.get( url ); 398 | if (loader === null) loader = new THREE.DDSLoader(); 399 | var manager = ( this.manager !== undefined ) ? this.manager : THREE.DefaultLoadingManager; 400 | 401 | if ( loader === null ) { 402 | 403 | loader = new THREE.TextureLoader( manager ); 404 | 405 | } 406 | 407 | if ( loader.setCrossOrigin ) loader.setCrossOrigin( this.crossOrigin ); 408 | texture = loader.load( url, onLoad, onProgress, onError ); 409 | 410 | if ( mapping !== undefined ) texture.mapping = mapping; 411 | 412 | return texture; 413 | 414 | } 415 | 416 | }; 417 | 418 | THREE.EventDispatcher.prototype.apply( THREE.MTLLoader.prototype ); 419 | -------------------------------------------------------------------------------- /dependencies/OBJLoader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author mrdoob / http://mrdoob.com/ 3 | */ 4 | 5 | THREE.OBJLoader = function ( manager ) { 6 | 7 | this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager; 8 | 9 | this.materials = null; 10 | 11 | this.regexp = { 12 | // v float float float 13 | vertex_pattern : /^v\s+([\d|\.|\+|\-|e|E]+)\s+([\d|\.|\+|\-|e|E]+)\s+([\d|\.|\+|\-|e|E]+)/, 14 | // vn float float float 15 | normal_pattern : /^vn\s+([\d|\.|\+|\-|e|E]+)\s+([\d|\.|\+|\-|e|E]+)\s+([\d|\.|\+|\-|e|E]+)/, 16 | // vt float float 17 | uv_pattern : /^vt\s+([\d|\.|\+|\-|e|E]+)\s+([\d|\.|\+|\-|e|E]+)/, 18 | // f vertex vertex vertex 19 | face_vertex : /^f\s+(-?\d+)\s+(-?\d+)\s+(-?\d+)(?:\s+(-?\d+))?/, 20 | // f vertex/uv vertex/uv vertex/uv 21 | face_vertex_uv : /^f\s+(-?\d+)\/(-?\d+)\s+(-?\d+)\/(-?\d+)\s+(-?\d+)\/(-?\d+)(?:\s+(-?\d+)\/(-?\d+))?/, 22 | // f vertex/uv/normal vertex/uv/normal vertex/uv/normal 23 | face_vertex_uv_normal : /^f\s+(-?\d+)\/(-?\d+)\/(-?\d+)\s+(-?\d+)\/(-?\d+)\/(-?\d+)\s+(-?\d+)\/(-?\d+)\/(-?\d+)(?:\s+(-?\d+)\/(-?\d+)\/(-?\d+))?/, 24 | // f vertex//normal vertex//normal vertex//normal 25 | face_vertex_normal : /^f\s+(-?\d+)\/\/(-?\d+)\s+(-?\d+)\/\/(-?\d+)\s+(-?\d+)\/\/(-?\d+)(?:\s+(-?\d+)\/\/(-?\d+))?/, 26 | // o object_name | g group_name 27 | object_pattern : /^[og]\s*(.+)?/, 28 | // s boolean 29 | smoothing_pattern : /^s\s+(\d+|on|off)/, 30 | // mtllib file_reference 31 | material_library_pattern : /^mtllib /, 32 | // usemtl material_name 33 | material_use_pattern : /^usemtl / 34 | }; 35 | 36 | }; 37 | 38 | THREE.OBJLoader.prototype = { 39 | 40 | constructor: THREE.OBJLoader, 41 | 42 | load: function ( url, onLoad, onProgress, onError ) { 43 | 44 | var scope = this; 45 | 46 | var loader = new THREE.XHRLoader( scope.manager ); 47 | loader.setPath( this.path ); 48 | loader.load( url, function ( text ) { 49 | 50 | onLoad( scope.parse( text ) ); 51 | 52 | }, onProgress, onError ); 53 | 54 | }, 55 | 56 | setPath: function ( value ) { 57 | 58 | this.path = value; 59 | 60 | }, 61 | 62 | setMaterials: function ( materials ) { 63 | 64 | this.materials = materials; 65 | 66 | }, 67 | 68 | _createParserState : function () { 69 | 70 | var state = { 71 | objects : [], 72 | object : {}, 73 | 74 | vertices : [], 75 | normals : [], 76 | uvs : [], 77 | 78 | materialLibraries : [], 79 | 80 | startObject: function ( name, fromDeclaration ) { 81 | 82 | // If the current object (initial from reset) is not from a g/o declaration in the parsed 83 | // file. We need to use it for the first parsed g/o to keep things in sync. 84 | if ( this.object && this.object.fromDeclaration === false ) { 85 | 86 | this.object.name = name; 87 | this.object.fromDeclaration = ( fromDeclaration !== false ); 88 | return; 89 | 90 | } 91 | 92 | var previousMaterial = ( this.object && typeof this.object.currentMaterial === 'function' ? this.object.currentMaterial() : undefined ); 93 | 94 | if ( this.object && typeof this.object._finalize === 'function' ) { 95 | 96 | this.object._finalize( true ); 97 | 98 | } 99 | 100 | this.object = { 101 | name : name || '', 102 | fromDeclaration : ( fromDeclaration !== false ), 103 | 104 | geometry : { 105 | vertices : [], 106 | normals : [], 107 | uvs : [] 108 | }, 109 | materials : [], 110 | smooth : true, 111 | 112 | startMaterial : function( name, libraries ) { 113 | 114 | var previous = this._finalize( false ); 115 | 116 | // New usemtl declaration overwrites an inherited material, except if faces were declared 117 | // after the material, then it must be preserved for proper MultiMaterial continuation. 118 | if ( previous && ( previous.inherited || previous.groupCount <= 0 ) ) { 119 | 120 | this.materials.splice( previous.index, 1 ); 121 | 122 | } 123 | 124 | var material = { 125 | index : this.materials.length, 126 | name : name || '', 127 | mtllib : ( Array.isArray( libraries ) && libraries.length > 0 ? libraries[ libraries.length - 1 ] : '' ), 128 | smooth : ( previous !== undefined ? previous.smooth : this.smooth ), 129 | groupStart : ( previous !== undefined ? previous.groupEnd : 0 ), 130 | groupEnd : -1, 131 | groupCount : -1, 132 | inherited : false, 133 | 134 | clone : function( index ) { 135 | var cloned = { 136 | index : ( typeof index === 'number' ? index : this.index ), 137 | name : this.name, 138 | mtllib : this.mtllib, 139 | smooth : this.smooth, 140 | groupStart : 0, 141 | groupEnd : -1, 142 | groupCount : -1, 143 | inherited : false 144 | }; 145 | cloned.clone = this.clone.bind(cloned); 146 | return cloned; 147 | } 148 | }; 149 | 150 | this.materials.push( material ); 151 | 152 | return material; 153 | 154 | }, 155 | 156 | currentMaterial : function() { 157 | 158 | if ( this.materials.length > 0 ) { 159 | return this.materials[ this.materials.length - 1 ]; 160 | } 161 | 162 | return undefined; 163 | 164 | }, 165 | 166 | _finalize : function( end ) { 167 | 168 | var lastMultiMaterial = this.currentMaterial(); 169 | if ( lastMultiMaterial && lastMultiMaterial.groupEnd === -1 ) { 170 | 171 | lastMultiMaterial.groupEnd = this.geometry.vertices.length / 3; 172 | lastMultiMaterial.groupCount = lastMultiMaterial.groupEnd - lastMultiMaterial.groupStart; 173 | lastMultiMaterial.inherited = false; 174 | 175 | } 176 | 177 | // Ignore objects tail materials if no face declarations followed them before a new o/g started. 178 | if ( end && this.materials.length > 1 ) { 179 | 180 | for ( var mi = this.materials.length - 1; mi >= 0; mi-- ) { 181 | if ( this.materials[mi].groupCount <= 0 ) { 182 | this.materials.splice( mi, 1 ); 183 | } 184 | } 185 | 186 | } 187 | 188 | // Guarantee at least one empty material, this makes the creation later more straight forward. 189 | if ( end && this.materials.length === 0 ) { 190 | 191 | this.materials.push({ 192 | name : '', 193 | smooth : this.smooth 194 | }); 195 | 196 | } 197 | 198 | return lastMultiMaterial; 199 | 200 | } 201 | }; 202 | 203 | // Inherit previous objects material. 204 | // Spec tells us that a declared material must be set to all objects until a new material is declared. 205 | // If a usemtl declaration is encountered while this new object is being parsed, it will 206 | // overwrite the inherited material. Exception being that there was already face declarations 207 | // to the inherited material, then it will be preserved for proper MultiMaterial continuation. 208 | 209 | if ( previousMaterial && previousMaterial.name && typeof previousMaterial.clone === "function" ) { 210 | 211 | var declared = previousMaterial.clone( 0 ); 212 | declared.inherited = true; 213 | this.object.materials.push( declared ); 214 | 215 | } 216 | 217 | this.objects.push( this.object ); 218 | 219 | }, 220 | 221 | finalize : function() { 222 | 223 | if ( this.object && typeof this.object._finalize === 'function' ) { 224 | 225 | this.object._finalize( true ); 226 | 227 | } 228 | 229 | }, 230 | 231 | parseVertexIndex: function ( value, len ) { 232 | 233 | var index = parseInt( value, 10 ); 234 | return ( index >= 0 ? index - 1 : index + len / 3 ) * 3; 235 | 236 | }, 237 | 238 | parseNormalIndex: function ( value, len ) { 239 | 240 | var index = parseInt( value, 10 ); 241 | return ( index >= 0 ? index - 1 : index + len / 3 ) * 3; 242 | 243 | }, 244 | 245 | parseUVIndex: function ( value, len ) { 246 | 247 | var index = parseInt( value, 10 ); 248 | return ( index >= 0 ? index - 1 : index + len / 2 ) * 2; 249 | 250 | }, 251 | 252 | addVertex: function ( a, b, c ) { 253 | 254 | var src = this.vertices; 255 | var dst = this.object.geometry.vertices; 256 | 257 | dst.push( src[ a + 0 ] ); 258 | dst.push( src[ a + 1 ] ); 259 | dst.push( src[ a + 2 ] ); 260 | dst.push( src[ b + 0 ] ); 261 | dst.push( src[ b + 1 ] ); 262 | dst.push( src[ b + 2 ] ); 263 | dst.push( src[ c + 0 ] ); 264 | dst.push( src[ c + 1 ] ); 265 | dst.push( src[ c + 2 ] ); 266 | 267 | }, 268 | 269 | addVertexLine: function ( a ) { 270 | 271 | var src = this.vertices; 272 | var dst = this.object.geometry.vertices; 273 | 274 | dst.push( src[ a + 0 ] ); 275 | dst.push( src[ a + 1 ] ); 276 | dst.push( src[ a + 2 ] ); 277 | 278 | }, 279 | 280 | addNormal : function ( a, b, c ) { 281 | 282 | var src = this.normals; 283 | var dst = this.object.geometry.normals; 284 | 285 | dst.push( src[ a + 0 ] ); 286 | dst.push( src[ a + 1 ] ); 287 | dst.push( src[ a + 2 ] ); 288 | dst.push( src[ b + 0 ] ); 289 | dst.push( src[ b + 1 ] ); 290 | dst.push( src[ b + 2 ] ); 291 | dst.push( src[ c + 0 ] ); 292 | dst.push( src[ c + 1 ] ); 293 | dst.push( src[ c + 2 ] ); 294 | 295 | }, 296 | 297 | addUV: function ( a, b, c ) { 298 | 299 | var src = this.uvs; 300 | var dst = this.object.geometry.uvs; 301 | 302 | dst.push( src[ a + 0 ] ); 303 | dst.push( src[ a + 1 ] ); 304 | dst.push( src[ b + 0 ] ); 305 | dst.push( src[ b + 1 ] ); 306 | dst.push( src[ c + 0 ] ); 307 | dst.push( src[ c + 1 ] ); 308 | 309 | }, 310 | 311 | addUVLine: function ( a ) { 312 | 313 | var src = this.uvs; 314 | var dst = this.object.geometry.uvs; 315 | 316 | dst.push( src[ a + 0 ] ); 317 | dst.push( src[ a + 1 ] ); 318 | 319 | }, 320 | 321 | addFace: function ( a, b, c, d, ua, ub, uc, ud, na, nb, nc, nd ) { 322 | 323 | var vLen = this.vertices.length; 324 | 325 | var ia = this.parseVertexIndex( a, vLen ); 326 | var ib = this.parseVertexIndex( b, vLen ); 327 | var ic = this.parseVertexIndex( c, vLen ); 328 | var id; 329 | 330 | if ( d === undefined ) { 331 | 332 | this.addVertex( ia, ib, ic ); 333 | 334 | } else { 335 | 336 | id = this.parseVertexIndex( d, vLen ); 337 | 338 | this.addVertex( ia, ib, id ); 339 | this.addVertex( ib, ic, id ); 340 | 341 | } 342 | 343 | if ( ua !== undefined ) { 344 | 345 | var uvLen = this.uvs.length; 346 | 347 | ia = this.parseUVIndex( ua, uvLen ); 348 | ib = this.parseUVIndex( ub, uvLen ); 349 | ic = this.parseUVIndex( uc, uvLen ); 350 | 351 | if ( d === undefined ) { 352 | 353 | this.addUV( ia, ib, ic ); 354 | 355 | } else { 356 | 357 | id = this.parseUVIndex( ud, uvLen ); 358 | 359 | this.addUV( ia, ib, id ); 360 | this.addUV( ib, ic, id ); 361 | 362 | } 363 | 364 | } 365 | 366 | if ( na !== undefined ) { 367 | 368 | // Normals are many times the same. If so, skip function call and parseInt. 369 | var nLen = this.normals.length; 370 | ia = this.parseNormalIndex( na, nLen ); 371 | 372 | ib = na === nb ? ia : this.parseNormalIndex( nb, nLen ); 373 | ic = na === nc ? ia : this.parseNormalIndex( nc, nLen ); 374 | 375 | if ( d === undefined ) { 376 | 377 | this.addNormal( ia, ib, ic ); 378 | 379 | } else { 380 | 381 | id = this.parseNormalIndex( nd, nLen ); 382 | 383 | this.addNormal( ia, ib, id ); 384 | this.addNormal( ib, ic, id ); 385 | 386 | } 387 | 388 | } 389 | 390 | }, 391 | 392 | addLineGeometry: function ( vertices, uvs ) { 393 | 394 | this.object.geometry.type = 'Line'; 395 | 396 | var vLen = this.vertices.length; 397 | var uvLen = this.uvs.length; 398 | 399 | for ( var vi = 0, l = vertices.length; vi < l; vi ++ ) { 400 | 401 | this.addVertexLine( this.parseVertexIndex( vertices[ vi ], vLen ) ); 402 | 403 | } 404 | 405 | for ( var uvi = 0, l = uvs.length; uvi < l; uvi ++ ) { 406 | 407 | this.addUVLine( this.parseUVIndex( uvs[ uvi ], uvLen ) ); 408 | 409 | } 410 | 411 | } 412 | 413 | }; 414 | 415 | state.startObject( '', false ); 416 | 417 | return state; 418 | 419 | }, 420 | 421 | parse: function ( text ) { 422 | 423 | console.time( 'OBJLoader' ); 424 | 425 | var state = this._createParserState(); 426 | 427 | if ( text.indexOf( '\r\n' ) !== - 1 ) { 428 | 429 | // This is faster than String.split with regex that splits on both 430 | text = text.replace( /\r\n/g, '\n' ); 431 | 432 | } 433 | 434 | if ( text.indexOf( '\\\n' ) !== - 1) { 435 | 436 | // join lines separated by a line continuation character (\) 437 | text = text.replace( /\\\n/g, '' ); 438 | 439 | } 440 | 441 | var lines = text.split( '\n' ); 442 | var line = '', lineFirstChar = '', lineSecondChar = ''; 443 | var lineLength = 0; 444 | var result = []; 445 | 446 | // Faster to just trim left side of the line. Use if available. 447 | var trimLeft = ( typeof ''.trimLeft === 'function' ); 448 | 449 | for ( var i = 0, l = lines.length; i < l; i ++ ) { 450 | 451 | line = lines[ i ]; 452 | 453 | line = trimLeft ? line.trimLeft() : line.trim(); 454 | 455 | lineLength = line.length; 456 | 457 | if ( lineLength === 0 ) continue; 458 | 459 | lineFirstChar = line.charAt( 0 ); 460 | 461 | // @todo invoke passed in handler if any 462 | if ( lineFirstChar === '#' ) continue; 463 | 464 | if ( lineFirstChar === 'v' ) { 465 | 466 | lineSecondChar = line.charAt( 1 ); 467 | 468 | if ( lineSecondChar === ' ' && ( result = this.regexp.vertex_pattern.exec( line ) ) !== null ) { 469 | 470 | // 0 1 2 3 471 | // ["v 1.0 2.0 3.0", "1.0", "2.0", "3.0"] 472 | 473 | state.vertices.push( 474 | parseFloat( result[ 1 ] ), 475 | parseFloat( result[ 2 ] ), 476 | parseFloat( result[ 3 ] ) 477 | ); 478 | 479 | } else if ( lineSecondChar === 'n' && ( result = this.regexp.normal_pattern.exec( line ) ) !== null ) { 480 | 481 | // 0 1 2 3 482 | // ["vn 1.0 2.0 3.0", "1.0", "2.0", "3.0"] 483 | 484 | state.normals.push( 485 | parseFloat( result[ 1 ] ), 486 | parseFloat( result[ 2 ] ), 487 | parseFloat( result[ 3 ] ) 488 | ); 489 | 490 | } else if ( lineSecondChar === 't' && ( result = this.regexp.uv_pattern.exec( line ) ) !== null ) { 491 | 492 | // 0 1 2 493 | // ["vt 0.1 0.2", "0.1", "0.2"] 494 | 495 | state.uvs.push( 496 | parseFloat( result[ 1 ] ), 497 | parseFloat( result[ 2 ] ) 498 | ); 499 | 500 | } else { 501 | 502 | throw new Error( "Unexpected vertex/normal/uv line: '" + line + "'" ); 503 | 504 | } 505 | 506 | } else if ( lineFirstChar === "f" ) { 507 | 508 | if ( ( result = this.regexp.face_vertex_uv_normal.exec( line ) ) !== null ) { 509 | 510 | // f vertex/uv/normal vertex/uv/normal vertex/uv/normal 511 | // 0 1 2 3 4 5 6 7 8 9 10 11 12 512 | // ["f 1/1/1 2/2/2 3/3/3", "1", "1", "1", "2", "2", "2", "3", "3", "3", undefined, undefined, undefined] 513 | 514 | state.addFace( 515 | result[ 1 ], result[ 4 ], result[ 7 ], result[ 10 ], 516 | result[ 2 ], result[ 5 ], result[ 8 ], result[ 11 ], 517 | result[ 3 ], result[ 6 ], result[ 9 ], result[ 12 ] 518 | ); 519 | 520 | } else if ( ( result = this.regexp.face_vertex_uv.exec( line ) ) !== null ) { 521 | 522 | // f vertex/uv vertex/uv vertex/uv 523 | // 0 1 2 3 4 5 6 7 8 524 | // ["f 1/1 2/2 3/3", "1", "1", "2", "2", "3", "3", undefined, undefined] 525 | 526 | state.addFace( 527 | result[ 1 ], result[ 3 ], result[ 5 ], result[ 7 ], 528 | result[ 2 ], result[ 4 ], result[ 6 ], result[ 8 ] 529 | ); 530 | 531 | } else if ( ( result = this.regexp.face_vertex_normal.exec( line ) ) !== null ) { 532 | 533 | // f vertex//normal vertex//normal vertex//normal 534 | // 0 1 2 3 4 5 6 7 8 535 | // ["f 1//1 2//2 3//3", "1", "1", "2", "2", "3", "3", undefined, undefined] 536 | 537 | state.addFace( 538 | result[ 1 ], result[ 3 ], result[ 5 ], result[ 7 ], 539 | undefined, undefined, undefined, undefined, 540 | result[ 2 ], result[ 4 ], result[ 6 ], result[ 8 ] 541 | ); 542 | 543 | } else if ( ( result = this.regexp.face_vertex.exec( line ) ) !== null ) { 544 | 545 | // f vertex vertex vertex 546 | // 0 1 2 3 4 547 | // ["f 1 2 3", "1", "2", "3", undefined] 548 | 549 | state.addFace( 550 | result[ 1 ], result[ 2 ], result[ 3 ], result[ 4 ] 551 | ); 552 | 553 | } else { 554 | 555 | throw new Error( "Unexpected face line: '" + line + "'" ); 556 | 557 | } 558 | 559 | } else if ( lineFirstChar === "l" ) { 560 | 561 | var lineParts = line.substring( 1 ).trim().split( " " ); 562 | var lineVertices = [], lineUVs = []; 563 | 564 | if ( line.indexOf( "/" ) === - 1 ) { 565 | 566 | lineVertices = lineParts; 567 | 568 | } else { 569 | 570 | for ( var li = 0, llen = lineParts.length; li < llen; li ++ ) { 571 | 572 | var parts = lineParts[ li ].split( "/" ); 573 | 574 | if ( parts[ 0 ] !== "" ) lineVertices.push( parts[ 0 ] ); 575 | if ( parts[ 1 ] !== "" ) lineUVs.push( parts[ 1 ] ); 576 | 577 | } 578 | 579 | } 580 | state.addLineGeometry( lineVertices, lineUVs ); 581 | 582 | } else if ( ( result = this.regexp.object_pattern.exec( line ) ) !== null ) { 583 | 584 | // o object_name 585 | // or 586 | // g group_name 587 | 588 | // WORKAROUND: https://bugs.chromium.org/p/v8/issues/detail?id=2869 589 | // var name = result[ 0 ].substr( 1 ).trim(); 590 | var name = ( " " + result[ 0 ].substr( 1 ).trim() ).substr( 1 ); 591 | 592 | state.startObject( name ); 593 | 594 | } else if ( this.regexp.material_use_pattern.test( line ) ) { 595 | 596 | // material 597 | 598 | state.object.startMaterial( line.substring( 7 ).trim(), state.materialLibraries ); 599 | 600 | } else if ( this.regexp.material_library_pattern.test( line ) ) { 601 | 602 | // mtl file 603 | 604 | state.materialLibraries.push( line.substring( 7 ).trim() ); 605 | 606 | } else if ( ( result = this.regexp.smoothing_pattern.exec( line ) ) !== null ) { 607 | 608 | // smooth shading 609 | 610 | // @todo Handle files that have varying smooth values for a set of faces inside one geometry, 611 | // but does not define a usemtl for each face set. 612 | // This should be detected and a dummy material created (later MultiMaterial and geometry groups). 613 | // This requires some care to not create extra material on each smooth value for "normal" obj files. 614 | // where explicit usemtl defines geometry groups. 615 | // Example asset: examples/models/obj/cerberus/Cerberus.obj 616 | 617 | var value = result[ 1 ].trim().toLowerCase(); 618 | state.object.smooth = ( value === '1' || value === 'on' ); 619 | 620 | var material = state.object.currentMaterial(); 621 | if ( material ) { 622 | 623 | material.smooth = state.object.smooth; 624 | 625 | } 626 | 627 | } else { 628 | 629 | // Handle null terminated files without exception 630 | if ( line === '\0' ) continue; 631 | 632 | throw new Error( "Unexpected line: '" + line + "'" ); 633 | 634 | } 635 | 636 | } 637 | 638 | state.finalize(); 639 | 640 | var container = new THREE.Group(); 641 | container.materialLibraries = [].concat( state.materialLibraries ); 642 | 643 | for ( var i = 0, l = state.objects.length; i < l; i ++ ) { 644 | 645 | var object = state.objects[ i ]; 646 | var geometry = object.geometry; 647 | var materials = object.materials; 648 | var isLine = ( geometry.type === 'Line' ); 649 | 650 | // Skip o/g line declarations that did not follow with any faces 651 | if ( geometry.vertices.length === 0 ) continue; 652 | 653 | var buffergeometry = new THREE.BufferGeometry(); 654 | 655 | buffergeometry.addAttribute( 'position', new THREE.BufferAttribute( new Float32Array( geometry.vertices ), 3 ) ); 656 | 657 | if ( geometry.normals.length > 0 ) { 658 | 659 | buffergeometry.addAttribute( 'normal', new THREE.BufferAttribute( new Float32Array( geometry.normals ), 3 ) ); 660 | 661 | } else { 662 | 663 | buffergeometry.computeVertexNormals(); 664 | 665 | } 666 | 667 | if ( geometry.uvs.length > 0 ) { 668 | 669 | buffergeometry.addAttribute( 'uv', new THREE.BufferAttribute( new Float32Array( geometry.uvs ), 2 ) ); 670 | 671 | } 672 | 673 | // Create materials 674 | 675 | var createdMaterials = []; 676 | 677 | for ( var mi = 0, miLen = materials.length; mi < miLen ; mi++ ) { 678 | 679 | var sourceMaterial = materials[mi]; 680 | var material = undefined; 681 | 682 | if ( this.materials !== null ) { 683 | 684 | material = this.materials.create( sourceMaterial.name ); 685 | 686 | // mtl etc. loaders probably can't create line materials correctly, copy properties to a line material. 687 | if ( isLine && material && ! ( material instanceof THREE.LineBasicMaterial ) ) { 688 | 689 | var materialLine = new THREE.LineBasicMaterial(); 690 | materialLine.copy( material ); 691 | material = materialLine; 692 | 693 | } 694 | 695 | } 696 | 697 | if ( ! material ) { 698 | 699 | material = ( ! isLine ? new THREE.MeshPhongMaterial() : new THREE.LineBasicMaterial() ); 700 | material.name = sourceMaterial.name; 701 | 702 | } 703 | 704 | material.shading = sourceMaterial.smooth ? THREE.SmoothShading : THREE.FlatShading; 705 | 706 | createdMaterials.push(material); 707 | 708 | } 709 | 710 | // Create mesh 711 | 712 | var mesh; 713 | 714 | if ( createdMaterials.length > 1 ) { 715 | 716 | for ( var mi = 0, miLen = materials.length; mi < miLen ; mi++ ) { 717 | 718 | var sourceMaterial = materials[mi]; 719 | buffergeometry.addGroup( sourceMaterial.groupStart, sourceMaterial.groupCount, mi ); 720 | 721 | } 722 | 723 | var multiMaterial = new THREE.MultiMaterial( createdMaterials ); 724 | mesh = ( ! isLine ? new THREE.Mesh( buffergeometry, multiMaterial ) : new THREE.LineSegments( buffergeometry, multiMaterial ) ); 725 | 726 | } else { 727 | 728 | mesh = ( ! isLine ? new THREE.Mesh( buffergeometry, createdMaterials[ 0 ] ) : new THREE.LineSegments( buffergeometry, createdMaterials[ 0 ] ) ); 729 | } 730 | 731 | mesh.name = object.name; 732 | 733 | container.add( mesh ); 734 | 735 | } 736 | 737 | console.timeEnd( 'OBJLoader' ); 738 | 739 | return container; 740 | 741 | } 742 | 743 | }; 744 | -------------------------------------------------------------------------------- /dependencies/OrbitControls.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author qiao / https://github.com/qiao 3 | * @author mrdoob / http://mrdoob.com 4 | * @author alteredq / http://alteredqualia.com/ 5 | * @author WestLangley / http://github.com/WestLangley 6 | * @author erich666 / http://erichaines.com 7 | */ 8 | 9 | // This set of controls performs orbiting, dollying (zooming), and panning. 10 | // Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default). 11 | // 12 | // Orbit - left mouse / touch: one finger move 13 | // Zoom - middle mouse, or mousewheel / touch: two finger spread or squish 14 | // Pan - right mouse, or arrow keys / touch: three finter swipe 15 | 16 | THREE.OrbitControls = function ( object, domElement ) { 17 | 18 | this.object = object; 19 | 20 | this.domElement = ( domElement !== undefined ) ? domElement : document; 21 | 22 | // Set to false to disable this control 23 | this.enabled = true; 24 | 25 | // "target" sets the location of focus, where the object orbits around 26 | this.target = new THREE.Vector3(); 27 | 28 | // How far you can dolly in and out ( PerspectiveCamera only ) 29 | this.minDistance = 0; 30 | this.maxDistance = Infinity; 31 | 32 | // How far you can zoom in and out ( OrthographicCamera only ) 33 | this.minZoom = 0; 34 | this.maxZoom = Infinity; 35 | 36 | // How far you can orbit vertically, upper and lower limits. 37 | // Range is 0 to Math.PI radians. 38 | this.minPolarAngle = 0; // radians 39 | this.maxPolarAngle = Math.PI; // radians 40 | 41 | // How far you can orbit horizontally, upper and lower limits. 42 | // If set, must be a sub-interval of the interval [ - Math.PI, Math.PI ]. 43 | this.minAzimuthAngle = - Infinity; // radians 44 | this.maxAzimuthAngle = Infinity; // radians 45 | 46 | // Set to true to enable damping (inertia) 47 | // If damping is enabled, you must call controls.update() in your animation loop 48 | this.enableDamping = false; 49 | this.dampingFactor = 0.25; 50 | 51 | // This option actually enables dollying in and out; left as "zoom" for backwards compatibility. 52 | // Set to false to disable zooming 53 | this.enableZoom = true; 54 | this.zoomSpeed = 1.0; 55 | 56 | // Set to false to disable rotating 57 | this.enableRotate = true; 58 | this.rotateSpeed = 1.0; 59 | 60 | // Set to false to disable panning 61 | this.enablePan = true; 62 | this.keyPanSpeed = 7.0; // pixels moved per arrow key push 63 | 64 | // Set to true to automatically rotate around the target 65 | // If auto-rotate is enabled, you must call controls.update() in your animation loop 66 | this.autoRotate = false; 67 | this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60 68 | 69 | // Set to false to disable use of the keys 70 | this.enableKeys = true; 71 | 72 | // The four arrow keys 73 | this.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40 }; 74 | 75 | // Mouse buttons 76 | this.mouseButtons = { ORBIT: THREE.MOUSE.LEFT, ZOOM: THREE.MOUSE.MIDDLE, PAN: THREE.MOUSE.RIGHT }; 77 | 78 | // for reset 79 | this.target0 = this.target.clone(); 80 | this.position0 = this.object.position.clone(); 81 | this.zoom0 = this.object.zoom; 82 | 83 | this.setTarget = function(target){//three vector 84 | this.target = target; 85 | this.target0 = this.target.clone(); 86 | }; 87 | 88 | // 89 | // public methods 90 | // 91 | 92 | this.getPolarAngle = function () { 93 | 94 | return spherical.phi; 95 | 96 | }; 97 | 98 | this.getAzimuthalAngle = function () { 99 | 100 | return spherical.theta; 101 | 102 | }; 103 | 104 | this.reset = function () { 105 | 106 | scope.target.copy( scope.target0 ); 107 | scope.object.position.copy( scope.position0 ); 108 | scope.object.zoom = scope.zoom0; 109 | 110 | //scope.object.updateProjectionMatrix(); 111 | //scope.dispatchEvent( changeEvent ); 112 | 113 | scope.update(); 114 | 115 | state = STATE.NONE; 116 | 117 | }; 118 | 119 | // this method is exposed, but perhaps it would be better if we can make it private... 120 | this.update = function() { 121 | 122 | var offset = new THREE.Vector3(); 123 | 124 | // so camera.up is the orbit axis 125 | var quat = new THREE.Quaternion().setFromUnitVectors( object.up, new THREE.Vector3( 0, 1, 0 ) ); 126 | var quatInverse = quat.clone().inverse(); 127 | 128 | var lastPosition = new THREE.Vector3(); 129 | var lastQuaternion = new THREE.Quaternion(); 130 | 131 | return function update () { 132 | 133 | var position = scope.object.position; 134 | 135 | offset.copy( position ).sub( scope.target ); 136 | 137 | // rotate offset to "y-axis-is-up" space 138 | offset.applyQuaternion( quat ); 139 | 140 | // angle from z-axis around y-axis 141 | spherical.setFromVector3( offset ); 142 | 143 | if ( scope.autoRotate && state === STATE.NONE ) { 144 | 145 | rotateLeft( getAutoRotationAngle() ); 146 | 147 | } 148 | 149 | spherical.theta += sphericalDelta.theta; 150 | spherical.phi += sphericalDelta.phi; 151 | 152 | // restrict theta to be between desired limits 153 | spherical.theta = Math.max( scope.minAzimuthAngle, Math.min( scope.maxAzimuthAngle, spherical.theta ) ); 154 | 155 | // restrict phi to be between desired limits 156 | spherical.phi = Math.max( scope.minPolarAngle, Math.min( scope.maxPolarAngle, spherical.phi ) ); 157 | 158 | spherical.makeSafe(); 159 | 160 | 161 | spherical.radius *= scale; 162 | 163 | // restrict radius to be between desired limits 164 | spherical.radius = Math.max( scope.minDistance, Math.min( scope.maxDistance, spherical.radius ) ); 165 | 166 | // move target to panned location 167 | scope.target.add( panOffset ); 168 | 169 | offset.setFromSpherical( spherical ); 170 | 171 | // rotate offset back to "camera-up-vector-is-up" space 172 | offset.applyQuaternion( quatInverse ); 173 | 174 | position.copy( scope.target ).add( offset ); 175 | 176 | scope.object.lookAt( scope.target ); 177 | 178 | if ( scope.enableDamping === true ) { 179 | 180 | sphericalDelta.theta *= ( 1 - scope.dampingFactor ); 181 | sphericalDelta.phi *= ( 1 - scope.dampingFactor ); 182 | 183 | } else { 184 | 185 | sphericalDelta.set( 0, 0, 0 ); 186 | 187 | } 188 | 189 | scale = 1; 190 | panOffset.set( 0, 0, 0 ); 191 | 192 | // update condition is: 193 | // min(camera displacement, camera rotation in radians)^2 > EPS 194 | // using small-angle approximation cos(x/2) = 1 - x^2 / 8 195 | 196 | if ( zoomChanged || 197 | lastPosition.distanceToSquared( scope.object.position ) > EPS || 198 | 8 * ( 1 - lastQuaternion.dot( scope.object.quaternion ) ) > EPS ) { 199 | 200 | scope.dispatchEvent( changeEvent ); 201 | 202 | lastPosition.copy( scope.object.position ); 203 | lastQuaternion.copy( scope.object.quaternion ); 204 | zoomChanged = false; 205 | 206 | return true; 207 | 208 | } 209 | 210 | return false; 211 | 212 | }; 213 | 214 | }(); 215 | 216 | this.dispose = function() { 217 | 218 | scope.domElement.removeEventListener( 'contextmenu', onContextMenu, false ); 219 | scope.domElement.removeEventListener( 'mousedown', onMouseDown, false ); 220 | scope.domElement.removeEventListener( 'wheel', onMouseWheel, false ); 221 | 222 | scope.domElement.removeEventListener( 'touchstart', onTouchStart, false ); 223 | scope.domElement.removeEventListener( 'touchend', onTouchEnd, false ); 224 | scope.domElement.removeEventListener( 'touchmove', onTouchMove, false ); 225 | 226 | document.removeEventListener( 'mousemove', onMouseMove, false ); 227 | document.removeEventListener( 'mouseup', onMouseUp, false ); 228 | 229 | window.removeEventListener( 'keydown', onKeyDown, false ); 230 | 231 | //scope.dispatchEvent( { type: 'dispose' } ); // should this be added here? 232 | 233 | }; 234 | 235 | // 236 | // internals 237 | // 238 | 239 | var scope = this; 240 | 241 | var changeEvent = { type: 'change' }; 242 | var startEvent = { type: 'start' }; 243 | var endEvent = { type: 'end' }; 244 | 245 | var STATE = { NONE : - 1, ROTATE : 0, DOLLY : 1, PAN : 2, TOUCH_ROTATE : 3, TOUCH_DOLLY : 4, TOUCH_PAN : 5 }; 246 | 247 | var state = STATE.NONE; 248 | 249 | var EPS = 0.000001; 250 | 251 | // current position in spherical coordinates 252 | var spherical = new THREE.Spherical(); 253 | var sphericalDelta = new THREE.Spherical(); 254 | 255 | var scale = 1; 256 | var panOffset = new THREE.Vector3(); 257 | var zoomChanged = false; 258 | 259 | var rotateStart = new THREE.Vector2(); 260 | var rotateEnd = new THREE.Vector2(); 261 | var rotateDelta = new THREE.Vector2(); 262 | 263 | var panStart = new THREE.Vector2(); 264 | var panEnd = new THREE.Vector2(); 265 | var panDelta = new THREE.Vector2(); 266 | 267 | var dollyStart = new THREE.Vector2(); 268 | var dollyEnd = new THREE.Vector2(); 269 | var dollyDelta = new THREE.Vector2(); 270 | 271 | function getAutoRotationAngle() { 272 | 273 | return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed; 274 | 275 | } 276 | 277 | function getZoomScale() { 278 | 279 | return Math.pow( 0.95, scope.zoomSpeed ); 280 | 281 | } 282 | 283 | function rotateLeft( angle ) { 284 | 285 | sphericalDelta.theta -= angle; 286 | 287 | } 288 | 289 | function rotateUp( angle ) { 290 | 291 | sphericalDelta.phi -= angle; 292 | 293 | } 294 | 295 | var panLeft = function() { 296 | 297 | var v = new THREE.Vector3(); 298 | 299 | return function panLeft( distance, objectMatrix ) { 300 | 301 | v.setFromMatrixColumn( objectMatrix, 0 ); // get X column of objectMatrix 302 | v.multiplyScalar( - distance ); 303 | 304 | panOffset.add( v ); 305 | 306 | }; 307 | 308 | }(); 309 | 310 | var panUp = function() { 311 | 312 | var v = new THREE.Vector3(); 313 | 314 | return function panUp( distance, objectMatrix ) { 315 | 316 | v.setFromMatrixColumn( objectMatrix, 1 ); // get Y column of objectMatrix 317 | v.multiplyScalar( distance ); 318 | 319 | panOffset.add( v ); 320 | 321 | }; 322 | 323 | }(); 324 | 325 | // deltaX and deltaY are in pixels; right and down are positive 326 | var pan = function() { 327 | 328 | var offset = new THREE.Vector3(); 329 | 330 | return function pan ( deltaX, deltaY ) { 331 | 332 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 333 | 334 | if ( scope.object instanceof THREE.PerspectiveCamera ) { 335 | 336 | // perspective 337 | var position = scope.object.position; 338 | offset.copy( position ).sub( scope.target ); 339 | var targetDistance = offset.length(); 340 | 341 | // half of the fov is center to top of screen 342 | targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 ); 343 | 344 | // we actually don't use screenWidth, since perspective camera is fixed to screen height 345 | panLeft( 2 * deltaX * targetDistance / element.clientHeight, scope.object.matrix ); 346 | panUp( 2 * deltaY * targetDistance / element.clientHeight, scope.object.matrix ); 347 | 348 | } else if ( scope.object instanceof THREE.OrthographicCamera ) { 349 | 350 | // orthographic 351 | panLeft( deltaX * ( scope.object.right - scope.object.left ) / scope.object.zoom / element.clientWidth, scope.object.matrix ); 352 | panUp( deltaY * ( scope.object.top - scope.object.bottom ) / scope.object.zoom / element.clientHeight, scope.object.matrix ); 353 | 354 | } else { 355 | 356 | // camera neither orthographic nor perspective 357 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' ); 358 | scope.enablePan = false; 359 | 360 | } 361 | 362 | }; 363 | 364 | }(); 365 | 366 | function dollyIn( dollyScale ) { 367 | 368 | if ( scope.object instanceof THREE.PerspectiveCamera ) { 369 | 370 | scale /= dollyScale; 371 | 372 | } else if ( scope.object instanceof THREE.OrthographicCamera ) { 373 | 374 | scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom * dollyScale ) ); 375 | scope.object.updateProjectionMatrix(); 376 | zoomChanged = true; 377 | 378 | } else { 379 | 380 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); 381 | scope.enableZoom = false; 382 | 383 | } 384 | 385 | } 386 | 387 | function dollyOut( dollyScale ) { 388 | 389 | if ( scope.object instanceof THREE.PerspectiveCamera ) { 390 | 391 | scale *= dollyScale; 392 | 393 | } else if ( scope.object instanceof THREE.OrthographicCamera ) { 394 | 395 | scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / dollyScale ) ); 396 | scope.object.updateProjectionMatrix(); 397 | zoomChanged = true; 398 | 399 | } else { 400 | 401 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); 402 | scope.enableZoom = false; 403 | 404 | } 405 | 406 | } 407 | 408 | // 409 | // event callbacks - update the object state 410 | // 411 | 412 | function handleMouseDownRotate( event ) { 413 | 414 | //console.log( 'handleMouseDownRotate' ); 415 | 416 | rotateStart.set( event.clientX, event.clientY ); 417 | 418 | } 419 | 420 | function handleMouseDownDolly( event ) { 421 | 422 | //console.log( 'handleMouseDownDolly' ); 423 | 424 | dollyStart.set( event.clientX, event.clientY ); 425 | 426 | } 427 | 428 | function handleMouseDownPan( event ) { 429 | 430 | //console.log( 'handleMouseDownPan' ); 431 | 432 | panStart.set( event.clientX, event.clientY ); 433 | 434 | } 435 | 436 | function handleMouseMoveRotate( event ) { 437 | 438 | //console.log( 'handleMouseMoveRotate' ); 439 | 440 | rotateEnd.set( event.clientX, event.clientY ); 441 | rotateDelta.subVectors( rotateEnd, rotateStart ); 442 | 443 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 444 | 445 | // rotating across whole screen goes 360 degrees around 446 | rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed ); 447 | 448 | // rotating up and down along whole screen attempts to go 360, but limited to 180 449 | rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed ); 450 | 451 | rotateStart.copy( rotateEnd ); 452 | 453 | scope.update(); 454 | 455 | } 456 | 457 | function handleMouseMoveDolly( event ) { 458 | 459 | //console.log( 'handleMouseMoveDolly' ); 460 | 461 | dollyEnd.set( event.clientX, event.clientY ); 462 | 463 | dollyDelta.subVectors( dollyEnd, dollyStart ); 464 | 465 | if ( dollyDelta.y > 0 ) { 466 | 467 | dollyIn( getZoomScale() ); 468 | 469 | } else if ( dollyDelta.y < 0 ) { 470 | 471 | dollyOut( getZoomScale() ); 472 | 473 | } 474 | 475 | dollyStart.copy( dollyEnd ); 476 | 477 | scope.update(); 478 | 479 | } 480 | 481 | function handleMouseMovePan( event ) { 482 | 483 | //console.log( 'handleMouseMovePan' ); 484 | 485 | panEnd.set( event.clientX, event.clientY ); 486 | 487 | panDelta.subVectors( panEnd, panStart ); 488 | 489 | pan( panDelta.x, panDelta.y ); 490 | 491 | panStart.copy( panEnd ); 492 | 493 | scope.update(); 494 | 495 | } 496 | 497 | function handleMouseUp( event ) { 498 | 499 | //console.log( 'handleMouseUp' ); 500 | 501 | } 502 | 503 | function handleMouseWheel( event ) { 504 | 505 | //console.log( 'handleMouseWheel' ); 506 | 507 | if ( event.deltaY < 0 ) { 508 | 509 | dollyOut( getZoomScale() ); 510 | 511 | } else if ( event.deltaY > 0 ) { 512 | 513 | dollyIn( getZoomScale() ); 514 | 515 | } 516 | 517 | scope.update(); 518 | 519 | } 520 | 521 | function handleKeyDown( event ) { 522 | 523 | //console.log( 'handleKeyDown' ); 524 | 525 | switch ( event.keyCode ) { 526 | 527 | case scope.keys.UP: 528 | pan( 0, scope.keyPanSpeed ); 529 | scope.update(); 530 | break; 531 | 532 | case scope.keys.BOTTOM: 533 | pan( 0, - scope.keyPanSpeed ); 534 | scope.update(); 535 | break; 536 | 537 | case scope.keys.LEFT: 538 | pan( scope.keyPanSpeed, 0 ); 539 | scope.update(); 540 | break; 541 | 542 | case scope.keys.RIGHT: 543 | pan( - scope.keyPanSpeed, 0 ); 544 | scope.update(); 545 | break; 546 | 547 | } 548 | 549 | } 550 | 551 | function handleTouchStartRotate( event ) { 552 | 553 | //console.log( 'handleTouchStartRotate' ); 554 | 555 | rotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 556 | 557 | } 558 | 559 | function handleTouchStartDolly( event ) { 560 | 561 | //console.log( 'handleTouchStartDolly' ); 562 | 563 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 564 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 565 | 566 | var distance = Math.sqrt( dx * dx + dy * dy ); 567 | 568 | dollyStart.set( 0, distance ); 569 | 570 | } 571 | 572 | function handleTouchStartPan( event ) { 573 | 574 | //console.log( 'handleTouchStartPan' ); 575 | 576 | panStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 577 | 578 | } 579 | 580 | function handleTouchMoveRotate( event ) { 581 | 582 | //console.log( 'handleTouchMoveRotate' ); 583 | 584 | rotateEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 585 | rotateDelta.subVectors( rotateEnd, rotateStart ); 586 | 587 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 588 | 589 | // rotating across whole screen goes 360 degrees around 590 | rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed ); 591 | 592 | // rotating up and down along whole screen attempts to go 360, but limited to 180 593 | rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed ); 594 | 595 | rotateStart.copy( rotateEnd ); 596 | 597 | scope.update(); 598 | 599 | } 600 | 601 | function handleTouchMoveDolly( event ) { 602 | 603 | //console.log( 'handleTouchMoveDolly' ); 604 | 605 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 606 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 607 | 608 | var distance = Math.sqrt( dx * dx + dy * dy ); 609 | 610 | dollyEnd.set( 0, distance ); 611 | 612 | dollyDelta.subVectors( dollyEnd, dollyStart ); 613 | 614 | if ( dollyDelta.y > 0 ) { 615 | 616 | dollyOut( getZoomScale() ); 617 | 618 | } else if ( dollyDelta.y < 0 ) { 619 | 620 | dollyIn( getZoomScale() ); 621 | 622 | } 623 | 624 | dollyStart.copy( dollyEnd ); 625 | 626 | scope.update(); 627 | 628 | } 629 | 630 | function handleTouchMovePan( event ) { 631 | 632 | //console.log( 'handleTouchMovePan' ); 633 | 634 | panEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 635 | 636 | panDelta.subVectors( panEnd, panStart ); 637 | 638 | pan( panDelta.x, panDelta.y ); 639 | 640 | panStart.copy( panEnd ); 641 | 642 | scope.update(); 643 | 644 | } 645 | 646 | function handleTouchEnd( event ) { 647 | 648 | //console.log( 'handleTouchEnd' ); 649 | 650 | } 651 | 652 | // 653 | // event handlers - FSM: listen for events and reset state 654 | // 655 | 656 | function onMouseDown( event ) { 657 | 658 | if ( scope.enabled === false ) return; 659 | 660 | event.preventDefault(); 661 | 662 | if ( event.button === scope.mouseButtons.ORBIT ) { 663 | 664 | if ( scope.enableRotate === false ) return; 665 | 666 | handleMouseDownRotate( event ); 667 | 668 | state = STATE.ROTATE; 669 | 670 | } else if ( event.button === scope.mouseButtons.ZOOM ) { 671 | 672 | if ( scope.enableZoom === false ) return; 673 | 674 | handleMouseDownDolly( event ); 675 | 676 | state = STATE.DOLLY; 677 | 678 | } else if ( event.button === scope.mouseButtons.PAN ) { 679 | 680 | if ( scope.enablePan === false ) return; 681 | 682 | handleMouseDownPan( event ); 683 | 684 | state = STATE.PAN; 685 | 686 | } 687 | 688 | if ( state !== STATE.NONE ) { 689 | 690 | document.addEventListener( 'mousemove', onMouseMove, false ); 691 | document.addEventListener( 'mouseup', onMouseUp, false ); 692 | 693 | scope.dispatchEvent( startEvent ); 694 | 695 | } 696 | 697 | } 698 | 699 | function onMouseMove( event ) { 700 | 701 | if ( scope.enabled === false ) return; 702 | 703 | event.preventDefault(); 704 | 705 | if ( state === STATE.ROTATE ) { 706 | 707 | if ( scope.enableRotate === false ) return; 708 | 709 | handleMouseMoveRotate( event ); 710 | 711 | } else if ( state === STATE.DOLLY ) { 712 | 713 | if ( scope.enableZoom === false ) return; 714 | 715 | handleMouseMoveDolly( event ); 716 | 717 | } else if ( state === STATE.PAN ) { 718 | 719 | if ( scope.enablePan === false ) return; 720 | 721 | handleMouseMovePan( event ); 722 | 723 | } 724 | 725 | } 726 | 727 | function onMouseUp( event ) { 728 | 729 | if ( scope.enabled === false ) return; 730 | 731 | handleMouseUp( event ); 732 | 733 | document.removeEventListener( 'mousemove', onMouseMove, false ); 734 | document.removeEventListener( 'mouseup', onMouseUp, false ); 735 | 736 | scope.dispatchEvent( endEvent ); 737 | 738 | state = STATE.NONE; 739 | 740 | } 741 | 742 | function onMouseWheel( event ) { 743 | 744 | if ( scope.enabled === false || scope.enableZoom === false || ( state !== STATE.NONE && state !== STATE.ROTATE ) ) return; 745 | 746 | event.preventDefault(); 747 | event.stopPropagation(); 748 | 749 | handleMouseWheel( event ); 750 | 751 | scope.dispatchEvent( startEvent ); // not sure why these are here... 752 | scope.dispatchEvent( endEvent ); 753 | 754 | } 755 | 756 | function onKeyDown( event ) { 757 | 758 | if ( scope.enabled === false || scope.enableKeys === false || scope.enablePan === false ) return; 759 | 760 | handleKeyDown( event ); 761 | 762 | } 763 | 764 | function onTouchStart( event ) { 765 | 766 | if ( scope.enabled === false ) return; 767 | 768 | switch ( event.touches.length ) { 769 | 770 | case 1: // one-fingered touch: rotate 771 | 772 | if ( scope.enableRotate === false ) return; 773 | 774 | handleTouchStartRotate( event ); 775 | 776 | state = STATE.TOUCH_ROTATE; 777 | 778 | break; 779 | 780 | case 2: // two-fingered touch: dolly 781 | 782 | if ( scope.enableZoom === false ) return; 783 | 784 | handleTouchStartDolly( event ); 785 | 786 | state = STATE.TOUCH_DOLLY; 787 | 788 | break; 789 | 790 | case 3: // three-fingered touch: pan 791 | 792 | if ( scope.enablePan === false ) return; 793 | 794 | handleTouchStartPan( event ); 795 | 796 | state = STATE.TOUCH_PAN; 797 | 798 | break; 799 | 800 | default: 801 | 802 | state = STATE.NONE; 803 | 804 | } 805 | 806 | if ( state !== STATE.NONE ) { 807 | 808 | scope.dispatchEvent( startEvent ); 809 | 810 | } 811 | 812 | } 813 | 814 | function onTouchMove( event ) { 815 | 816 | if ( scope.enabled === false ) return; 817 | 818 | event.preventDefault(); 819 | event.stopPropagation(); 820 | 821 | switch ( event.touches.length ) { 822 | 823 | case 1: // one-fingered touch: rotate 824 | 825 | if ( scope.enableRotate === false ) return; 826 | if ( state !== STATE.TOUCH_ROTATE ) return; // is this needed?... 827 | 828 | handleTouchMoveRotate( event ); 829 | 830 | break; 831 | 832 | case 2: // two-fingered touch: dolly 833 | 834 | if ( scope.enableZoom === false ) return; 835 | if ( state !== STATE.TOUCH_DOLLY ) return; // is this needed?... 836 | 837 | handleTouchMoveDolly( event ); 838 | 839 | break; 840 | 841 | case 3: // three-fingered touch: pan 842 | 843 | if ( scope.enablePan === false ) return; 844 | if ( state !== STATE.TOUCH_PAN ) return; // is this needed?... 845 | 846 | handleTouchMovePan( event ); 847 | 848 | break; 849 | 850 | default: 851 | 852 | state = STATE.NONE; 853 | 854 | } 855 | 856 | } 857 | 858 | function onTouchEnd( event ) { 859 | 860 | if ( scope.enabled === false ) return; 861 | 862 | handleTouchEnd( event ); 863 | 864 | scope.dispatchEvent( endEvent ); 865 | 866 | state = STATE.NONE; 867 | 868 | } 869 | 870 | function onContextMenu( event ) { 871 | 872 | event.preventDefault(); 873 | 874 | } 875 | 876 | // 877 | 878 | scope.domElement.addEventListener( 'contextmenu', onContextMenu, false ); 879 | 880 | scope.domElement.addEventListener( 'mousedown', onMouseDown, false ); 881 | scope.domElement.addEventListener( 'wheel', onMouseWheel, false ); 882 | 883 | scope.domElement.addEventListener( 'touchstart', onTouchStart, false ); 884 | scope.domElement.addEventListener( 'touchend', onTouchEnd, false ); 885 | scope.domElement.addEventListener( 'touchmove', onTouchMove, false ); 886 | 887 | window.addEventListener( 'keydown', onKeyDown, false ); 888 | 889 | // force an update at start 890 | 891 | this.update(); 892 | 893 | }; 894 | 895 | THREE.OrbitControls.prototype = Object.create( THREE.EventDispatcher.prototype ); 896 | THREE.OrbitControls.prototype.constructor = THREE.OrbitControls; 897 | 898 | Object.defineProperties( THREE.OrbitControls.prototype, { 899 | 900 | center: { 901 | 902 | get: function () { 903 | 904 | console.warn( 'THREE.OrbitControls: .center has been renamed to .target' ); 905 | return this.target; 906 | 907 | } 908 | 909 | }, 910 | 911 | // backward compatibility 912 | 913 | noZoom: { 914 | 915 | get: function () { 916 | 917 | console.warn( 'THREE.OrbitControls: .noZoom has been deprecated. Use .enableZoom instead.' ); 918 | return ! this.enableZoom; 919 | 920 | }, 921 | 922 | set: function ( value ) { 923 | 924 | console.warn( 'THREE.OrbitControls: .noZoom has been deprecated. Use .enableZoom instead.' ); 925 | this.enableZoom = ! value; 926 | 927 | } 928 | 929 | }, 930 | 931 | noRotate: { 932 | 933 | get: function () { 934 | 935 | console.warn( 'THREE.OrbitControls: .noRotate has been deprecated. Use .enableRotate instead.' ); 936 | return ! this.enableRotate; 937 | 938 | }, 939 | 940 | set: function ( value ) { 941 | 942 | console.warn( 'THREE.OrbitControls: .noRotate has been deprecated. Use .enableRotate instead.' ); 943 | this.enableRotate = ! value; 944 | 945 | } 946 | 947 | }, 948 | 949 | noPan: { 950 | 951 | get: function () { 952 | 953 | console.warn( 'THREE.OrbitControls: .noPan has been deprecated. Use .enablePan instead.' ); 954 | return ! this.enablePan; 955 | 956 | }, 957 | 958 | set: function ( value ) { 959 | 960 | console.warn( 'THREE.OrbitControls: .noPan has been deprecated. Use .enablePan instead.' ); 961 | this.enablePan = ! value; 962 | 963 | } 964 | 965 | }, 966 | 967 | noKeys: { 968 | 969 | get: function () { 970 | 971 | console.warn( 'THREE.OrbitControls: .noKeys has been deprecated. Use .enableKeys instead.' ); 972 | return ! this.enableKeys; 973 | 974 | }, 975 | 976 | set: function ( value ) { 977 | 978 | console.warn( 'THREE.OrbitControls: .noKeys has been deprecated. Use .enableKeys instead.' ); 979 | this.enableKeys = ! value; 980 | 981 | } 982 | 983 | }, 984 | 985 | staticMoving : { 986 | 987 | get: function () { 988 | 989 | console.warn( 'THREE.OrbitControls: .staticMoving has been deprecated. Use .enableDamping instead.' ); 990 | return ! this.enableDamping; 991 | 992 | }, 993 | 994 | set: function ( value ) { 995 | 996 | console.warn( 'THREE.OrbitControls: .staticMoving has been deprecated. Use .enableDamping instead.' ); 997 | this.enableDamping = ! value; 998 | 999 | } 1000 | 1001 | }, 1002 | 1003 | dynamicDampingFactor : { 1004 | 1005 | get: function () { 1006 | 1007 | console.warn( 'THREE.OrbitControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead.' ); 1008 | return this.dampingFactor; 1009 | 1010 | }, 1011 | 1012 | set: function ( value ) { 1013 | 1014 | console.warn( 'THREE.OrbitControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead.' ); 1015 | this.dampingFactor = value; 1016 | 1017 | } 1018 | 1019 | } 1020 | 1021 | } ); 1022 | -------------------------------------------------------------------------------- /dependencies/compatibility.js: -------------------------------------------------------------------------------- 1 | /** 2 | * this code is from all around the web :) 3 | * if u want to put some credits u are welcome! 4 | */ 5 | var compatibility = (function() { 6 | var lastTime = 0, 7 | isLittleEndian = true, 8 | 9 | URL = window.URL || window.webkitURL, 10 | 11 | requestAnimationFrame = function(callback, element) { 12 | var requestAnimationFrame = 13 | window.requestAnimationFrame || 14 | window.webkitRequestAnimationFrame || 15 | window.mozRequestAnimationFrame || 16 | window.oRequestAnimationFrame || 17 | window.msRequestAnimationFrame || 18 | function(callback, element) { 19 | var currTime = new Date().getTime(); 20 | var timeToCall = Math.max(0, 16 - (currTime - lastTime)); 21 | var id = window.setTimeout(function() { 22 | callback(currTime + timeToCall); 23 | }, timeToCall); 24 | lastTime = currTime + timeToCall; 25 | return id; 26 | }; 27 | 28 | return requestAnimationFrame.call(window, callback, element); 29 | }, 30 | 31 | cancelAnimationFrame = function(id) { 32 | var cancelAnimationFrame = window.cancelAnimationFrame || 33 | function(id) { 34 | clearTimeout(id); 35 | }; 36 | return cancelAnimationFrame.call(window, id); 37 | }, 38 | 39 | getUserMedia = function(options, success, error) { 40 | var getUserMedia = 41 | window.navigator.getUserMedia || 42 | window.navigator.mozGetUserMedia || 43 | window.navigator.webkitGetUserMedia || 44 | window.navigator.msGetUserMedia || 45 | function(options, success, error) { 46 | error(); 47 | }; 48 | 49 | return getUserMedia.call(window.navigator, options, success, error); 50 | }, 51 | 52 | detectEndian = function() { 53 | var buf = new ArrayBuffer(8); 54 | var data = new Uint32Array(buf); 55 | data[0] = 0xff000000; 56 | isLittleEndian = true; 57 | if (buf[0] === 0xff) { 58 | isLittleEndian = false; 59 | } 60 | return isLittleEndian; 61 | }; 62 | 63 | return { 64 | URL: URL, 65 | requestAnimationFrame: requestAnimationFrame, 66 | cancelAnimationFrame: cancelAnimationFrame, 67 | getUserMedia: getUserMedia, 68 | detectEndian: detectEndian, 69 | isLittleEndian: isLittleEndian 70 | }; 71 | })(); -------------------------------------------------------------------------------- /dependencies/jquery-ui.min.css: -------------------------------------------------------------------------------- 1 | /*! jQuery UI - v1.12.0 - 2016-08-13 2 | * http://jqueryui.com 3 | * Includes: core.css, slider.css, theme.css 4 | * To view and modify this theme, visit http://jqueryui.com/themeroller/?scope=&folderName=base&cornerRadiusShadow=8px&offsetLeftShadow=0px&offsetTopShadow=0px&thicknessShadow=5px&opacityShadow=30&bgImgOpacityShadow=0&bgTextureShadow=flat&bgColorShadow=666666&opacityOverlay=30&bgImgOpacityOverlay=0&bgTextureOverlay=flat&bgColorOverlay=aaaaaa&iconColorError=cc0000&fcError=5f3f3f&borderColorError=f1a899&bgTextureError=flat&bgColorError=fddfdf&iconColorHighlight=777620&fcHighlight=777620&borderColorHighlight=dad55e&bgTextureHighlight=flat&bgColorHighlight=fffa90&iconColorActive=ffffff&fcActive=ffffff&borderColorActive=003eff&bgTextureActive=flat&bgColorActive=007fff&iconColorHover=555555&fcHover=2b2b2b&borderColorHover=cccccc&bgTextureHover=flat&bgColorHover=ededed&iconColorDefault=777777&fcDefault=454545&borderColorDefault=c5c5c5&bgTextureDefault=flat&bgColorDefault=f6f6f6&iconColorContent=444444&fcContent=333333&borderColorContent=dddddd&bgTextureContent=flat&bgColorContent=ffffff&iconColorHeader=444444&fcHeader=333333&borderColorHeader=dddddd&bgTextureHeader=flat&bgColorHeader=e9e9e9&cornerRadius=3px&fwDefault=normal&fsDefault=1em&ffDefault=Arial%2CHelvetica%2Csans-serif 5 | * Copyright jQuery Foundation and other contributors; Licensed MIT */ 6 | 7 | .ui-helper-hidden{display:none}.ui-helper-hidden-accessible{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.ui-helper-reset{margin:0;padding:0;border:0;outline:0;line-height:1.3;text-decoration:none;font-size:100%;list-style:none}.ui-helper-clearfix:before,.ui-helper-clearfix:after{content:"";display:table;border-collapse:collapse}.ui-helper-clearfix:after{clear:both}.ui-helper-zfix{width:100%;height:100%;top:0;left:0;position:absolute;opacity:0;filter:Alpha(Opacity=0)}.ui-front{z-index:100}.ui-state-disabled{cursor:default!important;pointer-events:none}.ui-icon{display:inline-block;vertical-align:middle;margin-top:-.25em;position:relative;text-indent:-99999px;overflow:hidden;background-repeat:no-repeat}.ui-widget-icon-block{left:50%;margin-left:-8px;display:block}.ui-widget-overlay{position:fixed;top:0;left:0;width:100%;height:100%}.ui-slider{position:relative;text-align:left}.ui-slider .ui-slider-handle{position:absolute;z-index:2;width:1.2em;height:1.2em;cursor:default;-ms-touch-action:none;touch-action:none}.ui-slider .ui-slider-range{position:absolute;z-index:1;font-size:.7em;display:block;border:0;background-position:0 0}.ui-slider.ui-state-disabled .ui-slider-handle,.ui-slider.ui-state-disabled .ui-slider-range{filter:inherit}.ui-slider-horizontal{height:.8em}.ui-slider-horizontal .ui-slider-handle{top:-.3em;margin-left:-.6em}.ui-slider-horizontal .ui-slider-range{top:0;height:100%}.ui-slider-horizontal .ui-slider-range-min{left:0}.ui-slider-horizontal .ui-slider-range-max{right:0}.ui-slider-vertical{width:.8em;height:100px}.ui-slider-vertical .ui-slider-handle{left:-.3em;margin-left:0;margin-bottom:-.6em}.ui-slider-vertical .ui-slider-range{left:0;width:100%}.ui-slider-vertical .ui-slider-range-min{bottom:0}.ui-slider-vertical .ui-slider-range-max{top:0}.ui-widget{font-family:Arial,Helvetica,sans-serif;font-size:1em}.ui-widget .ui-widget{font-size:1em}.ui-widget input,.ui-widget select,.ui-widget textarea,.ui-widget button{font-family:Arial,Helvetica,sans-serif;font-size:1em}.ui-widget.ui-widget-content{border:1px solid #c5c5c5}.ui-widget-content{border:1px solid #ddd;background:#fff;color:#333}.ui-widget-content a{color:#333}.ui-widget-header{border:1px solid #ddd;background:#e9e9e9;color:#333;font-weight:bold}.ui-widget-header a{color:#333}.ui-state-default,.ui-widget-content .ui-state-default,.ui-widget-header .ui-state-default,.ui-button,html .ui-button.ui-state-disabled:hover,html .ui-button.ui-state-disabled:active{border:1px solid #c5c5c5;background:#f6f6f6;font-weight:normal;color:#454545}.ui-state-default a,.ui-state-default a:link,.ui-state-default a:visited,a.ui-button,a:link.ui-button,a:visited.ui-button,.ui-button{color:#454545;text-decoration:none}.ui-state-hover,.ui-widget-content .ui-state-hover,.ui-widget-header .ui-state-hover,.ui-state-focus,.ui-widget-content .ui-state-focus,.ui-widget-header .ui-state-focus,.ui-button:hover,.ui-button:focus{border:1px solid #ccc;background:#ededed;font-weight:normal;color:#2b2b2b}.ui-state-hover a,.ui-state-hover a:hover,.ui-state-hover a:link,.ui-state-hover a:visited,.ui-state-focus a,.ui-state-focus a:hover,.ui-state-focus a:link,.ui-state-focus a:visited,a.ui-button:hover,a.ui-button:focus{color:#2b2b2b;text-decoration:none}.ui-visual-focus{box-shadow:0 0 3px 1px rgb(94,158,214)}.ui-state-active,.ui-widget-content .ui-state-active,.ui-widget-header .ui-state-active,a.ui-button:active,.ui-button:active,.ui-button.ui-state-active:hover{border:1px solid #003eff;background:#007fff;font-weight:normal;color:#fff}.ui-icon-background,.ui-state-active .ui-icon-background{border:#003eff;background-color:#fff}.ui-state-active a,.ui-state-active a:link,.ui-state-active a:visited{color:#fff;text-decoration:none}.ui-state-highlight,.ui-widget-content .ui-state-highlight,.ui-widget-header .ui-state-highlight{border:1px solid #dad55e;background:#fffa90;color:#777620}.ui-state-checked{border:1px solid #dad55e;background:#fffa90}.ui-state-highlight a,.ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a{color:#777620}.ui-state-error,.ui-widget-content .ui-state-error,.ui-widget-header .ui-state-error{border:1px solid #f1a899;background:#fddfdf;color:#5f3f3f}.ui-state-error a,.ui-widget-content .ui-state-error a,.ui-widget-header .ui-state-error a{color:#5f3f3f}.ui-state-error-text,.ui-widget-content .ui-state-error-text,.ui-widget-header .ui-state-error-text{color:#5f3f3f}.ui-priority-primary,.ui-widget-content .ui-priority-primary,.ui-widget-header .ui-priority-primary{font-weight:bold}.ui-priority-secondary,.ui-widget-content .ui-priority-secondary,.ui-widget-header .ui-priority-secondary{opacity:.7;filter:Alpha(Opacity=70);font-weight:normal}.ui-state-disabled,.ui-widget-content .ui-state-disabled,.ui-widget-header .ui-state-disabled{opacity:.35;filter:Alpha(Opacity=35);background-image:none}.ui-state-disabled .ui-icon{filter:Alpha(Opacity=35)}.ui-icon{width:16px;height:16px}.ui-icon,.ui-widget-content .ui-icon{background-image:url("images/ui-icons_444444_256x240.png")}.ui-widget-header .ui-icon{background-image:url("images/ui-icons_444444_256x240.png")}.ui-button .ui-icon{background-image:url("images/ui-icons_777777_256x240.png")}.ui-state-hover .ui-icon,.ui-state-focus .ui-icon,.ui-button:hover .ui-icon,.ui-button:focus .ui-icon,.ui-state-default .ui-icon{background-image:url("images/ui-icons_555555_256x240.png")}.ui-state-active .ui-icon,.ui-button:active .ui-icon{background-image:url("images/ui-icons_ffffff_256x240.png")}.ui-state-highlight .ui-icon,.ui-button .ui-state-highlight.ui-icon{background-image:url("images/ui-icons_777620_256x240.png")}.ui-state-error .ui-icon,.ui-state-error-text .ui-icon{background-image:url("images/ui-icons_cc0000_256x240.png")}.ui-icon-blank{background-position:16px 16px}.ui-icon-caret-1-n{background-position:0 0}.ui-icon-caret-1-ne{background-position:-16px 0}.ui-icon-caret-1-e{background-position:-32px 0}.ui-icon-caret-1-se{background-position:-48px 0}.ui-icon-caret-1-s{background-position:-65px 0}.ui-icon-caret-1-sw{background-position:-80px 0}.ui-icon-caret-1-w{background-position:-96px 0}.ui-icon-caret-1-nw{background-position:-112px 0}.ui-icon-caret-2-n-s{background-position:-128px 0}.ui-icon-caret-2-e-w{background-position:-144px 0}.ui-icon-triangle-1-n{background-position:0 -16px}.ui-icon-triangle-1-ne{background-position:-16px -16px}.ui-icon-triangle-1-e{background-position:-32px -16px}.ui-icon-triangle-1-se{background-position:-48px -16px}.ui-icon-triangle-1-s{background-position:-65px -16px}.ui-icon-triangle-1-sw{background-position:-80px -16px}.ui-icon-triangle-1-w{background-position:-96px -16px}.ui-icon-triangle-1-nw{background-position:-112px -16px}.ui-icon-triangle-2-n-s{background-position:-128px -16px}.ui-icon-triangle-2-e-w{background-position:-144px -16px}.ui-icon-arrow-1-n{background-position:0 -32px}.ui-icon-arrow-1-ne{background-position:-16px -32px}.ui-icon-arrow-1-e{background-position:-32px -32px}.ui-icon-arrow-1-se{background-position:-48px -32px}.ui-icon-arrow-1-s{background-position:-65px -32px}.ui-icon-arrow-1-sw{background-position:-80px -32px}.ui-icon-arrow-1-w{background-position:-96px -32px}.ui-icon-arrow-1-nw{background-position:-112px -32px}.ui-icon-arrow-2-n-s{background-position:-128px -32px}.ui-icon-arrow-2-ne-sw{background-position:-144px -32px}.ui-icon-arrow-2-e-w{background-position:-160px -32px}.ui-icon-arrow-2-se-nw{background-position:-176px -32px}.ui-icon-arrowstop-1-n{background-position:-192px -32px}.ui-icon-arrowstop-1-e{background-position:-208px -32px}.ui-icon-arrowstop-1-s{background-position:-224px -32px}.ui-icon-arrowstop-1-w{background-position:-240px -32px}.ui-icon-arrowthick-1-n{background-position:1px -48px}.ui-icon-arrowthick-1-ne{background-position:-16px -48px}.ui-icon-arrowthick-1-e{background-position:-32px -48px}.ui-icon-arrowthick-1-se{background-position:-48px -48px}.ui-icon-arrowthick-1-s{background-position:-64px -48px}.ui-icon-arrowthick-1-sw{background-position:-80px -48px}.ui-icon-arrowthick-1-w{background-position:-96px -48px}.ui-icon-arrowthick-1-nw{background-position:-112px -48px}.ui-icon-arrowthick-2-n-s{background-position:-128px -48px}.ui-icon-arrowthick-2-ne-sw{background-position:-144px -48px}.ui-icon-arrowthick-2-e-w{background-position:-160px -48px}.ui-icon-arrowthick-2-se-nw{background-position:-176px -48px}.ui-icon-arrowthickstop-1-n{background-position:-192px -48px}.ui-icon-arrowthickstop-1-e{background-position:-208px -48px}.ui-icon-arrowthickstop-1-s{background-position:-224px -48px}.ui-icon-arrowthickstop-1-w{background-position:-240px -48px}.ui-icon-arrowreturnthick-1-w{background-position:0 -64px}.ui-icon-arrowreturnthick-1-n{background-position:-16px -64px}.ui-icon-arrowreturnthick-1-e{background-position:-32px -64px}.ui-icon-arrowreturnthick-1-s{background-position:-48px -64px}.ui-icon-arrowreturn-1-w{background-position:-64px -64px}.ui-icon-arrowreturn-1-n{background-position:-80px -64px}.ui-icon-arrowreturn-1-e{background-position:-96px -64px}.ui-icon-arrowreturn-1-s{background-position:-112px -64px}.ui-icon-arrowrefresh-1-w{background-position:-128px -64px}.ui-icon-arrowrefresh-1-n{background-position:-144px -64px}.ui-icon-arrowrefresh-1-e{background-position:-160px -64px}.ui-icon-arrowrefresh-1-s{background-position:-176px -64px}.ui-icon-arrow-4{background-position:0 -80px}.ui-icon-arrow-4-diag{background-position:-16px -80px}.ui-icon-extlink{background-position:-32px -80px}.ui-icon-newwin{background-position:-48px -80px}.ui-icon-refresh{background-position:-64px -80px}.ui-icon-shuffle{background-position:-80px -80px}.ui-icon-transfer-e-w{background-position:-96px -80px}.ui-icon-transferthick-e-w{background-position:-112px -80px}.ui-icon-folder-collapsed{background-position:0 -96px}.ui-icon-folder-open{background-position:-16px -96px}.ui-icon-document{background-position:-32px -96px}.ui-icon-document-b{background-position:-48px -96px}.ui-icon-note{background-position:-64px -96px}.ui-icon-mail-closed{background-position:-80px -96px}.ui-icon-mail-open{background-position:-96px -96px}.ui-icon-suitcase{background-position:-112px -96px}.ui-icon-comment{background-position:-128px -96px}.ui-icon-person{background-position:-144px -96px}.ui-icon-print{background-position:-160px -96px}.ui-icon-trash{background-position:-176px -96px}.ui-icon-locked{background-position:-192px -96px}.ui-icon-unlocked{background-position:-208px -96px}.ui-icon-bookmark{background-position:-224px -96px}.ui-icon-tag{background-position:-240px -96px}.ui-icon-home{background-position:0 -112px}.ui-icon-flag{background-position:-16px -112px}.ui-icon-calendar{background-position:-32px -112px}.ui-icon-cart{background-position:-48px -112px}.ui-icon-pencil{background-position:-64px -112px}.ui-icon-clock{background-position:-80px -112px}.ui-icon-disk{background-position:-96px -112px}.ui-icon-calculator{background-position:-112px -112px}.ui-icon-zoomin{background-position:-128px -112px}.ui-icon-zoomout{background-position:-144px -112px}.ui-icon-search{background-position:-160px -112px}.ui-icon-wrench{background-position:-176px -112px}.ui-icon-gear{background-position:-192px -112px}.ui-icon-heart{background-position:-208px -112px}.ui-icon-star{background-position:-224px -112px}.ui-icon-link{background-position:-240px -112px}.ui-icon-cancel{background-position:0 -128px}.ui-icon-plus{background-position:-16px -128px}.ui-icon-plusthick{background-position:-32px -128px}.ui-icon-minus{background-position:-48px -128px}.ui-icon-minusthick{background-position:-64px -128px}.ui-icon-close{background-position:-80px -128px}.ui-icon-closethick{background-position:-96px -128px}.ui-icon-key{background-position:-112px -128px}.ui-icon-lightbulb{background-position:-128px -128px}.ui-icon-scissors{background-position:-144px -128px}.ui-icon-clipboard{background-position:-160px -128px}.ui-icon-copy{background-position:-176px -128px}.ui-icon-contact{background-position:-192px -128px}.ui-icon-image{background-position:-208px -128px}.ui-icon-video{background-position:-224px -128px}.ui-icon-script{background-position:-240px -128px}.ui-icon-alert{background-position:0 -144px}.ui-icon-info{background-position:-16px -144px}.ui-icon-notice{background-position:-32px -144px}.ui-icon-help{background-position:-48px -144px}.ui-icon-check{background-position:-64px -144px}.ui-icon-bullet{background-position:-80px -144px}.ui-icon-radio-on{background-position:-96px -144px}.ui-icon-radio-off{background-position:-112px -144px}.ui-icon-pin-w{background-position:-128px -144px}.ui-icon-pin-s{background-position:-144px -144px}.ui-icon-play{background-position:0 -160px}.ui-icon-pause{background-position:-16px -160px}.ui-icon-seek-next{background-position:-32px -160px}.ui-icon-seek-prev{background-position:-48px -160px}.ui-icon-seek-end{background-position:-64px -160px}.ui-icon-seek-start{background-position:-80px -160px}.ui-icon-seek-first{background-position:-80px -160px}.ui-icon-stop{background-position:-96px -160px}.ui-icon-eject{background-position:-112px -160px}.ui-icon-volume-off{background-position:-128px -160px}.ui-icon-volume-on{background-position:-144px -160px}.ui-icon-power{background-position:0 -176px}.ui-icon-signal-diag{background-position:-16px -176px}.ui-icon-signal{background-position:-32px -176px}.ui-icon-battery-0{background-position:-48px -176px}.ui-icon-battery-1{background-position:-64px -176px}.ui-icon-battery-2{background-position:-80px -176px}.ui-icon-battery-3{background-position:-96px -176px}.ui-icon-circle-plus{background-position:0 -192px}.ui-icon-circle-minus{background-position:-16px -192px}.ui-icon-circle-close{background-position:-32px -192px}.ui-icon-circle-triangle-e{background-position:-48px -192px}.ui-icon-circle-triangle-s{background-position:-64px -192px}.ui-icon-circle-triangle-w{background-position:-80px -192px}.ui-icon-circle-triangle-n{background-position:-96px -192px}.ui-icon-circle-arrow-e{background-position:-112px -192px}.ui-icon-circle-arrow-s{background-position:-128px -192px}.ui-icon-circle-arrow-w{background-position:-144px -192px}.ui-icon-circle-arrow-n{background-position:-160px -192px}.ui-icon-circle-zoomin{background-position:-176px -192px}.ui-icon-circle-zoomout{background-position:-192px -192px}.ui-icon-circle-check{background-position:-208px -192px}.ui-icon-circlesmall-plus{background-position:0 -208px}.ui-icon-circlesmall-minus{background-position:-16px -208px}.ui-icon-circlesmall-close{background-position:-32px -208px}.ui-icon-squaresmall-plus{background-position:-48px -208px}.ui-icon-squaresmall-minus{background-position:-64px -208px}.ui-icon-squaresmall-close{background-position:-80px -208px}.ui-icon-grip-dotted-vertical{background-position:0 -224px}.ui-icon-grip-dotted-horizontal{background-position:-16px -224px}.ui-icon-grip-solid-vertical{background-position:-32px -224px}.ui-icon-grip-solid-horizontal{background-position:-48px -224px}.ui-icon-gripsmall-diagonal-se{background-position:-64px -224px}.ui-icon-grip-diagonal-se{background-position:-80px -224px}.ui-corner-all,.ui-corner-top,.ui-corner-left,.ui-corner-tl{border-top-left-radius:3px}.ui-corner-all,.ui-corner-top,.ui-corner-right,.ui-corner-tr{border-top-right-radius:3px}.ui-corner-all,.ui-corner-bottom,.ui-corner-left,.ui-corner-bl{border-bottom-left-radius:3px}.ui-corner-all,.ui-corner-bottom,.ui-corner-right,.ui-corner-br{border-bottom-right-radius:3px}.ui-widget-overlay{background:#aaa;opacity:.3;filter:Alpha(Opacity=30)}.ui-widget-shadow{-webkit-box-shadow:0 0 5px #666;box-shadow:0 0 5px #666} -------------------------------------------------------------------------------- /dependencies/vr/VRControls.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author dmarcos / https://github.com/dmarcos 3 | * @author mrdoob / http://mrdoob.com 4 | */ 5 | 6 | THREE.VRControls = function ( object, onError ) { 7 | 8 | var scope = this; 9 | 10 | var vrDisplay, vrDisplays; 11 | 12 | var standingMatrix = new THREE.Matrix4(); 13 | 14 | function gotVRDisplays( displays ) { 15 | 16 | vrDisplays = displays; 17 | 18 | for ( var i = 0; i < displays.length; i ++ ) { 19 | 20 | if ( ( 'VRDisplay' in window && displays[ i ] instanceof VRDisplay ) || 21 | ( 'PositionSensorVRDevice' in window && displays[ i ] instanceof PositionSensorVRDevice ) ) { 22 | 23 | vrDisplay = displays[ i ]; 24 | break; // We keep the first we encounter 25 | 26 | } 27 | 28 | } 29 | 30 | if ( vrDisplay === undefined ) { 31 | 32 | if ( onError ) onError( 'VR input not available.' ); 33 | 34 | } 35 | 36 | } 37 | 38 | if ( navigator.getVRDisplays ) { 39 | 40 | navigator.getVRDisplays().then( gotVRDisplays ); 41 | 42 | } else if ( navigator.getVRDevices ) { 43 | 44 | // Deprecated API. 45 | navigator.getVRDevices().then( gotVRDisplays ); 46 | 47 | } 48 | 49 | // the Rift SDK returns the position in meters 50 | // this scale factor allows the user to define how meters 51 | // are converted to scene units. 52 | 53 | this.scale = 1; 54 | 55 | // If true will use "standing space" coordinate system where y=0 is the 56 | // floor and x=0, z=0 is the center of the room. 57 | this.standing = false; 58 | 59 | // Distance from the users eyes to the floor in meters. Used when 60 | // standing=true but the VRDisplay doesn't provide stageParameters. 61 | this.userHeight = 1.6; 62 | 63 | this.getVRDisplay = function () { 64 | 65 | return vrDisplay; 66 | 67 | }; 68 | 69 | this.getVRDisplays = function () { 70 | 71 | return vrDisplays; 72 | 73 | }; 74 | 75 | this.getStandingMatrix = function () { 76 | 77 | return standingMatrix; 78 | 79 | }; 80 | 81 | this.update = function () { 82 | 83 | if ( vrDisplay ) { 84 | 85 | if ( vrDisplay.getPose ) { 86 | 87 | var pose = vrDisplay.getPose(); 88 | 89 | if ( pose.orientation !== null ) { 90 | 91 | object.quaternion.fromArray( pose.orientation ); 92 | 93 | } 94 | 95 | if ( pose.position !== null ) { 96 | 97 | object.position.fromArray( pose.position ); 98 | 99 | } else { 100 | 101 | object.position.set( 0, 0, 0 ); 102 | 103 | } 104 | 105 | } else { 106 | 107 | // Deprecated API. 108 | var state = vrDisplay.getState(); 109 | 110 | if ( state.orientation !== null ) { 111 | 112 | object.quaternion.copy( state.orientation ); 113 | 114 | } 115 | 116 | if ( state.position !== null ) { 117 | 118 | object.position.copy( state.position ); 119 | 120 | } else { 121 | 122 | object.position.set( 0, 0, 0 ); 123 | 124 | } 125 | 126 | } 127 | 128 | if ( this.standing ) { 129 | 130 | if ( vrDisplay.stageParameters ) { 131 | 132 | object.updateMatrix(); 133 | 134 | standingMatrix.fromArray( vrDisplay.stageParameters.sittingToStandingTransform ); 135 | object.applyMatrix( standingMatrix ); 136 | 137 | } else { 138 | 139 | object.position.setY( object.position.y + this.userHeight ); 140 | 141 | } 142 | 143 | } 144 | 145 | object.position.multiplyScalar( scope.scale ); 146 | 147 | } 148 | 149 | }; 150 | 151 | this.resetPose = function () { 152 | 153 | if ( vrDisplay ) { 154 | 155 | if ( vrDisplay.resetPose !== undefined ) { 156 | 157 | vrDisplay.resetPose(); 158 | 159 | } else if ( vrDisplay.resetSensor !== undefined ) { 160 | 161 | // Deprecated API. 162 | vrDisplay.resetSensor(); 163 | 164 | } else if ( vrDisplay.zeroSensor !== undefined ) { 165 | 166 | // Really deprecated API. 167 | vrDisplay.zeroSensor(); 168 | 169 | } 170 | 171 | } 172 | 173 | }; 174 | 175 | this.resetSensor = function () { 176 | 177 | console.warn( 'THREE.VRControls: .resetSensor() is now .resetPose().' ); 178 | this.resetPose(); 179 | 180 | }; 181 | 182 | this.zeroSensor = function () { 183 | 184 | console.warn( 'THREE.VRControls: .zeroSensor() is now .resetPose().' ); 185 | this.resetPose(); 186 | 187 | }; 188 | 189 | this.dispose = function () { 190 | 191 | vrDisplay = null; 192 | 193 | }; 194 | 195 | }; 196 | -------------------------------------------------------------------------------- /dependencies/vr/VREffect.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author dmarcos / https://github.com/dmarcos 3 | * @author mrdoob / http://mrdoob.com 4 | * 5 | * WebVR Spec: http://mozvr.github.io/webvr-spec/webvr.html 6 | * 7 | * Firefox: http://mozvr.com/downloads/ 8 | * Chromium: https://drive.google.com/folderview?id=0BzudLt22BqGRbW9WTHMtOWMzNjQ&usp=sharing#list 9 | * 10 | */ 11 | 12 | THREE.VREffect = function ( renderer, onError ) { 13 | 14 | var isWebVR1 = true; 15 | 16 | var vrDisplay, vrDisplays; 17 | var eyeTranslationL = new THREE.Vector3(); 18 | var eyeTranslationR = new THREE.Vector3(); 19 | var renderRectL, renderRectR; 20 | var eyeFOVL, eyeFOVR; 21 | 22 | function gotVRDisplays( displays ) { 23 | 24 | vrDisplays = displays; 25 | 26 | for ( var i = 0; i < displays.length; i ++ ) { 27 | 28 | if ( 'VRDisplay' in window && displays[ i ] instanceof VRDisplay ) { 29 | 30 | vrDisplay = displays[ i ]; 31 | isWebVR1 = true; 32 | break; // We keep the first we encounter 33 | 34 | } else if ( 'HMDVRDevice' in window && displays[ i ] instanceof HMDVRDevice ) { 35 | 36 | vrDisplay = displays[ i ]; 37 | isWebVR1 = false; 38 | break; // We keep the first we encounter 39 | 40 | } 41 | 42 | } 43 | 44 | if ( vrDisplay === undefined ) { 45 | 46 | if ( onError ) onError( 'HMD not available' ); 47 | 48 | } 49 | 50 | } 51 | 52 | if ( navigator.getVRDisplays ) { 53 | 54 | navigator.getVRDisplays().then( gotVRDisplays ); 55 | 56 | } else if ( navigator.getVRDevices ) { 57 | 58 | // Deprecated API. 59 | navigator.getVRDevices().then( gotVRDisplays ); 60 | 61 | } 62 | 63 | // 64 | 65 | this.isPresenting = false; 66 | this.scale = 1; 67 | 68 | var scope = this; 69 | 70 | var rendererSize = renderer.getSize(); 71 | var rendererPixelRatio = renderer.getPixelRatio(); 72 | 73 | this.getVRDisplay = function () { 74 | 75 | return vrDisplay; 76 | 77 | }; 78 | 79 | this.getVRDisplays = function () { 80 | 81 | return vrDisplays; 82 | 83 | }; 84 | 85 | this.setSize = function ( width, height ) { 86 | 87 | rendererSize = { width: width, height: height }; 88 | 89 | if ( scope.isPresenting ) { 90 | 91 | var eyeParamsL = vrDisplay.getEyeParameters( 'left' ); 92 | renderer.setPixelRatio( 1 ); 93 | 94 | if ( isWebVR1 ) { 95 | 96 | renderer.setSize( eyeParamsL.renderWidth * 2, eyeParamsL.renderHeight, false ); 97 | 98 | } else { 99 | 100 | renderer.setSize( eyeParamsL.renderRect.width * 2, eyeParamsL.renderRect.height, false ); 101 | 102 | } 103 | 104 | } else { 105 | 106 | renderer.setPixelRatio( rendererPixelRatio ); 107 | renderer.setSize( width, height ); 108 | 109 | } 110 | 111 | }; 112 | 113 | // fullscreen 114 | 115 | var canvas = renderer.domElement; 116 | var requestFullscreen; 117 | var exitFullscreen; 118 | var fullscreenElement; 119 | var leftBounds = [ 0.0, 0.0, 0.5, 1.0 ]; 120 | var rightBounds = [ 0.5, 0.0, 0.5, 1.0 ]; 121 | 122 | function onFullscreenChange() { 123 | 124 | var wasPresenting = scope.isPresenting; 125 | scope.isPresenting = vrDisplay !== undefined && ( vrDisplay.isPresenting || ( ! isWebVR1 && document[ fullscreenElement ] instanceof window.HTMLElement ) ); 126 | 127 | if ( scope.isPresenting ) { 128 | 129 | var eyeParamsL = vrDisplay.getEyeParameters( 'left' ); 130 | var eyeWidth, eyeHeight; 131 | 132 | if ( isWebVR1 ) { 133 | 134 | eyeWidth = eyeParamsL.renderWidth; 135 | eyeHeight = eyeParamsL.renderHeight; 136 | 137 | if ( vrDisplay.getLayers ) { 138 | 139 | var layers = vrDisplay.getLayers(); 140 | if ( layers.length ) { 141 | 142 | leftBounds = layers[0].leftBounds || [ 0.0, 0.0, 0.5, 1.0 ]; 143 | rightBounds = layers[0].rightBounds || [ 0.5, 0.0, 0.5, 1.0 ]; 144 | 145 | } 146 | 147 | } 148 | 149 | } else { 150 | 151 | eyeWidth = eyeParamsL.renderRect.width; 152 | eyeHeight = eyeParamsL.renderRect.height; 153 | 154 | } 155 | 156 | if ( !wasPresenting ) { 157 | 158 | rendererPixelRatio = renderer.getPixelRatio(); 159 | rendererSize = renderer.getSize(); 160 | 161 | renderer.setPixelRatio( 1 ); 162 | renderer.setSize( eyeWidth * 2, eyeHeight, false ); 163 | 164 | } 165 | 166 | } else if ( wasPresenting ) { 167 | 168 | renderer.setPixelRatio( rendererPixelRatio ); 169 | renderer.setSize( rendererSize.width, rendererSize.height ); 170 | 171 | } 172 | 173 | } 174 | 175 | if ( canvas.requestFullscreen ) { 176 | 177 | requestFullscreen = 'requestFullscreen'; 178 | fullscreenElement = 'fullscreenElement'; 179 | exitFullscreen = 'exitFullscreen'; 180 | document.addEventListener( 'fullscreenchange', onFullscreenChange, false ); 181 | 182 | } else if ( canvas.mozRequestFullScreen ) { 183 | 184 | requestFullscreen = 'mozRequestFullScreen'; 185 | fullscreenElement = 'mozFullScreenElement'; 186 | exitFullscreen = 'mozCancelFullScreen'; 187 | document.addEventListener( 'mozfullscreenchange', onFullscreenChange, false ); 188 | 189 | } else { 190 | 191 | requestFullscreen = 'webkitRequestFullscreen'; 192 | fullscreenElement = 'webkitFullscreenElement'; 193 | exitFullscreen = 'webkitExitFullscreen'; 194 | document.addEventListener( 'webkitfullscreenchange', onFullscreenChange, false ); 195 | 196 | } 197 | 198 | window.addEventListener( 'vrdisplaypresentchange', onFullscreenChange, false ); 199 | 200 | this.setFullScreen = function ( boolean ) { 201 | 202 | return new Promise( function ( resolve, reject ) { 203 | 204 | if ( vrDisplay === undefined ) { 205 | 206 | reject( new Error( 'No VR hardware found.' ) ); 207 | return; 208 | 209 | } 210 | 211 | if ( scope.isPresenting === boolean ) { 212 | 213 | resolve(); 214 | return; 215 | 216 | } 217 | 218 | if ( isWebVR1 ) { 219 | 220 | if ( boolean ) { 221 | 222 | resolve( vrDisplay.requestPresent( [ { source: canvas } ] ) ); 223 | 224 | } else { 225 | 226 | resolve( vrDisplay.exitPresent() ); 227 | 228 | } 229 | 230 | } else { 231 | 232 | if ( canvas[ requestFullscreen ] ) { 233 | 234 | canvas[ boolean ? requestFullscreen : exitFullscreen ]( { vrDisplay: vrDisplay } ); 235 | resolve(); 236 | 237 | } else { 238 | 239 | console.error( 'No compatible requestFullscreen method found.' ); 240 | reject( new Error( 'No compatible requestFullscreen method found.' ) ); 241 | 242 | } 243 | 244 | } 245 | 246 | } ); 247 | 248 | }; 249 | 250 | this.requestPresent = function () { 251 | 252 | return this.setFullScreen( true ); 253 | 254 | }; 255 | 256 | this.exitPresent = function () { 257 | 258 | return this.setFullScreen( false ); 259 | 260 | }; 261 | 262 | this.requestAnimationFrame = function ( f ) { 263 | 264 | if ( isWebVR1 && vrDisplay !== undefined ) { 265 | 266 | return vrDisplay.requestAnimationFrame( f ); 267 | 268 | } else { 269 | 270 | return window.requestAnimationFrame( f ); 271 | 272 | } 273 | 274 | }; 275 | 276 | this.cancelAnimationFrame = function ( h ) { 277 | 278 | if ( isWebVR1 && vrDisplay !== undefined ) { 279 | 280 | vrDisplay.cancelAnimationFrame( h ); 281 | 282 | } else { 283 | 284 | window.cancelAnimationFrame( h ); 285 | 286 | } 287 | 288 | }; 289 | 290 | this.submitFrame = function () { 291 | 292 | if ( isWebVR1 && vrDisplay !== undefined && scope.isPresenting ) { 293 | 294 | vrDisplay.submitFrame(); 295 | 296 | } 297 | 298 | }; 299 | 300 | this.autoSubmitFrame = true; 301 | 302 | // render 303 | 304 | var cameraL = new THREE.PerspectiveCamera(); 305 | cameraL.layers.enable( 1 ); 306 | 307 | var cameraR = new THREE.PerspectiveCamera(); 308 | cameraR.layers.enable( 2 ); 309 | 310 | this.render = function ( scene, camera, renderTarget, forceClear ) { 311 | 312 | if ( vrDisplay && scope.isPresenting ) { 313 | 314 | var autoUpdate = scene.autoUpdate; 315 | 316 | if ( autoUpdate ) { 317 | 318 | scene.updateMatrixWorld(); 319 | scene.autoUpdate = false; 320 | 321 | } 322 | 323 | var eyeParamsL = vrDisplay.getEyeParameters( 'left' ); 324 | var eyeParamsR = vrDisplay.getEyeParameters( 'right' ); 325 | 326 | if ( isWebVR1 ) { 327 | 328 | eyeTranslationL.fromArray( eyeParamsL.offset ); 329 | eyeTranslationR.fromArray( eyeParamsR.offset ); 330 | eyeFOVL = eyeParamsL.fieldOfView; 331 | eyeFOVR = eyeParamsR.fieldOfView; 332 | 333 | } else { 334 | 335 | eyeTranslationL.copy( eyeParamsL.eyeTranslation ); 336 | eyeTranslationR.copy( eyeParamsR.eyeTranslation ); 337 | eyeFOVL = eyeParamsL.recommendedFieldOfView; 338 | eyeFOVR = eyeParamsR.recommendedFieldOfView; 339 | 340 | } 341 | 342 | if ( Array.isArray( scene ) ) { 343 | 344 | console.warn( 'THREE.VREffect.render() no longer supports arrays. Use object.layers instead.' ); 345 | scene = scene[ 0 ]; 346 | 347 | } 348 | 349 | // When rendering we don't care what the recommended size is, only what the actual size 350 | // of the backbuffer is. 351 | var size = renderer.getSize(); 352 | renderRectL = { 353 | x: Math.round( size.width * leftBounds[ 0 ] ), 354 | y: Math.round( size.height * leftBounds[ 1 ] ), 355 | width: Math.round( size.width * leftBounds[ 2 ] ), 356 | height: Math.round(size.height * leftBounds[ 3 ] ) 357 | }; 358 | renderRectR = { 359 | x: Math.round( size.width * rightBounds[ 0 ] ), 360 | y: Math.round( size.height * rightBounds[ 1 ] ), 361 | width: Math.round( size.width * rightBounds[ 2 ] ), 362 | height: Math.round(size.height * rightBounds[ 3 ] ) 363 | }; 364 | 365 | if ( renderTarget ) { 366 | 367 | renderer.setRenderTarget( renderTarget ); 368 | renderTarget.scissorTest = true; 369 | 370 | } else { 371 | 372 | renderer.setScissorTest( true ); 373 | 374 | } 375 | 376 | if ( renderer.autoClear || forceClear ) renderer.clear(); 377 | 378 | if ( camera.parent === null ) camera.updateMatrixWorld(); 379 | 380 | cameraL.projectionMatrix = fovToProjection( eyeFOVL, true, camera.near, camera.far ); 381 | cameraR.projectionMatrix = fovToProjection( eyeFOVR, true, camera.near, camera.far ); 382 | 383 | camera.matrixWorld.decompose( cameraL.position, cameraL.quaternion, cameraL.scale ); 384 | camera.matrixWorld.decompose( cameraR.position, cameraR.quaternion, cameraR.scale ); 385 | 386 | var scale = this.scale; 387 | cameraL.translateOnAxis( eyeTranslationL, scale ); 388 | cameraR.translateOnAxis( eyeTranslationR, scale ); 389 | 390 | 391 | // render left eye 392 | if ( renderTarget ) { 393 | 394 | renderTarget.viewport.set( renderRectL.x, renderRectL.y, renderRectL.width, renderRectL.height ); 395 | renderTarget.scissor.set( renderRectL.x, renderRectL.y, renderRectL.width, renderRectL.height ); 396 | 397 | } else { 398 | 399 | renderer.setViewport( renderRectL.x, renderRectL.y, renderRectL.width, renderRectL.height ); 400 | renderer.setScissor( renderRectL.x, renderRectL.y, renderRectL.width, renderRectL.height ); 401 | 402 | } 403 | renderer.render( scene, cameraL, renderTarget, forceClear ); 404 | 405 | // render right eye 406 | if ( renderTarget ) { 407 | 408 | renderTarget.viewport.set( renderRectR.x, renderRectR.y, renderRectR.width, renderRectR.height ); 409 | renderTarget.scissor.set( renderRectR.x, renderRectR.y, renderRectR.width, renderRectR.height ); 410 | 411 | } else { 412 | 413 | renderer.setViewport( renderRectR.x, renderRectR.y, renderRectR.width, renderRectR.height ); 414 | renderer.setScissor( renderRectR.x, renderRectR.y, renderRectR.width, renderRectR.height ); 415 | 416 | } 417 | renderer.render( scene, cameraR, renderTarget, forceClear ); 418 | 419 | if ( renderTarget ) { 420 | 421 | renderTarget.viewport.set( 0, 0, size.width, size.height ); 422 | renderTarget.scissor.set( 0, 0, size.width, size.height ); 423 | renderTarget.scissorTest = false; 424 | renderer.setRenderTarget( null ); 425 | 426 | } else { 427 | 428 | renderer.setScissorTest( false ); 429 | 430 | } 431 | 432 | if ( autoUpdate ) { 433 | 434 | scene.autoUpdate = true; 435 | 436 | } 437 | 438 | if ( scope.autoSubmitFrame ) { 439 | 440 | scope.submitFrame(); 441 | 442 | } 443 | 444 | return; 445 | 446 | } 447 | 448 | // Regular render mode if not HMD 449 | 450 | renderer.render( scene, camera, renderTarget, forceClear ); 451 | 452 | }; 453 | 454 | // 455 | 456 | function fovToNDCScaleOffset( fov ) { 457 | 458 | var pxscale = 2.0 / ( fov.leftTan + fov.rightTan ); 459 | var pxoffset = ( fov.leftTan - fov.rightTan ) * pxscale * 0.5; 460 | var pyscale = 2.0 / ( fov.upTan + fov.downTan ); 461 | var pyoffset = ( fov.upTan - fov.downTan ) * pyscale * 0.5; 462 | return { scale: [ pxscale, pyscale ], offset: [ pxoffset, pyoffset ] }; 463 | 464 | } 465 | 466 | function fovPortToProjection( fov, rightHanded, zNear, zFar ) { 467 | 468 | rightHanded = rightHanded === undefined ? true : rightHanded; 469 | zNear = zNear === undefined ? 0.01 : zNear; 470 | zFar = zFar === undefined ? 10000.0 : zFar; 471 | 472 | var handednessScale = rightHanded ? - 1.0 : 1.0; 473 | 474 | // start with an identity matrix 475 | var mobj = new THREE.Matrix4(); 476 | var m = mobj.elements; 477 | 478 | // and with scale/offset info for normalized device coords 479 | var scaleAndOffset = fovToNDCScaleOffset( fov ); 480 | 481 | // X result, map clip edges to [-w,+w] 482 | m[ 0 * 4 + 0 ] = scaleAndOffset.scale[ 0 ]; 483 | m[ 0 * 4 + 1 ] = 0.0; 484 | m[ 0 * 4 + 2 ] = scaleAndOffset.offset[ 0 ] * handednessScale; 485 | m[ 0 * 4 + 3 ] = 0.0; 486 | 487 | // Y result, map clip edges to [-w,+w] 488 | // Y offset is negated because this proj matrix transforms from world coords with Y=up, 489 | // but the NDC scaling has Y=down (thanks D3D?) 490 | m[ 1 * 4 + 0 ] = 0.0; 491 | m[ 1 * 4 + 1 ] = scaleAndOffset.scale[ 1 ]; 492 | m[ 1 * 4 + 2 ] = - scaleAndOffset.offset[ 1 ] * handednessScale; 493 | m[ 1 * 4 + 3 ] = 0.0; 494 | 495 | // Z result (up to the app) 496 | m[ 2 * 4 + 0 ] = 0.0; 497 | m[ 2 * 4 + 1 ] = 0.0; 498 | m[ 2 * 4 + 2 ] = zFar / ( zNear - zFar ) * - handednessScale; 499 | m[ 2 * 4 + 3 ] = ( zFar * zNear ) / ( zNear - zFar ); 500 | 501 | // W result (= Z in) 502 | m[ 3 * 4 + 0 ] = 0.0; 503 | m[ 3 * 4 + 1 ] = 0.0; 504 | m[ 3 * 4 + 2 ] = handednessScale; 505 | m[ 3 * 4 + 3 ] = 0.0; 506 | 507 | mobj.transpose(); 508 | 509 | return mobj; 510 | 511 | } 512 | 513 | function fovToProjection( fov, rightHanded, zNear, zFar ) { 514 | 515 | var DEG2RAD = Math.PI / 180.0; 516 | 517 | var fovPort = { 518 | upTan: Math.tan( fov.upDegrees * DEG2RAD ), 519 | downTan: Math.tan( fov.downDegrees * DEG2RAD ), 520 | leftTan: Math.tan( fov.leftDegrees * DEG2RAD ), 521 | rightTan: Math.tan( fov.rightDegrees * DEG2RAD ) 522 | }; 523 | 524 | return fovPortToProjection( fovPort, rightHanded, zNear, zFar ); 525 | 526 | } 527 | 528 | }; 529 | -------------------------------------------------------------------------------- /dependencies/vr/ViveController.js: -------------------------------------------------------------------------------- 1 | THREE.ViveController = function ( id ) { 2 | 3 | THREE.Object3D.call( this ); 4 | 5 | var gamepad; 6 | 7 | this.getGamepad = function () { return gamepad; }; 8 | this.matrixAutoUpdate = false; 9 | this.standingMatrix = new THREE.Matrix4(); 10 | 11 | var scope = this; 12 | 13 | function update() { 14 | 15 | requestAnimationFrame( update ); 16 | 17 | gamepad = navigator.getGamepads()[ id ]; 18 | 19 | if ( gamepad !== undefined && gamepad.pose !== null ) { 20 | 21 | var pose = gamepad.pose; 22 | 23 | scope.position.fromArray( pose.position ); 24 | scope.quaternion.fromArray( pose.orientation ); 25 | scope.matrix.compose( scope.position, scope.quaternion, scope.scale ); 26 | scope.matrix.multiplyMatrices( scope.standingMatrix, scope.matrix ); 27 | scope.matrixWorldNeedsUpdate = true; 28 | 29 | scope.visible = true; 30 | 31 | } else { 32 | 33 | scope.visible = false; 34 | 35 | } 36 | 37 | } 38 | 39 | update(); 40 | 41 | }; 42 | 43 | THREE.ViveController.prototype = Object.create( THREE.Object3D.prototype ); 44 | THREE.ViveController.prototype.constructor = THREE.ViveController; 45 | -------------------------------------------------------------------------------- /dependencies/vr/WebVR.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author mrdoob / http://mrdoob.com 3 | * Based on @tojiro's vr-samples-utils.js 4 | */ 5 | 6 | var WEBVR = { 7 | 8 | isLatestAvailable: function () { 9 | 10 | return navigator.getVRDisplays !== undefined; 11 | 12 | }, 13 | 14 | isAvailable: function () { 15 | 16 | return navigator.getVRDisplays !== undefined || navigator.getVRDevices !== undefined; 17 | 18 | }, 19 | 20 | getMessage: function () { 21 | 22 | var message; 23 | 24 | if ( navigator.getVRDisplays ) { 25 | 26 | navigator.getVRDisplays().then( function ( displays ) { 27 | 28 | if ( displays.length === 0 ) message = 'WebVR supported, but no VRDisplays found.'; 29 | 30 | } ); 31 | 32 | } else if ( navigator.getVRDevices ) { 33 | 34 | message = 'Your browser supports WebVR but not the latest version. See webvr.info for more info.'; 35 | 36 | } else { 37 | 38 | message = 'Your browser does not support WebVR. See webvr.info for assistance.'; 39 | 40 | } 41 | 42 | if ( message !== undefined ) { 43 | 44 | var container = document.createElement( 'div' ); 45 | container.style.position = 'absolute'; 46 | container.style.left = '0'; 47 | container.style.top = '0'; 48 | container.style.right = '0'; 49 | container.style.zIndex = '999'; 50 | container.align = 'center'; 51 | 52 | var error = document.createElement( 'div' ); 53 | error.style.fontFamily = 'sans-serif'; 54 | error.style.fontSize = '16px'; 55 | error.style.fontStyle = 'normal'; 56 | error.style.lineHeight = '26px'; 57 | error.style.backgroundColor = '#fff'; 58 | error.style.color = '#000'; 59 | error.style.padding = '10px 20px'; 60 | error.style.margin = '50px'; 61 | error.style.display = 'inline-block'; 62 | error.innerHTML = message; 63 | container.appendChild( error ); 64 | 65 | return container; 66 | 67 | } 68 | 69 | }, 70 | 71 | getButton: function ( effect ) { 72 | 73 | var button = document.createElement( 'button' ); 74 | button.style.position = 'absolute'; 75 | button.style.left = 'calc(50% - 50px)'; 76 | button.style.bottom = '20px'; 77 | button.style.width = '100px'; 78 | button.style.border = '0'; 79 | button.style.padding = '8px'; 80 | button.style.cursor = 'pointer'; 81 | button.style.backgroundColor = '#000'; 82 | button.style.color = '#fff'; 83 | button.style.fontFamily = 'sans-serif'; 84 | button.style.fontSize = '13px'; 85 | button.style.fontStyle = 'normal'; 86 | button.style.textAlign = 'center'; 87 | button.style.zIndex = '999'; 88 | button.textContent = 'ENTER VR'; 89 | button.onclick = function() { 90 | 91 | effect.isPresenting ? effect.exitPresent() : effect.requestPresent(); 92 | 93 | }; 94 | 95 | window.addEventListener( 'vrdisplaypresentchange', function ( event ) { 96 | 97 | button.textContent = effect.isPresenting ? 'EXIT VR' : 'ENTER VR'; 98 | 99 | }, false ); 100 | 101 | return button; 102 | 103 | } 104 | 105 | }; 106 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | OBJ 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 |
39 |
40 |
41 | 42 | 43 | Load OBJ
44 | Load Material
45 |
46 | Show/Hide Instructions

47 | Instructions: load obj, then hit load material - either select one img file or select a mtl and its dds files at once - the dds and mtl files must be in the same directory.

48 | Lightning bolt icons start auto optimization process, with step size indicated to the right of each icon. Press spacebar to cancel optimization.

49 | To change webcam input (Chrome): go to Preferences > Show Advanced Settings... > Privacy > Content Settings... > Camera and select from dropdown.

50 | To setup Node server (mac instructions): 51 | install node: brew install node
52 | navigate to the 'node' folder in this app's file directory in the Terminal: cd node
53 | install socket.io: npm install socket.io
54 | install SerialPort: npm install serialport
55 | run node server: node nodeServer.js (do this from inside the node directory in the app)
56 | select serial port from dropdown below
57 |
58 | To run in VR mode: run in a browser that supports WebVR API (tested in Chromium experimental build on Windows). Select "Enter VR" when button appears. 59 |

60 |
61 | 62 |
brightness 63 |
64 | 65 |
66 |
opacity 67 |
68 | 69 |
70 |
71 | 72 | Current Port:  not connected
73 | 78 |
rotation 79 |
80 | 81 |

82 | Rotation Zero:
83 |
rotation zero 84 |
85 | 86 |

87 |   Show Origin 88 |
X origin 89 |
90 | 91 |
92 | 93 |
Z origin 94 |
95 | 96 |
97 | 98 |

99 | 100 |
scale 101 |
102 | 103 |
104 | 105 |

106 | Outline:
107 |   Show Outline 108 |
offset 109 |
110 | 111 |
112 |
width 113 |
114 | 115 |
116 | 117 |

118 | 119 | Play/Pause Camera 120 | 121 |
122 | 123 |
124 |
threshold 125 |
126 | 127 |

128 | Show/Hide Occlusion Background 129 |
width 130 |
131 | 132 |
133 |
height 134 |
135 | 136 |
137 |
x 138 |
139 | 140 |
141 |
y 142 |
143 | 144 |
145 | 146 |
147 | 148 |
149 | 150 |
151 |
152 | 153 |
154 | 155 | Perspective 156 | Orthographic

157 | 158 |
159 | Perspective Camera Controls

160 | 161 |
162 | 163 |
164 | FOV (deg) 165 |
166 | 167 |
168 | 169 |
170 | Zoom 171 |
172 |
173 | 174 |

175 | 176 |
177 | Ortho Camera Controls

178 | 179 | 180 |
181 | 182 |
183 | FOV (deg) 184 |
185 | 186 |
187 | 188 |
189 | Zoom 190 |
191 |
192 | 193 |

194 | 195 | Camera Pos (XYZ):
196 |
197 | X 198 |
199 | 200 |
201 |
202 | Y 203 |
204 | 205 |
206 |
207 | Z 208 |
209 | 210 |


211 | 212 | Camera Roll:
213 |
214 | roll 215 |
216 | 217 |

218 | 219 | Camera Look At (XYZ):
220 |
221 | X 222 |
223 | 224 |
225 |
226 | Y 227 |
228 | 229 |
230 |
231 | Z   232 |
233 |
234 |   Show Crosshairs 235 | 236 |
237 |
238 |
239 | 240 | 241 | 242 | -------------------------------------------------------------------------------- /js/controls.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by ghassaei on 9/27/16. 3 | */ 4 | 5 | var brightness = 255; 6 | var opacity = 1; 7 | var rotation = 0; 8 | var rotationZero = 1.727; 9 | var geoOffset = new THREE.Vector3(-0.462,0,-0.18); 10 | var scale = 1; 11 | 12 | var cameraRotation = 0; 13 | 14 | var perspecitveFOV = 21.99; 15 | var perspectiveZoom = 1.02; 16 | 17 | var orthoFOV = 20; 18 | var orthoZoom = 1.02; 19 | 20 | var isPerspective = true; 21 | 22 | var cameraPosition = new THREE.Vector3(75.5, 94.5, 938); 23 | var lookAt = new THREE.Vector3(2.14,101.93,0); 24 | 25 | var sliderInputs = []; 26 | var currentValues = []; 27 | 28 | function initControls(ambientLight){ 29 | 30 | window.addEventListener("keyup", function(e){ 31 | // console.log(e.keyCode); 32 | if (e.keyCode == 72){//h 33 | if ($("#controls").is(":visible")) { 34 | $("#controls").fadeOut(); 35 | $("#cameraControls").fadeOut(); 36 | } 37 | else { 38 | $("#controls").fadeIn(); 39 | $("#cameraControls").fadeIn(); 40 | } 41 | } else if (e.keyCode == 70){ 42 | toggleFullScreen(); 43 | } else if (e.keyCode == 32){ 44 | if (optimizer.isRunning()) optimizer.pause(); 45 | } 46 | }, true); 47 | 48 | setSliderInput("#cameraRotation", cameraRotation, 0, 2*Math.PI, 0.01, function(val){ 49 | cameraRotation = val; 50 | perspectiveCamera.rotation.z = cameraRotation; 51 | orthoCamera.rotation.z = cameraRotation; 52 | render(); 53 | }); 54 | 55 | function toggleFullScreen() { 56 | if ((document.fullScreenElement && document.fullScreenElement !== null) || // alternative standard method 57 | (!document.mozFullScreen && !document.webkitIsFullScreen)) { // current working methods 58 | if (document.documentElement.requestFullScreen) { 59 | document.documentElement.requestFullScreen(); 60 | } else if (document.documentElement.mozRequestFullScreen) { 61 | document.documentElement.mozRequestFullScreen(); 62 | } else if (document.documentElement.webkitRequestFullScreen) { 63 | document.documentElement.webkitRequestFullScreen(Element.ALLOW_KEYBOARD_INPUT); 64 | } 65 | } else { 66 | if (document.cancelFullScreen) { 67 | document.cancelFullScreen(); 68 | } else if (document.mozCancelFullScreen) { 69 | document.mozCancelFullScreen(); 70 | } else if (document.webkitCancelFullScreen) { 71 | document.webkitCancelFullScreen(); 72 | } 73 | } 74 | } 75 | 76 | setSliderInput("#brightness", brightness, 0, 255, 1, function(val){ 77 | brightness = val; 78 | ambientLight.color.setRGB(brightness/255, brightness/255, brightness/255); 79 | render(); 80 | }); 81 | setSliderInput("#opacity", opacity, 0, 1, 0.01, function(val){ 82 | opacity = val; 83 | if (mesh) { 84 | mesh.children[0].material.opacity = val; 85 | } 86 | render(); 87 | }); 88 | 89 | setSliderInput("#rotationZero", rotationZero, 0, 2*Math.PI, 2*Math.PI/400, function(val){ 90 | rotationZero = val; 91 | if (mesh) { 92 | mesh.rotation.set(0,rotationZero + rotation,0); 93 | render(); 94 | } 95 | }); 96 | 97 | socket.on("dataIn", function(data){ 98 | var json = JSON.parse(data); 99 | if (json.sr && json.sr.posx !== undefined){ 100 | rotation = json.sr.posx; 101 | mesh.rotation.set(0,rotationZero + rotation,0); 102 | render(); 103 | } 104 | }); 105 | setSliderInput("#rotation", rotation, 0, 2*Math.PI, 0.01, function(val){ 106 | if (mesh) { 107 | socket.emit('rotation', "g0 x" + val); 108 | } 109 | render(); 110 | }); 111 | 112 | setSliderInput("#rotationX", geoOffset.x, -1, 1, 0.01, function(val){ 113 | geoOffset.x = val; 114 | if (mesh) { 115 | var scalingFactor = 0.09; 116 | mesh.children[0].position.x = mesh.children[0].geometry.boundingBox.max.x*scalingFactor*val; 117 | } 118 | render(); 119 | }); 120 | 121 | setSliderInput("#rotationZ", geoOffset.z, -1, 1, 0.01, function(val){ 122 | geoOffset.z = val; 123 | if (mesh) { 124 | var scalingFactor = 0.09; 125 | mesh.children[0].position.z = mesh.children[0].geometry.boundingBox.max.z*scalingFactor*val; 126 | } 127 | render(); 128 | }); 129 | 130 | setLogSliderInput("#scale", scale, 0.0001, 10, 0.01, function(val){ 131 | scale = val; 132 | if (mesh) mesh.scale.set(scale,scale,scale); 133 | render(); 134 | }); 135 | 136 | //camera controls 137 | setSliderInput("#fovPerspective", perspecitveFOV, 1, 180, 0.01, function(val){ 138 | perspecitveFOV = val; 139 | perspectiveCamera.fov = val; 140 | perspectiveCamera.updateProjectionMatrix(); 141 | render(); 142 | }); 143 | 144 | setSliderInput("#zoomPerspective", perspectiveZoom, 0.001, 20, 0.01, function(val){ 145 | perspectiveZoom = val; 146 | perspectiveCamera.zoom = val; 147 | perspectiveCamera.updateProjectionMatrix(); 148 | render(); 149 | }); 150 | 151 | 152 | setSliderInput("#fovOrtho", orthoFOV, 1, 180, 0.01, function(val){ 153 | orthoFOV = val; 154 | orthoCamera.fov = val; 155 | orthoCamera.updateProjectionMatrix(); 156 | render(); 157 | }); 158 | 159 | setSliderInput("#zoomOrtho", orthoZoom, 0.001, 20, 0.01, function(val){ 160 | orthoZoom = val; 161 | orthoCamera.zoom = val; 162 | orthoCamera.updateProjectionMatrix(); 163 | render(); 164 | }); 165 | 166 | if (isPerspective) $("input:radio[value=perspective]").prop('checked', true); 167 | else $("input:radio[value=ortho]").prop('checked', true); 168 | changeCamera(); 169 | 170 | $("input:radio").change(function(){ 171 | isPerspective = $("input:radio[value=perspective]").prop('checked'); 172 | changeCamera(); 173 | render(); 174 | }); 175 | 176 | perspectiveCamera.position.set(cameraPosition.x, cameraPosition.y, cameraPosition.z); 177 | orthoCamera.position.set(cameraPosition.x, cameraPosition.y, cameraPosition.z); 178 | setSliderInput("#cameraX", cameraPosition.x, -100, 100, 0.01, function(val){ 179 | cameraPosition.x = val; 180 | perspectiveCamera.position.x = val; 181 | orthoCamera.position.x = val; 182 | perspectiveCamera.lookAt(lookAt); 183 | orthoCamera.lookAt(lookAt); 184 | perspectiveCamera.rotation.z = cameraRotation; 185 | orthoCamera.rotation.z = cameraRotation; 186 | render(); 187 | }); 188 | setSliderInput("#cameraY", cameraPosition.y, -300, 300, 0.01, function(val){ 189 | cameraPosition.y = val; 190 | perspectiveCamera.position.y = val; 191 | orthoCamera.position.y = val; 192 | perspectiveCamera.lookAt(lookAt); 193 | orthoCamera.lookAt(lookAt); 194 | perspectiveCamera.rotation.z = cameraRotation; 195 | orthoCamera.rotation.z = cameraRotation; 196 | render(); 197 | }); 198 | setSliderInput("#cameraZ", cameraPosition.z, 0, 1000, 0.01, function(val){ 199 | cameraPosition.z = val; 200 | perspectiveCamera.position.z = val; 201 | orthoCamera.position.z = val; 202 | perspectiveCamera.lookAt(lookAt); 203 | orthoCamera.lookAt(lookAt); 204 | perspectiveCamera.rotation.z = cameraRotation; 205 | orthoCamera.rotation.z = cameraRotation; 206 | render(); 207 | }); 208 | 209 | perspectiveCamera.lookAt(lookAt); 210 | orthoCamera.lookAt(lookAt); 211 | setSliderInput("#lookAtX", lookAt.x, -20, 20, 0.01, function(val){ 212 | lookAt.x = val; 213 | perspectiveCamera.lookAt(lookAt); 214 | orthoCamera.lookAt(lookAt); 215 | perspectiveCamera.rotation.z = cameraRotation; 216 | orthoCamera.rotation.z = cameraRotation; 217 | render(); 218 | }); 219 | setSliderInput("#lookAtY", lookAt.y, -10, 300, 0.01, function(val){ 220 | lookAt.y = val; 221 | perspectiveCamera.lookAt(lookAt); 222 | orthoCamera.lookAt(lookAt); 223 | perspectiveCamera.rotation.z = cameraRotation; 224 | orthoCamera.rotation.z = cameraRotation; 225 | render(); 226 | }); 227 | setInput("#lookAtZ", lookAt.z, function(val){ 228 | lookAt.z = val; 229 | perspectiveCamera.lookAt(lookAt); 230 | orthoCamera.lookAt(lookAt); 231 | perspectiveCamera.rotation.z = cameraRotation; 232 | orthoCamera.rotation.z = cameraRotation; 233 | render(); 234 | }); 235 | 236 | 237 | $('#showCrosshairs').prop('checked', true); 238 | $('#showCrosshairs').change(function() { 239 | var $crosshairs = $("#crosshairs"); 240 | if (this.checked) $crosshairs.show(); 241 | else $crosshairs.hide(); 242 | }); 243 | 244 | $('#showGeoOrigin').prop('checked', false); 245 | origin.visible = false; 246 | $('#showGeoOrigin').change(function() { 247 | origin.visible = this.checked; 248 | render(); 249 | }); 250 | 251 | 252 | /* When the user clicks on the button, 253 | toggle between hiding and showing the dropdown content */ 254 | $("#serialDropdown").click(function(e) { 255 | e.preventDefault(); 256 | document.getElementById("myDropdown").classList.toggle("show"); 257 | }); 258 | 259 | // Close the dropdown menu if the user clicks outside of it 260 | window.onclick = function(event) { 261 | if (!event.target.matches('.dropbtn')) { 262 | 263 | var dropdowns = document.getElementsByClassName("dropdown-content"); 264 | var i; 265 | for (i = 0; i < dropdowns.length; i++) { 266 | var openDropdown = dropdowns[i]; 267 | if (openDropdown.classList.contains('show')) { 268 | openDropdown.classList.remove('show'); 269 | } 270 | } 271 | } 272 | }; 273 | 274 | $("#showInstructions").click(function(e){ 275 | e.preventDefault(); 276 | if ($("#instructions").is(":visible")){ 277 | $("#instructions").hide(); 278 | } else { 279 | $("#instructions").show(); 280 | } 281 | }) 282 | } 283 | 284 | function showWarn(text){ 285 | $("#warning").show(); 286 | $("#warning>div").html(text); 287 | } 288 | 289 | function changeCamera(){ 290 | if (isPerspective){ 291 | $("#orthoControls").css("opacity","0.4"); 292 | $("#perspectiveControls").css("opacity","1"); 293 | } else { 294 | $("#perspectiveControls").css("opacity","0.4"); 295 | $("#orthoControls").css("opacity","1"); 296 | } 297 | } 298 | 299 | function setLogSliderInput(id, val, min, max, incr, callback){ 300 | 301 | var _scale = (Math.log(max)-Math.log(min)) / (max-min); 302 | 303 | var slider = $(id+">div").slider({ 304 | orientation: 'horizontal', 305 | range: false, 306 | value: (Math.log(val)-Math.log(min)) / _scale + min, 307 | min: min, 308 | max: max, 309 | step: incr 310 | }); 311 | 312 | var $input = $(id+">input"); 313 | $input.change(function(){ 314 | var val = $input.val(); 315 | if ($input.hasClass("int")){ 316 | if (isNaN(parseInt(val))) return; 317 | val = parseInt(val); 318 | } else { 319 | if (isNaN(parseFloat(val))) return; 320 | val = parseFloat(val); 321 | } 322 | 323 | var min = slider.slider("option", "min"); 324 | if (val < min) val = min; 325 | if (val > max) val = max; 326 | $input.val(val); 327 | slider.slider('value', (Math.log(val)-Math.log(min)) / _scale + min); 328 | callback(val, id); 329 | }); 330 | $input.val(val); 331 | slider.on("slide", function(e, ui){ 332 | var val = ui.value; 333 | val = Math.exp(Math.log(min) + _scale*(val-min)); 334 | $input.val(val.toFixed(4)); 335 | callback(val, id); 336 | }); 337 | } 338 | 339 | function setSliderInput(el, val, min, max, step, callback){ 340 | currentValues[el] = val; 341 | var slider = $(el+">.flat-slider").slider({ 342 | orientation: 'horizontal', 343 | range: false, 344 | value: val, 345 | min: min, 346 | max: max, 347 | step: step 348 | }); 349 | var $input = $(el+">input"); 350 | sliderInputs[el] = function(_manualVal){ 351 | currentValues[el] = _manualVal; 352 | $input.val(_manualVal); 353 | slider.slider('value', _manualVal); 354 | callback(_manualVal); 355 | }; 356 | $input.val(val); 357 | slider.on("slide", function(e, ui){ 358 | var val = ui.value; 359 | currentValues[el] = val; 360 | callback(val); 361 | $input.val(val); 362 | }); 363 | 364 | $input.change(function(){ 365 | var val = $input.val(); 366 | if (isNaN(val)){ 367 | showWarn("val is NaN"); 368 | console.warn("val is NaN"); 369 | return; 370 | } 371 | slider.slider('value', val); 372 | currentValues[el] = val; 373 | callback(val); 374 | }); 375 | return slider; 376 | } 377 | 378 | function setSliderStopInput(el, val, min, max, step, callback){ 379 | var slider = $(el+">.flat-slider").slider({ 380 | orientation: 'horizontal', 381 | range: false, 382 | value: val, 383 | min: min, 384 | max: max, 385 | step: step 386 | }); 387 | var $input = $(el+">input"); 388 | $input.val(val); 389 | slider.on("slidestop", function(){ 390 | var val = slider.slider('value'); 391 | callback(val); 392 | $input.val(val); 393 | }); 394 | $input.change(function(){ 395 | var val = $input.val(); 396 | if (isNaN(val)){ 397 | showWarn("val is NaN"); 398 | console.warn("val is NaN"); 399 | return; 400 | } 401 | slider.slider('value', val); 402 | callback(val); 403 | }); 404 | } 405 | 406 | function setInput(el, val, callback){ 407 | var $input = $(el+">input"); 408 | $input.val(val); 409 | $input.change(function(){ 410 | var val = parseFloat($input.val()); 411 | if (isNaN(val)){ 412 | showWarn("val is NaN"); 413 | console.warn("val is NaN"); 414 | return; 415 | } 416 | callback(val); 417 | }); 418 | } 419 | -------------------------------------------------------------------------------- /js/fitness.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by amandaghassaei on 12/2/16. 3 | */ 4 | 5 | function initFitness(){ 6 | 7 | var _mesh; 8 | var fitnessMesh; 9 | var shadowMesh; 10 | 11 | var outlineWidth = 5; 12 | var outlineOffset = 10; 13 | 14 | function getOutlineOffset(){ 15 | return outlineOffset; 16 | } 17 | 18 | var vertexShader = 19 | "uniform float offset;" + 20 | "void main(){"+ 21 | "vec4 pos = modelViewMatrix * vec4( position + normal * offset, 1.0 );"+ 22 | "gl_Position = projectionMatrix * pos;"+ 23 | "}\n"; 24 | 25 | var fragmentShaderColor = 26 | "void main(){"+ 27 | "gl_FragColor = vec4( 1.0, 0.0, 0.0, 1.0 );"+ 28 | "}\n"; 29 | var fragmentShaderBlack = 30 | "void main(){"+ 31 | "gl_FragColor = vec4( 0.0, 0.0, 0.0, 1.0 );"+ 32 | "}\n"; 33 | 34 | var outlineMaterial = new THREE.ShaderMaterial({ 35 | uniforms: { 36 | offset: {type: 'f', value: outlineOffset+outlineWidth} 37 | }, 38 | vertexShader: vertexShader, 39 | fragmentShader: fragmentShaderColor 40 | }); 41 | var shadowMaterial = new THREE.ShaderMaterial({ 42 | uniforms: { 43 | offset: {type: 'f', value: outlineOffset} 44 | }, 45 | vertexShader: vertexShader, 46 | fragmentShader: fragmentShaderBlack 47 | }); 48 | 49 | function setMesh(mesh){ 50 | _mesh = mesh; 51 | if (fitnessMesh) outlineScene1.remove(fitnessMesh); 52 | if (shadowMesh) outlineScene2.remove(shadowMesh); 53 | fitnessMesh = mesh.clone(); 54 | shadowMesh = mesh.clone(); 55 | outlineScene1.add(fitnessMesh); 56 | outlineScene2.add(shadowMesh); 57 | fitnessMesh.traverse( function ( child ) { 58 | if ( child instanceof THREE.Mesh ) { 59 | child.material = outlineMaterial; 60 | } 61 | }); 62 | shadowMesh.traverse( function ( child ) { 63 | if ( child instanceof THREE.Mesh ) { 64 | child.material = shadowMaterial; 65 | } 66 | }); 67 | } 68 | 69 | function sync(){ 70 | if (!fitnessMesh) return; 71 | fitnessMesh.position.set(_mesh.position.x, _mesh.position.y, _mesh.position.z); 72 | fitnessMesh.children[0].position.set(_mesh.children[0].position.x, _mesh.children[0].position.y, _mesh.children[0].position.z); 73 | fitnessMesh.scale.set(_mesh.scale.x, _mesh.scale.y, _mesh.scale.z); 74 | fitnessMesh.rotation.set(_mesh.rotation.x, _mesh.rotation.y, _mesh.rotation.z); 75 | shadowMesh.position.set(_mesh.position.x, _mesh.position.y, _mesh.position.z); 76 | shadowMesh.children[0].position.set(_mesh.children[0].position.x, _mesh.children[0].position.y, _mesh.children[0].position.z); 77 | shadowMesh.scale.set(_mesh.scale.x, _mesh.scale.y, _mesh.scale.z); 78 | shadowMesh.rotation.set(_mesh.rotation.x, _mesh.rotation.y, _mesh.rotation.z); 79 | 80 | } 81 | 82 | setSliderInput("#outlineWidth", outlineWidth, 0, 20, 0.01, function(val){ 83 | outlineWidth = val; 84 | outlineMaterial.uniforms.offset.value = outlineOffset + outlineWidth; 85 | outlineMaterial.uniforms.offset.needsUpdate = true; 86 | render(); 87 | }); 88 | setSliderInput("#outlineOffset", outlineOffset, 0, 100, 0.1, function(val){ 89 | outlineOffset = val; 90 | outlineMaterial.uniforms.offset.value = outlineOffset + outlineWidth; 91 | outlineMaterial.uniforms.offset.needsUpdate = true; 92 | shadowMaterial.uniforms.offset.value = outlineOffset; 93 | shadowMaterial.uniforms.offset.needsUpdate = true; 94 | render(); 95 | }); 96 | 97 | $('#showOutline').prop('checked', false); 98 | $('#showOutline').change(function() { 99 | setVisiblity(this.checked); 100 | render(); 101 | }); 102 | setVisiblity(false); 103 | 104 | function setVisiblity(state){ 105 | outlineMaterial.visible = state; 106 | shadowMaterial.visible = state; 107 | } 108 | 109 | function groupRegions(segmentNum, img_u8){ 110 | var numIter = 0; 111 | var solved = false; 112 | while (solved == false){ 113 | numIter++; 114 | var _solved = true; 115 | for (var i=0;i0) { 120 | neighbors.push(segmentNum[i-1]); 121 | if (i>=img_u8.cols+1){ 122 | neighbors.push(segmentNum[i-img_u8.cols-1]); 123 | } 124 | if (i>=img_u8.cols){ 125 | neighbors.push(segmentNum[i-img_u8.cols]); 126 | } 127 | if (i>=img_u8.cols-1){ 128 | neighbors.push(segmentNum[i-img_u8.cols+1]); 129 | } 130 | } 131 | if (i=0;k--){ 144 | if (neighbors[k]<0) neighbors.splice(k, 1); 145 | } 146 | var min = Math.min.apply(null, neighbors); 147 | if (min1000){ 154 | showWarn("exceeded 1000 iterations of segmentation"); 155 | console.warn("exceeded 1000 iterations of segmentation"); 156 | solved = true; 157 | } 158 | } 159 | var allSegments = []; 160 | for (var i=0;i0) { 190 | segmentNum[i] = i; 191 | } else { 192 | segmentNum[i] = -1; 193 | } 194 | } 195 | var allSegments = groupRegions(segmentNum, img_u8); 196 | 197 | //get segment areas 198 | var allAreas = calcAreas(segmentNum, allSegments); 199 | 200 | //find largest partition by area 201 | var maxSegment = Math.max.apply(null, allAreas); 202 | var loopIndex = allAreas.indexOf(maxSegment); 203 | var segment = allSegments[loopIndex]; 204 | 205 | //check for closed loop (two distinct non-segment regions) 206 | for (var i=0;i"; 85 | } 86 | $("#myDropdown").html(items); 87 | $(".portSelection").click(function(e){ 88 | e.preventDefault(); 89 | socket.emit("portName", $(e.target).html()); 90 | }); 91 | 92 | }); 93 | socket.on("portConnected", function(data){ 94 | socket.emit('rotation', "g0 x" + 0); 95 | }); 96 | 97 | initControls(ambient, mesh); 98 | webcam = initWebcam(); 99 | fitness = initFitness(); 100 | optimizer = initOptimizer(fitness); 101 | 102 | loadOBJ("data/ballerina/ballerina_orig_10percentwTex.obj"); 103 | loadImage("data/ballerina/ballerina_small.png"); 104 | } 105 | 106 | function handleFileSelectMaterials(evt) { 107 | var files = evt.target.files; // FileList object 108 | if (files.length < 1) { 109 | console.warn("no files"); 110 | return; 111 | } 112 | 113 | if (files.length == 1 && files[0].type.match('image.*')){ 114 | //load image as texture 115 | var file = files[0]; 116 | var reader = new FileReader(); 117 | reader.onload = function(){ 118 | return function(e) { 119 | loadImage(e.target.result); 120 | } 121 | }(file); 122 | reader.readAsDataURL(file); 123 | return; 124 | } 125 | 126 | var mtlFile = findMTLFile(files); 127 | if (!mtlFile) return; 128 | 129 | var ddsFiles = findDDSFiles(files); 130 | if (ddsFiles.length == 0) loadMTL(); 131 | for (var i=0;i= ddsFiles.length){ 146 | loadMTL(); 147 | } 148 | } 149 | 150 | function loadMTL(){ 151 | var reader = new FileReader(); 152 | reader.onload = function(){ 153 | return function(e) { 154 | loadMaterial(e.target.result, ddsURLS); 155 | } 156 | }(mtlFile); 157 | reader.readAsDataURL(mtlFile); 158 | } 159 | } 160 | function findMTLFile(files){ 161 | for (var i=0;iscalingFactor) scalingFactor = size.y; 281 | //if (size.z>scalingFactor) scalingFactor = size.z; 282 | //scalingFactor = 1/scalingFactor; 283 | var scalingFactor = 0.09; 284 | mesh.children[0].scale.set(scalingFactor, scalingFactor, scalingFactor); 285 | 286 | //put origin in screwhole 287 | mesh.children[0].position.y = mesh.children[0].geometry.boundingBox.max.y*scalingFactor; 288 | mesh.children[0].position.x = mesh.children[0].geometry.boundingBox.max.x*scalingFactor*geoOffset.x; 289 | mesh.children[0].position.z = mesh.children[0].geometry.boundingBox.max.z*scalingFactor*geoOffset.z; 290 | 291 | mesh.position.set(0,0,0); 292 | mesh.rotation.set(0,rotationZero + rotation,0); 293 | 294 | fitness.setMesh(mesh); 295 | 296 | updateIMGTexture(); 297 | scene.add( object ); 298 | render(); 299 | }, onProgress, onError ); 300 | } 301 | 302 | 303 | 304 | 305 | function onWindowResize() { 306 | perspectiveCamera.aspect = window.innerWidth / window.innerHeight; 307 | orthoCamera.aspect = window.innerWidth / window.innerHeight; 308 | renderer.setSize( window.innerWidth, window.innerHeight ); 309 | orthoCamera.left = -window.innerWidth / 2; 310 | orthoCamera.right = window.innerWidth / 2; 311 | orthoCamera.top = window.innerHeight / 2; 312 | orthoCamera.bottom = -window.innerHeight / 2; 313 | perspectiveCamera.updateProjectionMatrix(); 314 | orthoCamera.updateProjectionMatrix(); 315 | render(); 316 | } 317 | 318 | function render() { 319 | fitness.sync(); 320 | renderer.clear(); 321 | _render(outlineScene1); 322 | renderer.clearDepth(); 323 | _render(outlineScene2); 324 | renderer.clearDepth(); 325 | _render(scene); 326 | } 327 | 328 | function _render(_scene){ 329 | if (isPerspective) renderer.render(_scene, perspectiveCamera); 330 | else renderer.render(_scene, orthoCamera ); 331 | } -------------------------------------------------------------------------------- /js/optimize.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by amandaghassaei on 12/3/16. 3 | */ 4 | 5 | 6 | function initOptimizer(fitness){ 7 | 8 | var running = false; 9 | var angles = [0, Math.PI/2, Math.PI, 3*Math.PI/2]; 10 | 11 | var rotationZeroTol = 0.1; 12 | setInput("#rotationZeroTol", rotationZeroTol, function(val){ 13 | rotationZeroTol = val; 14 | }); 15 | var cameraTol = 1; 16 | setInput("#cameraTol", cameraTol, function(val){ 17 | cameraTol = val; 18 | }); 19 | var lookatTol = 1; 20 | setInput("#lookAtTol", lookatTol, function(val){ 21 | lookatTol = val; 22 | }); 23 | var cameraRotationTol = 0.1; 24 | setInput("#cameraRotationTol", cameraRotationTol, function(val){ 25 | cameraRotationTol = val; 26 | }); 27 | 28 | 29 | var originVis, crosshairVis; 30 | 31 | function optimize(params, stepSize){ 32 | 33 | webcam.pause(); 34 | 35 | if (!mesh) { 36 | showWarn("upload a mesh before starting optimization"); 37 | return; 38 | } 39 | mesh.visible = false; 40 | originVis = origin.visible; 41 | origin.visible = false; 42 | crosshairVis = $("#crosshairs").is(":visible"); 43 | $("#crosshairs").hide(); 44 | fitness.setVisiblity(true); 45 | $('#showOutline').prop('checked', true); 46 | render(); 47 | 48 | $("#controls").hide(); 49 | $("#cameraControls").hide(); 50 | $("#warning").hide(); 51 | 52 | running = true; 53 | 54 | socket.emit('rotation', "g0 x" + angles[0]); 55 | var bestFitnessAngles = []; 56 | 57 | setTimeout(function(){//waste time to make sure we get rotation done 58 | window.requestAnimationFrame(function(){ 59 | findInitialFitness(params, bestFitnessAngles, stepSize); 60 | }); 61 | }, 1000); 62 | 63 | 64 | } 65 | 66 | function findInitialFitness(params, bestFitnessAngles, stepSize){ 67 | sliderInputs['#outlineOffset'](0);//start at zero 68 | evaluate(function(initialFitness, initialOffset){ 69 | if (initialFitness == -1) { 70 | showWarn("bad initial fitness"); 71 | pause(); 72 | return; 73 | } 74 | bestFitnessAngles.push([initialFitness, initialOffset]); 75 | if (bestFitnessAngles.length == angles.length){ 76 | //move to zero angle 77 | socket.emit('rotation', "g0 x" + angles[0]); 78 | setTimeout(function(){//waste time to make sure we get rotation done 79 | gradient(params, bestFitnessAngles, stepSize); 80 | }, 1000); 81 | } else { 82 | //move to next angles 83 | socket.emit('rotation', "g0 x" + angles[bestFitnessAngles.length]); 84 | setTimeout(function(){//waste time to make sure we get rotation done 85 | findInitialFitness(params, bestFitnessAngles, stepSize); 86 | }, 500); 87 | } 88 | }, 0); 89 | 90 | } 91 | 92 | function moveParams(params, allFitnesses, bestFitnessAngles, stepSize){ 93 | 94 | var avgVector = []; 95 | for (var j = 0; j < params.length; j++) { 96 | avgVector.push(0); 97 | } 98 | 99 | for (var i=0;i oldData[0]) return true;//num pixels 191 | } 192 | return false; 193 | } 194 | 195 | function evaluate(callback, phase, bestStats){ 196 | if (!running) return; 197 | if (phase < 1){//render 198 | render(); 199 | webcam.getFrame(); 200 | setTimeout(function(){//waste time to make sure we get next webcam frame 201 | window.requestAnimationFrame(function(){ 202 | evaluate(callback, phase+1, bestStats); 203 | }); 204 | }, 500); 205 | } else { 206 | var _fitness = fitness.calcFitness(); 207 | var currentOffset = fitness.getOutlineOffset(); 208 | showWarn("offset: " + currentOffset + ", fitness: " + _fitness); 209 | if (_fitness < 0) { 210 | var nextOutlineOffset = currentOffset + 1; 211 | if (bestStats && nextOutlineOffset>bestStats[1]){//already inited 212 | callback(-1, nextOutlineOffset); 213 | return; 214 | } 215 | //looking for best stats 216 | if (nextOutlineOffset > 30){ 217 | callback(_fitness, currentOffset); 218 | return; 219 | } 220 | sliderInputs['#outlineOffset'](nextOutlineOffset); 221 | window.requestAnimationFrame(function(){ 222 | evaluate(callback, 0, bestStats); 223 | }); 224 | } 225 | else callback(_fitness, currentOffset); 226 | } 227 | } 228 | 229 | function pause(){ 230 | running = false; 231 | webcam.start(); 232 | $("#controls").show(); 233 | $("#cameraControls").show(); 234 | if (mesh) mesh.visible = true; 235 | origin.visible = originVis; 236 | if (crosshairVis) $("#crosshairs").show(); 237 | render(); 238 | } 239 | 240 | function isRunning(){ 241 | return running; 242 | } 243 | 244 | 245 | $(".optimize").click(function(e){ 246 | e.preventDefault(); 247 | var $target = $(e.target); 248 | var id = $target.parent().data("id"); 249 | var params = []; 250 | var stepSize; 251 | if (id == "camera"){ 252 | params.push("cameraX"); 253 | params.push("cameraY"); 254 | params.push("cameraZ"); 255 | stepSize = cameraTol; 256 | } else if (id == "lookAt"){ 257 | params.push("lookAtX"); 258 | params.push("lookAtY"); 259 | stepSize = lookatTol; 260 | } else if (id == "rotationZero") { 261 | params.push("rotationZero"); 262 | stepSize = rotationZeroTol; 263 | } else if (id == "cameraRotation") { 264 | params.push("cameraRotation"); 265 | stepSize = cameraRotationTol; 266 | } else { 267 | showWarn("unknown optimization parameter " + id); 268 | console.warn("unknown optimization parameter " + id); 269 | return; 270 | } 271 | optimize(params, stepSize); 272 | }); 273 | 274 | return { 275 | pause: pause, 276 | isRunning: isRunning 277 | } 278 | } -------------------------------------------------------------------------------- /js/webcam.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by amandaghassaei on 12/3/16. 3 | */ 4 | 5 | 6 | function initWebcam(){ 7 | 8 | var play = true; 9 | 10 | function pause(){ 11 | play = false; 12 | } 13 | function start(){ 14 | if (play == true) return; 15 | play = true; 16 | compatibility.requestAnimationFrame(tick); 17 | } 18 | 19 | var redThreshold = 200; 20 | var antiOcclusionX = 161; 21 | var antiOcclusionY = 185; 22 | var antiOcclusionWidth = 86; 23 | var antiOcclusionHeight = 42; 24 | 25 | var video = document.getElementById('webcam'); 26 | var canvas = document.getElementById('canvas'); 27 | try { 28 | var attempts = 0; 29 | var readyListener = function(event) { 30 | findVideoSize(); 31 | }; 32 | var findVideoSize = function() { 33 | if(video.videoWidth > 0 && video.videoHeight > 0) { 34 | video.removeEventListener('loadeddata', readyListener); 35 | onDimensionsReady(video.videoWidth, video.videoHeight); 36 | } else { 37 | if(attempts < 10) { 38 | attempts++; 39 | setTimeout(findVideoSize, 200); 40 | } else { 41 | onDimensionsReady(320, 240); 42 | } 43 | } 44 | }; 45 | var onDimensionsReady = function(width, height) { 46 | demo_app(width, height); 47 | compatibility.requestAnimationFrame(tick); 48 | }; 49 | 50 | video.addEventListener('loadeddata', readyListener); 51 | 52 | compatibility.getUserMedia({video: true}, function(stream) { 53 | try { 54 | video.src = compatibility.URL.createObjectURL(stream); 55 | } catch (error) { 56 | video.src = stream; 57 | } 58 | setTimeout(function() { 59 | video.play(); 60 | demo_app(); 61 | 62 | compatibility.requestAnimationFrame(tick); 63 | }, 500); 64 | }, function (error) { 65 | console.warn("WebRTC not available"); 66 | }); 67 | } catch (error) { 68 | console.warn("error: " + error); 69 | } 70 | 71 | 72 | var gui,ctx,canvasWidth,canvasHeight; 73 | var img_u8; 74 | 75 | function demo_app(videoWidth, videoHeight) { 76 | canvasWidth = canvas.width; 77 | canvasHeight = canvas.height; 78 | ctx = canvas.getContext('2d'); 79 | 80 | ctx.fillStyle = "rgb(0,255,0)"; 81 | ctx.strokeStyle = "rgb(0,255,0)"; 82 | 83 | img_u8 = new jsfeat.matrix_t(320, 240, jsfeat.U8_t | jsfeat.C1_t); 84 | 85 | } 86 | 87 | function tick() { 88 | if (!play) return; 89 | compatibility.requestAnimationFrame(tick); 90 | _getFrame(); 91 | } 92 | 93 | function getFrame(){ 94 | return _getFrame(true); 95 | } 96 | 97 | function _getFrame(forEval){ 98 | if (video.readyState === video.HAVE_ENOUGH_DATA) { 99 | ctx.drawImage(video, 0, 0, 320, 240); 100 | var imageData = ctx.getImageData(0, 0, 320, 240); 101 | 102 | redChannel(imageData.data, 320, 240, img_u8); 103 | 104 | //add anti-occlusion 105 | var halfWidth = Math.floor(antiOcclusionWidth/2); 106 | var halfHeight = Math.floor(antiOcclusionHeight/2); 107 | for (var y=antiOcclusionY-halfHeight;y=img_u8.cols || y>=img_u8.rows) continue; 111 | if (img_u8.data[index]>0) continue; 112 | img_u8.data[index] = 100; 113 | } 114 | } 115 | 116 | // render result back to canvas 117 | var data_u32 = new Uint32Array(imageData.data.buffer); 118 | var alpha = (0xff << 24); 119 | var i = img_u8.cols * img_u8.rows, pix = 0; 120 | while (--i >= 0) { 121 | pix = img_u8.data[i]; 122 | data_u32[i] = alpha | (pix << 16) | (pix << 8) | pix; 123 | } 124 | 125 | ctx.putImageData(imageData, 0, 0); 126 | 127 | if (forEval) return img_u8; 128 | } else if (forEval) { 129 | showWarn("video not ready"); 130 | console.warn("video not ready"); 131 | } 132 | } 133 | 134 | function redChannel(src, w, h, dst, code) { 135 | // this is default image data representation in browser 136 | if (typeof code === "undefined") { code = jsfeat.COLOR_RGBA2GRAY; } 137 | var x=0, y=0, i=0, j=0, ir=0,jr=0; 138 | var coeff_r = 4899, coeff_g = 9617, coeff_b = 1868, cn = 4; 139 | 140 | var cn2 = cn<<1, cn3 = (cn*3)|0; 141 | 142 | dst.resize(w, h, 1); 143 | var dst_u8 = dst.data; 144 | 145 | var thresh = redThreshold; 146 | 147 | for(y = 0; y < h; ++y, j+=w, i+=w*cn) { 148 | for(x = 0, ir = i, jr = j; x <= w-4; x+=4, ir+=cn<<2, jr+=4) { 149 | dst_u8[jr] = src[ir] > thresh ? 255 : 0; 150 | dst_u8[jr + 1] = src[ir+cn] > thresh ? 255 : 0; 151 | dst_u8[jr + 2] = src[ir+cn2] > thresh ? 255 : 0; 152 | dst_u8[jr + 3] = src[ir+cn3] > thresh ? 255 : 0; 153 | } 154 | for (; x < w; ++x, ++jr, ir+=cn) { 155 | dst_u8[jr] = src[ir] > thresh ? src[ir] : 0; 156 | } 157 | } 158 | } 159 | 160 | $(window).on('beforeunload ',function() { 161 | video.pause(); 162 | video.src=null; 163 | }); 164 | 165 | setSliderInput("#redThreshold", redThreshold, 0, 255, 1, function(val){ 166 | redThreshold = val; 167 | }); 168 | setSliderInput("#antiOcclusionX", antiOcclusionX, 0, 319, 1, function(val){ 169 | antiOcclusionX = val; 170 | }); 171 | setSliderInput("#antiOcclusionY", antiOcclusionY, 0, 239, 1, function(val){ 172 | antiOcclusionY = val; 173 | }); 174 | setSliderInput("#antiOcclusionWidth", antiOcclusionWidth, 0, 320, 1, function(val){ 175 | antiOcclusionWidth = val; 176 | }); 177 | setSliderInput("#antiOcclusionHeight", antiOcclusionHeight, 0, 240, 1, function(val){ 178 | antiOcclusionHeight = val; 179 | }); 180 | 181 | var occlusionMode = false; 182 | $("#occlusionMode").click(function(e){ 183 | occlusionMode = !occlusionMode; 184 | if (occlusionMode){ 185 | scene.background = new THREE.Color(0xff0000); 186 | if (mesh) mesh.visible = false; 187 | } else { 188 | outlineScene1.background = new THREE.Color(0x000000); 189 | scene.background = null; 190 | if (mesh) mesh.visible = true; 191 | } 192 | render(); 193 | }); 194 | 195 | $("#playPauseWebcam").click(function(e){ 196 | e.preventDefault(); 197 | if (play) pause(); 198 | else start(); 199 | }); 200 | 201 | return { 202 | getFrame: getFrame, 203 | pause: pause, 204 | start: start 205 | } 206 | 207 | } -------------------------------------------------------------------------------- /node/nodeServer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by aghassaei on 6/17/15. 3 | */ 4 | 5 | var SerialPort = require('SerialPort'); 6 | 7 | var app = require('http').createServer(); 8 | var io = require('socket.io')(app); 9 | app.listen(8080); 10 | 11 | //defaults 12 | var portName = "/dev/cu.usbserial-DA00XAJE"; 13 | var currentPort = null; 14 | var baudRate = 115200; 15 | 16 | io.on('connection', function(socket){ 17 | 18 | var allPorts = []; 19 | refreshAvailablePorts(function(_allPorts, _portName, _baudRate){ 20 | if (checkThatPortExists(_portName)){ 21 | currentPort = changePort(_portName, _baudRate); 22 | } else { 23 | portName = _allPorts[0]; 24 | currentPort = changePort(portName, _baudRate); 25 | } 26 | console.log(allPorts); 27 | }); 28 | 29 | socket.on('baudRate', function(value){ 30 | refreshAvailablePorts(function(){ 31 | if (!checkThatPortExists(portName)) return; 32 | currentPort = changePort(portName, value); 33 | baudRate = value; 34 | }); 35 | }); 36 | 37 | socket.on('portName', function(value){ 38 | refreshAvailablePorts(function(){ 39 | if (!checkThatPortExists(value)) return; 40 | currentPort = changePort(value, baudRate); 41 | portName = value; 42 | }); 43 | }); 44 | 45 | socket.on('rotation', function(data){ 46 | console.log(data); 47 | outputData(data); 48 | }); 49 | 50 | function outputData(data){ 51 | io.emit('dataSent', data); 52 | data += '\n'; 53 | // console.log("Sending data: " + data); 54 | currentPort.write(new Buffer(data), function(err, res) { 55 | if (err) onPortError(err); 56 | }); 57 | // currentPort.write(new Buffer([parseInt(data)]));//write byte 58 | } 59 | 60 | socket.on('flush', function(){ 61 | if (currentPort) currentPort.flush(function(){ 62 | console.log("port " + portName + " flushed"); 63 | }); 64 | }); 65 | 66 | socket.on('refreshPorts', function(){ 67 | console.log("refreshing ports list"); 68 | allPorts = refreshAvailablePorts(); 69 | }); 70 | 71 | function checkThatPortExists(_portName){ 72 | if (allPorts.indexOf(_portName) < 0) { 73 | onPortError("no available port called " + _portName); 74 | return false; 75 | } 76 | return true; 77 | } 78 | 79 | 80 | 81 | 82 | function refreshAvailablePorts(callback){ 83 | var _allPorts = []; 84 | SerialPort.list(function(err, ports){ 85 | ports.forEach(function(port) { 86 | _allPorts.push(port.comName); 87 | }); 88 | 89 | allPorts = _allPorts; 90 | 91 | if (!portName && _allPorts.length>0) portName = _allPorts[0]; 92 | if (callback) callback(allPorts, portName, baudRate); 93 | 94 | io.emit('connected', { 95 | baudRate: baudRate, 96 | portName: portName, 97 | availablePorts: _allPorts 98 | }); 99 | }); 100 | } 101 | 102 | function initPort(_portName, _baudRate){ 103 | 104 | console.log("initing port " + _portName + " at " + _baudRate); 105 | var port = new SerialPort(_portName, { 106 | baudRate: _baudRate, 107 | parser: SerialPort.parsers.readline("\n"), 108 | autoOpen: false 109 | // parser: SerialPort.parsers.raw 110 | }); 111 | 112 | port.open(function(error){ 113 | if (error) { 114 | onPortError(error); 115 | currentPort = null; 116 | return; 117 | } 118 | onPortOpen(_portName, _baudRate); 119 | port.on('data', onPortData); 120 | port.on('close', onPortClose); 121 | port.on('error', onPortError); 122 | }); 123 | return port; 124 | } 125 | 126 | function changePort(_portName, _baudRate){ 127 | console.log("change"); 128 | if (!_portName) { 129 | onPortError("no port name specified"); 130 | return null; 131 | } 132 | if (currentPort) { 133 | var oldBaud = baudRate; 134 | var oldName = portName; 135 | console.log("disconnecting port " + oldName + " at " + oldBaud); 136 | if (currentPort.isOpen()) currentPort.close(function(error){ 137 | if (error) { 138 | onPortError(error); 139 | return null; 140 | } 141 | io.emit("portDisconnected", {baudRate:oldBaud, portName:oldName}); 142 | }); 143 | } 144 | return initPort(_portName, _baudRate); 145 | } 146 | 147 | function onPortOpen(name, baud){ 148 | console.log("connected to port " + portName + " at " + baudRate); 149 | io.emit("portConnected", {baudRate:baud, portName:name}); 150 | } 151 | 152 | function onPortData(data){ 153 | console.log(data); 154 | io.emit('dataIn', data); 155 | } 156 | 157 | function onPortClose(){ 158 | // console.log("port closed"); 159 | } 160 | 161 | function onPortError(error){ 162 | console.log("Serial port error " + error); 163 | io.emit("errorMsg", {error:String(error)}); 164 | } 165 | 166 | }); 167 | 168 | 169 | 170 | 171 | 172 | 173 | -------------------------------------------------------------------------------- /node/stepper_angles.py: -------------------------------------------------------------------------------- 1 | 2 | # A photo taking machine 3 | # uses a rotary stage and gphoto2 for taking pictures 4 | # dependencies include serial, libgphoto2, libgphoto 5 | # sudo apt-get install libgphoto2-dev libgphoto2-port10 gphoto2 6 | # also https://pypi.python.org/pypi/gphoto2/1.3.3/ 7 | # 8 | # 9 | # set portname 10 | # set location of hex file for bootloader 11 | # 12 | # Nadya Peek, peek@mit.edu 13 | # spring 2016 14 | 15 | #------IMPORTS------- 16 | from pygestalt import nodes 17 | from pygestalt import interfaces 18 | from pygestalt import machines 19 | from pygestalt import functions 20 | from pygestalt.machines import elements 21 | from pygestalt.machines import kinematics 22 | from pygestalt.machines import state 23 | from pygestalt.utilities import notice 24 | from pygestalt.publish import rpc #remote procedure call dispatcher 25 | import time 26 | import io 27 | import sys 28 | import json 29 | import os 30 | import logging 31 | import subprocess 32 | 33 | 34 | #------VIRTUAL MACHINE------ 35 | class virtualMachine(machines.virtualMachine): 36 | 37 | def initInterfaces(self): 38 | if self.providedInterface: self.fabnet = self.providedInterface #providedInterface is defined in the virtualMachine class. 39 | else: self.fabnet = interfaces.gestaltInterface('FABNET', interfaces.serialInterface(baudRate = 115200, interfaceType = 'ftdi', portName = '/dev/tty.usbserial-FTXYJGOO')) 40 | 41 | def initControllers(self): 42 | self.aAxisNode = nodes.networkedGestaltNode('A Axis', self.fabnet, filename = '086-005a.py', persistence = self.persistence) 43 | self.aNode = nodes.compoundNode(self.aAxisNode) 44 | 45 | def initCoordinates(self): 46 | self.position = state.coordinate(['mm']) 47 | 48 | def initKinematics(self): 49 | self.aAxis = elements.elementChain.forward([elements.microstep.forward(4), elements.stepper.forward(1.8), elements.pulley.forward(36), elements.invert.forward(True)]) 50 | 51 | self.stageKinematics = kinematics.direct(1) #direct drive on all axes 52 | 53 | def initFunctions(self): 54 | self.move = functions.move(virtualMachine = self, virtualNode = self.aNode, axes = [self.aAxis], kinematics = self.stageKinematics, machinePosition = self.position,planner = 'null') 55 | self.jog = functions.jog(self.move) #an incremental wrapper for the move function 56 | pass 57 | 58 | def initLast(self): 59 | # self.machineControl.setMotorCurrents(aCurrent = 0.8, bCurrent = 0.8, cCurrent = 0.8) 60 | # self.aNode.setVelocityRequest(0) #clear velocity on nodes. Eventually this will be put in the motion planner on initialization to match state. 61 | pass 62 | 63 | def publish(self): 64 | # self.publisher.addNodes(self.machineControl) 65 | pass 66 | 67 | def getPosition(self): 68 | return {'position':self.position.future()} 69 | 70 | def setPosition(self, position = [None]): 71 | self.position.future.set(position) 72 | 73 | def setSpindleSpeed(self, speedFraction): 74 | # self.machineControl.pwmRequest(speedFraction) 75 | pass 76 | 77 | #------IF RUN DIRECTLY FROM TERMINAL------ 78 | if __name__ == '__main__': 79 | # The persistence file remembers the node you set. It'll generate the first time you run the 80 | # file. If you are hooking up a new node, delete the previous persistence file. 81 | stage = virtualMachine(persistenceFile = "virtualmachineids.vmp") 82 | 83 | # You can load a new program onto the nodes if you are so inclined. This is currently set to 84 | # the path to the 086-005 repository on Nadya's machine. 85 | #stages.xyzNode.loadProgram('../../../086-005/086-005a.hex') 86 | 87 | # This is a widget for setting the potentiometer to set the motor current limit on the nodes. 88 | # The A4982 has max 2A of current, running the widget will interactively help you set. 89 | #stages.xyzNode.setMotorCurrent(0.7) 90 | 91 | # This is for how fast the motors move 92 | stage.aNode.setVelocityRequest(1) 93 | 94 | # Pull the moves out of the provided file 95 | # Where coordinates are provided as 96 | # [[angle1],[angle2]] 97 | moves = [] 98 | 99 | try: 100 | movestr = sys.argv[1] # moves are passed as a string, this may break with very long strings 101 | except: 102 | print "No moves file provided" 103 | 104 | 105 | segs = json.loads(movestr) 106 | for move in segs: 107 | moves.append(move) 108 | #print "Moves:" 109 | #print moves 110 | 111 | #gphoto setup 112 | #logging.basicConfig( 113 | # format='%(levelname)s: %(name)s: %(message)s', level=logging.WARNING) 114 | #gp.check_result(gp.use_python_logging()) 115 | #context = gp.gp_context_new() 116 | #camera = gp.check_result(gp.gp_camera_new()) 117 | #gp.check_result(gp.gp_camera_init(camera, context)) 118 | 119 | 120 | for coords in moves: 121 | time.sleep(5) 122 | stage.move(coords, 0) 123 | print('Capturing image') 124 | #file_path = gp.check_result(gp.gp_camera_capture( 125 | # camera, gp.GP_CAPTURE_IMAGE, context)) 126 | #print('Camera file path: {0}/{1}'.format(file_path.folder, file_path.name)) 127 | #target = os.path.join('/tmp', file_path.name) 128 | #print('Copying image to', target) 129 | #camera_file = gp.check_result(gp.gp_camera_file_get( 130 | # camera, file_path.folder, file_path.name, 131 | # gp.GP_FILE_TYPE_NORMAL, context)) 132 | #gp.check_result(gp.gp_file_save(camera_file, target)) 133 | #subprocess.call(['xdg-open', target]) 134 | 135 | status = stage.aAxisNode.spinStatusRequest() 136 | while status['stepsRemaining'] > 0: 137 | time.sleep(0.001) 138 | status = stage.aAxisNode.spinStatusRequest() 139 | 140 | #gp.check_result(gp.gp_camera_exit(camera, context)) 141 | -------------------------------------------------------------------------------- /node/test.py: -------------------------------------------------------------------------------- 1 | import sys 2 | for v in sys.argv[1:]: 3 | print v 4 | 5 | print("hi") 6 | 7 | def getData(): 8 | data = raw_input() 9 | print(data) 10 | getData() 11 | 12 | 13 | getData() -------------------------------------------------------------------------------- /projectionAR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amandaghassaei/ProjectionMappingAlignment/1e617c63ff7527b509eee8a75d2ec52e67865cae/projectionAR.png -------------------------------------------------------------------------------- /style.css: -------------------------------------------------------------------------------- 1 | #three { 2 | width:100%; 3 | height:100%; 4 | } 5 | 6 | body{ 7 | margin: 0; 8 | overflow: hidden; 9 | color:lightgray; 10 | } 11 | 12 | a{ 13 | color:blueviolet; 14 | } 15 | 16 | #warning{ 17 | margin-top:10px; 18 | z-index: 2; 19 | width:100%; 20 | position: absolute; 21 | } 22 | #warning>div{ 23 | text-align: center; 24 | color: yellow; 25 | width: 500px; 26 | margin: 0 auto; 27 | } 28 | 29 | #controls{ 30 | width: 360px; 31 | height: 100%; 32 | padding: 20px; 33 | position: absolute; 34 | top: 0; 35 | z-index: 2; 36 | right: 0; 37 | background: rgba(255,255,255,0.0); 38 | text-align: right; 39 | overflow: auto; 40 | } 41 | 42 | #cameraControls{ 43 | width: 320px; 44 | height: 100%; 45 | padding: 20px; 46 | position: absolute; 47 | top: 0; 48 | z-index: 2; 49 | left: 0; 50 | background: rgba(0,0,0,0.0); 51 | text-align: left; 52 | overflow: auto; 53 | } 54 | 55 | #crosshairs{ 56 | position: absolute; 57 | top: 0; 58 | left: 0; 59 | z-index: 6; 60 | right: 0; 61 | margin: auto; 62 | bottom: 0; 63 | width: 100px; 64 | } 65 | 66 | 67 | #instructions{ 68 | text-align: right; 69 | margin: 15px; 70 | display: inline-block; 71 | } 72 | 73 | input[type=file] { 74 | display: none; 75 | } 76 | 77 | input.numInput{ 78 | width: 40px; 79 | display:inline-block; 80 | } 81 | 82 | .inline{ 83 | display: inline; 84 | } 85 | 86 | 87 | .slider.slider-horizontal { 88 | width: 160px; 89 | margin: 0 10px; 90 | padding: 20px; 91 | top: 10px; 92 | } 93 | 94 | .slider.slider-vertical { 95 | height: 200px; 96 | margin: 0; 97 | padding: 20px; 98 | } 99 | 100 | .flat-slider.ui-corner-all, 101 | .flat-slider .ui-corner-all { 102 | border-radius: 0; 103 | } 104 | 105 | .flat-slider.ui-slider { 106 | border: 0; 107 | background: #ffffff; 108 | border-radius: 7px; 109 | } 110 | 111 | .flat-slider.ui-slider-horizontal { 112 | height: 6px; 113 | } 114 | 115 | .flat-slider.ui-slider-vertical { 116 | width: 6px; 117 | } 118 | 119 | .flat-slider .ui-slider-handle { 120 | width: 20px; 121 | height: 20px; 122 | background: #6dd0f2; 123 | border-radius: 50%; 124 | border: none; 125 | cursor: pointer; 126 | } 127 | 128 | .flat-slider.ui-slider-horizontal .ui-slider-handle { 129 | top: 50%; 130 | margin-top: -10px; 131 | opacity: 0.8; 132 | } 133 | 134 | .flat-slider.ui-slider-vertical .ui-slider-handle { 135 | left: 50%; 136 | margin-left: -10px; 137 | opacity: 0.8; 138 | } 139 | 140 | .flat-slider .ui-slider-handle:hover { 141 | opacity: 0.7; 142 | } 143 | 144 | .flat-slider .ui-slider-handle:focus { outline: none; } 145 | 146 | 147 | .flat-slider{ 148 | vertical-align: middle; 149 | display: inline-block; 150 | width: 160px; 151 | position: relative; 152 | margin: 10px; 153 | /*padding: 4px 0;*/ 154 | } 155 | 156 | .extraSpace{ 157 | height:40px; 158 | width: 100%; 159 | } 160 | 161 | .flipped 162 | { 163 | direction: rtl; 164 | } 165 | .flipped>div 166 | { 167 | direction: ltr; 168 | } 169 | 170 | .optimize{ 171 | text-decoration: none; 172 | } 173 | 174 | b{ 175 | color:#f28e6d; 176 | } 177 | 178 | .code{ 179 | color:#007fff; 180 | } 181 | 182 | /* Dropdown Button */ 183 | .dropbtn { 184 | font-family: Times; 185 | /*background-color: white;*/ 186 | /*color: black;*/ 187 | padding: 3px; 188 | font-size: 16px; 189 | border: none; 190 | cursor: pointer; 191 | width:100%; 192 | display: inline-block; 193 | text-align: right; 194 | } 195 | 196 | /* Dropdown button on hover & focus */ 197 | .dropbtn:hover, .dropbtn:focus { 198 | /*background-color: white;*/ 199 | } 200 | 201 | /* The container
- needed to position the dropdown content */ 202 | .dropdown { 203 | position: relative; 204 | display: inline-block; 205 | width: 100%; 206 | } 207 | 208 | /* Dropdown Content (Hidden by Default) */ 209 | .dropdown-content { 210 | display: none; 211 | position: absolute; 212 | z-index: 5; 213 | background-color: #f9f9f9; 214 | width: 100%; 215 | box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2); 216 | } 217 | 218 | /* Links inside the dropdown */ 219 | .dropdown-content a { 220 | color: black; 221 | padding: 2px 5px; 222 | text-decoration: none; 223 | display: block; 224 | text-align: left; 225 | } 226 | 227 | /* Change color of dropdown links on hover */ 228 | .dropdown-content a:hover {background-color: #f1f1f1} 229 | 230 | /* Show the dropdown menu (use JS to add this class to the .dropdown-content container when the user clicks on the dropdown button) */ 231 | .show {display:block;} 232 | 233 | #instructions{ 234 | display: none; 235 | } 236 | #serialDropdown{ 237 | display: none; 238 | } --------------------------------------------------------------------------------