VOXLoader.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919
  1. import {
  2. BufferGeometry,
  3. Color,
  4. Data3DTexture,
  5. FileLoader,
  6. Float32BufferAttribute,
  7. Group,
  8. Loader,
  9. LinearFilter,
  10. Matrix4,
  11. Mesh,
  12. MeshStandardMaterial,
  13. NearestFilter,
  14. RedFormat,
  15. SRGBColorSpace
  16. } from 'three';
  17. // Helper function to read a STRING from the data view
  18. function readString( data, offset ) {
  19. const size = data.getUint32( offset, true );
  20. offset += 4;
  21. let str = '';
  22. for ( let i = 0; i < size; i ++ ) {
  23. str += String.fromCharCode( data.getUint8( offset ++ ) );
  24. }
  25. return { value: str, size: 4 + size };
  26. }
  27. // Helper function to read a DICT from the data view
  28. function readDict( data, offset ) {
  29. const dict = {};
  30. const count = data.getUint32( offset, true );
  31. offset += 4;
  32. let totalSize = 4;
  33. for ( let i = 0; i < count; i ++ ) {
  34. const key = readString( data, offset );
  35. offset += key.size;
  36. totalSize += key.size;
  37. const value = readString( data, offset );
  38. offset += value.size;
  39. totalSize += value.size;
  40. dict[ key.value ] = value.value;
  41. }
  42. return { value: dict, size: totalSize };
  43. }
  44. // Helper function to decode ROTATION byte into a rotation matrix
  45. function decodeRotation( byte ) {
  46. // The rotation is stored as a row-major 3x3 matrix encoded in a single byte
  47. // Bits 0-1: index of the non-zero entry in the first row
  48. // Bits 2-3: index of the non-zero entry in the second row
  49. // Bit 4: sign of the first row entry (0 = positive, 1 = negative)
  50. // Bit 5: sign of the second row entry
  51. // Bit 6: sign of the third row entry
  52. // The third row index is determined by the remaining column
  53. const index1 = byte & 0x3;
  54. const index2 = ( byte >> 2 ) & 0x3;
  55. const sign1 = ( byte >> 4 ) & 0x1 ? - 1 : 1;
  56. const sign2 = ( byte >> 5 ) & 0x1 ? - 1 : 1;
  57. const sign3 = ( byte >> 6 ) & 0x1 ? - 1 : 1;
  58. // Find the third row index (the one not used by row 0 or row 1)
  59. const index3 = 3 - index1 - index2;
  60. // Build the VOX rotation matrix (row-major 3x3)
  61. // r[row][col] - each row has one non-zero entry
  62. const r = [
  63. [ 0, 0, 0 ],
  64. [ 0, 0, 0 ],
  65. [ 0, 0, 0 ]
  66. ];
  67. r[ 0 ][ index1 ] = sign1;
  68. r[ 1 ][ index2 ] = sign2;
  69. r[ 2 ][ index3 ] = sign3;
  70. // Convert from VOX coordinate system (Z-up) to Three.js (Y-up)
  71. // VOX: X-right, Y-forward, Z-up
  72. // Three.js: X-right, Y-up, Z-backward
  73. // Transformation: x' = x, y' = z, z' = -y
  74. //
  75. // To convert rotation matrix R_vox to R_three:
  76. // R_three = C * R_vox * C^-1
  77. // where C converts VOX coords to Three.js coords
  78. // Apply coordinate change: swap Y and Z, negate new Z
  79. // This is equivalent to: C * R * C^-1
  80. const m = new Matrix4();
  81. m.set(
  82. r[ 0 ][ 0 ], r[ 0 ][ 2 ], - r[ 0 ][ 1 ], 0,
  83. r[ 2 ][ 0 ], r[ 2 ][ 2 ], - r[ 2 ][ 1 ], 0,
  84. - r[ 1 ][ 0 ], - r[ 1 ][ 2 ], r[ 1 ][ 1 ], 0,
  85. 0, 0, 0, 1
  86. );
  87. return m;
  88. }
  89. // Apply VOX transform to a Three.js object
  90. function applyTransform( object, node ) {
  91. if ( node.attributes._name ) {
  92. object.name = node.attributes._name;
  93. }
  94. if ( node.frames.length > 0 ) {
  95. const frame = node.frames[ 0 ];
  96. if ( frame.rotation ) {
  97. object.applyMatrix4( frame.rotation );
  98. }
  99. if ( frame.translation ) {
  100. // VOX uses Z-up, Three.js uses Y-up
  101. object.position.set(
  102. frame.translation.x,
  103. frame.translation.z,
  104. - frame.translation.y
  105. );
  106. }
  107. }
  108. }
  109. // Recursively build Three.js object graph from VOX nodes
  110. function buildObject( nodeId, nodes, chunks ) {
  111. const node = nodes[ nodeId ];
  112. if ( node.type === 'transform' ) {
  113. const childNode = nodes[ node.childNodeId ];
  114. // Check if this transform has actual transformation data
  115. const frame = node.frames[ 0 ];
  116. const hasTransform = frame && ( frame.rotation || frame.translation );
  117. // Flatten: if child is a single-model shape, apply transform directly to mesh
  118. if ( childNode.type === 'shape' && childNode.models.length === 1 ) {
  119. const chunk = chunks[ childNode.models[ 0 ].modelId ];
  120. const mesh = buildMesh( chunk );
  121. applyTransform( mesh, node );
  122. return mesh;
  123. }
  124. // If no transform, just return the child directly (avoid unnecessary group)
  125. if ( ! hasTransform ) {
  126. const child = buildObject( node.childNodeId, nodes, chunks );
  127. if ( child && node.attributes._name ) child.name = node.attributes._name;
  128. return child;
  129. }
  130. // Otherwise create a group
  131. const group = new Group();
  132. applyTransform( group, node );
  133. const child = buildObject( node.childNodeId, nodes, chunks );
  134. if ( child ) group.add( child );
  135. return group;
  136. } else if ( node.type === 'group' ) {
  137. const group = new Group();
  138. for ( const childId of node.childIds ) {
  139. const child = buildObject( childId, nodes, chunks );
  140. if ( child ) group.add( child );
  141. }
  142. return group;
  143. } else if ( node.type === 'shape' ) {
  144. // Shape reached directly (shouldn't happen in well-formed files, but handle it)
  145. if ( node.models.length === 1 ) {
  146. const chunk = chunks[ node.models[ 0 ].modelId ];
  147. return buildMesh( chunk );
  148. }
  149. const group = new Group();
  150. for ( const model of node.models ) {
  151. const chunk = chunks[ model.modelId ];
  152. group.add( buildMesh( chunk ) );
  153. }
  154. return group;
  155. }
  156. return null;
  157. }
  158. /**
  159. * A loader for the VOX format.
  160. *
  161. * ```js
  162. * const loader = new VOXLoader();
  163. * const result = await loader.loadAsync( 'models/vox/monu10.vox' );
  164. *
  165. * scene.add( result.scene.children[ 0 ] );
  166. * ```
  167. * @augments Loader
  168. * @three_import import { VOXLoader } from 'three/addons/loaders/VOXLoader.js';
  169. */
  170. class VOXLoader extends Loader {
  171. /**
  172. * Starts loading from the given URL and passes the loaded VOX asset
  173. * to the `onLoad()` callback.
  174. *
  175. * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI.
  176. * @param {function(Object)} onLoad - Executed when the loading process has been finished.
  177. * @param {onProgressCallback} onProgress - Executed while the loading is in progress.
  178. * @param {onErrorCallback} onError - Executed when errors occur.
  179. */
  180. load( url, onLoad, onProgress, onError ) {
  181. const scope = this;
  182. const loader = new FileLoader( scope.manager );
  183. loader.setPath( scope.path );
  184. loader.setResponseType( 'arraybuffer' );
  185. loader.setRequestHeader( scope.requestHeader );
  186. loader.load( url, function ( buffer ) {
  187. try {
  188. onLoad( scope.parse( buffer ) );
  189. } catch ( e ) {
  190. if ( onError ) {
  191. onError( e );
  192. } else {
  193. console.error( e );
  194. }
  195. scope.manager.itemError( url );
  196. }
  197. }, onProgress, onError );
  198. }
  199. /**
  200. * Parses the given VOX data and returns the result object.
  201. *
  202. * @param {ArrayBuffer} buffer - The raw VOX data as an array buffer.
  203. * @return {Object} The parsed VOX data with properties: chunks, scene.
  204. */
  205. parse( buffer ) {
  206. const data = new DataView( buffer );
  207. const id = data.getUint32( 0, true );
  208. const version = data.getUint32( 4, true );
  209. if ( id !== 542658390 ) {
  210. console.error( 'THREE.VOXLoader: Invalid VOX file.' );
  211. return;
  212. }
  213. if ( version !== 150 && version !== 200 ) {
  214. console.error( 'THREE.VOXLoader: Invalid VOX file. Unsupported version:', version );
  215. return;
  216. }
  217. const DEFAULT_PALETTE = [
  218. 0x00000000, 0xffffffff, 0xffccffff, 0xff99ffff, 0xff66ffff, 0xff33ffff, 0xff00ffff, 0xffffccff,
  219. 0xffccccff, 0xff99ccff, 0xff66ccff, 0xff33ccff, 0xff00ccff, 0xffff99ff, 0xffcc99ff, 0xff9999ff,
  220. 0xff6699ff, 0xff3399ff, 0xff0099ff, 0xffff66ff, 0xffcc66ff, 0xff9966ff, 0xff6666ff, 0xff3366ff,
  221. 0xff0066ff, 0xffff33ff, 0xffcc33ff, 0xff9933ff, 0xff6633ff, 0xff3333ff, 0xff0033ff, 0xffff00ff,
  222. 0xffcc00ff, 0xff9900ff, 0xff6600ff, 0xff3300ff, 0xff0000ff, 0xffffffcc, 0xffccffcc, 0xff99ffcc,
  223. 0xff66ffcc, 0xff33ffcc, 0xff00ffcc, 0xffffcccc, 0xffcccccc, 0xff99cccc, 0xff66cccc, 0xff33cccc,
  224. 0xff00cccc, 0xffff99cc, 0xffcc99cc, 0xff9999cc, 0xff6699cc, 0xff3399cc, 0xff0099cc, 0xffff66cc,
  225. 0xffcc66cc, 0xff9966cc, 0xff6666cc, 0xff3366cc, 0xff0066cc, 0xffff33cc, 0xffcc33cc, 0xff9933cc,
  226. 0xff6633cc, 0xff3333cc, 0xff0033cc, 0xffff00cc, 0xffcc00cc, 0xff9900cc, 0xff6600cc, 0xff3300cc,
  227. 0xff0000cc, 0xffffff99, 0xffccff99, 0xff99ff99, 0xff66ff99, 0xff33ff99, 0xff00ff99, 0xffffcc99,
  228. 0xffcccc99, 0xff99cc99, 0xff66cc99, 0xff33cc99, 0xff00cc99, 0xffff9999, 0xffcc9999, 0xff999999,
  229. 0xff669999, 0xff339999, 0xff009999, 0xffff6699, 0xffcc6699, 0xff996699, 0xff666699, 0xff336699,
  230. 0xff006699, 0xffff3399, 0xffcc3399, 0xff993399, 0xff663399, 0xff333399, 0xff003399, 0xffff0099,
  231. 0xffcc0099, 0xff990099, 0xff660099, 0xff330099, 0xff000099, 0xffffff66, 0xffccff66, 0xff99ff66,
  232. 0xff66ff66, 0xff33ff66, 0xff00ff66, 0xffffcc66, 0xffcccc66, 0xff99cc66, 0xff66cc66, 0xff33cc66,
  233. 0xff00cc66, 0xffff9966, 0xffcc9966, 0xff999966, 0xff669966, 0xff339966, 0xff009966, 0xffff6666,
  234. 0xffcc6666, 0xff996666, 0xff666666, 0xff336666, 0xff006666, 0xffff3366, 0xffcc3366, 0xff993366,
  235. 0xff663366, 0xff333366, 0xff003366, 0xffff0066, 0xffcc0066, 0xff990066, 0xff660066, 0xff330066,
  236. 0xff000066, 0xffffff33, 0xffccff33, 0xff99ff33, 0xff66ff33, 0xff33ff33, 0xff00ff33, 0xffffcc33,
  237. 0xffcccc33, 0xff99cc33, 0xff66cc33, 0xff33cc33, 0xff00cc33, 0xffff9933, 0xffcc9933, 0xff999933,
  238. 0xff669933, 0xff339933, 0xff009933, 0xffff6633, 0xffcc6633, 0xff996633, 0xff666633, 0xff336633,
  239. 0xff006633, 0xffff3333, 0xffcc3333, 0xff993333, 0xff663333, 0xff333333, 0xff003333, 0xffff0033,
  240. 0xffcc0033, 0xff990033, 0xff660033, 0xff330033, 0xff000033, 0xffffff00, 0xffccff00, 0xff99ff00,
  241. 0xff66ff00, 0xff33ff00, 0xff00ff00, 0xffffcc00, 0xffcccc00, 0xff99cc00, 0xff66cc00, 0xff33cc00,
  242. 0xff00cc00, 0xffff9900, 0xffcc9900, 0xff999900, 0xff669900, 0xff339900, 0xff009900, 0xffff6600,
  243. 0xffcc6600, 0xff996600, 0xff666600, 0xff336600, 0xff006600, 0xffff3300, 0xffcc3300, 0xff993300,
  244. 0xff663300, 0xff333300, 0xff003300, 0xffff0000, 0xffcc0000, 0xff990000, 0xff660000, 0xff330000,
  245. 0xff0000ee, 0xff0000dd, 0xff0000bb, 0xff0000aa, 0xff000088, 0xff000077, 0xff000055, 0xff000044,
  246. 0xff000022, 0xff000011, 0xff00ee00, 0xff00dd00, 0xff00bb00, 0xff00aa00, 0xff008800, 0xff007700,
  247. 0xff005500, 0xff004400, 0xff002200, 0xff001100, 0xffee0000, 0xffdd0000, 0xffbb0000, 0xffaa0000,
  248. 0xff880000, 0xff770000, 0xff550000, 0xff440000, 0xff220000, 0xff110000, 0xffeeeeee, 0xffdddddd,
  249. 0xffbbbbbb, 0xffaaaaaa, 0xff888888, 0xff777777, 0xff555555, 0xff444444, 0xff222222, 0xff111111
  250. ];
  251. let i = 8;
  252. let chunk;
  253. const chunks = [];
  254. // Extension data
  255. const nodes = {};
  256. let palette = DEFAULT_PALETTE;
  257. while ( i < data.byteLength ) {
  258. let id = '';
  259. for ( let j = 0; j < 4; j ++ ) {
  260. id += String.fromCharCode( data.getUint8( i ++ ) );
  261. }
  262. const chunkSize = data.getUint32( i, true ); i += 4;
  263. i += 4; // childChunks
  264. if ( id === 'SIZE' ) {
  265. const x = data.getUint32( i, true ); i += 4;
  266. const y = data.getUint32( i, true ); i += 4;
  267. const z = data.getUint32( i, true ); i += 4;
  268. chunk = {
  269. palette: DEFAULT_PALETTE,
  270. size: { x: x, y: y, z: z },
  271. };
  272. chunks.push( chunk );
  273. i += chunkSize - ( 3 * 4 );
  274. } else if ( id === 'XYZI' ) {
  275. const numVoxels = data.getUint32( i, true ); i += 4;
  276. chunk.data = new Uint8Array( buffer, i, numVoxels * 4 );
  277. i += numVoxels * 4;
  278. } else if ( id === 'RGBA' ) {
  279. palette = [ 0 ];
  280. for ( let j = 0; j < 256; j ++ ) {
  281. palette[ j + 1 ] = data.getUint32( i, true ); i += 4;
  282. }
  283. chunk.palette = palette;
  284. } else if ( id === 'nTRN' ) {
  285. // Transform Node
  286. const nodeId = data.getUint32( i, true ); i += 4;
  287. const attributes = readDict( data, i );
  288. i += attributes.size;
  289. const childNodeId = data.getUint32( i, true ); i += 4;
  290. i += 4; // reserved (-1)
  291. const layerId = data.getInt32( i, true ); i += 4;
  292. const numFrames = data.getUint32( i, true ); i += 4;
  293. const frames = [];
  294. for ( let f = 0; f < numFrames; f ++ ) {
  295. const frameDict = readDict( data, i );
  296. i += frameDict.size;
  297. const frame = { rotation: null, translation: null };
  298. if ( frameDict.value._r !== undefined ) {
  299. frame.rotation = decodeRotation( parseInt( frameDict.value._r ) );
  300. }
  301. if ( frameDict.value._t !== undefined ) {
  302. const parts = frameDict.value._t.split( ' ' ).map( Number );
  303. frame.translation = { x: parts[ 0 ], y: parts[ 1 ], z: parts[ 2 ] };
  304. }
  305. frames.push( frame );
  306. }
  307. nodes[ nodeId ] = {
  308. type: 'transform',
  309. id: nodeId,
  310. attributes: attributes.value,
  311. childNodeId: childNodeId,
  312. layerId: layerId,
  313. frames: frames
  314. };
  315. } else if ( id === 'nGRP' ) {
  316. // Group Node
  317. const nodeId = data.getUint32( i, true ); i += 4;
  318. const attributes = readDict( data, i );
  319. i += attributes.size;
  320. const numChildren = data.getUint32( i, true ); i += 4;
  321. const childIds = [];
  322. for ( let c = 0; c < numChildren; c ++ ) {
  323. childIds.push( data.getUint32( i, true ) ); i += 4;
  324. }
  325. nodes[ nodeId ] = {
  326. type: 'group',
  327. id: nodeId,
  328. attributes: attributes.value,
  329. childIds: childIds
  330. };
  331. } else if ( id === 'nSHP' ) {
  332. // Shape Node
  333. const nodeId = data.getUint32( i, true ); i += 4;
  334. const attributes = readDict( data, i );
  335. i += attributes.size;
  336. const numModels = data.getUint32( i, true ); i += 4;
  337. const models = [];
  338. for ( let m = 0; m < numModels; m ++ ) {
  339. const modelId = data.getUint32( i, true ); i += 4;
  340. const modelAttributes = readDict( data, i );
  341. i += modelAttributes.size;
  342. models.push( {
  343. modelId: modelId,
  344. attributes: modelAttributes.value
  345. } );
  346. }
  347. nodes[ nodeId ] = {
  348. type: 'shape',
  349. id: nodeId,
  350. attributes: attributes.value,
  351. models: models
  352. };
  353. } else {
  354. // Skip unknown chunks
  355. i += chunkSize;
  356. }
  357. }
  358. // Apply palette to all chunks
  359. for ( let c = 0; c < chunks.length; c ++ ) {
  360. chunks[ c ].palette = palette;
  361. }
  362. // Build Three.js scene graph from nodes
  363. let scene = null;
  364. if ( Object.keys( nodes ).length > 0 ) {
  365. scene = buildObject( 0, nodes, chunks );
  366. }
  367. // Build result object
  368. const result = {
  369. chunks: chunks,
  370. scene: scene
  371. };
  372. // @deprecated, r182
  373. // Proxy for backwards compatibility with array-like access
  374. let warned = false;
  375. return new Proxy( result, {
  376. get( target, prop ) {
  377. // Handle numeric indices
  378. if ( typeof prop === 'string' && /^\d+$/.test( prop ) ) {
  379. if ( ! warned ) {
  380. console.warn( 'THREE.VOXLoader: Accessing result as an array is deprecated. Use result.chunks[] instead.' );
  381. warned = true;
  382. }
  383. return target.chunks[ parseInt( prop ) ];
  384. }
  385. // Handle array properties/methods
  386. if ( prop === 'length' ) {
  387. if ( ! warned ) {
  388. console.warn( 'THREE.VOXLoader: Accessing result as an array is deprecated. Use result.chunks instead.' );
  389. warned = true;
  390. }
  391. return target.chunks.length;
  392. }
  393. // Handle iteration
  394. if ( prop === Symbol.iterator ) {
  395. if ( ! warned ) {
  396. console.warn( 'THREE.VOXLoader: Iterating result as an array is deprecated. Use result.chunks instead.' );
  397. warned = true;
  398. }
  399. return target.chunks[ Symbol.iterator ].bind( target.chunks );
  400. }
  401. return target[ prop ];
  402. }
  403. } );
  404. }
  405. }
  406. /**
  407. * Builds a mesh from a VOX chunk.
  408. *
  409. * @param {Object} chunk - A VOX chunk loaded via {@link VOXLoader}.
  410. * @return {Mesh} The generated mesh.
  411. */
  412. function buildMesh( chunk ) {
  413. const data = chunk.data;
  414. const size = chunk.size;
  415. const palette = chunk.palette;
  416. const sx = size.x;
  417. const sy = size.y;
  418. const sz = size.z;
  419. // Build volume with color indices
  420. const volume = new Uint8Array( sx * sy * sz );
  421. for ( let j = 0; j < data.length; j += 4 ) {
  422. const x = data[ j + 0 ];
  423. const y = data[ j + 1 ];
  424. const z = data[ j + 2 ];
  425. const c = data[ j + 3 ];
  426. volume[ x + y * sx + z * sx * sy ] = c;
  427. }
  428. // Greedy meshing
  429. const vertices = [];
  430. const indices = [];
  431. const colors = [];
  432. const _color = new Color();
  433. let hasColors = false;
  434. // Process each of the 6 face directions
  435. // dims: the 3 axis sizes, d: which axis is normal to the face
  436. const dims = [ sx, sy, sz ];
  437. for ( let d = 0; d < 3; d ++ ) {
  438. const u = ( d + 1 ) % 3;
  439. const v = ( d + 2 ) % 3;
  440. const dimsD = dims[ d ];
  441. const dimsU = dims[ u ];
  442. const dimsV = dims[ v ];
  443. const q = [ 0, 0, 0 ];
  444. const mask = new Int16Array( dimsU * dimsV );
  445. q[ d ] = 1;
  446. // Sweep through slices
  447. for ( let slice = 0; slice <= dimsD; slice ++ ) {
  448. // Build mask for this slice
  449. let n = 0;
  450. for ( let vv = 0; vv < dimsV; vv ++ ) {
  451. for ( let uu = 0; uu < dimsU; uu ++ ) {
  452. const pos = [ 0, 0, 0 ];
  453. pos[ d ] = slice;
  454. pos[ u ] = uu;
  455. pos[ v ] = vv;
  456. const x0 = pos[ 0 ], y0 = pos[ 1 ], z0 = pos[ 2 ];
  457. // Get voxel behind and in front of this face
  458. const behind = ( slice > 0 ) ? volume[ ( x0 - q[ 0 ] ) + ( y0 - q[ 1 ] ) * sx + ( z0 - q[ 2 ] ) * sx * sy ] : 0;
  459. const infront = ( slice < dimsD ) ? volume[ x0 + y0 * sx + z0 * sx * sy ] : 0;
  460. // Face exists if exactly one side is solid
  461. if ( behind > 0 && infront === 0 ) {
  462. mask[ n ] = behind; // positive face
  463. } else if ( infront > 0 && behind === 0 ) {
  464. mask[ n ] = - infront; // negative face
  465. } else {
  466. mask[ n ] = 0;
  467. }
  468. n ++;
  469. }
  470. }
  471. // Greedy merge mask into quads
  472. n = 0;
  473. for ( let vv = 0; vv < dimsV; vv ++ ) {
  474. for ( let uu = 0; uu < dimsU; ) {
  475. const c = mask[ n ];
  476. if ( c !== 0 ) {
  477. // Find width
  478. let w = 1;
  479. while ( uu + w < dimsU && mask[ n + w ] === c ) {
  480. w ++;
  481. }
  482. // Find height
  483. let h = 1;
  484. let done = false;
  485. while ( vv + h < dimsV && ! done ) {
  486. for ( let k = 0; k < w; k ++ ) {
  487. if ( mask[ n + k + h * dimsU ] !== c ) {
  488. done = true;
  489. break;
  490. }
  491. }
  492. if ( ! done ) h ++;
  493. }
  494. // Add quad
  495. const pos = [ 0, 0, 0 ];
  496. pos[ d ] = slice;
  497. pos[ u ] = uu;
  498. pos[ v ] = vv;
  499. const du = [ 0, 0, 0 ];
  500. const dv = [ 0, 0, 0 ];
  501. du[ u ] = w;
  502. dv[ v ] = h;
  503. // Get color
  504. const colorIndex = Math.abs( c );
  505. const hex = palette[ colorIndex ];
  506. const r = ( hex >> 0 & 0xff ) / 0xff;
  507. const g = ( hex >> 8 & 0xff ) / 0xff;
  508. const b = ( hex >> 16 & 0xff ) / 0xff;
  509. if ( r > 0 || g > 0 || b > 0 ) hasColors = true;
  510. _color.setRGB( r, g, b, SRGBColorSpace );
  511. // Convert VOX coords to Three.js coords (Y-up)
  512. // VOX: X right, Y forward, Z up -> Three.js: X right, Y up, Z back
  513. const toThree = ( p ) => [
  514. p[ 0 ] - sx / 2,
  515. p[ 2 ] - sz / 2,
  516. - p[ 1 ] + sy / 2
  517. ];
  518. const v0 = toThree( pos );
  519. const v1 = toThree( [ pos[ 0 ] + du[ 0 ], pos[ 1 ] + du[ 1 ], pos[ 2 ] + du[ 2 ] ] );
  520. const v2 = toThree( [ pos[ 0 ] + du[ 0 ] + dv[ 0 ], pos[ 1 ] + du[ 1 ] + dv[ 1 ], pos[ 2 ] + du[ 2 ] + dv[ 2 ] ] );
  521. const v3 = toThree( [ pos[ 0 ] + dv[ 0 ], pos[ 1 ] + dv[ 1 ], pos[ 2 ] + dv[ 2 ] ] );
  522. const idx = vertices.length / 3;
  523. // Winding order depends on face direction
  524. if ( c > 0 ) {
  525. vertices.push( ...v0, ...v1, ...v2, ...v3 );
  526. indices.push( idx, idx + 1, idx + 2, idx, idx + 2, idx + 3 );
  527. } else {
  528. vertices.push( ...v0, ...v3, ...v2, ...v1 );
  529. indices.push( idx, idx + 1, idx + 2, idx, idx + 2, idx + 3 );
  530. }
  531. colors.push(
  532. _color.r, _color.g, _color.b,
  533. _color.r, _color.g, _color.b,
  534. _color.r, _color.g, _color.b,
  535. _color.r, _color.g, _color.b
  536. );
  537. // Clear mask
  538. for ( let hh = 0; hh < h; hh ++ ) {
  539. for ( let ww = 0; ww < w; ww ++ ) {
  540. mask[ n + ww + hh * dimsU ] = 0;
  541. }
  542. }
  543. uu += w;
  544. n += w;
  545. } else {
  546. uu ++;
  547. n ++;
  548. }
  549. }
  550. }
  551. }
  552. }
  553. const geometry = new BufferGeometry();
  554. geometry.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) );
  555. geometry.setIndex( indices );
  556. geometry.computeVertexNormals();
  557. const material = new MeshStandardMaterial();
  558. if ( hasColors ) {
  559. geometry.setAttribute( 'color', new Float32BufferAttribute( colors, 3 ) );
  560. material.vertexColors = true;
  561. }
  562. return new Mesh( geometry, material );
  563. }
  564. /**
  565. * Builds a 3D texture from a VOX chunk.
  566. *
  567. * @param {Object} chunk - A VOX chunk loaded via {@link VOXLoader}.
  568. * @return {Data3DTexture} The generated 3D texture.
  569. */
  570. function buildData3DTexture( chunk ) {
  571. const data = chunk.data;
  572. const size = chunk.size;
  573. const offsety = size.x;
  574. const offsetz = size.x * size.y;
  575. const array = new Uint8Array( size.x * size.y * size.z );
  576. for ( let j = 0; j < data.length; j += 4 ) {
  577. const x = data[ j + 0 ];
  578. const y = data[ j + 1 ];
  579. const z = data[ j + 2 ];
  580. const index = x + ( y * offsety ) + ( z * offsetz );
  581. array[ index ] = 255;
  582. }
  583. const texture = new Data3DTexture( array, size.x, size.y, size.z );
  584. texture.format = RedFormat;
  585. texture.minFilter = NearestFilter;
  586. texture.magFilter = LinearFilter;
  587. texture.unpackAlignment = 1;
  588. texture.needsUpdate = true;
  589. return texture;
  590. }
  591. // @deprecated, r182
  592. class VOXMesh extends Mesh {
  593. constructor( chunk ) {
  594. console.warn( 'VOXMesh has been deprecated. Use buildMesh() instead.' );
  595. const mesh = buildMesh( chunk );
  596. super( mesh.geometry, mesh.material );
  597. }
  598. }
  599. class VOXData3DTexture extends Data3DTexture {
  600. constructor( chunk ) {
  601. console.warn( 'VOXData3DTexture has been deprecated. Use buildData3DTexture() instead.' );
  602. const texture = buildData3DTexture( chunk );
  603. super( texture.image.data, texture.image.width, texture.image.height, texture.image.depth );
  604. this.format = texture.format;
  605. this.minFilter = texture.minFilter;
  606. this.magFilter = texture.magFilter;
  607. this.unpackAlignment = texture.unpackAlignment;
  608. this.needsUpdate = true;
  609. }
  610. }
  611. export { VOXLoader, buildMesh, buildData3DTexture, VOXMesh, VOXData3DTexture };
粤ICP备19079148号