DRACOLoader.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441
  1. ;(function () {
  2. const _taskCache = new WeakMap()
  3. class DRACOLoader extends THREE.Loader {
  4. constructor(manager) {
  5. super(manager)
  6. this.decoderPath = ''
  7. this.decoderConfig = {}
  8. this.decoderBinary = null
  9. this.decoderPending = null
  10. this.workerLimit = 4
  11. this.workerPool = []
  12. this.workerNextTaskID = 1
  13. this.workerSourceURL = ''
  14. this.defaultAttributeIDs = {
  15. position: 'POSITION',
  16. normal: 'NORMAL',
  17. color: 'COLOR',
  18. uv: 'TEX_COORD',
  19. }
  20. this.defaultAttributeTypes = {
  21. position: 'Float32Array',
  22. normal: 'Float32Array',
  23. color: 'Float32Array',
  24. uv: 'Float32Array',
  25. }
  26. }
  27. setDecoderPath(path) {
  28. this.decoderPath = path
  29. return this
  30. }
  31. setDecoderConfig(config) {
  32. this.decoderConfig = config
  33. return this
  34. }
  35. setWorkerLimit(workerLimit) {
  36. this.workerLimit = workerLimit
  37. return this
  38. }
  39. load(url, onLoad, onProgress, onError) {
  40. const loader = new THREE.FileLoader(this.manager)
  41. loader.setPath(this.path)
  42. loader.setResponseType('arraybuffer')
  43. loader.setRequestHeader(this.requestHeader)
  44. loader.setWithCredentials(this.withCredentials)
  45. loader.load(
  46. url,
  47. buffer => {
  48. this.decodeDracoFile(buffer, onLoad).catch(onError)
  49. },
  50. onProgress,
  51. onError
  52. )
  53. }
  54. decodeDracoFile(buffer, callback, attributeIDs, attributeTypes) {
  55. const taskConfig = {
  56. attributeIDs: attributeIDs || this.defaultAttributeIDs,
  57. attributeTypes: attributeTypes || this.defaultAttributeTypes,
  58. useUniqueIDs: !!attributeIDs,
  59. }
  60. return this.decodeGeometry(buffer, taskConfig).then(callback)
  61. }
  62. decodeGeometry(buffer, taskConfig) {
  63. const taskKey = JSON.stringify(taskConfig) // Check for an existing task using this buffer. A transferred buffer cannot be transferred
  64. // again from this thread.
  65. if (_taskCache.has(buffer)) {
  66. const cachedTask = _taskCache.get(buffer)
  67. if (cachedTask.key === taskKey) {
  68. return cachedTask.promise
  69. } else if (buffer.byteLength === 0) {
  70. // Technically, it would be possible to wait for the previous task to complete,
  71. // transfer the buffer back, and decode again with the second configuration. That
  72. // is complex, and I don't know of any reason to decode a Draco buffer twice in
  73. // different ways, so this is left unimplemented.
  74. throw new Error('THREE.DRACOLoader: Unable to re-decode a buffer with different ' + 'settings. Buffer has already been transferred.')
  75. }
  76. } //
  77. let worker
  78. const taskID = this.workerNextTaskID++
  79. const taskCost = buffer.byteLength // Obtain a worker and assign a task, and construct a geometry instance
  80. // when the task completes.
  81. const geometryPending = this._getWorker(taskID, taskCost)
  82. .then(_worker => {
  83. worker = _worker
  84. return new Promise((resolve, reject) => {
  85. worker._callbacks[taskID] = {
  86. resolve,
  87. reject,
  88. }
  89. worker.postMessage(
  90. {
  91. type: 'decode',
  92. id: taskID,
  93. taskConfig,
  94. buffer,
  95. },
  96. [buffer]
  97. ) // this.debug();
  98. })
  99. })
  100. .then(message => this._createGeometry(message.geometry)) // Remove task from the task list.
  101. // Note: replaced '.finally()' with '.catch().then()' block - iOS 11 support (#19416)
  102. geometryPending
  103. .catch(() => true)
  104. .then(() => {
  105. if (worker && taskID) {
  106. this._releaseTask(worker, taskID) // this.debug();
  107. }
  108. }) // Cache the task result.
  109. _taskCache.set(buffer, {
  110. key: taskKey,
  111. promise: geometryPending,
  112. })
  113. return geometryPending
  114. }
  115. _createGeometry(geometryData) {
  116. const geometry = new THREE.BufferGeometry()
  117. if (geometryData.index) {
  118. geometry.setIndex(new THREE.BufferAttribute(geometryData.index.array, 1))
  119. }
  120. for (let i = 0; i < geometryData.attributes.length; i++) {
  121. const attribute = geometryData.attributes[i]
  122. const name = attribute.name
  123. const array = attribute.array
  124. const itemSize = attribute.itemSize
  125. geometry.setAttribute(name, new THREE.BufferAttribute(array, itemSize))
  126. }
  127. return geometry
  128. }
  129. _loadLibrary(url, responseType) {
  130. const loader = new THREE.FileLoader(this.manager)
  131. loader.setPath(this.decoderPath)
  132. loader.setResponseType(responseType)
  133. loader.setWithCredentials(this.withCredentials)
  134. return new Promise((resolve, reject) => {
  135. loader.load(url, resolve, undefined, reject)
  136. })
  137. }
  138. preload() {
  139. this._initDecoder()
  140. return this
  141. }
  142. _initDecoder() {
  143. if (this.decoderPending) return this.decoderPending
  144. const useJS = typeof WebAssembly !== 'object' || this.decoderConfig.type === 'js'
  145. const librariesPending = []
  146. if (useJS) {
  147. librariesPending.push(this._loadLibrary('draco_decoder.js', 'text'))
  148. } else {
  149. librariesPending.push(this._loadLibrary('draco_wasm_wrapper.js', 'text'))
  150. librariesPending.push(this._loadLibrary('draco_decoder.wasm', 'arraybuffer'))
  151. }
  152. this.decoderPending = Promise.all(librariesPending).then(libraries => {
  153. const jsContent = libraries[0]
  154. if (!useJS) {
  155. this.decoderConfig.wasmBinary = libraries[1]
  156. }
  157. const fn = DRACOWorker.toString()
  158. const body = ['/* draco decoder */', jsContent, '', '/* worker */', fn.substring(fn.indexOf('{') + 1, fn.lastIndexOf('}'))].join('\n')
  159. this.workerSourceURL = URL.createObjectURL(new Blob([body]))
  160. })
  161. return this.decoderPending
  162. }
  163. _getWorker(taskID, taskCost) {
  164. return this._initDecoder().then(() => {
  165. if (this.workerPool.length < this.workerLimit) {
  166. const worker = new Worker(this.workerSourceURL)
  167. worker._callbacks = {}
  168. worker._taskCosts = {}
  169. worker._taskLoad = 0
  170. worker.postMessage({
  171. type: 'init',
  172. decoderConfig: this.decoderConfig,
  173. })
  174. worker.onmessage = function (e) {
  175. const message = e.data
  176. switch (message.type) {
  177. case 'decode':
  178. worker._callbacks[message.id].resolve(message)
  179. break
  180. case 'error':
  181. worker._callbacks[message.id].reject(message)
  182. break
  183. default:
  184. console.error('THREE.DRACOLoader: Unexpected message, "' + message.type + '"')
  185. }
  186. }
  187. this.workerPool.push(worker)
  188. } else {
  189. this.workerPool.sort(function (a, b) {
  190. return a._taskLoad > b._taskLoad ? -1 : 1
  191. })
  192. }
  193. const worker = this.workerPool[this.workerPool.length - 1]
  194. worker._taskCosts[taskID] = taskCost
  195. worker._taskLoad += taskCost
  196. return worker
  197. })
  198. }
  199. _releaseTask(worker, taskID) {
  200. worker._taskLoad -= worker._taskCosts[taskID]
  201. delete worker._callbacks[taskID]
  202. delete worker._taskCosts[taskID]
  203. }
  204. debug() {
  205. console.log(
  206. 'Task load: ',
  207. this.workerPool.map(worker => worker._taskLoad)
  208. )
  209. }
  210. dispose() {
  211. for (let i = 0; i < this.workerPool.length; ++i) {
  212. this.workerPool[i].terminate()
  213. }
  214. this.workerPool.length = 0
  215. return this
  216. }
  217. }
  218. /* WEB WORKER */
  219. function DRACOWorker() {
  220. let decoderConfig
  221. let decoderPending
  222. onmessage = function (e) {
  223. const message = e.data
  224. switch (message.type) {
  225. case 'init':
  226. decoderConfig = message.decoderConfig
  227. decoderPending = new Promise(function (
  228. resolve
  229. /*, reject*/
  230. ) {
  231. decoderConfig.onModuleLoaded = function (draco) {
  232. // Module is Promise-like. Wrap before resolving to avoid loop.
  233. resolve({
  234. draco: draco,
  235. })
  236. }
  237. DracoDecoderModule(decoderConfig) // eslint-disable-line no-undef
  238. })
  239. break
  240. case 'decode':
  241. const buffer = message.buffer
  242. const taskConfig = message.taskConfig
  243. decoderPending.then(module => {
  244. const draco = module.draco
  245. const decoder = new draco.Decoder()
  246. const decoderBuffer = new draco.DecoderBuffer()
  247. decoderBuffer.Init(new Int8Array(buffer), buffer.byteLength)
  248. try {
  249. const geometry = decodeGeometry(draco, decoder, decoderBuffer, taskConfig)
  250. const buffers = geometry.attributes.map(attr => attr.array.buffer)
  251. if (geometry.index) buffers.push(geometry.index.array.buffer)
  252. self.postMessage(
  253. {
  254. type: 'decode',
  255. id: message.id,
  256. geometry,
  257. },
  258. buffers
  259. )
  260. } catch (error) {
  261. console.error(error)
  262. self.postMessage({
  263. type: 'error',
  264. id: message.id,
  265. error: error.message,
  266. })
  267. } finally {
  268. draco.destroy(decoderBuffer)
  269. draco.destroy(decoder)
  270. }
  271. })
  272. break
  273. }
  274. }
  275. function decodeGeometry(draco, decoder, decoderBuffer, taskConfig) {
  276. const attributeIDs = taskConfig.attributeIDs
  277. const attributeTypes = taskConfig.attributeTypes
  278. let dracoGeometry
  279. let decodingStatus
  280. const geometryType = decoder.GetEncodedGeometryType(decoderBuffer)
  281. if (geometryType === draco.TRIANGULAR_MESH) {
  282. dracoGeometry = new draco.Mesh()
  283. decodingStatus = decoder.DecodeBufferToMesh(decoderBuffer, dracoGeometry)
  284. } else if (geometryType === draco.POINT_CLOUD) {
  285. dracoGeometry = new draco.PointCloud()
  286. decodingStatus = decoder.DecodeBufferToPointCloud(decoderBuffer, dracoGeometry)
  287. } else {
  288. throw new Error('THREE.DRACOLoader: Unexpected geometry type.')
  289. }
  290. if (!decodingStatus.ok() || dracoGeometry.ptr === 0) {
  291. throw new Error('THREE.DRACOLoader: Decoding failed: ' + decodingStatus.error_msg())
  292. }
  293. const geometry = {
  294. index: null,
  295. attributes: [],
  296. } // Gather all vertex attributes.
  297. for (const attributeName in attributeIDs) {
  298. const attributeType = self[attributeTypes[attributeName]]
  299. let attribute
  300. let attributeID // A Draco file may be created with default vertex attributes, whose attribute IDs
  301. // are mapped 1:1 from their semantic name (POSITION, NORMAL, ...). Alternatively,
  302. // a Draco file may contain a custom set of attributes, identified by known unique
  303. // IDs. glTF files always do the latter, and `.drc` files typically do the former.
  304. if (taskConfig.useUniqueIDs) {
  305. attributeID = attributeIDs[attributeName]
  306. attribute = decoder.GetAttributeByUniqueId(dracoGeometry, attributeID)
  307. } else {
  308. attributeID = decoder.GetAttributeId(dracoGeometry, draco[attributeIDs[attributeName]])
  309. if (attributeID === -1) continue
  310. attribute = decoder.GetAttribute(dracoGeometry, attributeID)
  311. }
  312. geometry.attributes.push(decodeAttribute(draco, decoder, dracoGeometry, attributeName, attributeType, attribute))
  313. } // Add index.
  314. if (geometryType === draco.TRIANGULAR_MESH) {
  315. geometry.index = decodeIndex(draco, decoder, dracoGeometry)
  316. }
  317. draco.destroy(dracoGeometry)
  318. return geometry
  319. }
  320. function decodeIndex(draco, decoder, dracoGeometry) {
  321. const numFaces = dracoGeometry.num_faces()
  322. const numIndices = numFaces * 3
  323. const byteLength = numIndices * 4
  324. const ptr = draco._malloc(byteLength)
  325. decoder.GetTrianglesUInt32Array(dracoGeometry, byteLength, ptr)
  326. const index = new Uint32Array(draco.HEAPF32.buffer, ptr, numIndices).slice()
  327. draco._free(ptr)
  328. return {
  329. array: index,
  330. itemSize: 1,
  331. }
  332. }
  333. function decodeAttribute(draco, decoder, dracoGeometry, attributeName, attributeType, attribute) {
  334. const numComponents = attribute.num_components()
  335. const numPoints = dracoGeometry.num_points()
  336. const numValues = numPoints * numComponents
  337. const byteLength = numValues * attributeType.BYTES_PER_ELEMENT
  338. const dataType = getDracoDataType(draco, attributeType)
  339. const ptr = draco._malloc(byteLength)
  340. decoder.GetAttributeDataArrayForAllPoints(dracoGeometry, attribute, dataType, byteLength, ptr)
  341. const array = new attributeType(draco.HEAPF32.buffer, ptr, numValues).slice()
  342. draco._free(ptr)
  343. return {
  344. name: attributeName,
  345. array: array,
  346. itemSize: numComponents,
  347. }
  348. }
  349. function getDracoDataType(draco, attributeType) {
  350. switch (attributeType) {
  351. case Float32Array:
  352. return draco.DT_FLOAT32
  353. case Int8Array:
  354. return draco.DT_INT8
  355. case Int16Array:
  356. return draco.DT_INT16
  357. case Int32Array:
  358. return draco.DT_INT32
  359. case Uint8Array:
  360. return draco.DT_UINT8
  361. case Uint16Array:
  362. return draco.DT_UINT16
  363. case Uint32Array:
  364. return draco.DT_UINT32
  365. }
  366. }
  367. }
  368. THREE.DRACOLoader = DRACOLoader
  369. })()