├── BLESense-test-dashboard ├── BLESense │ └── BLESense.ino ├── BLE_spec.txt ├── GLTFLoader.js ├── README.md ├── aiot.png ├── index.html ├── logo.png └── models │ └── nano33ble.glb ├── NiclaSenseME-dashboard ├── BLE_spec.txt ├── GLTFLoader.js ├── Logo-Arduino-Pro-inline.svg ├── NiclaSenseME │ └── NiclaSenseME.ino ├── README.md ├── index.html └── models │ └── niclaSenseME.glb └── README.md /BLESense-test-dashboard/BLESense/BLESense.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | #define BLE_SENSE_UUID(val) ("6fbe1da7-" val "-44de-92c4-bb6e04fb0212") 12 | 13 | const int VERSION = 0x00000000; 14 | 15 | BLEService service (BLE_SENSE_UUID("0000")); 16 | BLEUnsignedIntCharacteristic versionCharacteristic (BLE_SENSE_UUID("1001"), BLERead); 17 | BLEUnsignedShortCharacteristic ambientLightCharacteristic (BLE_SENSE_UUID("2001"), BLENotify); // 16-bit 18 | BLECharacteristic colorCharacteristic (BLE_SENSE_UUID("2002"), BLENotify, 3 * sizeof(unsigned short)); // Array of 16-bit, RGB 19 | BLEUnsignedCharCharacteristic proximityCharacteristic (BLE_SENSE_UUID("2003"), BLENotify); // Byte, 0 - 255 => close to far 20 | BLEByteCharacteristic gestureCharacteristic (BLE_SENSE_UUID("2004"), BLENotify); // NONE = -1, UP = 0, DOWN = 1, LEFT = 2, RIGHT = 3 21 | BLECharacteristic accelerationCharacteristic (BLE_SENSE_UUID("3001"), BLENotify, 3 * sizeof(float)); // Array of 3 floats, G 22 | BLECharacteristic gyroscopeCharacteristic (BLE_SENSE_UUID("3002"), BLENotify, 3 * sizeof(float)); // Array of 3 floats, dps 23 | BLECharacteristic magneticFieldCharacteristic (BLE_SENSE_UUID("3003"), BLENotify, 3 * sizeof(float)); // Array of 3 floats, uT 24 | 25 | BLEFloatCharacteristic pressureCharacteristic (BLE_SENSE_UUID("4001"), BLERead); // Float, kPa 26 | BLEFloatCharacteristic temperatureCharacteristic (BLE_SENSE_UUID("4002"), BLERead); // Float, Celcius 27 | BLEFloatCharacteristic humidityCharacteristic (BLE_SENSE_UUID("4003"), BLERead); // Float, Percentage 28 | BLECharacteristic microphoneLevelCharacteristic (BLE_SENSE_UUID("5001"), BLENotify, 32); // Int, RMS of audio input 29 | BLECharacteristic rgbLedCharacteristic (BLE_SENSE_UUID("6001"), BLERead | BLEWrite, 3 * sizeof(byte)); // Array of 3 bytes, RGB 30 | 31 | // String to calculate the local and device name 32 | String name; 33 | 34 | // buffer to read samples into, each sample is 16-bits 35 | short sampleBuffer[256]; 36 | 37 | arm_rfft_instance_q15 FFT; 38 | 39 | // number of samples read 40 | volatile int samplesRead; 41 | 42 | void setup() { 43 | Serial.begin(9600); 44 | 45 | //while (!Serial); 46 | Serial.println("Started"); 47 | 48 | 49 | if (!APDS.begin()) { 50 | Serial.println("Failled to initialized APDS!"); 51 | 52 | while (1); 53 | } 54 | 55 | if (!HTS.begin()) { 56 | Serial.println("Failled to initialized HTS!"); 57 | 58 | while (1); 59 | } 60 | 61 | if (!BARO.begin()) { 62 | Serial.println("Failled to initialized BARO!"); 63 | 64 | while (1); 65 | } 66 | 67 | if (!IMU.begin()) { 68 | Serial.println("Failled to initialized IMU!"); 69 | 70 | while (1); 71 | } 72 | 73 | // configure the data receive callback 74 | PDM.onReceive(onPDMdata); 75 | 76 | // initialize PDM with: 77 | // - one channel (mono mode) 78 | // - a 16 kHz sample rate 79 | if (!PDM.begin(1, 16000)) { 80 | Serial.println("Failed to start PDM!"); 81 | while (1); 82 | } 83 | 84 | if (!BLE.begin()) { 85 | Serial.println("Failled to initialized BLE!"); 86 | 87 | while (1); 88 | } 89 | 90 | String address = BLE.address(); 91 | 92 | Serial.print("address = "); 93 | Serial.println(address); 94 | 95 | address.toUpperCase(); 96 | 97 | name = "BLESense-"; 98 | name += address[address.length() - 5]; 99 | name += address[address.length() - 4]; 100 | name += address[address.length() - 2]; 101 | name += address[address.length() - 1]; 102 | 103 | Serial.print("name = "); 104 | Serial.println(name); 105 | 106 | 107 | BLE.setLocalName(name.c_str()); 108 | BLE.setDeviceName(name.c_str()); 109 | BLE.setAdvertisedService(service); 110 | 111 | service.addCharacteristic(versionCharacteristic); 112 | service.addCharacteristic(ambientLightCharacteristic); 113 | service.addCharacteristic(colorCharacteristic); 114 | service.addCharacteristic(proximityCharacteristic); 115 | service.addCharacteristic(gestureCharacteristic); 116 | service.addCharacteristic(accelerationCharacteristic); 117 | service.addCharacteristic(gyroscopeCharacteristic); 118 | service.addCharacteristic(magneticFieldCharacteristic); 119 | 120 | service.addCharacteristic(pressureCharacteristic); 121 | service.addCharacteristic(temperatureCharacteristic); 122 | service.addCharacteristic(humidityCharacteristic); 123 | service.addCharacteristic(microphoneLevelCharacteristic); 124 | service.addCharacteristic(rgbLedCharacteristic); 125 | 126 | versionCharacteristic.setValue(VERSION); 127 | pressureCharacteristic.setEventHandler(BLERead, onPressureCharacteristicRead); 128 | temperatureCharacteristic.setEventHandler(BLERead, onTemperatureCharacteristicRead); 129 | humidityCharacteristic.setEventHandler(BLERead, onHumidityCharacteristicRead); 130 | rgbLedCharacteristic.setEventHandler(BLEWritten, onRgbLedCharacteristicWrite); 131 | 132 | BLE.addService(service); 133 | 134 | 135 | BLE.advertise(); 136 | } 137 | 138 | void loop() { 139 | while (BLE.connected()) { 140 | if ((ambientLightCharacteristic.subscribed() || colorCharacteristic.subscribed()) && APDS.colorAvailable()) { 141 | int r, g, b, ambientLight; 142 | 143 | APDS.readColor(r, g, b, ambientLight); 144 | 145 | ambientLightCharacteristic.writeValue(ambientLight); 146 | 147 | unsigned short colors[3] = { r, g, b }; 148 | 149 | colorCharacteristic.writeValue(colors, sizeof(colors)); 150 | } 151 | 152 | if (proximityCharacteristic.subscribed() && APDS.proximityAvailable()) { 153 | int proximity = APDS.readProximity(); 154 | 155 | proximityCharacteristic.writeValue(proximity); 156 | } 157 | 158 | if (gestureCharacteristic.subscribed() && APDS.gestureAvailable()) { 159 | int gesture = APDS.readGesture(); 160 | 161 | gestureCharacteristic.writeValue(gesture); 162 | } 163 | 164 | if (accelerationCharacteristic.subscribed() && IMU.accelerationAvailable()) { 165 | float x, y, z; 166 | 167 | IMU.readAcceleration(x, y, z); 168 | 169 | float acceleration[3] = { x, y, z }; 170 | 171 | accelerationCharacteristic.writeValue(acceleration, sizeof(acceleration)); 172 | } 173 | 174 | if (gyroscopeCharacteristic.subscribed() && IMU.gyroscopeAvailable()) { 175 | float x, y, z; 176 | 177 | IMU.readGyroscope(x, y, z); 178 | 179 | float dps[3] = { x, y, z }; 180 | 181 | gyroscopeCharacteristic.writeValue(dps, sizeof(dps)); 182 | } 183 | 184 | if (magneticFieldCharacteristic.subscribed() && IMU.magneticFieldAvailable()) { 185 | float x, y, z; 186 | 187 | IMU.readMagneticField(x, y, z); 188 | 189 | float magneticField[3] = { x, y, z }; 190 | 191 | magneticFieldCharacteristic.writeValue(magneticField, sizeof(magneticField)); 192 | } 193 | 194 | if (microphoneLevelCharacteristic.subscribed() && samplesRead) { 195 | short micLevel; 196 | // arm_rms_q15 (sampleBuffer, samplesRead, &micLevel); 197 | 198 | static arm_rfft_instance_q15 fft_instance; 199 | static q15_t fftoutput[256*2]; //has to be twice FFT size 200 | static byte spectrum[32]; 201 | arm_rfft_init_q15(&fft_instance, 256/*bin count*/, 0/*forward FFT*/, 1/*output bit order is normal*/); 202 | arm_rfft_q15(&fft_instance, (q15_t*)sampleBuffer, fftoutput); 203 | arm_abs_q15(fftoutput, fftoutput, 256); 204 | 205 | float temp = 0; 206 | for (int i = 1; i < 256; i++) { 207 | temp = temp + fftoutput[i]; 208 | if ((i &3) == 2){ 209 | if (temp>1023) {temp=1023;}; 210 | spectrum[i>>3] = (byte)(temp/2); 211 | temp = 0; 212 | } 213 | } 214 | microphoneLevelCharacteristic.writeValue((byte *) &spectrum, 32); 215 | samplesRead = 0; 216 | } 217 | } 218 | } 219 | 220 | void onPressureCharacteristicRead(BLEDevice central, BLECharacteristic characteristic) { 221 | float pressure = BARO.readPressure(); 222 | 223 | pressureCharacteristic.writeValue(pressure); 224 | } 225 | 226 | void onTemperatureCharacteristicRead(BLEDevice central, BLECharacteristic characteristic) { 227 | float temperature = HTS.readTemperature()-5; 228 | 229 | temperatureCharacteristic.writeValue(temperature); 230 | } 231 | 232 | void onHumidityCharacteristicRead(BLEDevice central, BLECharacteristic characteristic) { 233 | float humidity = HTS.readHumidity(); 234 | 235 | humidityCharacteristic.writeValue(humidity); 236 | } 237 | 238 | void onRgbLedCharacteristicWrite(BLEDevice central, BLECharacteristic characteristic) { 239 | byte r = rgbLedCharacteristic[0]; 240 | byte g = rgbLedCharacteristic[1]; 241 | byte b = rgbLedCharacteristic[2]; 242 | 243 | setLedPinValue(LEDR, r); 244 | setLedPinValue(LEDG, g); 245 | setLedPinValue(LEDB, b); 246 | } 247 | 248 | void setLedPinValue(int pin, int value) { 249 | // RGB LED's are pulled up, so the PWM needs to be inverted 250 | 251 | if (value == 0) { 252 | // special hack to clear LED 253 | analogWrite(pin, 256); 254 | } else { 255 | analogWrite(pin, 255 - value); 256 | } 257 | } 258 | 259 | void onPDMdata() { 260 | // query the number of bytes available 261 | int bytesAvailable = PDM.available(); 262 | 263 | // read into the sample buffer 264 | PDM.read(sampleBuffer, bytesAvailable); 265 | 266 | // 16-bit, 2 bytes per sample 267 | samplesRead = bytesAvailable / 2; 268 | } 269 | -------------------------------------------------------------------------------- /BLESense-test-dashboard/BLE_spec.txt: -------------------------------------------------------------------------------- 1 | BLE Sense - BLE specifications.txt 2 | ================================== 3 | 4 | GAP (Adversisement) 5 | ------------------- 6 | Local name: BLESense- 7 | Serivice UUID: 6fbe1da7-0000-44de-92c4-bb6e04fb0212 8 | 9 | 10 | GATT 11 | ---- 12 | 13 | Service 14 | ~~~~~~~ 15 | 16 | UUID: 6fbe1da7-0000-44de-92c4-bb6e04fb0212 17 | 18 | 19 | Version Characteristic 20 | ~~~~~~~~~~~~~~~~~~~~~~ 21 | 22 | UUID: 6fbe1da7-1001-44de-92c4-bb6e04fb0212 23 | Properties: read 24 | Value size: 4 bytes 25 | Data format: 32-bit unsigned integer (little endian) 26 | Description: Version of firmware 27 | 28 | Ambient Light Characteristic 29 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 30 | 31 | UUID: 6fbe1da7-2001-44de-92c4-bb6e04fb0212 32 | Properties: notify 33 | Value size: 2 bytes 34 | Data format: 16-bit unsigned integer (little endian) 35 | Description: Ambient light level, 0 => dark 36 | 37 | Color Characteristic 38 | ~~~~~~~~~~~~~~~~~~~~ 39 | 40 | UUID: 6fbe1da7-2002-44de-92c4-bb6e04fb0212 41 | Properties: notify 42 | Value size: 6 bytes 43 | Data format: array of 16-bit unsigned integers (little endian), RGB 44 | Description: RGB colour sensor values 45 | 46 | Proximity Characteristic 47 | ~~~~~~~~~~~~~~~~~~~~~~~~ 48 | 49 | UUID: 6fbe1da7-2003-44de-92c4-bb6e04fb0212 50 | Properties: notify 51 | Value size: 1 byte 52 | Data format: 8-bit 53 | Description: Proximity sensor value, 0 => close, 255 far 54 | 55 | Gesture Characteristic 56 | ~~~~~~~~~~~~~~~~~~~~~~ 57 | 58 | UUID: 6fbe1da7-2004-44de-92c4-bb6e04fb0212 59 | Properties: notify 60 | Value size: 1 byte 61 | Data format: 8-bit 62 | Description: Gesture detected, NONE = -1, UP = 0, DOWN = 1, LEFT = 2, RIGHT = 3 63 | 64 | Acceleration Characteristic 65 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~ 66 | 67 | UUID: 6fbe1da7-3001-44de-92c4-bb6e04fb0212 68 | Properties: notify 69 | Value size: 12 bytes 70 | Data format: Array of 3 x 32-bit IEEE floats (little endian) 71 | Description: X, Y, Z acceleration values in G's 72 | 73 | Gyroscope Characteristic 74 | ~~~~~~~~~~~~~~~~~~~~~~~~ 75 | 76 | UUID: 6fbe1da7-3002-44de-92c4-bb6e04fb0212 77 | Properties: notify 78 | Value size: 12 bytes 79 | Data format: Array of 3 x 32-bit IEEE floats (little endian) 80 | Description: X, Y, Z gyroscope values in degrees per second 81 | 82 | Magnetic Field Characteristic 83 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 84 | 85 | UUID: 6fbe1da7-3002-44de-92c4-bb6e04fb0212 86 | Properties: notify 87 | Value size: 12 bytes 88 | Data format: Array of 3 x 32-bit IEEE floats (little endian) 89 | Description: X, Y, Z magnetic fields values in uT 90 | 91 | Pressure Characteristic 92 | ~~~~~~~~~~~~~~~~~~~~~~~ 93 | 94 | UUID: 6fbe1da7-4001-44de-92c4-bb6e04fb0212 95 | Properties: read 96 | Value size: 4 bytes 97 | Data format: 32-bit IEEE floats (little endian) 98 | Description: Pressure sensor value in kPA 99 | 100 | Temperature Characteristic 101 | ~~~~~~~~~~~~~~~~~~~~~~~~~~ 102 | 103 | UUID: 6fbe1da7-4002-44de-92c4-bb6e04fb0212 104 | Properties: read 105 | Value size: 4 bytes 106 | Data format: 32-bit IEEE floats (little endian) 107 | Description: Temperature sensor value Celcius 108 | 109 | Humidity Characteristic 110 | ~~~~~~~~~~~~~~~~~~~~~~~ 111 | 112 | UUID: 6fbe1da7-4003-44de-92c4-bb6e04fb0212 113 | Properties: read 114 | Value size: 4 bytes 115 | Data format: 32-bit IEEE floats (little endian) 116 | Description: Humidity sensor value % 117 | 118 | Microphone Value Characteristic 119 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 120 | 121 | UUID: 6fbe1da7-5001-44de-92c4-bb6e04fb0212 122 | Properties: notify 123 | Value size: 2 bytes 124 | Data format: 16-bit signed (little endian) 125 | Description: Mic level (RMS) 126 | 127 | RGB LED Characteristic 128 | ~~~~~~~~~~~~~~~~~~~~~~ 129 | 130 | UUID: 6fbe1da7-6001-44de-92c4-bb6e04fb0212 131 | Properties: write 132 | Value size: 3 bytes 133 | Data format: Array of unsigned 8-bits (little endian) 134 | Description: RGB led value, 0 => off, 255 => on 135 | -------------------------------------------------------------------------------- /BLESense-test-dashboard/GLTFLoader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Rich Tibbett / https://github.com/richtr 3 | * @author mrdoob / http://mrdoob.com/ 4 | * @author Tony Parisi / http://www.tonyparisi.com/ 5 | * @author Takahiro / https://github.com/takahirox 6 | * @author Don McCurdy / https://www.donmccurdy.com 7 | */ 8 | 9 | THREE.GLTFLoader = ( function () { 10 | 11 | function GLTFLoader( manager ) { 12 | 13 | this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager; 14 | this.dracoLoader = null; 15 | 16 | } 17 | 18 | GLTFLoader.prototype = { 19 | 20 | constructor: GLTFLoader, 21 | 22 | crossOrigin: 'anonymous', 23 | 24 | load: function ( url, onLoad, onProgress, onError ) { 25 | 26 | var scope = this; 27 | 28 | var resourcePath; 29 | 30 | if ( this.resourcePath !== undefined ) { 31 | 32 | resourcePath = this.resourcePath; 33 | 34 | } else if ( this.path !== undefined ) { 35 | 36 | resourcePath = this.path; 37 | 38 | } else { 39 | 40 | resourcePath = THREE.LoaderUtils.extractUrlBase( url ); 41 | 42 | } 43 | 44 | // Tells the LoadingManager to track an extra item, which resolves after 45 | // the model is fully loaded. This means the count of items loaded will 46 | // be incorrect, but ensures manager.onLoad() does not fire early. 47 | scope.manager.itemStart( url ); 48 | 49 | var _onError = function ( e ) { 50 | 51 | if ( onError ) { 52 | 53 | onError( e ); 54 | 55 | } else { 56 | 57 | console.error( e ); 58 | 59 | } 60 | 61 | scope.manager.itemEnd( url ); 62 | scope.manager.itemError( url ); 63 | 64 | }; 65 | 66 | var loader = new THREE.FileLoader( scope.manager ); 67 | 68 | loader.setPath( this.path ); 69 | loader.setResponseType( 'arraybuffer' ); 70 | 71 | loader.load( url, function ( data ) { 72 | 73 | try { 74 | 75 | scope.parse( data, resourcePath, function ( gltf ) { 76 | 77 | onLoad( gltf ); 78 | 79 | scope.manager.itemEnd( url ); 80 | 81 | }, _onError ); 82 | 83 | } catch ( e ) { 84 | 85 | _onError( e ); 86 | 87 | } 88 | 89 | }, onProgress, _onError ); 90 | 91 | }, 92 | 93 | setCrossOrigin: function ( value ) { 94 | 95 | this.crossOrigin = value; 96 | return this; 97 | 98 | }, 99 | 100 | setPath: function ( value ) { 101 | 102 | this.path = value; 103 | return this; 104 | 105 | }, 106 | 107 | setResourcePath: function ( value ) { 108 | 109 | this.resourcePath = value; 110 | return this; 111 | 112 | }, 113 | 114 | setDRACOLoader: function ( dracoLoader ) { 115 | 116 | this.dracoLoader = dracoLoader; 117 | return this; 118 | 119 | }, 120 | 121 | parse: function ( data, path, onLoad, onError ) { 122 | 123 | var content; 124 | var extensions = {}; 125 | 126 | if ( typeof data === 'string' ) { 127 | 128 | content = data; 129 | 130 | } else { 131 | 132 | var magic = THREE.LoaderUtils.decodeText( new Uint8Array( data, 0, 4 ) ); 133 | 134 | if ( magic === BINARY_EXTENSION_HEADER_MAGIC ) { 135 | 136 | try { 137 | 138 | extensions[ EXTENSIONS.KHR_BINARY_GLTF ] = new GLTFBinaryExtension( data ); 139 | 140 | } catch ( error ) { 141 | 142 | if ( onError ) onError( error ); 143 | return; 144 | 145 | } 146 | 147 | content = extensions[ EXTENSIONS.KHR_BINARY_GLTF ].content; 148 | 149 | } else { 150 | 151 | content = THREE.LoaderUtils.decodeText( new Uint8Array( data ) ); 152 | 153 | } 154 | 155 | } 156 | 157 | var json = JSON.parse( content ); 158 | 159 | if ( json.asset === undefined || json.asset.version[ 0 ] < 2 ) { 160 | 161 | if ( onError ) onError( new Error( 'THREE.GLTFLoader: Unsupported asset. glTF versions >=2.0 are supported. Use LegacyGLTFLoader instead.' ) ); 162 | return; 163 | 164 | } 165 | 166 | if ( json.extensionsUsed ) { 167 | 168 | for ( var i = 0; i < json.extensionsUsed.length; ++ i ) { 169 | 170 | var extensionName = json.extensionsUsed[ i ]; 171 | var extensionsRequired = json.extensionsRequired || []; 172 | 173 | switch ( extensionName ) { 174 | 175 | case EXTENSIONS.KHR_LIGHTS_PUNCTUAL: 176 | extensions[ extensionName ] = new GLTFLightsExtension( json ); 177 | break; 178 | 179 | case EXTENSIONS.KHR_MATERIALS_UNLIT: 180 | extensions[ extensionName ] = new GLTFMaterialsUnlitExtension( json ); 181 | break; 182 | 183 | case EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS: 184 | extensions[ extensionName ] = new GLTFMaterialsPbrSpecularGlossinessExtension(); 185 | break; 186 | 187 | case EXTENSIONS.KHR_DRACO_MESH_COMPRESSION: 188 | extensions[ extensionName ] = new GLTFDracoMeshCompressionExtension( json, this.dracoLoader ); 189 | break; 190 | 191 | case EXTENSIONS.MSFT_TEXTURE_DDS: 192 | extensions[ EXTENSIONS.MSFT_TEXTURE_DDS ] = new GLTFTextureDDSExtension(); 193 | break; 194 | 195 | default: 196 | 197 | if ( extensionsRequired.indexOf( extensionName ) >= 0 ) { 198 | 199 | console.warn( 'THREE.GLTFLoader: Unknown extension "' + extensionName + '".' ); 200 | 201 | } 202 | 203 | } 204 | 205 | } 206 | 207 | } 208 | 209 | var parser = new GLTFParser( json, extensions, { 210 | 211 | path: path || this.resourcePath || '', 212 | crossOrigin: this.crossOrigin, 213 | manager: this.manager 214 | 215 | } ); 216 | 217 | parser.parse( function ( scene, scenes, cameras, animations, json ) { 218 | 219 | var glTF = { 220 | scene: scene, 221 | scenes: scenes, 222 | cameras: cameras, 223 | animations: animations, 224 | asset: json.asset, 225 | parser: parser, 226 | userData: {} 227 | }; 228 | 229 | addUnknownExtensionsToUserData( extensions, glTF, json ); 230 | 231 | onLoad( glTF ); 232 | 233 | }, onError ); 234 | 235 | } 236 | 237 | }; 238 | 239 | /* GLTFREGISTRY */ 240 | 241 | function GLTFRegistry() { 242 | 243 | var objects = {}; 244 | 245 | return { 246 | 247 | get: function ( key ) { 248 | 249 | return objects[ key ]; 250 | 251 | }, 252 | 253 | add: function ( key, object ) { 254 | 255 | objects[ key ] = object; 256 | 257 | }, 258 | 259 | remove: function ( key ) { 260 | 261 | delete objects[ key ]; 262 | 263 | }, 264 | 265 | removeAll: function () { 266 | 267 | objects = {}; 268 | 269 | } 270 | 271 | }; 272 | 273 | } 274 | 275 | /*********************************/ 276 | /********** EXTENSIONS ***********/ 277 | /*********************************/ 278 | 279 | var EXTENSIONS = { 280 | KHR_BINARY_GLTF: 'KHR_binary_glTF', 281 | KHR_DRACO_MESH_COMPRESSION: 'KHR_draco_mesh_compression', 282 | KHR_LIGHTS_PUNCTUAL: 'KHR_lights_punctual', 283 | KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS: 'KHR_materials_pbrSpecularGlossiness', 284 | KHR_MATERIALS_UNLIT: 'KHR_materials_unlit', 285 | MSFT_TEXTURE_DDS: 'MSFT_texture_dds' 286 | }; 287 | 288 | /** 289 | * DDS Texture Extension 290 | * 291 | * Specification: 292 | * https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/MSFT_texture_dds 293 | * 294 | */ 295 | function GLTFTextureDDSExtension() { 296 | 297 | if ( ! THREE.DDSLoader ) { 298 | 299 | throw new Error( 'THREE.GLTFLoader: Attempting to load .dds texture without importing THREE.DDSLoader' ); 300 | 301 | } 302 | 303 | this.name = EXTENSIONS.MSFT_TEXTURE_DDS; 304 | this.ddsLoader = new THREE.DDSLoader(); 305 | 306 | } 307 | 308 | /** 309 | * Lights Extension 310 | * 311 | * Specification: PENDING 312 | */ 313 | function GLTFLightsExtension( json ) { 314 | 315 | this.name = EXTENSIONS.KHR_LIGHTS_PUNCTUAL; 316 | 317 | this.lights = []; 318 | 319 | var extension = ( json.extensions && json.extensions[ EXTENSIONS.KHR_LIGHTS_PUNCTUAL ] ) || {}; 320 | var lightDefs = extension.lights || []; 321 | 322 | for ( var i = 0; i < lightDefs.length; i ++ ) { 323 | 324 | var lightDef = lightDefs[ i ]; 325 | var lightNode; 326 | 327 | var color = new THREE.Color( 0xffffff ); 328 | if ( lightDef.color !== undefined ) color.fromArray( lightDef.color ); 329 | 330 | var range = lightDef.range !== undefined ? lightDef.range : 0; 331 | 332 | switch ( lightDef.type ) { 333 | 334 | case 'directional': 335 | lightNode = new THREE.DirectionalLight( color ); 336 | lightNode.target.position.set( 0, 0, -1 ); 337 | lightNode.add( lightNode.target ); 338 | break; 339 | 340 | case 'point': 341 | lightNode = new THREE.PointLight( color ); 342 | lightNode.distance = range; 343 | break; 344 | 345 | case 'spot': 346 | lightNode = new THREE.SpotLight( color ); 347 | lightNode.distance = range; 348 | // Handle spotlight properties. 349 | lightDef.spot = lightDef.spot || {}; 350 | lightDef.spot.innerConeAngle = lightDef.spot.innerConeAngle !== undefined ? lightDef.spot.innerConeAngle : 0; 351 | lightDef.spot.outerConeAngle = lightDef.spot.outerConeAngle !== undefined ? lightDef.spot.outerConeAngle : Math.PI / 4.0; 352 | lightNode.angle = lightDef.spot.outerConeAngle; 353 | lightNode.penumbra = 1.0 - lightDef.spot.innerConeAngle / lightDef.spot.outerConeAngle; 354 | lightNode.target.position.set( 0, 0, -1 ); 355 | lightNode.add( lightNode.target ); 356 | break; 357 | 358 | default: 359 | throw new Error( 'THREE.GLTFLoader: Unexpected light type, "' + lightDef.type + '".' ); 360 | 361 | } 362 | 363 | lightNode.decay = 2; 364 | 365 | if ( lightDef.intensity !== undefined ) lightNode.intensity = lightDef.intensity; 366 | 367 | lightNode.name = lightDef.name || ( 'light_' + i ); 368 | 369 | this.lights.push( lightNode ); 370 | 371 | } 372 | 373 | } 374 | 375 | /** 376 | * Unlit Materials Extension (pending) 377 | * 378 | * PR: https://github.com/KhronosGroup/glTF/pull/1163 379 | */ 380 | function GLTFMaterialsUnlitExtension( json ) { 381 | 382 | this.name = EXTENSIONS.KHR_MATERIALS_UNLIT; 383 | 384 | } 385 | 386 | GLTFMaterialsUnlitExtension.prototype.getMaterialType = function ( material ) { 387 | 388 | return THREE.MeshBasicMaterial; 389 | 390 | }; 391 | 392 | GLTFMaterialsUnlitExtension.prototype.extendParams = function ( materialParams, material, parser ) { 393 | 394 | var pending = []; 395 | 396 | materialParams.color = new THREE.Color( 1.0, 1.0, 1.0 ); 397 | materialParams.opacity = 1.0; 398 | 399 | var metallicRoughness = material.pbrMetallicRoughness; 400 | 401 | if ( metallicRoughness ) { 402 | 403 | if ( Array.isArray( metallicRoughness.baseColorFactor ) ) { 404 | 405 | var array = metallicRoughness.baseColorFactor; 406 | 407 | materialParams.color.fromArray( array ); 408 | materialParams.opacity = array[ 3 ]; 409 | 410 | } 411 | 412 | if ( metallicRoughness.baseColorTexture !== undefined ) { 413 | 414 | pending.push( parser.assignTexture( materialParams, 'map', metallicRoughness.baseColorTexture.index ) ); 415 | 416 | } 417 | 418 | } 419 | 420 | return Promise.all( pending ); 421 | 422 | }; 423 | 424 | /* BINARY EXTENSION */ 425 | 426 | var BINARY_EXTENSION_BUFFER_NAME = 'binary_glTF'; 427 | var BINARY_EXTENSION_HEADER_MAGIC = 'glTF'; 428 | var BINARY_EXTENSION_HEADER_LENGTH = 12; 429 | var BINARY_EXTENSION_CHUNK_TYPES = { JSON: 0x4E4F534A, BIN: 0x004E4942 }; 430 | 431 | function GLTFBinaryExtension( data ) { 432 | 433 | this.name = EXTENSIONS.KHR_BINARY_GLTF; 434 | this.content = null; 435 | this.body = null; 436 | 437 | var headerView = new DataView( data, 0, BINARY_EXTENSION_HEADER_LENGTH ); 438 | 439 | this.header = { 440 | magic: THREE.LoaderUtils.decodeText( new Uint8Array( data.slice( 0, 4 ) ) ), 441 | version: headerView.getUint32( 4, true ), 442 | length: headerView.getUint32( 8, true ) 443 | }; 444 | 445 | if ( this.header.magic !== BINARY_EXTENSION_HEADER_MAGIC ) { 446 | 447 | throw new Error( 'THREE.GLTFLoader: Unsupported glTF-Binary header.' ); 448 | 449 | } else if ( this.header.version < 2.0 ) { 450 | 451 | throw new Error( 'THREE.GLTFLoader: Legacy binary file detected. Use LegacyGLTFLoader instead.' ); 452 | 453 | } 454 | 455 | var chunkView = new DataView( data, BINARY_EXTENSION_HEADER_LENGTH ); 456 | var chunkIndex = 0; 457 | 458 | while ( chunkIndex < chunkView.byteLength ) { 459 | 460 | var chunkLength = chunkView.getUint32( chunkIndex, true ); 461 | chunkIndex += 4; 462 | 463 | var chunkType = chunkView.getUint32( chunkIndex, true ); 464 | chunkIndex += 4; 465 | 466 | if ( chunkType === BINARY_EXTENSION_CHUNK_TYPES.JSON ) { 467 | 468 | var contentArray = new Uint8Array( data, BINARY_EXTENSION_HEADER_LENGTH + chunkIndex, chunkLength ); 469 | this.content = THREE.LoaderUtils.decodeText( contentArray ); 470 | 471 | } else if ( chunkType === BINARY_EXTENSION_CHUNK_TYPES.BIN ) { 472 | 473 | var byteOffset = BINARY_EXTENSION_HEADER_LENGTH + chunkIndex; 474 | this.body = data.slice( byteOffset, byteOffset + chunkLength ); 475 | 476 | } 477 | 478 | // Clients must ignore chunks with unknown types. 479 | 480 | chunkIndex += chunkLength; 481 | 482 | } 483 | 484 | if ( this.content === null ) { 485 | 486 | throw new Error( 'THREE.GLTFLoader: JSON content not found.' ); 487 | 488 | } 489 | 490 | } 491 | 492 | /** 493 | * DRACO Mesh Compression Extension 494 | * 495 | * Specification: https://github.com/KhronosGroup/glTF/pull/874 496 | */ 497 | function GLTFDracoMeshCompressionExtension( json, dracoLoader ) { 498 | 499 | if ( ! dracoLoader ) { 500 | 501 | throw new Error( 'THREE.GLTFLoader: No DRACOLoader instance provided.' ); 502 | 503 | } 504 | 505 | this.name = EXTENSIONS.KHR_DRACO_MESH_COMPRESSION; 506 | this.json = json; 507 | this.dracoLoader = dracoLoader; 508 | 509 | } 510 | 511 | GLTFDracoMeshCompressionExtension.prototype.decodePrimitive = function ( primitive, parser ) { 512 | 513 | var json = this.json; 514 | var dracoLoader = this.dracoLoader; 515 | var bufferViewIndex = primitive.extensions[ this.name ].bufferView; 516 | var gltfAttributeMap = primitive.extensions[ this.name ].attributes; 517 | var threeAttributeMap = {}; 518 | var attributeNormalizedMap = {}; 519 | var attributeTypeMap = {}; 520 | 521 | for ( var attributeName in gltfAttributeMap ) { 522 | 523 | if ( ! ( attributeName in ATTRIBUTES ) ) continue; 524 | 525 | threeAttributeMap[ ATTRIBUTES[ attributeName ] ] = gltfAttributeMap[ attributeName ]; 526 | 527 | } 528 | 529 | for ( attributeName in primitive.attributes ) { 530 | 531 | if ( ATTRIBUTES[ attributeName ] !== undefined && gltfAttributeMap[ attributeName ] !== undefined ) { 532 | 533 | var accessorDef = json.accessors[ primitive.attributes[ attributeName ] ]; 534 | var componentType = WEBGL_COMPONENT_TYPES[ accessorDef.componentType ]; 535 | 536 | attributeTypeMap[ ATTRIBUTES[ attributeName ] ] = componentType; 537 | attributeNormalizedMap[ ATTRIBUTES[ attributeName ] ] = accessorDef.normalized === true; 538 | 539 | } 540 | 541 | } 542 | 543 | return parser.getDependency( 'bufferView', bufferViewIndex ).then( function ( bufferView ) { 544 | 545 | return new Promise( function ( resolve ) { 546 | 547 | dracoLoader.decodeDracoFile( bufferView, function ( geometry ) { 548 | 549 | for ( var attributeName in geometry.attributes ) { 550 | 551 | var attribute = geometry.attributes[ attributeName ]; 552 | var normalized = attributeNormalizedMap[ attributeName ]; 553 | 554 | if ( normalized !== undefined ) attribute.normalized = normalized; 555 | 556 | } 557 | 558 | resolve( geometry ); 559 | 560 | }, threeAttributeMap, attributeTypeMap ); 561 | 562 | } ); 563 | 564 | } ); 565 | 566 | }; 567 | 568 | /** 569 | * Specular-Glossiness Extension 570 | * 571 | * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_pbrSpecularGlossiness 572 | */ 573 | function GLTFMaterialsPbrSpecularGlossinessExtension() { 574 | 575 | return { 576 | 577 | name: EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS, 578 | 579 | specularGlossinessParams: [ 580 | 'color', 581 | 'map', 582 | 'lightMap', 583 | 'lightMapIntensity', 584 | 'aoMap', 585 | 'aoMapIntensity', 586 | 'emissive', 587 | 'emissiveIntensity', 588 | 'emissiveMap', 589 | 'bumpMap', 590 | 'bumpScale', 591 | 'normalMap', 592 | 'displacementMap', 593 | 'displacementScale', 594 | 'displacementBias', 595 | 'specularMap', 596 | 'specular', 597 | 'glossinessMap', 598 | 'glossiness', 599 | 'alphaMap', 600 | 'envMap', 601 | 'envMapIntensity', 602 | 'refractionRatio', 603 | ], 604 | 605 | getMaterialType: function () { 606 | 607 | return THREE.ShaderMaterial; 608 | 609 | }, 610 | 611 | extendParams: function ( params, material, parser ) { 612 | 613 | var pbrSpecularGlossiness = material.extensions[ this.name ]; 614 | 615 | var shader = THREE.ShaderLib[ 'standard' ]; 616 | 617 | var uniforms = THREE.UniformsUtils.clone( shader.uniforms ); 618 | 619 | var specularMapParsFragmentChunk = [ 620 | '#ifdef USE_SPECULARMAP', 621 | ' uniform sampler2D specularMap;', 622 | '#endif' 623 | ].join( '\n' ); 624 | 625 | var glossinessMapParsFragmentChunk = [ 626 | '#ifdef USE_GLOSSINESSMAP', 627 | ' uniform sampler2D glossinessMap;', 628 | '#endif' 629 | ].join( '\n' ); 630 | 631 | var specularMapFragmentChunk = [ 632 | 'vec3 specularFactor = specular;', 633 | '#ifdef USE_SPECULARMAP', 634 | ' vec4 texelSpecular = texture2D( specularMap, vUv );', 635 | ' texelSpecular = sRGBToLinear( texelSpecular );', 636 | ' // reads channel RGB, compatible with a glTF Specular-Glossiness (RGBA) texture', 637 | ' specularFactor *= texelSpecular.rgb;', 638 | '#endif' 639 | ].join( '\n' ); 640 | 641 | var glossinessMapFragmentChunk = [ 642 | 'float glossinessFactor = glossiness;', 643 | '#ifdef USE_GLOSSINESSMAP', 644 | ' vec4 texelGlossiness = texture2D( glossinessMap, vUv );', 645 | ' // reads channel A, compatible with a glTF Specular-Glossiness (RGBA) texture', 646 | ' glossinessFactor *= texelGlossiness.a;', 647 | '#endif' 648 | ].join( '\n' ); 649 | 650 | var lightPhysicalFragmentChunk = [ 651 | 'PhysicalMaterial material;', 652 | 'material.diffuseColor = diffuseColor.rgb;', 653 | 'material.specularRoughness = clamp( 1.0 - glossinessFactor, 0.04, 1.0 );', 654 | 'material.specularColor = specularFactor.rgb;', 655 | ].join( '\n' ); 656 | 657 | var fragmentShader = shader.fragmentShader 658 | .replace( 'uniform float roughness;', 'uniform vec3 specular;' ) 659 | .replace( 'uniform float metalness;', 'uniform float glossiness;' ) 660 | .replace( '#include ', specularMapParsFragmentChunk ) 661 | .replace( '#include ', glossinessMapParsFragmentChunk ) 662 | .replace( '#include ', specularMapFragmentChunk ) 663 | .replace( '#include ', glossinessMapFragmentChunk ) 664 | .replace( '#include ', lightPhysicalFragmentChunk ); 665 | 666 | delete uniforms.roughness; 667 | delete uniforms.metalness; 668 | delete uniforms.roughnessMap; 669 | delete uniforms.metalnessMap; 670 | 671 | uniforms.specular = { value: new THREE.Color().setHex( 0x111111 ) }; 672 | uniforms.glossiness = { value: 0.5 }; 673 | uniforms.specularMap = { value: null }; 674 | uniforms.glossinessMap = { value: null }; 675 | 676 | params.vertexShader = shader.vertexShader; 677 | params.fragmentShader = fragmentShader; 678 | params.uniforms = uniforms; 679 | params.defines = { 'STANDARD': '' }; 680 | 681 | params.color = new THREE.Color( 1.0, 1.0, 1.0 ); 682 | params.opacity = 1.0; 683 | 684 | var pending = []; 685 | 686 | if ( Array.isArray( pbrSpecularGlossiness.diffuseFactor ) ) { 687 | 688 | var array = pbrSpecularGlossiness.diffuseFactor; 689 | 690 | params.color.fromArray( array ); 691 | params.opacity = array[ 3 ]; 692 | 693 | } 694 | 695 | if ( pbrSpecularGlossiness.diffuseTexture !== undefined ) { 696 | 697 | pending.push( parser.assignTexture( params, 'map', pbrSpecularGlossiness.diffuseTexture.index ) ); 698 | 699 | } 700 | 701 | params.emissive = new THREE.Color( 0.0, 0.0, 0.0 ); 702 | params.glossiness = pbrSpecularGlossiness.glossinessFactor !== undefined ? pbrSpecularGlossiness.glossinessFactor : 1.0; 703 | params.specular = new THREE.Color( 1.0, 1.0, 1.0 ); 704 | 705 | if ( Array.isArray( pbrSpecularGlossiness.specularFactor ) ) { 706 | 707 | params.specular.fromArray( pbrSpecularGlossiness.specularFactor ); 708 | 709 | } 710 | 711 | if ( pbrSpecularGlossiness.specularGlossinessTexture !== undefined ) { 712 | 713 | var specGlossIndex = pbrSpecularGlossiness.specularGlossinessTexture.index; 714 | pending.push( parser.assignTexture( params, 'glossinessMap', specGlossIndex ) ); 715 | pending.push( parser.assignTexture( params, 'specularMap', specGlossIndex ) ); 716 | 717 | } 718 | 719 | return Promise.all( pending ); 720 | 721 | }, 722 | 723 | createMaterial: function ( params ) { 724 | 725 | // setup material properties based on MeshStandardMaterial for Specular-Glossiness 726 | 727 | var material = new THREE.ShaderMaterial( { 728 | defines: params.defines, 729 | vertexShader: params.vertexShader, 730 | fragmentShader: params.fragmentShader, 731 | uniforms: params.uniforms, 732 | fog: true, 733 | lights: true, 734 | opacity: params.opacity, 735 | transparent: params.transparent 736 | } ); 737 | 738 | material.isGLTFSpecularGlossinessMaterial = true; 739 | 740 | material.color = params.color; 741 | 742 | material.map = params.map === undefined ? null : params.map; 743 | 744 | material.lightMap = null; 745 | material.lightMapIntensity = 1.0; 746 | 747 | material.aoMap = params.aoMap === undefined ? null : params.aoMap; 748 | material.aoMapIntensity = 1.0; 749 | 750 | material.emissive = params.emissive; 751 | material.emissiveIntensity = 1.0; 752 | material.emissiveMap = params.emissiveMap === undefined ? null : params.emissiveMap; 753 | 754 | material.bumpMap = params.bumpMap === undefined ? null : params.bumpMap; 755 | material.bumpScale = 1; 756 | 757 | material.normalMap = params.normalMap === undefined ? null : params.normalMap; 758 | if ( params.normalScale ) material.normalScale = params.normalScale; 759 | 760 | material.displacementMap = null; 761 | material.displacementScale = 1; 762 | material.displacementBias = 0; 763 | 764 | material.specularMap = params.specularMap === undefined ? null : params.specularMap; 765 | material.specular = params.specular; 766 | 767 | material.glossinessMap = params.glossinessMap === undefined ? null : params.glossinessMap; 768 | material.glossiness = params.glossiness; 769 | 770 | material.alphaMap = null; 771 | 772 | material.envMap = params.envMap === undefined ? null : params.envMap; 773 | material.envMapIntensity = 1.0; 774 | 775 | material.refractionRatio = 0.98; 776 | 777 | material.extensions.derivatives = true; 778 | 779 | return material; 780 | 781 | }, 782 | 783 | /** 784 | * Clones a GLTFSpecularGlossinessMaterial instance. The ShaderMaterial.copy() method can 785 | * copy only properties it knows about or inherits, and misses many properties that would 786 | * normally be defined by MeshStandardMaterial. 787 | * 788 | * This method allows GLTFSpecularGlossinessMaterials to be cloned in the process of 789 | * loading a glTF model, but cloning later (e.g. by the user) would require these changes 790 | * AND also updating `.onBeforeRender` on the parent mesh. 791 | * 792 | * @param {THREE.ShaderMaterial} source 793 | * @return {THREE.ShaderMaterial} 794 | */ 795 | cloneMaterial: function ( source ) { 796 | 797 | var target = source.clone(); 798 | 799 | target.isGLTFSpecularGlossinessMaterial = true; 800 | 801 | var params = this.specularGlossinessParams; 802 | 803 | for ( var i = 0, il = params.length; i < il; i ++ ) { 804 | 805 | target[ params[ i ] ] = source[ params[ i ] ]; 806 | 807 | } 808 | 809 | return target; 810 | 811 | }, 812 | 813 | // Here's based on refreshUniformsCommon() and refreshUniformsStandard() in WebGLRenderer. 814 | refreshUniforms: function ( renderer, scene, camera, geometry, material, group ) { 815 | 816 | if ( material.isGLTFSpecularGlossinessMaterial !== true ) { 817 | 818 | return; 819 | 820 | } 821 | 822 | var uniforms = material.uniforms; 823 | var defines = material.defines; 824 | 825 | uniforms.opacity.value = material.opacity; 826 | 827 | uniforms.diffuse.value.copy( material.color ); 828 | uniforms.emissive.value.copy( material.emissive ).multiplyScalar( material.emissiveIntensity ); 829 | 830 | uniforms.map.value = material.map; 831 | uniforms.specularMap.value = material.specularMap; 832 | uniforms.alphaMap.value = material.alphaMap; 833 | 834 | uniforms.lightMap.value = material.lightMap; 835 | uniforms.lightMapIntensity.value = material.lightMapIntensity; 836 | 837 | uniforms.aoMap.value = material.aoMap; 838 | uniforms.aoMapIntensity.value = material.aoMapIntensity; 839 | 840 | // uv repeat and offset setting priorities 841 | // 1. color map 842 | // 2. specular map 843 | // 3. normal map 844 | // 4. bump map 845 | // 5. alpha map 846 | // 6. emissive map 847 | 848 | var uvScaleMap; 849 | 850 | if ( material.map ) { 851 | 852 | uvScaleMap = material.map; 853 | 854 | } else if ( material.specularMap ) { 855 | 856 | uvScaleMap = material.specularMap; 857 | 858 | } else if ( material.displacementMap ) { 859 | 860 | uvScaleMap = material.displacementMap; 861 | 862 | } else if ( material.normalMap ) { 863 | 864 | uvScaleMap = material.normalMap; 865 | 866 | } else if ( material.bumpMap ) { 867 | 868 | uvScaleMap = material.bumpMap; 869 | 870 | } else if ( material.glossinessMap ) { 871 | 872 | uvScaleMap = material.glossinessMap; 873 | 874 | } else if ( material.alphaMap ) { 875 | 876 | uvScaleMap = material.alphaMap; 877 | 878 | } else if ( material.emissiveMap ) { 879 | 880 | uvScaleMap = material.emissiveMap; 881 | 882 | } 883 | 884 | if ( uvScaleMap !== undefined ) { 885 | 886 | // backwards compatibility 887 | if ( uvScaleMap.isWebGLRenderTarget ) { 888 | 889 | uvScaleMap = uvScaleMap.texture; 890 | 891 | } 892 | 893 | if ( uvScaleMap.matrixAutoUpdate === true ) { 894 | 895 | uvScaleMap.updateMatrix(); 896 | 897 | } 898 | 899 | uniforms.uvTransform.value.copy( uvScaleMap.matrix ); 900 | 901 | } 902 | 903 | uniforms.envMap.value = material.envMap; 904 | uniforms.envMapIntensity.value = material.envMapIntensity; 905 | uniforms.flipEnvMap.value = ( material.envMap && material.envMap.isCubeTexture ) ? - 1 : 1; 906 | 907 | uniforms.refractionRatio.value = material.refractionRatio; 908 | 909 | uniforms.specular.value.copy( material.specular ); 910 | uniforms.glossiness.value = material.glossiness; 911 | 912 | uniforms.glossinessMap.value = material.glossinessMap; 913 | 914 | uniforms.emissiveMap.value = material.emissiveMap; 915 | uniforms.bumpMap.value = material.bumpMap; 916 | uniforms.normalMap.value = material.normalMap; 917 | 918 | uniforms.displacementMap.value = material.displacementMap; 919 | uniforms.displacementScale.value = material.displacementScale; 920 | uniforms.displacementBias.value = material.displacementBias; 921 | 922 | if ( uniforms.glossinessMap.value !== null && defines.USE_GLOSSINESSMAP === undefined ) { 923 | 924 | defines.USE_GLOSSINESSMAP = ''; 925 | // set USE_ROUGHNESSMAP to enable vUv 926 | defines.USE_ROUGHNESSMAP = ''; 927 | 928 | } 929 | 930 | if ( uniforms.glossinessMap.value === null && defines.USE_GLOSSINESSMAP !== undefined ) { 931 | 932 | delete defines.USE_GLOSSINESSMAP; 933 | delete defines.USE_ROUGHNESSMAP; 934 | 935 | } 936 | 937 | } 938 | 939 | }; 940 | 941 | } 942 | 943 | /*********************************/ 944 | /********** INTERPOLATION ********/ 945 | /*********************************/ 946 | 947 | // Spline Interpolation 948 | // Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#appendix-c-spline-interpolation 949 | function GLTFCubicSplineInterpolant( parameterPositions, sampleValues, sampleSize, resultBuffer ) { 950 | 951 | THREE.Interpolant.call( this, parameterPositions, sampleValues, sampleSize, resultBuffer ); 952 | 953 | } 954 | 955 | GLTFCubicSplineInterpolant.prototype = Object.create( THREE.Interpolant.prototype ); 956 | GLTFCubicSplineInterpolant.prototype.constructor = GLTFCubicSplineInterpolant; 957 | 958 | GLTFCubicSplineInterpolant.prototype.copySampleValue_ = function ( index ) { 959 | 960 | // Copies a sample value to the result buffer. See description of glTF 961 | // CUBICSPLINE values layout in interpolate_() function below. 962 | 963 | var result = this.resultBuffer, 964 | values = this.sampleValues, 965 | valueSize = this.valueSize, 966 | offset = index * valueSize * 3 + valueSize; 967 | 968 | for ( var i = 0; i !== valueSize; i ++ ) { 969 | 970 | result[ i ] = values[ offset + i ]; 971 | 972 | } 973 | 974 | return result; 975 | 976 | }; 977 | 978 | GLTFCubicSplineInterpolant.prototype.beforeStart_ = GLTFCubicSplineInterpolant.prototype.copySampleValue_; 979 | 980 | GLTFCubicSplineInterpolant.prototype.afterEnd_ = GLTFCubicSplineInterpolant.prototype.copySampleValue_; 981 | 982 | GLTFCubicSplineInterpolant.prototype.interpolate_ = function ( i1, t0, t, t1 ) { 983 | 984 | var result = this.resultBuffer; 985 | var values = this.sampleValues; 986 | var stride = this.valueSize; 987 | 988 | var stride2 = stride * 2; 989 | var stride3 = stride * 3; 990 | 991 | var td = t1 - t0; 992 | 993 | var p = ( t - t0 ) / td; 994 | var pp = p * p; 995 | var ppp = pp * p; 996 | 997 | var offset1 = i1 * stride3; 998 | var offset0 = offset1 - stride3; 999 | 1000 | var s0 = 2 * ppp - 3 * pp + 1; 1001 | var s1 = ppp - 2 * pp + p; 1002 | var s2 = - 2 * ppp + 3 * pp; 1003 | var s3 = ppp - pp; 1004 | 1005 | // Layout of keyframe output values for CUBICSPLINE animations: 1006 | // [ inTangent_1, splineVertex_1, outTangent_1, inTangent_2, splineVertex_2, ... ] 1007 | for ( var i = 0; i !== stride; i ++ ) { 1008 | 1009 | var p0 = values[ offset0 + i + stride ]; // splineVertex_k 1010 | var m0 = values[ offset0 + i + stride2 ] * td; // outTangent_k * (t_k+1 - t_k) 1011 | var p1 = values[ offset1 + i + stride ]; // splineVertex_k+1 1012 | var m1 = values[ offset1 + i ] * td; // inTangent_k+1 * (t_k+1 - t_k) 1013 | 1014 | result[ i ] = s0 * p0 + s1 * m0 + s2 * p1 + s3 * m1; 1015 | 1016 | } 1017 | 1018 | return result; 1019 | 1020 | }; 1021 | 1022 | /*********************************/ 1023 | /********** INTERNALS ************/ 1024 | /*********************************/ 1025 | 1026 | /* CONSTANTS */ 1027 | 1028 | var WEBGL_CONSTANTS = { 1029 | FLOAT: 5126, 1030 | //FLOAT_MAT2: 35674, 1031 | FLOAT_MAT3: 35675, 1032 | FLOAT_MAT4: 35676, 1033 | FLOAT_VEC2: 35664, 1034 | FLOAT_VEC3: 35665, 1035 | FLOAT_VEC4: 35666, 1036 | LINEAR: 9729, 1037 | REPEAT: 10497, 1038 | SAMPLER_2D: 35678, 1039 | POINTS: 0, 1040 | LINES: 1, 1041 | LINE_LOOP: 2, 1042 | LINE_STRIP: 3, 1043 | TRIANGLES: 4, 1044 | TRIANGLE_STRIP: 5, 1045 | TRIANGLE_FAN: 6, 1046 | UNSIGNED_BYTE: 5121, 1047 | UNSIGNED_SHORT: 5123 1048 | }; 1049 | 1050 | var WEBGL_TYPE = { 1051 | 5126: Number, 1052 | //35674: THREE.Matrix2, 1053 | 35675: THREE.Matrix3, 1054 | 35676: THREE.Matrix4, 1055 | 35664: THREE.Vector2, 1056 | 35665: THREE.Vector3, 1057 | 35666: THREE.Vector4, 1058 | 35678: THREE.Texture 1059 | }; 1060 | 1061 | var WEBGL_COMPONENT_TYPES = { 1062 | 5120: Int8Array, 1063 | 5121: Uint8Array, 1064 | 5122: Int16Array, 1065 | 5123: Uint16Array, 1066 | 5125: Uint32Array, 1067 | 5126: Float32Array 1068 | }; 1069 | 1070 | var WEBGL_FILTERS = { 1071 | 9728: THREE.NearestFilter, 1072 | 9729: THREE.LinearFilter, 1073 | 9984: THREE.NearestMipMapNearestFilter, 1074 | 9985: THREE.LinearMipMapNearestFilter, 1075 | 9986: THREE.NearestMipMapLinearFilter, 1076 | 9987: THREE.LinearMipMapLinearFilter 1077 | }; 1078 | 1079 | var WEBGL_WRAPPINGS = { 1080 | 33071: THREE.ClampToEdgeWrapping, 1081 | 33648: THREE.MirroredRepeatWrapping, 1082 | 10497: THREE.RepeatWrapping 1083 | }; 1084 | 1085 | var WEBGL_SIDES = { 1086 | 1028: THREE.BackSide, // Culling front 1087 | 1029: THREE.FrontSide // Culling back 1088 | //1032: THREE.NoSide // Culling front and back, what to do? 1089 | }; 1090 | 1091 | var WEBGL_DEPTH_FUNCS = { 1092 | 512: THREE.NeverDepth, 1093 | 513: THREE.LessDepth, 1094 | 514: THREE.EqualDepth, 1095 | 515: THREE.LessEqualDepth, 1096 | 516: THREE.GreaterEqualDepth, 1097 | 517: THREE.NotEqualDepth, 1098 | 518: THREE.GreaterEqualDepth, 1099 | 519: THREE.AlwaysDepth 1100 | }; 1101 | 1102 | var WEBGL_BLEND_EQUATIONS = { 1103 | 32774: THREE.AddEquation, 1104 | 32778: THREE.SubtractEquation, 1105 | 32779: THREE.ReverseSubtractEquation 1106 | }; 1107 | 1108 | var WEBGL_BLEND_FUNCS = { 1109 | 0: THREE.ZeroFactor, 1110 | 1: THREE.OneFactor, 1111 | 768: THREE.SrcColorFactor, 1112 | 769: THREE.OneMinusSrcColorFactor, 1113 | 770: THREE.SrcAlphaFactor, 1114 | 771: THREE.OneMinusSrcAlphaFactor, 1115 | 772: THREE.DstAlphaFactor, 1116 | 773: THREE.OneMinusDstAlphaFactor, 1117 | 774: THREE.DstColorFactor, 1118 | 775: THREE.OneMinusDstColorFactor, 1119 | 776: THREE.SrcAlphaSaturateFactor 1120 | // The followings are not supported by Three.js yet 1121 | //32769: CONSTANT_COLOR, 1122 | //32770: ONE_MINUS_CONSTANT_COLOR, 1123 | //32771: CONSTANT_ALPHA, 1124 | //32772: ONE_MINUS_CONSTANT_COLOR 1125 | }; 1126 | 1127 | var WEBGL_TYPE_SIZES = { 1128 | 'SCALAR': 1, 1129 | 'VEC2': 2, 1130 | 'VEC3': 3, 1131 | 'VEC4': 4, 1132 | 'MAT2': 4, 1133 | 'MAT3': 9, 1134 | 'MAT4': 16 1135 | }; 1136 | 1137 | var ATTRIBUTES = { 1138 | POSITION: 'position', 1139 | NORMAL: 'normal', 1140 | TEXCOORD_0: 'uv', 1141 | TEXCOORD0: 'uv', // deprecated 1142 | TEXCOORD: 'uv', // deprecated 1143 | TEXCOORD_1: 'uv2', 1144 | COLOR_0: 'color', 1145 | COLOR0: 'color', // deprecated 1146 | COLOR: 'color', // deprecated 1147 | WEIGHTS_0: 'skinWeight', 1148 | WEIGHT: 'skinWeight', // deprecated 1149 | JOINTS_0: 'skinIndex', 1150 | JOINT: 'skinIndex' // deprecated 1151 | }; 1152 | 1153 | var PATH_PROPERTIES = { 1154 | scale: 'scale', 1155 | translation: 'position', 1156 | rotation: 'quaternion', 1157 | weights: 'morphTargetInfluences' 1158 | }; 1159 | 1160 | var INTERPOLATION = { 1161 | CUBICSPLINE: THREE.InterpolateSmooth, // We use custom interpolation GLTFCubicSplineInterpolation for CUBICSPLINE. 1162 | // KeyframeTrack.optimize() can't handle glTF Cubic Spline output values layout, 1163 | // using THREE.InterpolateSmooth for KeyframeTrack instantiation to prevent optimization. 1164 | // See KeyframeTrack.optimize() for the detail. 1165 | LINEAR: THREE.InterpolateLinear, 1166 | STEP: THREE.InterpolateDiscrete 1167 | }; 1168 | 1169 | var STATES_ENABLES = { 1170 | 2884: 'CULL_FACE', 1171 | 2929: 'DEPTH_TEST', 1172 | 3042: 'BLEND', 1173 | 3089: 'SCISSOR_TEST', 1174 | 32823: 'POLYGON_OFFSET_FILL', 1175 | 32926: 'SAMPLE_ALPHA_TO_COVERAGE' 1176 | }; 1177 | 1178 | var ALPHA_MODES = { 1179 | OPAQUE: 'OPAQUE', 1180 | MASK: 'MASK', 1181 | BLEND: 'BLEND' 1182 | }; 1183 | 1184 | var MIME_TYPE_FORMATS = { 1185 | 'image/png': THREE.RGBAFormat, 1186 | 'image/jpeg': THREE.RGBFormat 1187 | }; 1188 | 1189 | /* UTILITY FUNCTIONS */ 1190 | 1191 | function resolveURL( url, path ) { 1192 | 1193 | // Invalid URL 1194 | if ( typeof url !== 'string' || url === '' ) return ''; 1195 | 1196 | // Absolute URL http://,https://,// 1197 | if ( /^(https?:)?\/\//i.test( url ) ) return url; 1198 | 1199 | // Data URI 1200 | if ( /^data:.*,.*$/i.test( url ) ) return url; 1201 | 1202 | // Blob URL 1203 | if ( /^blob:.*$/i.test( url ) ) return url; 1204 | 1205 | // Relative URL 1206 | return path + url; 1207 | 1208 | } 1209 | 1210 | /** 1211 | * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#default-material 1212 | */ 1213 | function createDefaultMaterial() { 1214 | 1215 | return new THREE.MeshStandardMaterial( { 1216 | color: 0xFFFFFF, 1217 | emissive: 0x000000, 1218 | metalness: 1, 1219 | roughness: 1, 1220 | transparent: false, 1221 | depthTest: true, 1222 | side: THREE.FrontSide 1223 | } ); 1224 | 1225 | } 1226 | 1227 | function addUnknownExtensionsToUserData( knownExtensions, object, objectDef ) { 1228 | 1229 | // Add unknown glTF extensions to an object's userData. 1230 | 1231 | for ( var name in objectDef.extensions ) { 1232 | 1233 | if ( knownExtensions[ name ] === undefined ) { 1234 | 1235 | object.userData.gltfExtensions = object.userData.gltfExtensions || {}; 1236 | object.userData.gltfExtensions[ name ] = objectDef.extensions[ name ]; 1237 | 1238 | } 1239 | 1240 | } 1241 | 1242 | } 1243 | 1244 | /** 1245 | * @param {THREE.Object3D|THREE.Material|THREE.BufferGeometry} object 1246 | * @param {GLTF.definition} def 1247 | */ 1248 | function assignExtrasToUserData( object, gltfDef ) { 1249 | 1250 | if ( gltfDef.extras !== undefined ) { 1251 | 1252 | if ( typeof gltfDef.extras === 'object' ) { 1253 | 1254 | object.userData = gltfDef.extras; 1255 | 1256 | } else { 1257 | 1258 | console.warn( 'THREE.GLTFLoader: Ignoring primitive type .extras, ' + gltfDef.extras ); 1259 | 1260 | } 1261 | 1262 | } 1263 | 1264 | } 1265 | 1266 | /** 1267 | * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#morph-targets 1268 | * 1269 | * @param {THREE.BufferGeometry} geometry 1270 | * @param {Array} targets 1271 | * @param {Array} accessors 1272 | */ 1273 | function addMorphTargets( geometry, targets, accessors ) { 1274 | 1275 | var hasMorphPosition = false; 1276 | var hasMorphNormal = false; 1277 | 1278 | for ( var i = 0, il = targets.length; i < il; i ++ ) { 1279 | 1280 | var target = targets[ i ]; 1281 | 1282 | if ( target.POSITION !== undefined ) hasMorphPosition = true; 1283 | if ( target.NORMAL !== undefined ) hasMorphNormal = true; 1284 | 1285 | if ( hasMorphPosition && hasMorphNormal ) break; 1286 | 1287 | } 1288 | 1289 | if ( ! hasMorphPosition && ! hasMorphNormal ) return; 1290 | 1291 | var morphPositions = []; 1292 | var morphNormals = []; 1293 | 1294 | for ( var i = 0, il = targets.length; i < il; i ++ ) { 1295 | 1296 | var target = targets[ i ]; 1297 | var attributeName = 'morphTarget' + i; 1298 | 1299 | if ( hasMorphPosition ) { 1300 | 1301 | // Three.js morph position is absolute value. The formula is 1302 | // basePosition 1303 | // + weight0 * ( morphPosition0 - basePosition ) 1304 | // + weight1 * ( morphPosition1 - basePosition ) 1305 | // ... 1306 | // while the glTF one is relative 1307 | // basePosition 1308 | // + weight0 * glTFmorphPosition0 1309 | // + weight1 * glTFmorphPosition1 1310 | // ... 1311 | // then we need to convert from relative to absolute here. 1312 | 1313 | if ( target.POSITION !== undefined ) { 1314 | 1315 | // Cloning not to pollute original accessor 1316 | var positionAttribute = cloneBufferAttribute( accessors[ target.POSITION ] ); 1317 | positionAttribute.name = attributeName; 1318 | 1319 | var position = geometry.attributes.position; 1320 | 1321 | for ( var j = 0, jl = positionAttribute.count; j < jl; j ++ ) { 1322 | 1323 | positionAttribute.setXYZ( 1324 | j, 1325 | positionAttribute.getX( j ) + position.getX( j ), 1326 | positionAttribute.getY( j ) + position.getY( j ), 1327 | positionAttribute.getZ( j ) + position.getZ( j ) 1328 | ); 1329 | 1330 | } 1331 | 1332 | } else { 1333 | 1334 | positionAttribute = geometry.attributes.position; 1335 | 1336 | } 1337 | 1338 | morphPositions.push( positionAttribute ); 1339 | 1340 | } 1341 | 1342 | if ( hasMorphNormal ) { 1343 | 1344 | // see target.POSITION's comment 1345 | 1346 | var normalAttribute; 1347 | 1348 | if ( target.NORMAL !== undefined ) { 1349 | 1350 | var normalAttribute = cloneBufferAttribute( accessors[ target.NORMAL ] ); 1351 | normalAttribute.name = attributeName; 1352 | 1353 | var normal = geometry.attributes.normal; 1354 | 1355 | for ( var j = 0, jl = normalAttribute.count; j < jl; j ++ ) { 1356 | 1357 | normalAttribute.setXYZ( 1358 | j, 1359 | normalAttribute.getX( j ) + normal.getX( j ), 1360 | normalAttribute.getY( j ) + normal.getY( j ), 1361 | normalAttribute.getZ( j ) + normal.getZ( j ) 1362 | ); 1363 | 1364 | } 1365 | 1366 | } else { 1367 | 1368 | normalAttribute = geometry.attributes.normal; 1369 | 1370 | } 1371 | 1372 | morphNormals.push( normalAttribute ); 1373 | 1374 | } 1375 | 1376 | } 1377 | 1378 | if ( hasMorphPosition ) geometry.morphAttributes.position = morphPositions; 1379 | if ( hasMorphNormal ) geometry.morphAttributes.normal = morphNormals; 1380 | 1381 | } 1382 | 1383 | /** 1384 | * @param {THREE.Mesh} mesh 1385 | * @param {GLTF.Mesh} meshDef 1386 | */ 1387 | function updateMorphTargets( mesh, meshDef ) { 1388 | 1389 | mesh.updateMorphTargets(); 1390 | 1391 | if ( meshDef.weights !== undefined ) { 1392 | 1393 | for ( var i = 0, il = meshDef.weights.length; i < il; i ++ ) { 1394 | 1395 | mesh.morphTargetInfluences[ i ] = meshDef.weights[ i ]; 1396 | 1397 | } 1398 | 1399 | } 1400 | 1401 | // .extras has user-defined data, so check that .extras.targetNames is an array. 1402 | if ( meshDef.extras && Array.isArray( meshDef.extras.targetNames ) ) { 1403 | 1404 | var targetNames = meshDef.extras.targetNames; 1405 | 1406 | if ( mesh.morphTargetInfluences.length === targetNames.length ) { 1407 | 1408 | mesh.morphTargetDictionary = {}; 1409 | 1410 | for ( var i = 0, il = targetNames.length; i < il; i ++ ) { 1411 | 1412 | mesh.morphTargetDictionary[ targetNames[ i ] ] = i; 1413 | 1414 | } 1415 | 1416 | } else { 1417 | 1418 | console.warn( 'THREE.GLTFLoader: Invalid extras.targetNames length. Ignoring names.' ); 1419 | 1420 | } 1421 | 1422 | } 1423 | 1424 | } 1425 | 1426 | function isPrimitiveEqual( a, b ) { 1427 | 1428 | if ( a.indices !== b.indices ) { 1429 | 1430 | return false; 1431 | 1432 | } 1433 | 1434 | return isObjectEqual( a.attributes, b.attributes ); 1435 | 1436 | } 1437 | 1438 | function isObjectEqual( a, b ) { 1439 | 1440 | if ( Object.keys( a ).length !== Object.keys( b ).length ) return false; 1441 | 1442 | for ( var key in a ) { 1443 | 1444 | if ( a[ key ] !== b[ key ] ) return false; 1445 | 1446 | } 1447 | 1448 | return true; 1449 | 1450 | } 1451 | 1452 | function isArrayEqual( a, b ) { 1453 | 1454 | if ( a.length !== b.length ) return false; 1455 | 1456 | for ( var i = 0, il = a.length; i < il; i ++ ) { 1457 | 1458 | if ( a[ i ] !== b[ i ] ) return false; 1459 | 1460 | } 1461 | 1462 | return true; 1463 | 1464 | } 1465 | 1466 | function getCachedGeometry( cache, newPrimitive ) { 1467 | 1468 | for ( var i = 0, il = cache.length; i < il; i ++ ) { 1469 | 1470 | var cached = cache[ i ]; 1471 | 1472 | if ( isPrimitiveEqual( cached.primitive, newPrimitive ) ) return cached.promise; 1473 | 1474 | } 1475 | 1476 | return null; 1477 | 1478 | } 1479 | 1480 | function getCachedCombinedGeometry( cache, geometries ) { 1481 | 1482 | for ( var i = 0, il = cache.length; i < il; i ++ ) { 1483 | 1484 | var cached = cache[ i ]; 1485 | 1486 | if ( isArrayEqual( geometries, cached.baseGeometries ) ) return cached.geometry; 1487 | 1488 | } 1489 | 1490 | return null; 1491 | 1492 | } 1493 | 1494 | function getCachedMultiPassGeometry( cache, geometry, primitives ) { 1495 | 1496 | for ( var i = 0, il = cache.length; i < il; i ++ ) { 1497 | 1498 | var cached = cache[ i ]; 1499 | 1500 | if ( geometry === cached.baseGeometry && isArrayEqual( primitives, cached.primitives ) ) return cached.geometry; 1501 | 1502 | } 1503 | 1504 | return null; 1505 | 1506 | } 1507 | 1508 | function cloneBufferAttribute( attribute ) { 1509 | 1510 | if ( attribute.isInterleavedBufferAttribute ) { 1511 | 1512 | var count = attribute.count; 1513 | var itemSize = attribute.itemSize; 1514 | var array = attribute.array.slice( 0, count * itemSize ); 1515 | 1516 | for ( var i = 0; i < count; ++ i ) { 1517 | 1518 | array[ i ] = attribute.getX( i ); 1519 | if ( itemSize >= 2 ) array[ i + 1 ] = attribute.getY( i ); 1520 | if ( itemSize >= 3 ) array[ i + 2 ] = attribute.getZ( i ); 1521 | if ( itemSize >= 4 ) array[ i + 3 ] = attribute.getW( i ); 1522 | 1523 | } 1524 | 1525 | return new THREE.BufferAttribute( array, itemSize, attribute.normalized ); 1526 | 1527 | } 1528 | 1529 | return attribute.clone(); 1530 | 1531 | } 1532 | 1533 | /** 1534 | * Checks if we can build a single Mesh with MultiMaterial from multiple primitives. 1535 | * Returns true if all primitives use the same attributes/morphAttributes/mode 1536 | * and also have index. Otherwise returns false. 1537 | * 1538 | * @param {Array} primitives 1539 | * @return {Boolean} 1540 | */ 1541 | function isMultiPassGeometry( primitives ) { 1542 | 1543 | if ( primitives.length < 2 ) return false; 1544 | 1545 | var primitive0 = primitives[ 0 ]; 1546 | var targets0 = primitive0.targets || []; 1547 | 1548 | if ( primitive0.indices === undefined ) return false; 1549 | 1550 | for ( var i = 1, il = primitives.length; i < il; i ++ ) { 1551 | 1552 | var primitive = primitives[ i ]; 1553 | 1554 | if ( primitive0.mode !== primitive.mode ) return false; 1555 | if ( primitive.indices === undefined ) return false; 1556 | if ( ! isObjectEqual( primitive0.attributes, primitive.attributes ) ) return false; 1557 | 1558 | var targets = primitive.targets || []; 1559 | 1560 | if ( targets0.length !== targets.length ) return false; 1561 | 1562 | for ( var j = 0, jl = targets0.length; j < jl; j ++ ) { 1563 | 1564 | if ( ! isObjectEqual( targets0[ j ], targets[ j ] ) ) return false; 1565 | 1566 | } 1567 | 1568 | } 1569 | 1570 | return true; 1571 | 1572 | } 1573 | 1574 | /* GLTF PARSER */ 1575 | 1576 | function GLTFParser( json, extensions, options ) { 1577 | 1578 | this.json = json || {}; 1579 | this.extensions = extensions || {}; 1580 | this.options = options || {}; 1581 | 1582 | // loader object cache 1583 | this.cache = new GLTFRegistry(); 1584 | 1585 | // BufferGeometry caching 1586 | this.primitiveCache = []; 1587 | this.multiplePrimitivesCache = []; 1588 | this.multiPassGeometryCache = []; 1589 | 1590 | this.textureLoader = new THREE.TextureLoader( this.options.manager ); 1591 | this.textureLoader.setCrossOrigin( this.options.crossOrigin ); 1592 | 1593 | this.fileLoader = new THREE.FileLoader( this.options.manager ); 1594 | this.fileLoader.setResponseType( 'arraybuffer' ); 1595 | 1596 | } 1597 | 1598 | GLTFParser.prototype.parse = function ( onLoad, onError ) { 1599 | 1600 | var json = this.json; 1601 | 1602 | // Clear the loader cache 1603 | this.cache.removeAll(); 1604 | 1605 | // Mark the special nodes/meshes in json for efficient parse 1606 | this.markDefs(); 1607 | 1608 | // Fire the callback on complete 1609 | this.getMultiDependencies( [ 1610 | 1611 | 'scene', 1612 | 'animation', 1613 | 'camera' 1614 | 1615 | ] ).then( function ( dependencies ) { 1616 | 1617 | var scenes = dependencies.scenes || []; 1618 | var scene = scenes[ json.scene || 0 ]; 1619 | var animations = dependencies.animations || []; 1620 | var cameras = dependencies.cameras || []; 1621 | 1622 | onLoad( scene, scenes, cameras, animations, json ); 1623 | 1624 | } ).catch( onError ); 1625 | 1626 | }; 1627 | 1628 | /** 1629 | * Marks the special nodes/meshes in json for efficient parse. 1630 | */ 1631 | GLTFParser.prototype.markDefs = function () { 1632 | 1633 | var nodeDefs = this.json.nodes || []; 1634 | var skinDefs = this.json.skins || []; 1635 | var meshDefs = this.json.meshes || []; 1636 | 1637 | var meshReferences = {}; 1638 | var meshUses = {}; 1639 | 1640 | // Nothing in the node definition indicates whether it is a Bone or an 1641 | // Object3D. Use the skins' joint references to mark bones. 1642 | for ( var skinIndex = 0, skinLength = skinDefs.length; skinIndex < skinLength; skinIndex ++ ) { 1643 | 1644 | var joints = skinDefs[ skinIndex ].joints; 1645 | 1646 | for ( var i = 0, il = joints.length; i < il; i ++ ) { 1647 | 1648 | nodeDefs[ joints[ i ] ].isBone = true; 1649 | 1650 | } 1651 | 1652 | } 1653 | 1654 | // Meshes can (and should) be reused by multiple nodes in a glTF asset. To 1655 | // avoid having more than one THREE.Mesh with the same name, count 1656 | // references and rename instances below. 1657 | // 1658 | // Example: CesiumMilkTruck sample model reuses "Wheel" meshes. 1659 | for ( var nodeIndex = 0, nodeLength = nodeDefs.length; nodeIndex < nodeLength; nodeIndex ++ ) { 1660 | 1661 | var nodeDef = nodeDefs[ nodeIndex ]; 1662 | 1663 | if ( nodeDef.mesh !== undefined ) { 1664 | 1665 | if ( meshReferences[ nodeDef.mesh ] === undefined ) { 1666 | 1667 | meshReferences[ nodeDef.mesh ] = meshUses[ nodeDef.mesh ] = 0; 1668 | 1669 | } 1670 | 1671 | meshReferences[ nodeDef.mesh ] ++; 1672 | 1673 | // Nothing in the mesh definition indicates whether it is 1674 | // a SkinnedMesh or Mesh. Use the node's mesh reference 1675 | // to mark SkinnedMesh if node has skin. 1676 | if ( nodeDef.skin !== undefined ) { 1677 | 1678 | meshDefs[ nodeDef.mesh ].isSkinnedMesh = true; 1679 | 1680 | } 1681 | 1682 | } 1683 | 1684 | } 1685 | 1686 | this.json.meshReferences = meshReferences; 1687 | this.json.meshUses = meshUses; 1688 | 1689 | }; 1690 | 1691 | /** 1692 | * Requests the specified dependency asynchronously, with caching. 1693 | * @param {string} type 1694 | * @param {number} index 1695 | * @return {Promise} 1696 | */ 1697 | GLTFParser.prototype.getDependency = function ( type, index ) { 1698 | 1699 | var cacheKey = type + ':' + index; 1700 | var dependency = this.cache.get( cacheKey ); 1701 | 1702 | if ( ! dependency ) { 1703 | 1704 | switch ( type ) { 1705 | 1706 | case 'scene': 1707 | dependency = this.loadScene( index ); 1708 | break; 1709 | 1710 | case 'node': 1711 | dependency = this.loadNode( index ); 1712 | break; 1713 | 1714 | case 'mesh': 1715 | dependency = this.loadMesh( index ); 1716 | break; 1717 | 1718 | case 'accessor': 1719 | dependency = this.loadAccessor( index ); 1720 | break; 1721 | 1722 | case 'bufferView': 1723 | dependency = this.loadBufferView( index ); 1724 | break; 1725 | 1726 | case 'buffer': 1727 | dependency = this.loadBuffer( index ); 1728 | break; 1729 | 1730 | case 'material': 1731 | dependency = this.loadMaterial( index ); 1732 | break; 1733 | 1734 | case 'texture': 1735 | dependency = this.loadTexture( index ); 1736 | break; 1737 | 1738 | case 'skin': 1739 | dependency = this.loadSkin( index ); 1740 | break; 1741 | 1742 | case 'animation': 1743 | dependency = this.loadAnimation( index ); 1744 | break; 1745 | 1746 | case 'camera': 1747 | dependency = this.loadCamera( index ); 1748 | break; 1749 | 1750 | default: 1751 | throw new Error( 'Unknown type: ' + type ); 1752 | 1753 | } 1754 | 1755 | this.cache.add( cacheKey, dependency ); 1756 | 1757 | } 1758 | 1759 | return dependency; 1760 | 1761 | }; 1762 | 1763 | /** 1764 | * Requests all dependencies of the specified type asynchronously, with caching. 1765 | * @param {string} type 1766 | * @return {Promise>} 1767 | */ 1768 | GLTFParser.prototype.getDependencies = function ( type ) { 1769 | 1770 | var dependencies = this.cache.get( type ); 1771 | 1772 | if ( ! dependencies ) { 1773 | 1774 | var parser = this; 1775 | var defs = this.json[ type + ( type === 'mesh' ? 'es' : 's' ) ] || []; 1776 | 1777 | dependencies = Promise.all( defs.map( function ( def, index ) { 1778 | 1779 | return parser.getDependency( type, index ); 1780 | 1781 | } ) ); 1782 | 1783 | this.cache.add( type, dependencies ); 1784 | 1785 | } 1786 | 1787 | return dependencies; 1788 | 1789 | }; 1790 | 1791 | /** 1792 | * Requests all multiple dependencies of the specified types asynchronously, with caching. 1793 | * @param {Array} types 1794 | * @return {Promise>>} 1795 | */ 1796 | GLTFParser.prototype.getMultiDependencies = function ( types ) { 1797 | 1798 | var results = {}; 1799 | var pendings = []; 1800 | 1801 | for ( var i = 0, il = types.length; i < il; i ++ ) { 1802 | 1803 | var type = types[ i ]; 1804 | var value = this.getDependencies( type ); 1805 | 1806 | value = value.then( function ( key, value ) { 1807 | 1808 | results[ key ] = value; 1809 | 1810 | }.bind( this, type + ( type === 'mesh' ? 'es' : 's' ) ) ); 1811 | 1812 | pendings.push( value ); 1813 | 1814 | } 1815 | 1816 | return Promise.all( pendings ).then( function () { 1817 | 1818 | return results; 1819 | 1820 | } ); 1821 | 1822 | }; 1823 | 1824 | /** 1825 | * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#buffers-and-buffer-views 1826 | * @param {number} bufferIndex 1827 | * @return {Promise} 1828 | */ 1829 | GLTFParser.prototype.loadBuffer = function ( bufferIndex ) { 1830 | 1831 | var bufferDef = this.json.buffers[ bufferIndex ]; 1832 | var loader = this.fileLoader; 1833 | 1834 | if ( bufferDef.type && bufferDef.type !== 'arraybuffer' ) { 1835 | 1836 | throw new Error( 'THREE.GLTFLoader: ' + bufferDef.type + ' buffer type is not supported.' ); 1837 | 1838 | } 1839 | 1840 | // If present, GLB container is required to be the first buffer. 1841 | if ( bufferDef.uri === undefined && bufferIndex === 0 ) { 1842 | 1843 | return Promise.resolve( this.extensions[ EXTENSIONS.KHR_BINARY_GLTF ].body ); 1844 | 1845 | } 1846 | 1847 | var options = this.options; 1848 | 1849 | return new Promise( function ( resolve, reject ) { 1850 | 1851 | loader.load( resolveURL( bufferDef.uri, options.path ), resolve, undefined, function () { 1852 | 1853 | reject( new Error( 'THREE.GLTFLoader: Failed to load buffer "' + bufferDef.uri + '".' ) ); 1854 | 1855 | } ); 1856 | 1857 | } ); 1858 | 1859 | }; 1860 | 1861 | /** 1862 | * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#buffers-and-buffer-views 1863 | * @param {number} bufferViewIndex 1864 | * @return {Promise} 1865 | */ 1866 | GLTFParser.prototype.loadBufferView = function ( bufferViewIndex ) { 1867 | 1868 | var bufferViewDef = this.json.bufferViews[ bufferViewIndex ]; 1869 | 1870 | return this.getDependency( 'buffer', bufferViewDef.buffer ).then( function ( buffer ) { 1871 | 1872 | var byteLength = bufferViewDef.byteLength || 0; 1873 | var byteOffset = bufferViewDef.byteOffset || 0; 1874 | return buffer.slice( byteOffset, byteOffset + byteLength ); 1875 | 1876 | } ); 1877 | 1878 | }; 1879 | 1880 | /** 1881 | * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#accessors 1882 | * @param {number} accessorIndex 1883 | * @return {Promise} 1884 | */ 1885 | GLTFParser.prototype.loadAccessor = function ( accessorIndex ) { 1886 | 1887 | var parser = this; 1888 | var json = this.json; 1889 | 1890 | var accessorDef = this.json.accessors[ accessorIndex ]; 1891 | 1892 | if ( accessorDef.bufferView === undefined && accessorDef.sparse === undefined ) { 1893 | 1894 | // Ignore empty accessors, which may be used to declare runtime 1895 | // information about attributes coming from another source (e.g. Draco 1896 | // compression extension). 1897 | return null; 1898 | 1899 | } 1900 | 1901 | var pendingBufferViews = []; 1902 | 1903 | if ( accessorDef.bufferView !== undefined ) { 1904 | 1905 | pendingBufferViews.push( this.getDependency( 'bufferView', accessorDef.bufferView ) ); 1906 | 1907 | } else { 1908 | 1909 | pendingBufferViews.push( null ); 1910 | 1911 | } 1912 | 1913 | if ( accessorDef.sparse !== undefined ) { 1914 | 1915 | pendingBufferViews.push( this.getDependency( 'bufferView', accessorDef.sparse.indices.bufferView ) ); 1916 | pendingBufferViews.push( this.getDependency( 'bufferView', accessorDef.sparse.values.bufferView ) ); 1917 | 1918 | } 1919 | 1920 | return Promise.all( pendingBufferViews ).then( function ( bufferViews ) { 1921 | 1922 | var bufferView = bufferViews[ 0 ]; 1923 | 1924 | var itemSize = WEBGL_TYPE_SIZES[ accessorDef.type ]; 1925 | var TypedArray = WEBGL_COMPONENT_TYPES[ accessorDef.componentType ]; 1926 | 1927 | // For VEC3: itemSize is 3, elementBytes is 4, itemBytes is 12. 1928 | var elementBytes = TypedArray.BYTES_PER_ELEMENT; 1929 | var itemBytes = elementBytes * itemSize; 1930 | var byteOffset = accessorDef.byteOffset || 0; 1931 | var byteStride = accessorDef.bufferView !== undefined ? json.bufferViews[ accessorDef.bufferView ].byteStride : undefined; 1932 | var normalized = accessorDef.normalized === true; 1933 | var array, bufferAttribute; 1934 | 1935 | // The buffer is not interleaved if the stride is the item size in bytes. 1936 | if ( byteStride && byteStride !== itemBytes ) { 1937 | 1938 | var ibCacheKey = 'InterleavedBuffer:' + accessorDef.bufferView + ':' + accessorDef.componentType; 1939 | var ib = parser.cache.get( ibCacheKey ); 1940 | 1941 | if ( ! ib ) { 1942 | 1943 | // Use the full buffer if it's interleaved. 1944 | array = new TypedArray( bufferView ); 1945 | 1946 | // Integer parameters to IB/IBA are in array elements, not bytes. 1947 | ib = new THREE.InterleavedBuffer( array, byteStride / elementBytes ); 1948 | 1949 | parser.cache.add( ibCacheKey, ib ); 1950 | 1951 | } 1952 | 1953 | bufferAttribute = new THREE.InterleavedBufferAttribute( ib, itemSize, byteOffset / elementBytes, normalized ); 1954 | 1955 | } else { 1956 | 1957 | if ( bufferView === null ) { 1958 | 1959 | array = new TypedArray( accessorDef.count * itemSize ); 1960 | 1961 | } else { 1962 | 1963 | array = new TypedArray( bufferView, byteOffset, accessorDef.count * itemSize ); 1964 | 1965 | } 1966 | 1967 | bufferAttribute = new THREE.BufferAttribute( array, itemSize, normalized ); 1968 | 1969 | } 1970 | 1971 | // https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#sparse-accessors 1972 | if ( accessorDef.sparse !== undefined ) { 1973 | 1974 | var itemSizeIndices = WEBGL_TYPE_SIZES.SCALAR; 1975 | var TypedArrayIndices = WEBGL_COMPONENT_TYPES[ accessorDef.sparse.indices.componentType ]; 1976 | 1977 | var byteOffsetIndices = accessorDef.sparse.indices.byteOffset || 0; 1978 | var byteOffsetValues = accessorDef.sparse.values.byteOffset || 0; 1979 | 1980 | var sparseIndices = new TypedArrayIndices( bufferViews[ 1 ], byteOffsetIndices, accessorDef.sparse.count * itemSizeIndices ); 1981 | var sparseValues = new TypedArray( bufferViews[ 2 ], byteOffsetValues, accessorDef.sparse.count * itemSize ); 1982 | 1983 | if ( bufferView !== null ) { 1984 | 1985 | // Avoid modifying the original ArrayBuffer, if the bufferView wasn't initialized with zeroes. 1986 | bufferAttribute.setArray( bufferAttribute.array.slice() ); 1987 | 1988 | } 1989 | 1990 | for ( var i = 0, il = sparseIndices.length; i < il; i ++ ) { 1991 | 1992 | var index = sparseIndices[ i ]; 1993 | 1994 | bufferAttribute.setX( index, sparseValues[ i * itemSize ] ); 1995 | if ( itemSize >= 2 ) bufferAttribute.setY( index, sparseValues[ i * itemSize + 1 ] ); 1996 | if ( itemSize >= 3 ) bufferAttribute.setZ( index, sparseValues[ i * itemSize + 2 ] ); 1997 | if ( itemSize >= 4 ) bufferAttribute.setW( index, sparseValues[ i * itemSize + 3 ] ); 1998 | if ( itemSize >= 5 ) throw new Error( 'THREE.GLTFLoader: Unsupported itemSize in sparse BufferAttribute.' ); 1999 | 2000 | } 2001 | 2002 | } 2003 | 2004 | return bufferAttribute; 2005 | 2006 | } ); 2007 | 2008 | }; 2009 | 2010 | /** 2011 | * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#textures 2012 | * @param {number} textureIndex 2013 | * @return {Promise} 2014 | */ 2015 | GLTFParser.prototype.loadTexture = function ( textureIndex ) { 2016 | 2017 | var parser = this; 2018 | var json = this.json; 2019 | var options = this.options; 2020 | var textureLoader = this.textureLoader; 2021 | 2022 | var URL = window.URL || window.webkitURL; 2023 | 2024 | var textureDef = json.textures[ textureIndex ]; 2025 | 2026 | var textureExtensions = textureDef.extensions || {}; 2027 | 2028 | var source; 2029 | 2030 | if ( textureExtensions[ EXTENSIONS.MSFT_TEXTURE_DDS ] ) { 2031 | 2032 | source = json.images[ textureExtensions[ EXTENSIONS.MSFT_TEXTURE_DDS ].source ]; 2033 | 2034 | } else { 2035 | 2036 | source = json.images[ textureDef.source ]; 2037 | 2038 | } 2039 | 2040 | var sourceURI = source.uri; 2041 | var isObjectURL = false; 2042 | 2043 | if ( source.bufferView !== undefined ) { 2044 | 2045 | // Load binary image data from bufferView, if provided. 2046 | 2047 | sourceURI = parser.getDependency( 'bufferView', source.bufferView ).then( function ( bufferView ) { 2048 | 2049 | isObjectURL = true; 2050 | var blob = new Blob( [ bufferView ], { type: source.mimeType } ); 2051 | sourceURI = URL.createObjectURL( blob ); 2052 | return sourceURI; 2053 | 2054 | } ); 2055 | 2056 | } 2057 | 2058 | return Promise.resolve( sourceURI ).then( function ( sourceURI ) { 2059 | 2060 | // Load Texture resource. 2061 | 2062 | var loader = THREE.Loader.Handlers.get( sourceURI ); 2063 | 2064 | if ( ! loader ) { 2065 | 2066 | loader = textureExtensions[ EXTENSIONS.MSFT_TEXTURE_DDS ] 2067 | ? parser.extensions[ EXTENSIONS.MSFT_TEXTURE_DDS ].ddsLoader 2068 | : textureLoader; 2069 | 2070 | } 2071 | 2072 | return new Promise( function ( resolve, reject ) { 2073 | 2074 | loader.load( resolveURL( sourceURI, options.path ), resolve, undefined, reject ); 2075 | 2076 | } ); 2077 | 2078 | } ).then( function ( texture ) { 2079 | 2080 | // Clean up resources and configure Texture. 2081 | 2082 | if ( isObjectURL === true ) { 2083 | 2084 | URL.revokeObjectURL( sourceURI ); 2085 | 2086 | } 2087 | 2088 | texture.flipY = false; 2089 | 2090 | if ( textureDef.name !== undefined ) texture.name = textureDef.name; 2091 | 2092 | // Ignore unknown mime types, like DDS files. 2093 | if ( source.mimeType in MIME_TYPE_FORMATS ) { 2094 | 2095 | texture.format = MIME_TYPE_FORMATS[ source.mimeType ]; 2096 | 2097 | } 2098 | 2099 | var samplers = json.samplers || {}; 2100 | var sampler = samplers[ textureDef.sampler ] || {}; 2101 | 2102 | texture.magFilter = WEBGL_FILTERS[ sampler.magFilter ] || THREE.LinearFilter; 2103 | texture.minFilter = WEBGL_FILTERS[ sampler.minFilter ] || THREE.LinearMipMapLinearFilter; 2104 | texture.wrapS = WEBGL_WRAPPINGS[ sampler.wrapS ] || THREE.RepeatWrapping; 2105 | texture.wrapT = WEBGL_WRAPPINGS[ sampler.wrapT ] || THREE.RepeatWrapping; 2106 | 2107 | return texture; 2108 | 2109 | } ); 2110 | 2111 | }; 2112 | 2113 | /** 2114 | * Asynchronously assigns a texture to the given material parameters. 2115 | * @param {Object} materialParams 2116 | * @param {string} textureName 2117 | * @param {number} textureIndex 2118 | * @return {Promise} 2119 | */ 2120 | GLTFParser.prototype.assignTexture = function ( materialParams, textureName, textureIndex ) { 2121 | 2122 | return this.getDependency( 'texture', textureIndex ).then( function ( texture ) { 2123 | 2124 | materialParams[ textureName ] = texture; 2125 | 2126 | } ); 2127 | 2128 | }; 2129 | 2130 | /** 2131 | * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#materials 2132 | * @param {number} materialIndex 2133 | * @return {Promise} 2134 | */ 2135 | GLTFParser.prototype.loadMaterial = function ( materialIndex ) { 2136 | 2137 | var parser = this; 2138 | var json = this.json; 2139 | var extensions = this.extensions; 2140 | var materialDef = json.materials[ materialIndex ]; 2141 | 2142 | var materialType; 2143 | var materialParams = {}; 2144 | var materialExtensions = materialDef.extensions || {}; 2145 | 2146 | var pending = []; 2147 | 2148 | if ( materialExtensions[ EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS ] ) { 2149 | 2150 | var sgExtension = extensions[ EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS ]; 2151 | materialType = sgExtension.getMaterialType( materialDef ); 2152 | pending.push( sgExtension.extendParams( materialParams, materialDef, parser ) ); 2153 | 2154 | } else if ( materialExtensions[ EXTENSIONS.KHR_MATERIALS_UNLIT ] ) { 2155 | 2156 | var kmuExtension = extensions[ EXTENSIONS.KHR_MATERIALS_UNLIT ]; 2157 | materialType = kmuExtension.getMaterialType( materialDef ); 2158 | pending.push( kmuExtension.extendParams( materialParams, materialDef, parser ) ); 2159 | 2160 | } else { 2161 | 2162 | // Specification: 2163 | // https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#metallic-roughness-material 2164 | 2165 | materialType = THREE.MeshStandardMaterial; 2166 | 2167 | var metallicRoughness = materialDef.pbrMetallicRoughness || {}; 2168 | 2169 | materialParams.color = new THREE.Color( 1.0, 1.0, 1.0 ); 2170 | materialParams.opacity = 1.0; 2171 | 2172 | if ( Array.isArray( metallicRoughness.baseColorFactor ) ) { 2173 | 2174 | var array = metallicRoughness.baseColorFactor; 2175 | 2176 | materialParams.color.fromArray( array ); 2177 | materialParams.opacity = array[ 3 ]; 2178 | 2179 | } 2180 | 2181 | if ( metallicRoughness.baseColorTexture !== undefined ) { 2182 | 2183 | pending.push( parser.assignTexture( materialParams, 'map', metallicRoughness.baseColorTexture.index ) ); 2184 | 2185 | } 2186 | 2187 | materialParams.metalness = metallicRoughness.metallicFactor !== undefined ? metallicRoughness.metallicFactor : 1.0; 2188 | materialParams.roughness = metallicRoughness.roughnessFactor !== undefined ? metallicRoughness.roughnessFactor : 1.0; 2189 | 2190 | if ( metallicRoughness.metallicRoughnessTexture !== undefined ) { 2191 | 2192 | var textureIndex = metallicRoughness.metallicRoughnessTexture.index; 2193 | pending.push( parser.assignTexture( materialParams, 'metalnessMap', textureIndex ) ); 2194 | pending.push( parser.assignTexture( materialParams, 'roughnessMap', textureIndex ) ); 2195 | 2196 | } 2197 | 2198 | } 2199 | 2200 | if ( materialDef.doubleSided === true ) { 2201 | 2202 | materialParams.side = THREE.DoubleSide; 2203 | 2204 | } 2205 | 2206 | var alphaMode = materialDef.alphaMode || ALPHA_MODES.OPAQUE; 2207 | 2208 | if ( alphaMode === ALPHA_MODES.BLEND ) { 2209 | 2210 | materialParams.transparent = true; 2211 | 2212 | } else { 2213 | 2214 | materialParams.transparent = false; 2215 | 2216 | if ( alphaMode === ALPHA_MODES.MASK ) { 2217 | 2218 | materialParams.alphaTest = materialDef.alphaCutoff !== undefined ? materialDef.alphaCutoff : 0.5; 2219 | 2220 | } 2221 | 2222 | } 2223 | 2224 | if ( materialDef.normalTexture !== undefined && materialType !== THREE.MeshBasicMaterial ) { 2225 | 2226 | pending.push( parser.assignTexture( materialParams, 'normalMap', materialDef.normalTexture.index ) ); 2227 | 2228 | materialParams.normalScale = new THREE.Vector2( 1, 1 ); 2229 | 2230 | if ( materialDef.normalTexture.scale !== undefined ) { 2231 | 2232 | materialParams.normalScale.set( materialDef.normalTexture.scale, materialDef.normalTexture.scale ); 2233 | 2234 | } 2235 | 2236 | } 2237 | 2238 | if ( materialDef.occlusionTexture !== undefined && materialType !== THREE.MeshBasicMaterial ) { 2239 | 2240 | pending.push( parser.assignTexture( materialParams, 'aoMap', materialDef.occlusionTexture.index ) ); 2241 | 2242 | if ( materialDef.occlusionTexture.strength !== undefined ) { 2243 | 2244 | materialParams.aoMapIntensity = materialDef.occlusionTexture.strength; 2245 | 2246 | } 2247 | 2248 | } 2249 | 2250 | if ( materialDef.emissiveFactor !== undefined && materialType !== THREE.MeshBasicMaterial ) { 2251 | 2252 | materialParams.emissive = new THREE.Color().fromArray( materialDef.emissiveFactor ); 2253 | 2254 | } 2255 | 2256 | if ( materialDef.emissiveTexture !== undefined && materialType !== THREE.MeshBasicMaterial ) { 2257 | 2258 | pending.push( parser.assignTexture( materialParams, 'emissiveMap', materialDef.emissiveTexture.index ) ); 2259 | 2260 | } 2261 | 2262 | return Promise.all( pending ).then( function () { 2263 | 2264 | var material; 2265 | 2266 | if ( materialType === THREE.ShaderMaterial ) { 2267 | 2268 | material = extensions[ EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS ].createMaterial( materialParams ); 2269 | 2270 | } else { 2271 | 2272 | material = new materialType( materialParams ); 2273 | 2274 | } 2275 | 2276 | if ( materialDef.name !== undefined ) material.name = materialDef.name; 2277 | 2278 | // Normal map textures use OpenGL conventions: 2279 | // https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#materialnormaltexture 2280 | if ( material.normalScale ) { 2281 | 2282 | material.normalScale.y = - material.normalScale.y; 2283 | 2284 | } 2285 | 2286 | // baseColorTexture, emissiveTexture, and specularGlossinessTexture use sRGB encoding. 2287 | if ( material.map ) material.map.encoding = THREE.sRGBEncoding; 2288 | if ( material.emissiveMap ) material.emissiveMap.encoding = THREE.sRGBEncoding; 2289 | if ( material.specularMap ) material.specularMap.encoding = THREE.sRGBEncoding; 2290 | 2291 | assignExtrasToUserData( material, materialDef ); 2292 | 2293 | if ( materialDef.extensions ) addUnknownExtensionsToUserData( extensions, material, materialDef ); 2294 | 2295 | return material; 2296 | 2297 | } ); 2298 | 2299 | }; 2300 | 2301 | /** 2302 | * @param {THREE.BufferGeometry} geometry 2303 | * @param {GLTF.Primitive} primitiveDef 2304 | * @param {Array} accessors 2305 | */ 2306 | function addPrimitiveAttributes( geometry, primitiveDef, accessors ) { 2307 | 2308 | var attributes = primitiveDef.attributes; 2309 | 2310 | for ( var gltfAttributeName in attributes ) { 2311 | 2312 | var threeAttributeName = ATTRIBUTES[ gltfAttributeName ]; 2313 | var bufferAttribute = accessors[ attributes[ gltfAttributeName ] ]; 2314 | 2315 | // Skip attributes already provided by e.g. Draco extension. 2316 | if ( ! threeAttributeName ) continue; 2317 | if ( threeAttributeName in geometry.attributes ) continue; 2318 | 2319 | geometry.addAttribute( threeAttributeName, bufferAttribute ); 2320 | 2321 | } 2322 | 2323 | if ( primitiveDef.indices !== undefined && ! geometry.index ) { 2324 | 2325 | geometry.setIndex( accessors[ primitiveDef.indices ] ); 2326 | 2327 | } 2328 | 2329 | if ( primitiveDef.targets !== undefined ) { 2330 | 2331 | addMorphTargets( geometry, primitiveDef.targets, accessors ); 2332 | 2333 | } 2334 | 2335 | assignExtrasToUserData( geometry, primitiveDef ); 2336 | 2337 | } 2338 | 2339 | /** 2340 | * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#geometry 2341 | * 2342 | * Creates BufferGeometries from primitives. 2343 | * If we can build a single BufferGeometry with .groups from multiple primitives, returns one BufferGeometry. 2344 | * Otherwise, returns BufferGeometries without .groups as many as primitives. 2345 | * 2346 | * @param {Array} primitives 2347 | * @return {Promise>} 2348 | */ 2349 | GLTFParser.prototype.loadGeometries = function ( primitives ) { 2350 | 2351 | var parser = this; 2352 | var extensions = this.extensions; 2353 | var cache = this.primitiveCache; 2354 | 2355 | var isMultiPass = isMultiPassGeometry( primitives ); 2356 | var originalPrimitives; 2357 | 2358 | if ( isMultiPass ) { 2359 | 2360 | originalPrimitives = primitives; // save original primitives and use later 2361 | 2362 | // We build a single BufferGeometry with .groups from multiple primitives 2363 | // because all primitives share the same attributes/morph/mode and have indices. 2364 | 2365 | primitives = [ primitives[ 0 ] ]; 2366 | 2367 | // Sets .groups and combined indices to a geometry later in this method. 2368 | 2369 | } 2370 | 2371 | return this.getDependencies( 'accessor' ).then( function ( accessors ) { 2372 | 2373 | var pending = []; 2374 | 2375 | for ( var i = 0, il = primitives.length; i < il; i ++ ) { 2376 | 2377 | var primitive = primitives[ i ]; 2378 | 2379 | // See if we've already created this geometry 2380 | var cached = getCachedGeometry( cache, primitive ); 2381 | 2382 | if ( cached ) { 2383 | 2384 | // Use the cached geometry if it exists 2385 | pending.push( cached ); 2386 | 2387 | } else if ( primitive.extensions && primitive.extensions[ EXTENSIONS.KHR_DRACO_MESH_COMPRESSION ] ) { 2388 | 2389 | // Use DRACO geometry if available 2390 | var geometryPromise = extensions[ EXTENSIONS.KHR_DRACO_MESH_COMPRESSION ] 2391 | .decodePrimitive( primitive, parser ) 2392 | .then( function ( geometry ) { 2393 | 2394 | addPrimitiveAttributes( geometry, primitive, accessors ); 2395 | 2396 | return geometry; 2397 | 2398 | } ); 2399 | 2400 | cache.push( { primitive: primitive, promise: geometryPromise } ); 2401 | 2402 | pending.push( geometryPromise ); 2403 | 2404 | } else { 2405 | 2406 | // Otherwise create a new geometry 2407 | var geometry = new THREE.BufferGeometry(); 2408 | 2409 | addPrimitiveAttributes( geometry, primitive, accessors ); 2410 | 2411 | var geometryPromise = Promise.resolve( geometry ); 2412 | 2413 | // Cache this geometry 2414 | cache.push( { primitive: primitive, promise: geometryPromise } ); 2415 | 2416 | pending.push( geometryPromise ); 2417 | 2418 | } 2419 | 2420 | } 2421 | 2422 | return Promise.all( pending ).then( function ( geometries ) { 2423 | 2424 | if ( isMultiPass ) { 2425 | 2426 | var baseGeometry = geometries[ 0 ]; 2427 | 2428 | // See if we've already created this combined geometry 2429 | var cache = parser.multiPassGeometryCache; 2430 | var cached = getCachedMultiPassGeometry( cache, baseGeometry, originalPrimitives ); 2431 | 2432 | if ( cached !== null ) return [ cached.geometry ]; 2433 | 2434 | // Cloning geometry because of index override. 2435 | // Attributes can be reused so cloning by myself here. 2436 | var geometry = new THREE.BufferGeometry(); 2437 | 2438 | geometry.name = baseGeometry.name; 2439 | geometry.userData = baseGeometry.userData; 2440 | 2441 | for ( var key in baseGeometry.attributes ) geometry.addAttribute( key, baseGeometry.attributes[ key ] ); 2442 | for ( var key in baseGeometry.morphAttributes ) geometry.morphAttributes[ key ] = baseGeometry.morphAttributes[ key ]; 2443 | 2444 | var indices = []; 2445 | var offset = 0; 2446 | 2447 | for ( var i = 0, il = originalPrimitives.length; i < il; i ++ ) { 2448 | 2449 | var accessor = accessors[ originalPrimitives[ i ].indices ]; 2450 | 2451 | for ( var j = 0, jl = accessor.count; j < jl; j ++ ) indices.push( accessor.array[ j ] ); 2452 | 2453 | geometry.addGroup( offset, accessor.count, i ); 2454 | 2455 | offset += accessor.count; 2456 | 2457 | } 2458 | 2459 | geometry.setIndex( indices ); 2460 | 2461 | cache.push( { geometry: geometry, baseGeometry: baseGeometry, primitives: originalPrimitives } ); 2462 | 2463 | return [ geometry ]; 2464 | 2465 | } else if ( geometries.length > 1 && THREE.BufferGeometryUtils !== undefined ) { 2466 | 2467 | // Tries to merge geometries with BufferGeometryUtils if possible 2468 | 2469 | for ( var i = 1, il = primitives.length; i < il; i ++ ) { 2470 | 2471 | // can't merge if draw mode is different 2472 | if ( primitives[ 0 ].mode !== primitives[ i ].mode ) return geometries; 2473 | 2474 | } 2475 | 2476 | // See if we've already created this combined geometry 2477 | var cache = parser.multiplePrimitivesCache; 2478 | var cached = getCachedCombinedGeometry( cache, geometries ); 2479 | 2480 | if ( cached ) { 2481 | 2482 | if ( cached.geometry !== null ) return [ cached.geometry ]; 2483 | 2484 | } else { 2485 | 2486 | var geometry = THREE.BufferGeometryUtils.mergeBufferGeometries( geometries, true ); 2487 | 2488 | cache.push( { geometry: geometry, baseGeometries: geometries } ); 2489 | 2490 | if ( geometry !== null ) return [ geometry ]; 2491 | 2492 | } 2493 | 2494 | } 2495 | 2496 | return geometries; 2497 | 2498 | } ); 2499 | 2500 | } ); 2501 | 2502 | }; 2503 | 2504 | /** 2505 | * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#meshes 2506 | * @param {number} meshIndex 2507 | * @return {Promise} 2508 | */ 2509 | GLTFParser.prototype.loadMesh = function ( meshIndex ) { 2510 | 2511 | var scope = this; 2512 | var json = this.json; 2513 | var extensions = this.extensions; 2514 | 2515 | var meshDef = json.meshes[ meshIndex ]; 2516 | 2517 | return this.getMultiDependencies( [ 2518 | 2519 | 'accessor', 2520 | 'material' 2521 | 2522 | ] ).then( function ( dependencies ) { 2523 | 2524 | var primitives = meshDef.primitives; 2525 | var originalMaterials = []; 2526 | 2527 | for ( var i = 0, il = primitives.length; i < il; i ++ ) { 2528 | 2529 | originalMaterials[ i ] = primitives[ i ].material === undefined 2530 | ? createDefaultMaterial() 2531 | : dependencies.materials[ primitives[ i ].material ]; 2532 | 2533 | } 2534 | 2535 | return scope.loadGeometries( primitives ).then( function ( geometries ) { 2536 | 2537 | var isMultiMaterial = geometries.length === 1 && geometries[ 0 ].groups.length > 0; 2538 | 2539 | var meshes = []; 2540 | 2541 | for ( var i = 0, il = geometries.length; i < il; i ++ ) { 2542 | 2543 | var geometry = geometries[ i ]; 2544 | var primitive = primitives[ i ]; 2545 | 2546 | // 1. create Mesh 2547 | 2548 | var mesh; 2549 | 2550 | var material = isMultiMaterial ? originalMaterials : originalMaterials[ i ]; 2551 | 2552 | if ( primitive.mode === WEBGL_CONSTANTS.TRIANGLES || 2553 | primitive.mode === WEBGL_CONSTANTS.TRIANGLE_STRIP || 2554 | primitive.mode === WEBGL_CONSTANTS.TRIANGLE_FAN || 2555 | primitive.mode === undefined ) { 2556 | 2557 | // .isSkinnedMesh isn't in glTF spec. See .markDefs() 2558 | mesh = meshDef.isSkinnedMesh === true 2559 | ? new THREE.SkinnedMesh( geometry, material ) 2560 | : new THREE.Mesh( geometry, material ); 2561 | 2562 | if ( primitive.mode === WEBGL_CONSTANTS.TRIANGLE_STRIP ) { 2563 | 2564 | mesh.drawMode = THREE.TriangleStripDrawMode; 2565 | 2566 | } else if ( primitive.mode === WEBGL_CONSTANTS.TRIANGLE_FAN ) { 2567 | 2568 | mesh.drawMode = THREE.TriangleFanDrawMode; 2569 | 2570 | } 2571 | 2572 | } else if ( primitive.mode === WEBGL_CONSTANTS.LINES ) { 2573 | 2574 | mesh = new THREE.LineSegments( geometry, material ); 2575 | 2576 | } else if ( primitive.mode === WEBGL_CONSTANTS.LINE_STRIP ) { 2577 | 2578 | mesh = new THREE.Line( geometry, material ); 2579 | 2580 | } else if ( primitive.mode === WEBGL_CONSTANTS.LINE_LOOP ) { 2581 | 2582 | mesh = new THREE.LineLoop( geometry, material ); 2583 | 2584 | } else if ( primitive.mode === WEBGL_CONSTANTS.POINTS ) { 2585 | 2586 | mesh = new THREE.Points( geometry, material ); 2587 | 2588 | } else { 2589 | 2590 | throw new Error( 'THREE.GLTFLoader: Primitive mode unsupported: ' + primitive.mode ); 2591 | 2592 | } 2593 | 2594 | if ( Object.keys( mesh.geometry.morphAttributes ).length > 0 ) { 2595 | 2596 | updateMorphTargets( mesh, meshDef ); 2597 | 2598 | } 2599 | 2600 | mesh.name = meshDef.name || ( 'mesh_' + meshIndex ); 2601 | 2602 | if ( geometries.length > 1 ) mesh.name += '_' + i; 2603 | 2604 | assignExtrasToUserData( mesh, meshDef ); 2605 | 2606 | meshes.push( mesh ); 2607 | 2608 | // 2. update Material depending on Mesh and BufferGeometry 2609 | 2610 | var materials = isMultiMaterial ? mesh.material : [ mesh.material ]; 2611 | 2612 | var useVertexColors = geometry.attributes.color !== undefined; 2613 | var useFlatShading = geometry.attributes.normal === undefined; 2614 | var useSkinning = mesh.isSkinnedMesh === true; 2615 | var useMorphTargets = Object.keys( geometry.morphAttributes ).length > 0; 2616 | var useMorphNormals = useMorphTargets && geometry.morphAttributes.normal !== undefined; 2617 | 2618 | for ( var j = 0, jl = materials.length; j < jl; j ++ ) { 2619 | 2620 | var material = materials[ j ]; 2621 | 2622 | if ( mesh.isPoints ) { 2623 | 2624 | var cacheKey = 'PointsMaterial:' + material.uuid; 2625 | 2626 | var pointsMaterial = scope.cache.get( cacheKey ); 2627 | 2628 | if ( ! pointsMaterial ) { 2629 | 2630 | pointsMaterial = new THREE.PointsMaterial(); 2631 | THREE.Material.prototype.copy.call( pointsMaterial, material ); 2632 | pointsMaterial.color.copy( material.color ); 2633 | pointsMaterial.map = material.map; 2634 | pointsMaterial.lights = false; // PointsMaterial doesn't support lights yet 2635 | 2636 | scope.cache.add( cacheKey, pointsMaterial ); 2637 | 2638 | } 2639 | 2640 | material = pointsMaterial; 2641 | 2642 | } else if ( mesh.isLine ) { 2643 | 2644 | var cacheKey = 'LineBasicMaterial:' + material.uuid; 2645 | 2646 | var lineMaterial = scope.cache.get( cacheKey ); 2647 | 2648 | if ( ! lineMaterial ) { 2649 | 2650 | lineMaterial = new THREE.LineBasicMaterial(); 2651 | THREE.Material.prototype.copy.call( lineMaterial, material ); 2652 | lineMaterial.color.copy( material.color ); 2653 | lineMaterial.lights = false; // LineBasicMaterial doesn't support lights yet 2654 | 2655 | scope.cache.add( cacheKey, lineMaterial ); 2656 | 2657 | } 2658 | 2659 | material = lineMaterial; 2660 | 2661 | } 2662 | 2663 | // Clone the material if it will be modified 2664 | if ( useVertexColors || useFlatShading || useSkinning || useMorphTargets ) { 2665 | 2666 | var cacheKey = 'ClonedMaterial:' + material.uuid + ':'; 2667 | 2668 | if ( material.isGLTFSpecularGlossinessMaterial ) cacheKey += 'specular-glossiness:'; 2669 | if ( useSkinning ) cacheKey += 'skinning:'; 2670 | if ( useVertexColors ) cacheKey += 'vertex-colors:'; 2671 | if ( useFlatShading ) cacheKey += 'flat-shading:'; 2672 | if ( useMorphTargets ) cacheKey += 'morph-targets:'; 2673 | if ( useMorphNormals ) cacheKey += 'morph-normals:'; 2674 | 2675 | var cachedMaterial = scope.cache.get( cacheKey ); 2676 | 2677 | if ( ! cachedMaterial ) { 2678 | 2679 | cachedMaterial = material.isGLTFSpecularGlossinessMaterial 2680 | ? extensions[ EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS ].cloneMaterial( material ) 2681 | : material.clone(); 2682 | 2683 | if ( useSkinning ) cachedMaterial.skinning = true; 2684 | if ( useVertexColors ) cachedMaterial.vertexColors = THREE.VertexColors; 2685 | if ( useFlatShading ) cachedMaterial.flatShading = true; 2686 | if ( useMorphTargets ) cachedMaterial.morphTargets = true; 2687 | if ( useMorphNormals ) cachedMaterial.morphNormals = true; 2688 | 2689 | scope.cache.add( cacheKey, cachedMaterial ); 2690 | 2691 | } 2692 | 2693 | material = cachedMaterial; 2694 | 2695 | } 2696 | 2697 | materials[ j ] = material; 2698 | 2699 | // workarounds for mesh and geometry 2700 | 2701 | if ( material.aoMap && geometry.attributes.uv2 === undefined && geometry.attributes.uv !== undefined ) { 2702 | 2703 | console.log( 'THREE.GLTFLoader: Duplicating UVs to support aoMap.' ); 2704 | geometry.addAttribute( 'uv2', new THREE.BufferAttribute( geometry.attributes.uv.array, 2 ) ); 2705 | 2706 | } 2707 | 2708 | if ( material.isGLTFSpecularGlossinessMaterial ) { 2709 | 2710 | // for GLTFSpecularGlossinessMaterial(ShaderMaterial) uniforms runtime update 2711 | mesh.onBeforeRender = extensions[ EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS ].refreshUniforms; 2712 | 2713 | } 2714 | 2715 | } 2716 | 2717 | mesh.material = isMultiMaterial ? materials : materials[ 0 ]; 2718 | 2719 | } 2720 | 2721 | if ( meshes.length === 1 ) { 2722 | 2723 | return meshes[ 0 ]; 2724 | 2725 | } 2726 | 2727 | var group = new THREE.Group(); 2728 | 2729 | for ( var i = 0, il = meshes.length; i < il; i ++ ) { 2730 | 2731 | group.add( meshes[ i ] ); 2732 | 2733 | } 2734 | 2735 | return group; 2736 | 2737 | } ); 2738 | 2739 | } ); 2740 | 2741 | }; 2742 | 2743 | /** 2744 | * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#cameras 2745 | * @param {number} cameraIndex 2746 | * @return {Promise} 2747 | */ 2748 | GLTFParser.prototype.loadCamera = function ( cameraIndex ) { 2749 | 2750 | var camera; 2751 | var cameraDef = this.json.cameras[ cameraIndex ]; 2752 | var params = cameraDef[ cameraDef.type ]; 2753 | 2754 | if ( ! params ) { 2755 | 2756 | console.warn( 'THREE.GLTFLoader: Missing camera parameters.' ); 2757 | return; 2758 | 2759 | } 2760 | 2761 | if ( cameraDef.type === 'perspective' ) { 2762 | 2763 | camera = new THREE.PerspectiveCamera( THREE.Math.radToDeg( params.yfov ), params.aspectRatio || 1, params.znear || 1, params.zfar || 2e6 ); 2764 | 2765 | } else if ( cameraDef.type === 'orthographic' ) { 2766 | 2767 | camera = new THREE.OrthographicCamera( params.xmag / - 2, params.xmag / 2, params.ymag / 2, params.ymag / - 2, params.znear, params.zfar ); 2768 | 2769 | } 2770 | 2771 | if ( cameraDef.name !== undefined ) camera.name = cameraDef.name; 2772 | 2773 | assignExtrasToUserData( camera, cameraDef ); 2774 | 2775 | return Promise.resolve( camera ); 2776 | 2777 | }; 2778 | 2779 | /** 2780 | * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#skins 2781 | * @param {number} skinIndex 2782 | * @return {Promise} 2783 | */ 2784 | GLTFParser.prototype.loadSkin = function ( skinIndex ) { 2785 | 2786 | var skinDef = this.json.skins[ skinIndex ]; 2787 | 2788 | var skinEntry = { joints: skinDef.joints }; 2789 | 2790 | if ( skinDef.inverseBindMatrices === undefined ) { 2791 | 2792 | return Promise.resolve( skinEntry ); 2793 | 2794 | } 2795 | 2796 | return this.getDependency( 'accessor', skinDef.inverseBindMatrices ).then( function ( accessor ) { 2797 | 2798 | skinEntry.inverseBindMatrices = accessor; 2799 | 2800 | return skinEntry; 2801 | 2802 | } ); 2803 | 2804 | }; 2805 | 2806 | /** 2807 | * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#animations 2808 | * @param {number} animationIndex 2809 | * @return {Promise} 2810 | */ 2811 | GLTFParser.prototype.loadAnimation = function ( animationIndex ) { 2812 | 2813 | var json = this.json; 2814 | 2815 | var animationDef = json.animations[ animationIndex ]; 2816 | 2817 | return this.getMultiDependencies( [ 2818 | 2819 | 'accessor', 2820 | 'node' 2821 | 2822 | ] ).then( function ( dependencies ) { 2823 | 2824 | var tracks = []; 2825 | 2826 | for ( var i = 0, il = animationDef.channels.length; i < il; i ++ ) { 2827 | 2828 | var channel = animationDef.channels[ i ]; 2829 | var sampler = animationDef.samplers[ channel.sampler ]; 2830 | 2831 | if ( sampler ) { 2832 | 2833 | var target = channel.target; 2834 | var name = target.node !== undefined ? target.node : target.id; // NOTE: target.id is deprecated. 2835 | var input = animationDef.parameters !== undefined ? animationDef.parameters[ sampler.input ] : sampler.input; 2836 | var output = animationDef.parameters !== undefined ? animationDef.parameters[ sampler.output ] : sampler.output; 2837 | 2838 | var inputAccessor = dependencies.accessors[ input ]; 2839 | var outputAccessor = dependencies.accessors[ output ]; 2840 | 2841 | var node = dependencies.nodes[ name ]; 2842 | 2843 | if ( node ) { 2844 | 2845 | node.updateMatrix(); 2846 | node.matrixAutoUpdate = true; 2847 | 2848 | var TypedKeyframeTrack; 2849 | 2850 | switch ( PATH_PROPERTIES[ target.path ] ) { 2851 | 2852 | case PATH_PROPERTIES.weights: 2853 | 2854 | TypedKeyframeTrack = THREE.NumberKeyframeTrack; 2855 | break; 2856 | 2857 | case PATH_PROPERTIES.rotation: 2858 | 2859 | TypedKeyframeTrack = THREE.QuaternionKeyframeTrack; 2860 | break; 2861 | 2862 | case PATH_PROPERTIES.position: 2863 | case PATH_PROPERTIES.scale: 2864 | default: 2865 | 2866 | TypedKeyframeTrack = THREE.VectorKeyframeTrack; 2867 | break; 2868 | 2869 | } 2870 | 2871 | var targetName = node.name ? node.name : node.uuid; 2872 | 2873 | var interpolation = sampler.interpolation !== undefined ? INTERPOLATION[ sampler.interpolation ] : THREE.InterpolateLinear; 2874 | 2875 | var targetNames = []; 2876 | 2877 | if ( PATH_PROPERTIES[ target.path ] === PATH_PROPERTIES.weights ) { 2878 | 2879 | // node can be THREE.Group here but 2880 | // PATH_PROPERTIES.weights(morphTargetInfluences) should be 2881 | // the property of a mesh object under group. 2882 | 2883 | node.traverse( function ( object ) { 2884 | 2885 | if ( object.isMesh === true && object.morphTargetInfluences ) { 2886 | 2887 | targetNames.push( object.name ? object.name : object.uuid ); 2888 | 2889 | } 2890 | 2891 | } ); 2892 | 2893 | } else { 2894 | 2895 | targetNames.push( targetName ); 2896 | 2897 | } 2898 | 2899 | // KeyframeTrack.optimize() will modify given 'times' and 'values' 2900 | // buffers before creating a truncated copy to keep. Because buffers may 2901 | // be reused by other tracks, make copies here. 2902 | for ( var j = 0, jl = targetNames.length; j < jl; j ++ ) { 2903 | 2904 | var track = new TypedKeyframeTrack( 2905 | targetNames[ j ] + '.' + PATH_PROPERTIES[ target.path ], 2906 | THREE.AnimationUtils.arraySlice( inputAccessor.array, 0 ), 2907 | THREE.AnimationUtils.arraySlice( outputAccessor.array, 0 ), 2908 | interpolation 2909 | ); 2910 | 2911 | // Here is the trick to enable custom interpolation. 2912 | // Overrides .createInterpolant in a factory method which creates custom interpolation. 2913 | if ( sampler.interpolation === 'CUBICSPLINE' ) { 2914 | 2915 | track.createInterpolant = function InterpolantFactoryMethodGLTFCubicSpline( result ) { 2916 | 2917 | // A CUBICSPLINE keyframe in glTF has three output values for each input value, 2918 | // representing inTangent, splineVertex, and outTangent. As a result, track.getValueSize() 2919 | // must be divided by three to get the interpolant's sampleSize argument. 2920 | 2921 | return new GLTFCubicSplineInterpolant( this.times, this.values, this.getValueSize() / 3, result ); 2922 | 2923 | }; 2924 | 2925 | // Workaround, provide an alternate way to know if the interpolant type is cubis spline to track. 2926 | // track.getInterpolation() doesn't return valid value for custom interpolant. 2927 | track.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline = true; 2928 | 2929 | } 2930 | 2931 | tracks.push( track ); 2932 | 2933 | } 2934 | 2935 | } 2936 | 2937 | } 2938 | 2939 | } 2940 | 2941 | var name = animationDef.name !== undefined ? animationDef.name : 'animation_' + animationIndex; 2942 | 2943 | return new THREE.AnimationClip( name, undefined, tracks ); 2944 | 2945 | } ); 2946 | 2947 | }; 2948 | 2949 | /** 2950 | * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#nodes-and-hierarchy 2951 | * @param {number} nodeIndex 2952 | * @return {Promise} 2953 | */ 2954 | GLTFParser.prototype.loadNode = function ( nodeIndex ) { 2955 | 2956 | var json = this.json; 2957 | var extensions = this.extensions; 2958 | 2959 | var meshReferences = json.meshReferences; 2960 | var meshUses = json.meshUses; 2961 | 2962 | var nodeDef = json.nodes[ nodeIndex ]; 2963 | 2964 | return this.getMultiDependencies( [ 2965 | 2966 | 'mesh', 2967 | 'skin', 2968 | 'camera', 2969 | 'light' 2970 | 2971 | ] ).then( function ( dependencies ) { 2972 | 2973 | var node; 2974 | 2975 | // .isBone isn't in glTF spec. See .markDefs 2976 | if ( nodeDef.isBone === true ) { 2977 | 2978 | node = new THREE.Bone(); 2979 | 2980 | } else if ( nodeDef.mesh !== undefined ) { 2981 | 2982 | var mesh = dependencies.meshes[ nodeDef.mesh ]; 2983 | 2984 | if ( meshReferences[ nodeDef.mesh ] > 1 ) { 2985 | 2986 | var instanceNum = meshUses[ nodeDef.mesh ] ++; 2987 | 2988 | node = mesh.clone(); 2989 | node.name += '_instance_' + instanceNum; 2990 | 2991 | // onBeforeRender copy for Specular-Glossiness 2992 | node.onBeforeRender = mesh.onBeforeRender; 2993 | 2994 | for ( var i = 0, il = node.children.length; i < il; i ++ ) { 2995 | 2996 | node.children[ i ].name += '_instance_' + instanceNum; 2997 | node.children[ i ].onBeforeRender = mesh.children[ i ].onBeforeRender; 2998 | 2999 | } 3000 | 3001 | } else { 3002 | 3003 | node = mesh; 3004 | 3005 | } 3006 | 3007 | } else if ( nodeDef.camera !== undefined ) { 3008 | 3009 | node = dependencies.cameras[ nodeDef.camera ]; 3010 | 3011 | } else if ( nodeDef.extensions 3012 | && nodeDef.extensions[ EXTENSIONS.KHR_LIGHTS_PUNCTUAL ] 3013 | && nodeDef.extensions[ EXTENSIONS.KHR_LIGHTS_PUNCTUAL ].light !== undefined ) { 3014 | 3015 | var lights = extensions[ EXTENSIONS.KHR_LIGHTS_PUNCTUAL ].lights; 3016 | node = lights[ nodeDef.extensions[ EXTENSIONS.KHR_LIGHTS_PUNCTUAL ].light ]; 3017 | 3018 | } else { 3019 | 3020 | node = new THREE.Object3D(); 3021 | 3022 | } 3023 | 3024 | if ( nodeDef.name !== undefined ) { 3025 | 3026 | node.name = THREE.PropertyBinding.sanitizeNodeName( nodeDef.name ); 3027 | 3028 | } 3029 | 3030 | assignExtrasToUserData( node, nodeDef ); 3031 | 3032 | if ( nodeDef.extensions ) addUnknownExtensionsToUserData( extensions, node, nodeDef ); 3033 | 3034 | if ( nodeDef.matrix !== undefined ) { 3035 | 3036 | var matrix = new THREE.Matrix4(); 3037 | matrix.fromArray( nodeDef.matrix ); 3038 | node.applyMatrix( matrix ); 3039 | 3040 | } else { 3041 | 3042 | if ( nodeDef.translation !== undefined ) { 3043 | 3044 | node.position.fromArray( nodeDef.translation ); 3045 | 3046 | } 3047 | 3048 | if ( nodeDef.rotation !== undefined ) { 3049 | 3050 | node.quaternion.fromArray( nodeDef.rotation ); 3051 | 3052 | } 3053 | 3054 | if ( nodeDef.scale !== undefined ) { 3055 | 3056 | node.scale.fromArray( nodeDef.scale ); 3057 | 3058 | } 3059 | 3060 | } 3061 | 3062 | return node; 3063 | 3064 | } ); 3065 | 3066 | }; 3067 | 3068 | /** 3069 | * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#scenes 3070 | * @param {number} sceneIndex 3071 | * @return {Promise} 3072 | */ 3073 | GLTFParser.prototype.loadScene = function () { 3074 | 3075 | // scene node hierachy builder 3076 | 3077 | function buildNodeHierachy( nodeId, parentObject, json, allNodes, skins ) { 3078 | 3079 | var node = allNodes[ nodeId ]; 3080 | var nodeDef = json.nodes[ nodeId ]; 3081 | 3082 | // build skeleton here as well 3083 | 3084 | if ( nodeDef.skin !== undefined ) { 3085 | 3086 | var meshes = node.isGroup === true ? node.children : [ node ]; 3087 | 3088 | for ( var i = 0, il = meshes.length; i < il; i ++ ) { 3089 | 3090 | var mesh = meshes[ i ]; 3091 | var skinEntry = skins[ nodeDef.skin ]; 3092 | 3093 | var bones = []; 3094 | var boneInverses = []; 3095 | 3096 | for ( var j = 0, jl = skinEntry.joints.length; j < jl; j ++ ) { 3097 | 3098 | var jointId = skinEntry.joints[ j ]; 3099 | var jointNode = allNodes[ jointId ]; 3100 | 3101 | if ( jointNode ) { 3102 | 3103 | bones.push( jointNode ); 3104 | 3105 | var mat = new THREE.Matrix4(); 3106 | 3107 | if ( skinEntry.inverseBindMatrices !== undefined ) { 3108 | 3109 | mat.fromArray( skinEntry.inverseBindMatrices.array, j * 16 ); 3110 | 3111 | } 3112 | 3113 | boneInverses.push( mat ); 3114 | 3115 | } else { 3116 | 3117 | console.warn( 'THREE.GLTFLoader: Joint "%s" could not be found.', jointId ); 3118 | 3119 | } 3120 | 3121 | } 3122 | 3123 | mesh.bind( new THREE.Skeleton( bones, boneInverses ), mesh.matrixWorld ); 3124 | 3125 | } 3126 | 3127 | } 3128 | 3129 | // build node hierachy 3130 | 3131 | parentObject.add( node ); 3132 | 3133 | if ( nodeDef.children ) { 3134 | 3135 | var children = nodeDef.children; 3136 | 3137 | for ( var i = 0, il = children.length; i < il; i ++ ) { 3138 | 3139 | var child = children[ i ]; 3140 | buildNodeHierachy( child, node, json, allNodes, skins ); 3141 | 3142 | } 3143 | 3144 | } 3145 | 3146 | } 3147 | 3148 | return function loadScene( sceneIndex ) { 3149 | 3150 | var json = this.json; 3151 | var extensions = this.extensions; 3152 | var sceneDef = this.json.scenes[ sceneIndex ]; 3153 | 3154 | return this.getMultiDependencies( [ 3155 | 3156 | 'node', 3157 | 'skin' 3158 | 3159 | ] ).then( function ( dependencies ) { 3160 | 3161 | var scene = new THREE.Scene(); 3162 | if ( sceneDef.name !== undefined ) scene.name = sceneDef.name; 3163 | 3164 | assignExtrasToUserData( scene, sceneDef ); 3165 | 3166 | if ( sceneDef.extensions ) addUnknownExtensionsToUserData( extensions, scene, sceneDef ); 3167 | 3168 | var nodeIds = sceneDef.nodes || []; 3169 | 3170 | for ( var i = 0, il = nodeIds.length; i < il; i ++ ) { 3171 | 3172 | buildNodeHierachy( nodeIds[ i ], scene, json, dependencies.nodes, dependencies.skins ); 3173 | 3174 | } 3175 | 3176 | return scene; 3177 | 3178 | } ); 3179 | 3180 | }; 3181 | 3182 | }(); 3183 | 3184 | return GLTFLoader; 3185 | 3186 | } )(); 3187 | -------------------------------------------------------------------------------- /BLESense-test-dashboard/README.md: -------------------------------------------------------------------------------- 1 | # BLESense Demo Dashboard 2 | 3 | (c) 2019 Arduino SA 4 | License GPL 5 | 6 | -------------------------------------------------------------------------------- /BLESense-test-dashboard/aiot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arduino/ArduinoAI/351d4db3d308cd89a745abbd3f60e6a812dcd762/BLESense-test-dashboard/aiot.png -------------------------------------------------------------------------------- /BLESense-test-dashboard/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Arduino Nano BLE Sense - Web BLE test 6 | 7 | 8 | 9 | 10 | 11 | 74 | 75 | 76 | 77 |
78 |
  IMPORTANT - first upload sketch to Arduino Nano 33 BLE Sense board
