threepipe
Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

GLTFWriter2.ts 9.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. import {GLTFExporter, GLTFExporterOptions} from 'three/examples/jsm/exporters/GLTFExporter.js'
  2. import {BufferGeometry, Material, MeshStandardMaterial, Object3D, PixelFormat, Texture} from 'three'
  3. import {blobToDataURL} from 'ts-browser-helpers'
  4. import type {GLTFExporter2Options} from './GLTFExporter2'
  5. import {ThreeSerialization} from '../../utils'
  6. export class GLTFWriter2 extends GLTFExporter.Utils.GLTFWriter {
  7. declare options: GLTFExporterOptions & {
  8. externalImagesInExtras: boolean,
  9. exporterOptions: GLTFExporter2Options
  10. }
  11. serializeUserData(object: Object3D | Material | BufferGeometry, objectDef: any): void {
  12. const userData = object.userData
  13. const temp: any = {}
  14. if (userData.__disposed) {
  15. console.error('Serializing a disposed object', object)
  16. }
  17. Object.entries(userData).forEach(([key, value]: any) => {
  18. if (!value ||
  19. typeof value === 'function' ||
  20. value.isObject3D ||
  21. value.isTexture ||
  22. value.isMaterial ||
  23. value.assetType != null ||
  24. key.startsWith('_') // private data
  25. ) {
  26. temp[key] = value
  27. delete userData[key]
  28. }
  29. })
  30. const ud2 = ThreeSerialization.Serialize(userData)
  31. Object.entries(temp).forEach(([key, value]) => {
  32. userData[key] = value
  33. delete temp[key]
  34. })
  35. object.userData = ud2
  36. super.serializeUserData(object, objectDef)
  37. object.userData = userData
  38. }
  39. processObjects(objects: Object3D[]) {
  40. if (objects.length === 1 && objects[0]?.userData.rootSceneModelRoot) {
  41. // objects[0].isScene = true
  42. this.processScene(objects[0])
  43. // delete objects[0].isScene
  44. } else
  45. super.processObjects(objects)
  46. }
  47. protected _defaultMaterial = new MeshStandardMaterial()
  48. /**
  49. * Checks for shader material and does the same thing...
  50. * @param material
  51. */
  52. processMaterial(material: Material): number|null {
  53. if (this.cache.materials.has(material)) return this.cache.materials.get(material)!
  54. let mat = material as any
  55. // set default material when material is null. shader material is processed further below for custom extensions like diamonds.
  56. if (!mat || mat.isShaderMaterial) mat = this._defaultMaterial
  57. const defIndex = super.processMaterial(mat)
  58. if (defIndex === null) {
  59. console.error('GLTFWriter2: Unexpected error: Failed to process material', material)
  60. return null
  61. }
  62. // when not a shader material
  63. if (!material || mat === material) return defIndex // todo: this line needds to be tested.
  64. // when shader material
  65. const defaultDef = JSON.stringify(this.json.materials[defIndex])
  66. const materialDef = JSON.parse(defaultDef) // for deep clone
  67. // console.log(defIndex, defaultDef, materialDef)
  68. this.serializeUserData(material, materialDef)
  69. this._invokeAll((ext)=>{
  70. ext.writeMaterial && ext.writeMaterial(material, materialDef)
  71. })
  72. // todo: test remove this
  73. // if (JSON.stringify(materialDef) === defaultDef) {
  74. // return defIndex
  75. // }
  76. const index = this.json.materials.push(materialDef) - 1
  77. this.cache.materials.set(material, index)
  78. return index
  79. }
  80. /**
  81. * Same as processImage but for image blobs
  82. * @param blob
  83. * @param texture
  84. */
  85. processImageBlob(blob: Blob, texture: Texture) {
  86. if (!blob) return -1
  87. const cache = this.cache
  88. const options = this.options
  89. const pending = this.pending
  90. const json = this.json
  91. const image = texture.image
  92. if (!cache.images.has(image)) cache.images.set(image, {})
  93. const cachedImages = cache.images.get(image)
  94. const key = blob.type + ':flipY/' + texture.flipY.toString()
  95. if (cachedImages[ key ] !== undefined) return cachedImages[ key ]
  96. if (!json.images) json.images = []
  97. const imageDef: any = {mimeType: blob.type}
  98. if (options.binary === true) {
  99. pending.push(new Promise<void>((resolve)=>{
  100. this.processBufferViewImage(blob).then((bufferViewIndex: number)=>{
  101. imageDef.bufferView = bufferViewIndex
  102. resolve()
  103. })
  104. }))
  105. } else {
  106. pending.push(blobToDataURL(blob).then((dataURL: string)=>{
  107. imageDef.uri = dataURL
  108. }))
  109. }
  110. const index = json.images.push(imageDef) - 1
  111. cachedImages[ key ] = index
  112. return index
  113. }
  114. processSampler(map: Texture) {
  115. const samplerIndex = super.processSampler(map)
  116. // todo: uncomment when sampler extras supported by gltf-transform: https://github.com/donmccurdy/glTF-Transform/issues/645
  117. // const samplerDef = this.json.samplers[samplerIndex]
  118. // if (!samplerDef.extras) samplerDef.extras = {}
  119. // samplerDef.extras.uuid = map.uuid
  120. return samplerIndex
  121. }
  122. processTexture(map: Texture) {
  123. const cache = this.cache
  124. const json = this.json
  125. if (cache.textures.has(map)) return cache.textures.get(map)!
  126. const srcData = map.source.data
  127. const mimeType = map.userData.mimeType
  128. if (map.userData.rootPath &&
  129. !this.options.exporterOptions.embedUrlImages
  130. && (map.userData.rootPath.startsWith('http') || map.userData.rootPath.startsWith('data:'))
  131. ) {
  132. if (map.source.data) { // handled below in GLTFWriter2.processImage
  133. if (!this.options.exporterOptions.embedUrlImagePreviews || (map as any).isDataTexture) map.source.data = null // todo make sure its only Texture, check for svg etc
  134. else map.source.data._savePreview = true
  135. }
  136. delete map.userData.mimeType // for extensions like ktx2
  137. }
  138. const processed = super.processTexture(map)
  139. const textureDef = json.textures[processed]
  140. if (!textureDef) {
  141. console.error('No texture def', processed, map)
  142. return processed
  143. }
  144. // if (!textureDef.extras) textureDef.extras = {}
  145. const imageDef = json.images ? json.images[textureDef.source] : null
  146. if (imageDef) {
  147. if (!imageDef.extras) imageDef.extras = {}
  148. if (map.source) imageDef.extras.uuid = map.source.uuid
  149. imageDef.extras.t_uuid = map.uuid // todo: remove when extras supported by gltf-transform: https://github.com/donmccurdy/glTF-Transform/issues/645
  150. }
  151. // map uuid saved in processSampler.
  152. if (map.userData.rootPath && !this.options.exporterOptions.embedUrlImages
  153. && (map.userData.rootPath.startsWith('http') || map.userData.rootPath.startsWith('data:'))
  154. ) {
  155. if (map.source.data) delete map.source.data._savePreview
  156. else map.source.data = srcData
  157. map.userData.mimeType = mimeType
  158. if (!textureDef) {
  159. console.error('textureDef is null', processed, map)
  160. return processed
  161. }
  162. if (textureDef.source >= 0) {
  163. // console.warn('textureDef.source is already set', processed, map)
  164. const img = this.json.images[textureDef.source]
  165. if (img.uri) {
  166. console.warn('uri already set', img.uri)
  167. } else {
  168. img.uri = map.userData.rootPath
  169. img.mimeType = mimeType
  170. if (!img.extras) img.extras = {}
  171. img.extras.flipY = map.flipY
  172. img.extras.uri = map.userData.rootPath // uri is removed by gltf-transform if bufferView is set
  173. }
  174. } else {
  175. textureDef.source = this.processImageUri(map.image, map.userData.rootPath, map.flipY, mimeType)
  176. }
  177. }
  178. if (textureDef.source < 0) {
  179. console.error('textureDef.source cannot be saved', textureDef, map)
  180. }
  181. return processed
  182. }
  183. // Add extra check for null images. This is set in processTexture when we have a rootPath
  184. processImage(image: any, format: PixelFormat, flipY: boolean, mimeType = 'image/png') {
  185. if (!image) return -1
  186. return super.processImage(image, format, flipY, mimeType, image._savePreview ? 32 : undefined, image._savePreview ? 32 : undefined)
  187. }
  188. /**
  189. * Used in GLTFWriter2.processTexture for rootPath. Note that this does not check for options.exporterOptions.embedUrlImages, it must be done separately.
  190. * @param image
  191. * @param uri
  192. * @param flipY
  193. * @param mimeType
  194. */
  195. processImageUri(image: any, uri: string, flipY: boolean, mimeType = 'image/png') {
  196. const cache = this.cache
  197. const json = this.json
  198. if (!cache.images.has(image)) cache.images.set(image, {})
  199. const cachedImages = cache.images.get(image)
  200. const key = mimeType + ':flipY/' + flipY.toString()
  201. if (cachedImages[ key ] !== undefined) return cachedImages[ key ]
  202. if (!json.images) json.images = []
  203. const imageDef: any = {
  204. mimeType, uri,
  205. extras: {flipY},
  206. }
  207. const index = json.images.push(imageDef) - 1
  208. cachedImages[ key ] = index
  209. return index
  210. }
  211. }