79 |
80 | 81 |
Click button to connect to the board
82 |
83 | 84 |
85 |
📦3D
86 |
87 | 88 |
89 |
🚀 Accelerometer
90 |
91 |
92 | 93 |
94 |
💫 Gyroscope
95 |
96 |
97 | 98 |
99 |
💡 RGB LED control
100 |
101 |
102 | 103 |
104 |
🎙Microphone
105 |
106 | 107 | 108 |
109 |
110 | 111 |
112 |
📺Spectrogram
113 | 114 |
115 | 116 |
117 |
🎨Colorimeter
118 | 119 |
120 | 121 |
122 |
🌡 Temperature -
123 |
124 |
°C
125 |
126 |
127 | 128 |
129 |
💧 Humidity -
130 |
131 |
%
132 |
133 | 134 |
135 | 136 |
137 |
⛅ Pressure -
138 |
139 |
kPa
140 |
141 |
142 | 143 |
144 |
👉 Proximity -
145 |
146 |
147 |
148 | 149 |
150 | 151 | 359 | 616 | 617 | -------------------------------------------------------------------------------- /BLESense-test-dashboard/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arduino/ArduinoAI/351d4db3d308cd89a745abbd3f60e6a812dcd762/BLESense-test-dashboard/logo.png -------------------------------------------------------------------------------- /BLESense-test-dashboard/models/nano33ble.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arduino/ArduinoAI/351d4db3d308cd89a745abbd3f60e6a812dcd762/BLESense-test-dashboard/models/nano33ble.glb -------------------------------------------------------------------------------- /NiclaSenseME-dashboard/BLE_spec.txt: -------------------------------------------------------------------------------- 1 | BLE Sense - BLE specifications.txt 2 | ================================== 3 | 4 | GAP (Adversisement) 5 | ------------------- 6 | Local name: BLESense- 7 | Serivice UUID: 19b10000-0000-537e-4f6c-d104768a1214 8 | 9 | 10 | GATT 11 | ---- 12 | 13 | Service 14 | ~~~~~~~ 15 | 16 | UUID: 19b10000-0000-537e-4f6c-d104768a1214 17 | 18 | 19 | Version Characteristic 20 | ~~~~~~~~~~~~~~~~~~~~~~ 21 | 22 | UUID: 19b10000-1001-537e-4f6c-d104768a1214 23 | Properties: read 24 | Value size: 4 bytes 25 | Data format: 32-bit unsigned integer (little endian) 26 | Description: Version of firmware 27 | 28 | Temperature Characteristic 29 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 30 | 31 | UUID: 19b10000-2001-537e-4f6c-d104768a1214 32 | Properties: read 33 | Value size: 2 bytes 34 | Data format: 16-bit float (little endian) 35 | Description: Temperature sensor value Celcius 36 | 37 | Humidity Characteristic 38 | ~~~~~~~~~~~~~~~~~~~~ 39 | 40 | UUID: 19b10000-3001-537e-4f6c-d104768a1214 41 | Properties: read 42 | Value size: 1 bytes 43 | Data format: 8-bit unsigned integer (little endian) 44 | Description: Humidity sensor value % 45 | 46 | Pressure Characteristic 47 | ~~~~~~~~~~~~~~~~~~~~~~~~ 48 | 49 | UUID: 19b10000-4001-537e-4f6c-d104768a1214 50 | Properties: read 51 | Value size: 1 byte 52 | Data format: 8-bit unsigned integer (little endian) 53 | Description: Pressure sensor value in kPA 54 | 55 | Acceleration Characteristic 56 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~ 57 | 58 | UUID: 19b10000-5001-537e-4f6c-d104768a1214 59 | Properties: notify 60 | Value size: 12 bytes 61 | Data format: Array of 3 x 16-bit IEEE floats (little endian) 62 | Description: X, Y, Z acceleration values in G's 63 | 64 | Gyroscope Characteristic 65 | ~~~~~~~~~~~~~~~~~~~~~~~~ 66 | 67 | UUID: 19b10000-6001-537e-4f6c-d104768a1214 68 | Properties: notify 69 | Value size: 12 bytes 70 | Data format: Array of 3 x 16-bit IEEE floats (little endian) 71 | Description: X, Y, Z gyroscope values in degrees per second 72 | 73 | Quaternion Characteristic 74 | ~~~~~~~~~~~~~~~~~~~~~~~~ 75 | 76 | UUID: 19b10000-7001-537e-4f6c-d104768a1214 77 | Properties: notify 78 | Value size: 16 bytes 79 | Data format: Array of 4 x 16-bit IEEE floats (little endian) 80 | Description: X, Y, Z, W quaternion values 81 | 82 | RGB LED Characteristic 83 | ~~~~~~~~~~~~~~~~~~~~~~ 84 | 85 | UUID: 19b10000-8001-537e-4f6c-d104768a1214 86 | Properties: write 87 | Value size: 3 bytes 88 | Data format: Array of unsigned 8-bits (little endian) 89 | Description: RGB led value, 0 => off, 255 => on 90 | 91 | BSEC Characteristic 92 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~ 93 | 94 | UUID: 19b10000-9001-537e-4f6c-d104768a1214 95 | Properties: read 96 | Value size: 4 bytes 97 | Data format: 32-bit float (little endian) 98 | Description: Indoor Air Quality (IAQ) sensor value 99 | 100 | Co2 Characteristic 101 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~ 102 | 103 | UUID: 19b10000-9002-537e-4f6c-d104768a1214 104 | Properties: read 105 | Value size: 4 bytes 106 | Data format: 32-bit unsigned integer (little endian) 107 | Description: carbon monoxide and hydrogen in the part per billion (ppb) range 108 | 109 | Gas Characteristic 110 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~ 111 | 112 | UUID: 19b10000-9003-537e-4f6c-d104768a1214 113 | Properties: read 114 | Value size: 1 byte 115 | Data format: 8-bit unsigned integer (little endian) 116 | Description: volatile sulfur compounds (VSCs) -------------------------------------------------------------------------------- /NiclaSenseME-dashboard/Logo-Arduino-Pro-inline.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /NiclaSenseME-dashboard/NiclaSenseME/NiclaSenseME.ino: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Arduino Nicla Sense ME WEB BLE Sense dashboard demo 4 | 5 | 6 | Hardware required: https://store.arduino.cc/nicla-sense-me 7 | 8 | 1) Upload this sketch to the Arduino Nano BLE sense board 9 | 10 | 2) Open the following web page in the Chrome browser: 11 | https://arduino.github.io/ArduinoAI/NiclaSenseME-dashboard/ 12 | 13 | 3) Click on the green button in the web page to connect the browser to the board over BLE 14 | 15 | 16 | Web dashboard by D. Pajak 17 | 18 | Device sketch based on example by Sandeep Mistry and Massimo Banzi 19 | Sketch and web dashboard copy-fixed to be used with the Nicla Sense ME by Pablo Marquínez 20 | 21 | */ 22 | 23 | #include "Nicla_System.h" 24 | #include "Arduino_BHY2.h" 25 | #include 26 | 27 | #define BLE_SENSE_UUID(val) ("19b10000-" val "-537e-4f6c-d104768a1214") 28 | 29 | const int VERSION = 0x00000000; 30 | 31 | BLEService service(BLE_SENSE_UUID("0000")); 32 | 33 | BLEUnsignedIntCharacteristic versionCharacteristic(BLE_SENSE_UUID("1001"), BLERead); 34 | BLEFloatCharacteristic temperatureCharacteristic(BLE_SENSE_UUID("2001"), BLERead); 35 | BLEUnsignedIntCharacteristic humidityCharacteristic(BLE_SENSE_UUID("3001"), BLERead); 36 | BLEFloatCharacteristic pressureCharacteristic(BLE_SENSE_UUID("4001"), BLERead); 37 | 38 | BLECharacteristic accelerometerCharacteristic(BLE_SENSE_UUID("5001"), BLERead | BLENotify, 3 * sizeof(float)); // Array of 3x 2 Bytes, XY 39 | BLECharacteristic gyroscopeCharacteristic(BLE_SENSE_UUID("6001"), BLERead | BLENotify, 3 * sizeof(float)); // Array of 3x 2 Bytes, XYZ 40 | BLECharacteristic quaternionCharacteristic(BLE_SENSE_UUID("7001"), BLERead | BLENotify, 4 * sizeof(float)); // Array of 4x 2 Bytes, XYZW 41 | 42 | BLECharacteristic rgbLedCharacteristic(BLE_SENSE_UUID("8001"), BLERead | BLEWrite, 3 * sizeof(byte)); // Array of 3 bytes, RGB 43 | 44 | BLEFloatCharacteristic bsecCharacteristic(BLE_SENSE_UUID("9001"), BLERead); 45 | BLEIntCharacteristic co2Characteristic(BLE_SENSE_UUID("9002"), BLERead); 46 | BLEUnsignedIntCharacteristic gasCharacteristic(BLE_SENSE_UUID("9003"), BLERead); 47 | 48 | // String to calculate the local and device name 49 | String name; 50 | 51 | Sensor temperature(SENSOR_ID_TEMP); 52 | Sensor humidity(SENSOR_ID_HUM); 53 | Sensor pressure(SENSOR_ID_BARO); 54 | Sensor gas(SENSOR_ID_GAS); 55 | SensorXYZ gyroscope(SENSOR_ID_GYRO); 56 | SensorXYZ accelerometer(SENSOR_ID_ACC); 57 | SensorQuaternion quaternion(SENSOR_ID_RV); 58 | SensorBSEC bsec(SENSOR_ID_BSEC); 59 | 60 | void setup(){ 61 | Serial.begin(115200); 62 | 63 | Serial.println("Start"); 64 | 65 | nicla::begin(); 66 | nicla::leds.begin(); 67 | nicla::leds.setColor(green); 68 | 69 | //Sensors initialization 70 | BHY2.begin(NICLA_STANDALONE); 71 | temperature.begin(); 72 | humidity.begin(); 73 | pressure.begin(); 74 | gyroscope.begin(); 75 | accelerometer.begin(); 76 | quaternion.begin(); 77 | bsec.begin(); 78 | gas.begin(); 79 | 80 | if (!BLE.begin()){ 81 | Serial.println("Failed to initialized BLE!"); 82 | 83 | while (1) 84 | ; 85 | } 86 | 87 | String address = BLE.address(); 88 | 89 | Serial.print("address = "); 90 | Serial.println(address); 91 | 92 | address.toUpperCase(); 93 | 94 | name = "BLESense-"; 95 | name += address[address.length() - 5]; 96 | name += address[address.length() - 4]; 97 | name += address[address.length() - 2]; 98 | name += address[address.length() - 1]; 99 | 100 | Serial.print("name = "); 101 | Serial.println(name); 102 | 103 | BLE.setLocalName(name.c_str()); 104 | BLE.setDeviceName(name.c_str()); 105 | BLE.setAdvertisedService(service); 106 | 107 | // Add all the previously defined Characteristics 108 | service.addCharacteristic(temperatureCharacteristic); 109 | service.addCharacteristic(humidityCharacteristic); 110 | service.addCharacteristic(pressureCharacteristic); 111 | service.addCharacteristic(versionCharacteristic); 112 | service.addCharacteristic(accelerometerCharacteristic); 113 | service.addCharacteristic(gyroscopeCharacteristic); 114 | service.addCharacteristic(quaternionCharacteristic); 115 | service.addCharacteristic(bsecCharacteristic); 116 | service.addCharacteristic(co2Characteristic); 117 | service.addCharacteristic(gasCharacteristic); 118 | service.addCharacteristic(rgbLedCharacteristic); 119 | 120 | // Disconnect event handler 121 | BLE.setEventHandler(BLEDisconnected, blePeripheralDisconnectHandler); 122 | 123 | // Sensors event handlers 124 | temperatureCharacteristic.setEventHandler(BLERead, onTemperatureCharacteristicRead); 125 | humidityCharacteristic.setEventHandler(BLERead, onHumidityCharacteristicRead); 126 | pressureCharacteristic.setEventHandler(BLERead, onPressureCharacteristicRead); 127 | bsecCharacteristic.setEventHandler(BLERead, onBsecCharacteristicRead); 128 | co2Characteristic.setEventHandler(BLERead, onCo2CharacteristicRead); 129 | gasCharacteristic.setEventHandler(BLERead, onGasCharacteristicRead); 130 | 131 | rgbLedCharacteristic.setEventHandler(BLEWritten, onRgbLedCharacteristicWrite); 132 | 133 | versionCharacteristic.setValue(VERSION); 134 | 135 | BLE.addService(service); 136 | BLE.advertise(); 137 | } 138 | 139 | void loop(){ 140 | while (BLE.connected()){ 141 | BHY2.update(); 142 | 143 | if (gyroscopeCharacteristic.subscribed()){ 144 | float x, y, z; 145 | 146 | x = gyroscope.x(); 147 | y = gyroscope.y(); 148 | z = gyroscope.z(); 149 | 150 | float gyroscopeValues[3] = {x, y, z}; 151 | 152 | gyroscopeCharacteristic.writeValue(gyroscopeValues, sizeof(gyroscopeValues)); 153 | } 154 | 155 | if (accelerometerCharacteristic.subscribed()){ 156 | float x, y, z; 157 | x = accelerometer.x(); 158 | y = accelerometer.y(); 159 | z = accelerometer.z(); 160 | 161 | float accelerometerValues[] = {x, y, z}; 162 | accelerometerCharacteristic.writeValue(accelerometerValues, sizeof(accelerometerValues)); 163 | } 164 | 165 | if(quaternionCharacteristic.subscribed()){ 166 | float x, y, z, w; 167 | x = quaternion.x(); 168 | y = quaternion.y(); 169 | z = quaternion.z(); 170 | w = quaternion.w(); 171 | 172 | float quaternionValues[] = {x,y,z,w}; 173 | quaternionCharacteristic.writeValue(quaternionValues, sizeof(quaternionValues)); 174 | } 175 | 176 | } 177 | } 178 | 179 | void blePeripheralDisconnectHandler(BLEDevice central){ 180 | nicla::leds.setColor(red); 181 | } 182 | 183 | void onTemperatureCharacteristicRead(BLEDevice central, BLECharacteristic characteristic){ 184 | float temperatureValue = temperature.value(); 185 | temperatureCharacteristic.writeValue(temperatureValue); 186 | } 187 | 188 | void onHumidityCharacteristicRead(BLEDevice central, BLECharacteristic characteristic){ 189 | uint8_t humidityValue = humidity.value() + 0.5f; //since we are truncating the float type to a uint8_t, we want to round it 190 | humidityCharacteristic.writeValue(humidityValue); 191 | } 192 | 193 | void onPressureCharacteristicRead(BLEDevice central, BLECharacteristic characteristic){ 194 | float pressureValue = pressure.value(); 195 | pressureCharacteristic.writeValue(pressureValue); 196 | } 197 | 198 | void onBsecCharacteristicRead(BLEDevice central, BLECharacteristic characteristic){ 199 | float airQuality = float(bsec.iaq()); 200 | bsecCharacteristic.writeValue(airQuality); 201 | } 202 | 203 | void onCo2CharacteristicRead(BLEDevice central, BLECharacteristic characteristic){ 204 | uint32_t co2 = bsec.co2_eq(); 205 | co2Characteristic.writeValue(co2); 206 | } 207 | 208 | void onGasCharacteristicRead(BLEDevice central, BLECharacteristic characteristic){ 209 | unsigned int g = gas.value(); 210 | gasCharacteristic.writeValue(g); 211 | } 212 | 213 | void onRgbLedCharacteristicWrite(BLEDevice central, BLECharacteristic characteristic){ 214 | byte r = rgbLedCharacteristic[0]; 215 | byte g = rgbLedCharacteristic[1]; 216 | byte b = rgbLedCharacteristic[2]; 217 | 218 | nicla::leds.setColor(r, g, b); 219 | } 220 | -------------------------------------------------------------------------------- /NiclaSenseME-dashboard/README.md: -------------------------------------------------------------------------------- 1 | # Nicla Sense ME Web-BLE Demo Dashboard 2 | 3 | (c) 2022 Arduino SA 4 | License GPL 5 | 6 | -------------------------------------------------------------------------------- /NiclaSenseME-dashboard/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Arduino Nicla Sense ME - Web BLE test 6 | 7 | 8 | 9 | 10 | 11 | 12 | 205 | 206 | 207 | 208 | 209 |
210 | 211 |
212 |
213 | 214 |
Click the connect button to connect your device
215 | 216 |
217 | 218 |
219 |
📦 Quaternion Rotation
220 |
221 | x: - 222 | y: - 223 | z: - 224 |
225 |
226 | 227 |
228 |
🚀 Accelerometer
229 |
230 |
231 | 232 |
233 |
💫 Gyroscope
234 |
235 |
236 |
237 | 238 |
239 |
💡 RGB LED control
240 |
241 |
242 | 243 |
244 |
🌡 Temperature -
245 |
246 |
°C
247 |
248 |
249 | 250 |
251 |
💧 Humidity -
252 |
253 |
%
254 |
255 | 256 |
257 | 258 |
259 |
⛅ Pressure -
260 |
261 |
kPa
262 |
263 |
264 | 265 |
266 |
🏠 Indoor Air Quality -
267 |
268 |
269 |
270 |
271 | 272 |
273 |
🌱 Co2 Value -
274 |
275 |
276 |
277 |
278 | 279 |
280 |
💨 Gas Value -
281 |
282 |
283 |
284 |
285 | 286 |
287 | 288 | 289 | 533 | 534 | 771 | 772 | 773 | -------------------------------------------------------------------------------- /NiclaSenseME-dashboard/models/niclaSenseME.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arduino/ArduinoAI/351d4db3d308cd89a745abbd3f60e6a812dcd762/NiclaSenseME-dashboard/models/niclaSenseME.glb -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Arduino and AI 2 | 3 | This is a temporary website linking some of the demos and tools we build to support the creation of AI/ML applications on the Arduino platform. 4 | 5 | 6 | ## Tutorials 7 | 8 | [Get started with machine learning on Arduino](https://blog.arduino.cc/2019/10/15/get-started-with-machine-learning-on-arduino/) 9 | 10 | [Fruit identification using Arduino and TensorFlow](https://blog.arduino.cc/2019/11/07/fruit-identification-using-arduino-and-tensorflow/) 11 | 12 | [Use the dashboard using the Arduino Nicla Sense ME](http://docs.arduino.cc/tutorials/nicla-sense-me/web-ble-dashboard) 13 | 14 | ## BLE Sense Demo Dashboard 15 | 16 | [BLE Sense Demo - Arduino sketch](https://create.arduino.cc/editor/dpajak/e4128780-d76d-438f-97a4-541740cb0473/preview) 17 | 18 | [BLE Sense Demo - Web Dashboard](https://arduino.github.io/ArduinoAI/BLESense-test-dashboard) (Chrome only) 19 | 20 | ## Nicla Sense ME Demo Dashboard 21 | 22 | [Nicla Sense ME BLE Demo - Arduino sketch](https://create.arduino.cc/editor/FT-CONTENT/333e2e07-ecc4-414c-bf08-005b611ddd75/preview) 23 | 24 | [Nicla Sense ME BLE - Web Dashboard](https://arduino.github.io/ArduinoAI/NiclaSenseME-dashboard) (Chrome only) --------------------------------------------------------------------------------