Selzier

Current ADTExporter.js

Jun 17th, 2020
157
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. /*!
  2.     wow.export (https://github.com/Kruithne/wow.export)
  3.     Authors: Kruithne <kruithne@gmail.com>
  4.     License: MIT
  5.  */
  6. const util = require('util');
  7. const core = require('../../core');
  8. const path = require('path');
  9. const fsp = require('fs').promises;
  10. const constants = require('../../constants');
  11. const generics = require('../../generics');
  12. const listfile = require('../../casc/listfile');
  13. const log = require('../../log');
  14. const TGA = require('../../2D/tga');
  15. const fs = require('fs');
  16.  
  17. const BufferWrapper = require('../../buffer');
  18. const BLPFile = require('../../casc/blp');
  19.  
  20. const WDTLoader = require('../loaders/WDTLoader');
  21. const ADTLoader = require('../loaders/ADTLoader');
  22.  
  23. const OBJWriter = require('../writers/OBJWriter');
  24. const MTLWriter = require('../writers/MTLWriter');
  25.  
  26. const WDCReader = require('../../db/WDCReader');
  27. const DB_GroundEffectTexture = require('../../db/schema/GroundEffectTexture');
  28. const DB_GroundEffectDoodad = require('../../db/schema/GroundEffectDoodad');
  29.  
  30. const ExportHelper = require('../../casc/export-helper');
  31. const M2Exporter = require('../../3D/exporters/M2Exporter');
  32. const WMOExporter = require('../../3D/exporters/WMOExporter');
  33. const CSVWriter = require('../../3D/writers/CSVWriter');
  34.  
  35. const MAP_SIZE = constants.GAME.MAP_SIZE;
  36. const TILE_SIZE = constants.GAME.TILE_SIZE;
  37. const CHUNK_SIZE = TILE_SIZE / 16;
  38. const UNIT_SIZE = CHUNK_SIZE / 8;
  39. const UNIT_SIZE_HALF = UNIT_SIZE / 2;
  40.  
  41. const wdtCache = new Map();
  42.  
  43. const FRAG_SHADER_SRC = path.join(constants.SHADER_PATH, 'adt.fragment.shader');
  44. const VERT_SHADER_SRC = path.join(constants.SHADER_PATH, 'adt.vertex.shader');
  45.  
  46. let isFoliageAvailable = false;
  47. let hasLoadedFoliage = false;
  48. let dbTextures;
  49. let dbDoodads;
  50.  
  51. let glShaderProg;
  52. let glCanvas;
  53. let gl;
  54.  
  55. /**
  56.  * Load a texture from CASC and bind it to the GL context.
  57.  * @param {number} fileDataID
  58.  */
  59. const loadTexture = async (fileDataID) => {
  60.     const texture = gl.createTexture();
  61.     const blp = new BLPFile(await core.view.casc.getFile(fileDataID));
  62.  
  63.     gl.bindTexture(gl.TEXTURE_2D, texture);
  64.  
  65.     // For unknown reasons, we have to store blpData as a variable. Inlining it into the
  66.     // parameter list causes issues, despite it being synchronous.
  67.     const blpData = blp.toUInt8Array(0);
  68.     gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, blp.scaledWidth, blp.scaledHeight, 0, gl.RGBA, gl.UNSIGNED_BYTE, blpData);
  69.     gl.generateMipmap(gl.TEXTURE_2D);
  70.  
  71.     return texture;
  72. };
  73.  
  74. /**
  75.  * Load and cache GroundEffectDoodad and GroundEffectTexture data tables.
  76.  */
  77. const loadFoliageTables = async () => {
  78.     if (!hasLoadedFoliage) {
  79.         try {
  80.             dbDoodads = new WDCReader('DBFilesClient/GroundEffectDoodad.db2', DB_GroundEffectDoodad);
  81.             dbTextures = new WDCReader('DBFilesClient/GroundEffectTexture.db2', DB_GroundEffectTexture);
  82.  
  83.             await dbDoodads.parse();
  84.             await dbTextures.parse();
  85.  
  86.             hasLoadedFoliage = true;
  87.             isFoliageAvailable = true;
  88.         } catch (e) {
  89.             isFoliageAvailable = false;
  90.             log.write('Unable to load foliage tables, foliage exporting will be unavailable for all tiles.');
  91.         }
  92.  
  93.         hasLoadedFoliage = true;
  94.     }
  95. };
  96.  
  97. /**
  98.  * Bind an alpha layer to the GL context.
  99.  * @param {Array} layer
  100.  */
  101. const bindAlphaLayer = (layer) => {
  102.     const texture = gl.createTexture();
  103.     gl.bindTexture(gl.TEXTURE_2D, texture);
  104.  
  105.     const data = new Uint8Array(layer.length * 4);
  106.     for (let i = 0, j = 0, n = layer.length; i < n; i++, j += 4)
  107.         data[j + 0] = data[j + 1] = data[j + 2] = data[j + 3] = layer[i];
  108.  
  109.     gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 64, 64, 0, gl.RGBA, gl.UNSIGNED_BYTE, data);
  110.     gl.generateMipmap(gl.TEXTURE_2D);
  111.  
  112.     return texture;
  113. };
  114.  
  115. /**
  116.  * Unbind all textures from the GL context.
  117.  */
  118. const unbindAllTextures = () => {
  119.     // Unbind textures.
  120.     for (let i = 0, n = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS); i < n; i++) {
  121.         gl.activeTexture(gl.TEXTURE0 + i);
  122.         gl.bindTexture(gl.TEXTURE_2D, null);
  123.     }
  124. };
  125.  
  126. /**
  127.  * Clear the canvas, resetting it to black.
  128.  */
  129. const clearCanvas = () => {
  130.     gl.viewport(0, 0, glCanvas.width, glCanvas.height);
  131.     gl.clearColor(0, 0, 0, 1);
  132.     gl.clear(gl.COLOR_BUFFER_BIT);
  133. };
  134.  
  135. /**
  136.  * Save the current canvas state to a file.
  137.  * @param {string} out
  138.  */
  139. const saveCanvas = async (out) => {
  140.     // This is a quick and easy fix to rotate tiles to their correct orientation.
  141.     const rotate = document.createElement('canvas');
  142.     rotate.width = glCanvas.width;
  143.     rotate.height = glCanvas.height;
  144.  
  145.     const ctx = rotate.getContext('2d');
  146.     ctx.translate(rotate.width / 2, rotate.height / 2);
  147.     ctx.rotate(Math.PI / 180 * 180);
  148.     ctx.drawImage(glCanvas, -(rotate.width / 2), -(rotate.height / 2));
  149.  
  150.     const buf = await BufferWrapper.fromCanvas(rotate, 'image/png');
  151.     await buf.writeToFile(out);
  152. };
  153.  
  154. /**
  155.  * Compile the vertex and fragment shaders used for baking.
  156.  * Will be attached to the current GL context.
  157.  */
  158. const compileShaders = async () => {
  159.     glShaderProg = gl.createProgram();
  160.  
  161.     // Compile fragment shader.
  162.     const fragShader = gl.createShader(gl.FRAGMENT_SHADER);
  163.     gl.shaderSource(fragShader, await fsp.readFile(FRAG_SHADER_SRC, 'utf8'));
  164.     gl.compileShader(fragShader);
  165.  
  166.     if (!gl.getShaderParameter(fragShader, gl.COMPILE_STATUS)) {
  167.         log.write('Fragment shader failed to compile: %s', gl.getShaderInfoLog(fragShader));
  168.         throw new Error('Failed to compile fragment shader');
  169.     }
  170.  
  171.     // Compile vertex shader.
  172.     const vertShader = gl.createShader(gl.VERTEX_SHADER);
  173.     gl.shaderSource(vertShader, await fsp.readFile(VERT_SHADER_SRC, 'utf8'));
  174.     gl.compileShader(vertShader);
  175.  
  176.     if (!gl.getShaderParameter(vertShader, gl.COMPILE_STATUS)) {
  177.         log.write('Vertex shader failed to compile: %s', gl.getShaderInfoLog(vertShader));
  178.         throw new Error('Failed to compile vertex shader');
  179.     }
  180.  
  181.     // Attach shaders.
  182.     gl.attachShader(glShaderProg, fragShader);
  183.     gl.attachShader(glShaderProg, vertShader);
  184.  
  185.     // Link program.
  186.     gl.linkProgram(glShaderProg);  
  187.     if (!gl.getProgramParameter(glShaderProg, gl.LINK_STATUS)) {
  188.         log.write('Unable to link shader program: %s', gl.getProgramInfoLog(glShaderProg));
  189.         throw new Error('Failed to link shader program');
  190.     }
  191.  
  192.     gl.useProgram(glShaderProg);
  193. };
  194.  
  195. class ADTExporter {
  196.     /**
  197.      * Construct a new ADTLoader instance.
  198.      * @param {number} mapID
  199.      * @param {string} mapDir
  200.      * @param {number} tileIndex
  201.      */
  202.     constructor(mapID, mapDir, tileIndex) {
  203.         this.mapID = mapID;
  204.         this.mapDir = mapDir;
  205.         this.tileX = tileIndex % MAP_SIZE;
  206.         this.tileY = Math.floor(tileIndex / MAP_SIZE);
  207.         this.tileID = this.tileY + '_' + this.tileX;
  208.         this.tileIndex = tileIndex;
  209.     }
  210.  
  211.     /**
  212.      * Export the ADT tile.
  213.      * @param {string} dir Directory to export the tile into.
  214.      * @param {number} textureRes
  215.      */
  216.     async export(dir, quality) {
  217.         const casc = core.view.casc;
  218.         const config = core.view.config;
  219.  
  220.         const prefix = util.format('world/maps/%s/%s', this.mapDir, this.mapDir);
  221.  
  222.         // Load the WDT. We cache this to speed up exporting large amounts of tiles
  223.         // from the same map. Make sure ADTLoader.clearCache() is called after exporting.
  224.         let wdt = wdtCache.get(this.mapDir);
  225.         if (!wdt) {
  226.             wdt = new WDTLoader(await casc.getFileByName(prefix + '.wdt'));
  227.             await wdt.load();
  228.             wdtCache.set(this.mapDir, wdt);
  229.         }
  230.  
  231.         console.log(wdt);
  232.         const tilePrefix = prefix + '_' + this.tileID;
  233.  
  234.         const maid = wdt.entries[this.tileIndex];
  235.         const rootFileDataID = maid.rootADT > 0 ? maid.rootADT : listfile.getByFilename(tilePrefix + '.adt');
  236.         const tex0FileDataID = maid.tex0ADT > 0 ? maid.tex0ADT : listfile.getByFilename(tilePrefix + '_obj0.adt');
  237.         const obj0FileDataID = maid.obj0ADT > 0 ? maid.obj0ADT : listfile.getByFilename(tilePrefix + '_tex0.adt');
  238.  
  239.         // Ensure we actually have the fileDataIDs for the files we need.
  240.         if (rootFileDataID === 0 || tex0FileDataID === 0 || obj0FileDataID === 0)
  241.             throw new Error('Missing fileDataID for ADT files: ' + [rootFileDataID, tex0FileDataID, obj0FileDataID].join(', '));
  242.  
  243.         const rootAdt = new ADTLoader(await casc.getFile(rootFileDataID));
  244.         rootAdt.loadRoot();
  245.  
  246.         const texAdt = new ADTLoader(await casc.getFile(tex0FileDataID));
  247.         texAdt.loadTex(wdt);
  248.  
  249.         const objAdt = new ADTLoader(await casc.getFile(obj0FileDataID));
  250.         objAdt.loadObj();
  251.  
  252.         const vertices = new Array(16 * 16 * 145 * 3);
  253.         const normals = new Array(16 * 16 * 145 * 3);
  254.         const uvs = new Array(16 * 16 * 145 * 2);
  255.         const uvsBake = new Array(16 * 16 * 145 * 2);
  256.         const vertexColors = new Array(16 * 16 * 145 * 4);
  257.  
  258.         const chunkMeshes = new Array(256);
  259.  
  260.         const obj = new OBJWriter(path.join(dir, 'adt_' + this.tileID + '.obj'));
  261.         const mtl = new MTLWriter(path.join(dir, 'adt_' + this.tileID + '.mtl'));
  262.  
  263.         const firstChunk = rootAdt.chunks[0];
  264.         const firstChunkX = firstChunk.position[0];
  265.         const firstChunkY = firstChunk.position[1];
  266.  
  267.         const splitTextures = quality >= 8192;
  268.    
  269.         let ofs = 0;
  270.         let chunkID = 0;
  271.         for (let x = 0, midX = 0; x < 16; x++) {
  272.             for (let y = 0; y < 16; y++) {
  273.                 const indices = [];
  274.  
  275.                 const chunkIndex = (x * 16) + y;
  276.                 const chunk = rootAdt.chunks[chunkIndex];
  277.  
  278.                 const chunkX = chunk.position[0];
  279.                 const chunkY = chunk.position[1];
  280.                 const chunkZ = chunk.position[2];
  281.  
  282.                 for (let row = 0, idx = 0; row < 17; row++) {
  283.                     const isShort = !!(row % 2);
  284.                     const colCount = isShort ? 8 : 9;
  285.  
  286.                     for (let col = 0; col < colCount; col++) {
  287.                         let vx = chunkY - (col * UNIT_SIZE);
  288.                         let vy = chunk.vertices[idx] + chunkZ;
  289.                         let vz = chunkX - (row * UNIT_SIZE_HALF);
  290.  
  291.                         if (isShort)
  292.                             vx -= UNIT_SIZE_HALF;
  293.  
  294.                         const vIndex = midX * 3;
  295.                         vertices[vIndex + 0] = vx;
  296.                         vertices[vIndex + 1] = vy;
  297.                         vertices[vIndex + 2] = vz;
  298.  
  299.                         const normal = chunk.normals[idx];
  300.                         normals[vIndex + 0] = normal[0] / 127;
  301.                         normals[vIndex + 1] = normal[1] / 127;
  302.                         normals[vIndex + 2] = normal[2] / 127;
  303.  
  304.                         const cIndex = midX * 4;
  305.                         if (chunk.vertexShading) {
  306.                             // Store vertex shading in BGRA format.
  307.                             const color = chunk.vertexShading[idx];
  308.                             vertexColors[cIndex + 0] = color.b / 255;
  309.                             vertexColors[cIndex + 1] = color.g / 255;
  310.                             vertexColors[cIndex + 2] = color.r / 255;
  311.                             vertexColors[cIndex + 3] = color.a / 255;
  312.                         } else {
  313.                             // No vertex shading, default to this.
  314.                             vertexColors[cIndex + 0] = 0.5;
  315.                             vertexColors[cIndex + 1] = 0.5;
  316.                             vertexColors[cIndex + 2] = 0.5;
  317.                             vertexColors[cIndex + 3] = 1;
  318.                         }
  319.  
  320.                         const uvIdx = isShort ? col + 0.5 : col;
  321.                         const uvIndex = midX * 2;
  322.  
  323.                         uvsBake[uvIndex + 0] = -(vx - firstChunkX) / TILE_SIZE;
  324.                         uvsBake[uvIndex + 1] = (vz - firstChunkY) / TILE_SIZE;
  325.  
  326.                         if (quality === 0) {
  327.                             uvs[uvIndex + 0] = uvIdx / 8;
  328.                             uvs[uvIndex + 1] = (row * 0.5) / 8;
  329.                         } else if (splitTextures || quality === -1) {
  330.                             uvs[uvIndex + 0] = uvIdx / 8;
  331.                             uvs[uvIndex + 1] = 1 - (row / 16);
  332.                         } else {
  333.                             uvs[uvIndex + 0] = uvsBake[uvIndex + 0];
  334.                             uvs[uvIndex + 1] = uvsBake[uvIndex + 1];
  335.                         }
  336.  
  337.                         idx++;
  338.                         midX++;
  339.                     }
  340.                 }
  341.  
  342.                 const holesHighRes = chunk.holesHighRes;
  343.                 for (let j = 9, xx = 0, yy = 0; j < 145; j++, xx++) {
  344.                     if (xx >= 8) {
  345.                         xx = 0;
  346.                         yy++;
  347.                     }
  348.  
  349.                     let isHole = true;
  350.                     if (!(chunk.flags & 0x10000)) {
  351.                         const current = Math.trunc(Math.pow(2, Math.floor(xx / 2) + Math.floor(yy / 2) * 4));
  352.  
  353.                         if (!(chunk.holesLowRes & current))
  354.                             isHole = false;
  355.                     } else {
  356.                         if (!((holesHighRes[yy] >> xx) & 1))
  357.                             isHole = false;
  358.                     }
  359.  
  360.                     if (!isHole) {
  361.                         const indOfs = ofs + j;
  362.                         indices.push(indOfs, indOfs - 9, indOfs + 8);
  363.                         indices.push(indOfs, indOfs - 8, indOfs - 9);
  364.                         indices.push(indOfs, indOfs + 9, indOfs - 8);
  365.                         indices.push(indOfs, indOfs + 8, indOfs + 9);
  366.                     }
  367.  
  368.                     if (!((j + 1) % (9 + 8)))
  369.                         j += 9;
  370.                 }
  371.            
  372.                 ofs = midX;
  373.  
  374.                 if (splitTextures || quality === -1) {
  375.                     const objName = this.tileID + '_' + chunkID;
  376.                     const matName = 'tex_' + objName;
  377.                     mtl.addMaterial(matName, matName + '.png');
  378.                     obj.addMesh(objName, indices, matName);
  379.                 } else {
  380.                     obj.addMesh(chunkID, indices, 'tex_' + this.tileID);
  381.                 }
  382.                 chunkMeshes[chunkIndex] = indices;
  383.  
  384.                 chunkID++;
  385.             }
  386.         }
  387.  
  388.         if (!splitTextures && quality !== -1)
  389.             mtl.addMaterial('tex_' + this.tileID, 'tex_' + this.tileID + '.png');
  390.  
  391.         obj.setVertArray(vertices);
  392.         obj.setNormalArray(normals);
  393.         obj.setUVArray(uvs);
  394.  
  395.         if (!mtl.isEmpty)
  396.             obj.setMaterialLibrary(path.basename(mtl.out));
  397.        
  398.         await obj.write(config.overwriteFiles);
  399.         await mtl.write(config.overwriteFiles);
  400.  
  401.         if (quality !== 0) {
  402.             if (quality === -2){
  403.                 // Export splat maps.
  404.                 const materialIDs = texAdt.diffuseTextureFileDataIDs;
  405.                 const texParams = texAdt.texParams;
  406.                 const prefix = this.tileID;
  407.  
  408.                 // Export the raw diffuse textures to disk.
  409.                 const materials = new Array(materialIDs.length);
  410.                 var materialCount = materials.length; // TEMP REMOVE ME
  411.                 for (let i = 0, n = materials.length; i < n; i++) {
  412.                     const diffuseFileDataID = materialIDs[i];
  413.                     const blp = new BLPFile(await core.view.casc.getFile(diffuseFileDataID));
  414.                     await blp.saveToFile(path.join(dir, diffuseFileDataID + '.png'), 'image/png', false);
  415.                     const mat = materials[i] = { scale: 1, id: diffuseFileDataID };
  416.                     if (texParams && texParams[i]) {
  417.                         const params = texParams[i];
  418.                         mat.scale = Math.pow(2, (params.flags & 0xF0) >> 4);
  419.                     }
  420.                 }
  421.  
  422.                 // New JSON file to save material data
  423.                 var materialJSON = '{ "chunkData" : [';            
  424.  
  425.                 // 1024x1024 image with 4 bytes per pixel
  426.                 var pixelData = new Uint8ClampedArray(1024 * 1024 * 4);
  427.  
  428.                 // Writing a 1024x1024 image in 64x64 chunks
  429.                 var bytesPerPixel     = 4;      // Each pixel has a R,G,B,A byte
  430.                 var bytesPerColumn    = 262144; // A 'column' is 1024 pixels vertical (chunk)
  431.                 var bytesPerRow       = 4096;   // A 'row' is 1024 pixels horizontal (chunk)
  432.                 var bytesPerSubColumn = 16384;  // A 'subcolumn' is 64 pixels vertical (subchunk)
  433.                 var bytesPerSubRow    = 256;    // a 'subrow' is 64 pixels horizontal (subchunk)
  434.                
  435.                 let chunkID = 0;
  436.                 const lines = [];
  437.                 var tga = new TGA(fs.readFileSync('./src/images/TGATemplate.tga'));
  438.                 // Loop Y first so we go left to right, top to bottom. Loop 16x16 subchunks to get the full chunk
  439.                 for (let x = 0; x < 16; x++) { 
  440.                     for (let y = 0; y < 16; y++) {
  441.                            
  442.                         const chunkIndex = (y * 16) + x;
  443.                         const texChunk = texAdt.texChunks[chunkIndex];
  444.                         const alphaLayers = texChunk.alphaLayers || [];
  445.                         const textureLayers = texChunk.layers;                     
  446.  
  447.                         for (let i = 0, n = texChunk.layers.length; i < n; i++) {
  448.                             const mat = materials[texChunk.layers[i].textureId];
  449.                             lines.push([chunkIndex, i, mat.id, mat.scale].join(','));
  450.                             materialJSON += '{ "chunkIndex":"' + chunkIndex + '", "channel":"' + i + '", "id":"' + mat.id + '", "scale":"' + mat.scale + '" },';                           
  451.                         }
  452.  
  453.                         // If there is no texture data just skip it
  454.                         if (textureLayers.length > 0) {
  455.                             // If there is texture data, we need a base layer of red to flood the subchunk                         
  456.                             for (let j = y * bytesPerColumn; j < (y * bytesPerColumn) + bytesPerColumn; j += bytesPerRow) { // 1024 pixels wide, 64 pixels high = 65536 * 4 bytes = 262144 (looping y axis)
  457.                                 // Now we need to loop the x axis, 64 pixels long                              
  458.                                 for (let i = x * bytesPerSubRow; i < (x * bytesPerSubRow) + bytesPerSubRow; i += bytesPerPixel) { // 64 pixels, 4 bytes each = 256
  459.                                     var yloop = ((j / bytesPerRow) - (y * bytesPerColumn) / bytesPerRow);
  460.                                     var xloop = ((i / 4) - ((x * bytesPerSubRow) / 4));
  461.                                     var alphaIndex = (yloop * 64) + xloop;
  462.                                     pixelData[j + i + 0] = 255; // Red
  463.                                     if (textureLayers.length > 1) { // Green
  464.                                         //pixelData[j + i + 3] -= alphaLayers[1][alphaIndex];
  465.                                         //pixelData[j + i + 2] -= alphaLayers[1][alphaIndex];
  466.                                         pixelData[j + i + 1] = alphaLayers[1][alphaIndex];
  467.                                         pixelData[j + i + 0] -= alphaLayers[1][alphaIndex];                                    
  468.                                     }
  469.                                     if (textureLayers.length > 2) { // Blue
  470.                                         //pixelData[j + i + 3] -= alphaLayers[2][alphaIndex];
  471.                                         pixelData[j + i + 2] = alphaLayers[2][alphaIndex];
  472.                                         pixelData[j + i + 1] -= alphaLayers[2][alphaIndex];
  473.                                         //pixelData[j + i + 0] -= alphaLayers[2][alphaIndex];
  474.                                     }
  475.                                     if (textureLayers.length > 3) { // Alpha
  476.                                         pixelData[j + i + 3] = alphaLayers[3][alphaIndex];
  477.                                         pixelData[j + i + 2] -= alphaLayers[3][alphaIndex];
  478.                                         //pixelData[j + i + 1] -= alphaLayers[3][alphaIndex];
  479.                                         //pixelData[j + i + 0] -= alphaLayers[3][alphaIndex];
  480.                                     }
  481.                                 }
  482.                             }                                                      
  483.                         }                      
  484.                     }
  485.                 }
  486.                
  487.                 materialJSON = materialJSON.substring(0, materialJSON.length - 1); // remove tailing comma
  488.                 materialJSON += ']}'; // Close the JSON data
  489.                 //log.write(materialJSON);
  490.                 var matJSON = JSON.parse(materialJSON);
  491.                 // Sort JSON data by chunkIndex
  492.                 matJSON.chunkData.sort(function(a, b) {
  493.                     var keyA = parseInt(a.chunkIndex),
  494.                         keyB = parseInt(b.chunkIndex);
  495.                     // Compare the 2 dates
  496.                     if (keyA < keyB) return -1;
  497.                     if (keyA > keyB) return 1;
  498.                     return 0;
  499.                 });
  500.                 log.write("Databegin: ");
  501.                 var matIds = [];
  502.                 for (var i in matJSON.chunkData) {
  503.                     if (matJSON.chunkData[i].channel == 0){
  504.                         //log.write(JSON.stringify(matJSON.chunkData[i].id));
  505.                         matIds.push(matJSON.chunkData[i].id);
  506.                     }
  507.                 }
  508.                
  509.                 // Checking all 256 subchunks to make sure the Base Material is the same
  510.                 var uniqueBaseMatIds = Array.from(new Set(matIds));
  511.                 if (uniqueBaseMatIds.length > 1){
  512.                     log.write("WARNING! Found more than 1 Base Materials for " + prefix);
  513.                 }
  514.                
  515.                 /*
  516.                 for (let i = 0; i < matJSON.chunkData.length; i++){
  517.                     var object = matJSON.chunkData[i];
  518.                     for (var key in object){
  519.                         var name = key;
  520.                         var value = object[key];
  521.                         log.write(name + ": " + value);
  522.                     }
  523.                 }*/
  524.  
  525.                 //var blah = matJSON.chunkData.sort((a,b) => matJSON.chunkData.filter(v => v===a.chunkIndex).length - matJSON.chunkData.filter(v => v===b.chunkIndex).length).pop();
  526.                 //log.write("Most Common: " + JSON.stringify(blah));
  527.                
  528.                 const jsonPath = path.join(dir, 'matData_' + prefix + '.json');
  529.                 try { fs.writeFileSync(jsonPath, JSON.stringify(matJSON.chunkData));
  530.                     } catch (err) { console.error(err); }
  531.                
  532.                 const tgaPath = path.join(dir, 'tex_' + prefix + '.tga');
  533.                 tga.pixels = pixelData;
  534.                 var buftga = TGA.createTgaBuffer(tga.width, tga.height, tga.pixels);
  535.                 fs.writeFileSync(tgaPath, buftga);
  536.  
  537.                 const metaOut = path.join(dir, 'tex_' + prefix + '.csv');
  538.                 await fsp.writeFile(metaOut, lines.join('\n'), 'utf8');
  539.  
  540.             } else if (quality === -1) {
  541.                 // Export alpha maps.
  542.  
  543.                 // Create a 2D canvas for drawing the alpha maps.
  544.                 const canvas = document.createElement('canvas');
  545.                 const ctx = canvas.getContext('2d');
  546.  
  547.                 const materialIDs = texAdt.diffuseTextureFileDataIDs;
  548.                 const texParams = texAdt.texParams;
  549.  
  550.                 // Export the raw diffuse textures to disk.
  551.                 const materials = new Array(materialIDs.length);
  552.                 for (let i = 0, n = materials.length; i < n; i++) {
  553.                     const diffuseFileDataID = materialIDs[i];
  554.                     const blp = new BLPFile(await core.view.casc.getFile(diffuseFileDataID));
  555.                     await blp.saveToFile(path.join(dir, diffuseFileDataID + '.png'), 'image/png', false);
  556.  
  557.                     const mat = materials[i] = { scale: 1, id: diffuseFileDataID };
  558.  
  559.                     if (texParams && texParams[i]) {
  560.                         const params = texParams[i];
  561.                         mat.scale = Math.pow(2, (params.flags & 0xF0) >> 4);
  562.                     }
  563.                 }
  564.  
  565.                 // Alpha maps are 64x64, we're not up-scaling here.
  566.                 canvas.width = 64;
  567.                 canvas.height = 64;
  568.  
  569.                 let chunkID = 0;
  570.                 for (let x = 0; x < 16; x++) {
  571.                     for (let y = 0; y < 16; y++) {
  572.                         const chunkIndex = (x * 16) + y;
  573.                         const texChunk = texAdt.texChunks[chunkIndex];
  574.  
  575.                         const alphaLayers = texChunk.alphaLayers || [];
  576.                         const imageData = ctx.createImageData(64, 64);
  577.  
  578.                         // Write each layer as RGB.
  579.                         for (let i = 1; i < alphaLayers.length; i++) {
  580.                             const layer = alphaLayers[i];
  581.  
  582.                             for (let j = 0; j < layer.length; j++)
  583.                                 imageData.data[(j * 4) + (i - 1)] = layer[j];
  584.                         }
  585.  
  586.                         // Set all the alpha values to max.
  587.                         for (let i = 0; i < 64 * 64; i++)
  588.                             imageData.data[(i * 4) + 3] = 255;
  589.  
  590.                         ctx.putImageData(imageData, 0, 0);
  591.  
  592.                         const prefix = this.tileID + '_' + (chunkID++);
  593.                         const tilePath = path.join(dir, 'tex_' + prefix + '.png');
  594.  
  595.                         const buf = await BufferWrapper.fromCanvas(canvas, 'image/png');
  596.                         await buf.writeToFile(tilePath);
  597.  
  598.                         const texLayers = texChunk.layers;
  599.                         const lines = [];
  600.                         for (let i = 0, n = texLayers.length; i < n; i++) {
  601.                             const mat = materials[texLayers[i].textureId];
  602.                             lines.push([i, mat.id, mat.scale].join(','));
  603.                         }
  604.  
  605.                         const metaOut = path.join(dir, 'tex_' + prefix + '.csv');
  606.                         await fsp.writeFile(metaOut, lines.join('\n'), 'utf8');
  607.                     }
  608.                 }
  609.             } else if (quality <= 512) {
  610.                 // Use minimaps for cheap textures.
  611.                 const tilePath = util.format('world/minimaps/%s/map%d_%d.blp', this.mapDir, this.tileY, this.tileX);
  612.                 const tileOutPath = path.join(dir, 'tex_' + this.tileID + '.png');
  613.  
  614.                 if (config.overwriteFiles || !await generics.fileExists(tileOutPath)) {
  615.                     const data = await casc.getFileByName(tilePath, false, true);
  616.                     const blp = new BLPFile(data);
  617.  
  618.                     // Draw the BLP onto a raw-sized canvas.
  619.                     const canvas = blp.toCanvas(false);
  620.  
  621.                     // Scale the image down by copying the raw canvas onto a
  622.                     // scaled canvas, and then returning the scaled image data.
  623.                     const scale = quality / blp.scaledWidth;
  624.                     const scaled = document.createElement('canvas');
  625.                     scaled.width = quality;
  626.                     scaled.height = quality;
  627.  
  628.                     const ctx = scaled.getContext('2d');
  629.                     ctx.scale(scale, scale);
  630.                     ctx.drawImage(canvas, 0, 0);
  631.  
  632.                     const buf = await BufferWrapper.fromCanvas(scaled, 'image/png');
  633.                     await buf.writeToFile(tileOutPath);
  634.                 } else {
  635.                     log.write('Skipping ADT bake of %s (file exists, overwrite disabled)', tileOutPath);
  636.                 }
  637.             } else {
  638.                 const tileOutPath = path.join(dir, 'tex_' + this.tileID + '.png');
  639.                 if (splitTextures || config.overwriteFiles || !await generics.fileExists(tileOutPath)) {
  640.                     // Create new GL context and compile shaders.
  641.                     if (!gl) {
  642.                         glCanvas = document.createElement('canvas');
  643.                         gl = glCanvas.getContext('webgl');
  644.  
  645.                         await compileShaders();
  646.                     }
  647.  
  648.                     // Materials
  649.                     const materialIDs = texAdt.diffuseTextureFileDataIDs;
  650.                     const heightIDs = texAdt.heightTextureFileDataIDs;
  651.                     const texParams = texAdt.texParams;
  652.  
  653.                     const materials = new Array(materialIDs.length);
  654.                     for (let i = 0, n = materials.length; i < n; i++) {
  655.                         const diffuseFileDataID = materialIDs[i];
  656.                         const heightFileDataID = heightIDs[i];
  657.  
  658.                         const mat = materials[i] = { scale: 1, heightScale: 0, heightOffset: 1 };
  659.                         mat.diffuseTex = await loadTexture(diffuseFileDataID);
  660.  
  661.                         if (texParams && texParams[i]) {
  662.                             const params = texParams[i];
  663.                             mat.scale = Math.pow(2, (params.flags & 0xF0) >> 4);
  664.  
  665.                             if (params.height !== 0 || params.offset !== 1) {
  666.                                 mat.heightScale = params.height;
  667.                                 mat.heightOffset = params.offset;
  668.                                 mat.heightTex = heightFileDataID ? await loadTexture(heightFileDataID) : mat.diffuseTex;
  669.                             }
  670.                         }
  671.                     }
  672.  
  673.                     const aVertexPosition = gl.getAttribLocation(glShaderProg, 'aVertexPosition');
  674.                     const aTexCoord = gl.getAttribLocation(glShaderProg, 'aTextureCoord');
  675.                     const aVertexColor = gl.getAttribLocation(glShaderProg, 'aVertexColor');
  676.  
  677.                     const uLayers = new Array(4);
  678.                     const uScales = new Array(4);
  679.                     const uHeights = new Array(4);
  680.                     const uBlends = new Array(4);
  681.  
  682.                     for (let i = 0; i < 4; i++) {
  683.                         uLayers[i] = gl.getUniformLocation(glShaderProg, 'pt_layer' + i);
  684.                         uScales[i] = gl.getUniformLocation(glShaderProg, 'layerScale' + i);
  685.                         uHeights[i] = gl.getUniformLocation(glShaderProg, 'pt_height' + i);
  686.  
  687.                         if (i > 0)
  688.                             uBlends[i] = gl.getUniformLocation(glShaderProg, 'pt_blend' + i);
  689.                     }
  690.  
  691.                     const uHeightScale = gl.getUniformLocation(glShaderProg, 'pc_heightScale');
  692.                     const uHeightOffset = gl.getUniformLocation(glShaderProg, 'pc_heightOffset');
  693.                     const uTranslation = gl.getUniformLocation(glShaderProg, 'uTranslation');
  694.                     const uResolution = gl.getUniformLocation(glShaderProg, 'uResolution');
  695.                     const uZoom = gl.getUniformLocation(glShaderProg, 'uZoom');
  696.  
  697.                     if (splitTextures) {
  698.                         glCanvas.width = quality / 16;
  699.                         glCanvas.height = quality / 16;
  700.                     } else {
  701.                         glCanvas.width = quality;
  702.                         glCanvas.height = quality;
  703.                     }
  704.  
  705.                     clearCanvas();
  706.  
  707.                     gl.uniform2f(uResolution, TILE_SIZE, TILE_SIZE);
  708.  
  709.                     const vertexBuffer = gl.createBuffer();
  710.                     gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
  711.                     gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
  712.                     gl.enableVertexAttribArray(aVertexPosition);
  713.                     gl.vertexAttribPointer(aVertexPosition, 3, gl.FLOAT, false, 0, 0);
  714.  
  715.                     const uvBuffer = gl.createBuffer();
  716.                     gl.bindBuffer(gl.ARRAY_BUFFER, uvBuffer);
  717.                     gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(uvsBake), gl.STATIC_DRAW);
  718.                     gl.enableVertexAttribArray(aTexCoord);
  719.                     gl.vertexAttribPointer(aTexCoord, 2, gl.FLOAT, false, 0, 0);
  720.  
  721.                     const vcBuffer = gl.createBuffer();
  722.                     gl.bindBuffer(gl.ARRAY_BUFFER, vcBuffer);
  723.                     gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertexColors), gl.STATIC_DRAW);
  724.                     gl.enableVertexAttribArray(aVertexColor);
  725.                     gl.vertexAttribPointer(aVertexColor, 4, gl.FLOAT, false, 0, 0);
  726.  
  727.                     const firstChunk = rootAdt.chunks[0];
  728.                     const deltaX = firstChunk.position[1] - TILE_SIZE;
  729.                     const deltaY = firstChunk.position[0] - TILE_SIZE;
  730.  
  731.                     if (!splitTextures)
  732.                         gl.uniform2f(uTranslation, -deltaX, -deltaY);
  733.  
  734.                     gl.uniform1f(uZoom, splitTextures ? 0.0625 : 1);
  735.  
  736.                     let chunkID = 0;
  737.                     for (let x = 0; x < 16; x++) {
  738.                         for (let y = 0; y < 16; y++) {
  739.                             if (splitTextures) {
  740.                                 const ofsX = -deltaX - (CHUNK_SIZE * 7.5) + (y * CHUNK_SIZE);
  741.                                 const ofsY = -deltaY - (CHUNK_SIZE * 7.5) + (x * CHUNK_SIZE);
  742.  
  743.                                 gl.uniform2f(uTranslation, ofsX, ofsY);
  744.                             }
  745.  
  746.                             const chunkIndex = (x * 16) + y;
  747.                             const texChunk = texAdt.texChunks[chunkIndex];
  748.                             const indices = chunkMeshes[chunkIndex];
  749.  
  750.                             const alphaLayers = texChunk.alphaLayers || [];
  751.                             const alphaTextures = new Array(alphaLayers.length);
  752.  
  753.                             for (let i = 1; i < alphaLayers.length; i++) {
  754.                                 gl.activeTexture(gl.TEXTURE3 + i);
  755.  
  756.                                 const alphaTex = bindAlphaLayer(alphaLayers[i]);
  757.                                 gl.bindTexture(gl.TEXTURE_2D, alphaTex);
  758.                                
  759.                                 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
  760.                                 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
  761.  
  762.                                 gl.uniform1i(uBlends[i], i + 3);
  763.  
  764.                                 // Store to clean up after render.
  765.                                 alphaTextures[i] = alphaTex;
  766.                             }
  767.  
  768.                             const texLayers = texChunk.layers;
  769.                             const heightScales = new Array(4).fill(1);
  770.                             const heightOffsets = new Array(4).fill(1);
  771.  
  772.                             for (let i = 0, n = texLayers.length; i < n; i++) {
  773.                                 const mat = materials[texLayers[i].textureId];
  774.                                 gl.activeTexture(gl.TEXTURE0 + i);
  775.                                 gl.bindTexture(gl.TEXTURE_2D, mat.diffuseTex);
  776.  
  777.                                 gl.uniform1i(uLayers[i], i);
  778.                                 gl.uniform1f(uScales[i], mat.scale);
  779.  
  780.                                 if (mat.heightTex) {
  781.                                     gl.activeTexture(gl.TEXTURE7 + i);
  782.                                     gl.bindTexture(gl.TEXTURE_2D, mat.heightTex);
  783.  
  784.                                     gl.uniform1i(uHeights[i], 7 + i);
  785.                                     heightScales[i] = mat.heightScale;
  786.                                     heightOffsets[i] = mat.heightOffset;
  787.                                 }
  788.                             }
  789.  
  790.                             gl.uniform4f(uHeightScale, ...heightScales);
  791.                             gl.uniform4f(uHeightOffset, ...heightOffsets);
  792.  
  793.                             const indexBuffer = gl.createBuffer();
  794.                             gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
  795.                             gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW);
  796.                             gl.drawElements(gl.TRIANGLES, indices.length, gl.UNSIGNED_SHORT, 0);
  797.  
  798.                             unbindAllTextures();
  799.                            
  800.                             // Destroy alpha layers rendered for the tile.
  801.                             for (const tex of alphaTextures)
  802.                                 gl.deleteTexture(tex);
  803.  
  804.                             // Save this individual chunk.
  805.                             if (splitTextures) {
  806.                                 const tilePath = path.join(dir, 'tex_' + this.tileID + '_' + (chunkID++) + '.png');
  807.  
  808.                                 if (config.overwriteFiles || !await generics.fileExists(tilePath))
  809.                                     await saveCanvas(tilePath);
  810.                             }
  811.                         }
  812.                     }
  813.  
  814.                     // Save the completed tile.
  815.                     if (!splitTextures)
  816.                         await saveCanvas(path.join(dir, 'tex_' + this.tileID + '.png'));
  817.  
  818.                     // Clear buffer.
  819.                     gl.bindBuffer(gl.ARRAY_BUFFER, null);
  820.  
  821.                     // Delete loaded textures.
  822.                     for (const mat of materials)
  823.                         gl.deleteTexture(mat.texture);
  824.                 }
  825.             }
  826.         }
  827.  
  828.         // Export dooads / WMOs.
  829.         if (config.mapsIncludeWMO || config.mapsIncludeM2) {
  830.             const objectCache = new Set();
  831.  
  832.             const csvPath = path.join(dir, 'adt_' + this.tileID + '_ModelPlacementInformation.csv');
  833.             if (config.overwriteFiles || !await generics.fileExists(csvPath)) {
  834.                 const csv = new CSVWriter(csvPath);
  835.                 csv.addField('ModelFile', 'PositionX', 'PositionY', 'PositionZ', 'RotationX', 'RotationY', 'RotationZ', 'ScaleFactor', 'ModelId', 'Type');
  836.  
  837.                 if (config.mapsIncludeM2) {
  838.                     log.write('Exporting %d doodads for ADT...', objAdt.models.length);
  839.                     for (const model of objAdt.models) {
  840.                         const fileDataID = model.mmidEntry;    
  841.                         let fileName = listfile.getByID(fileDataID);
  842.  
  843.                         try {  
  844.  
  845.                             if (fileName !== undefined) {
  846.                                 // Replace M2 extension with OBJ.
  847.                                 fileName = ExportHelper.replaceExtension(fileName, '.obj');
  848.                             } else {
  849.                                 // Handle unknown file.
  850.                                 fileName = 'unknown/' + fileDataID + '.obj';
  851.                             }
  852.  
  853.                             const modelPath = ExportHelper.getExportPath(fileName);
  854.  
  855.                             // Export the model if we haven't done so for this export session.
  856.                             if (!objectCache.has(fileDataID)) {
  857.                                 const m2 = new M2Exporter(await casc.getFile(fileDataID));
  858.                                 await m2.exportAsOBJ(modelPath);
  859.                                 objectCache.add(fileDataID);
  860.                             }
  861.  
  862.                             csv.addRow({
  863.                                 ModelFile: path.relative(dir, modelPath),
  864.                                 PositionX: model.position[0],
  865.                                 PositionY: model.position[1],
  866.                                 PositionZ: model.position[2],
  867.                                 RotationX: model.rotation[0],
  868.                                 RotationY: model.rotation[1],
  869.                                 RotationZ: model.rotation[2],
  870.                                 ScaleFactor: model.scale / 1024,
  871.                                 ModelId: model.uniqueId,
  872.                                 Type: 'm2'
  873.                             });
  874.                         } catch {
  875.                             log.write('Failed to export %s [%d]', fileName, fileDataID);
  876.                         }
  877.                     }
  878.                 }
  879.  
  880.                 if (config.mapsIncludeWMO) {
  881.                     log.write('Exporting %d WMOs for ADT...', objAdt.worldModels.length);
  882.  
  883.                     const usingNames = !!objAdt.wmoNames;
  884.                     for (const model of objAdt.worldModels) {
  885.                         let fileDataID;
  886.                         let fileName;
  887.  
  888.                         try {
  889.                             if (usingNames) {
  890.                                 fileName = objAdt.wmoNames[objAdt.wmoOffsets[model.mwidEntry]];
  891.                                 fileDataID = listfile.getByFilename(fileName);
  892.                             } else {
  893.                                 fileDataID = model.mwidEntry;
  894.                                 fileName = listfile.getByID(fileDataID);
  895.                             }
  896.  
  897.                             if (fileName !== undefined) {
  898.                                 // Replace WMO extension with OBJ.
  899.                                 fileName = ExportHelper.replaceExtension(fileName, '_set' + model.doodadSet + '.obj');
  900.                             } else {
  901.                                 // Handle unknown WMO files.
  902.                                 fileName = 'unknown/' + fileDataID + '_set' + model.doodadSet + '.obj';
  903.                             }
  904.  
  905.                             const modelPath = ExportHelper.getExportPath(fileName);
  906.                             const cacheID = fileDataID + '-' + model.doodadSet;
  907.  
  908.                             if (!objectCache.has(cacheID)) {
  909.                                 const data = await casc.getFile(fileDataID);
  910.                                 const wmo = new WMOExporter(data, fileDataID);
  911.  
  912.                                 if (config.mapsIncludeWMOSets)
  913.                                     wmo.setDoodadSetMask({ [model.doodadSet]: { checked: true } });
  914.  
  915.                                 await wmo.exportAsOBJ(modelPath);
  916.                                 objectCache.add(cacheID);
  917.                             }
  918.  
  919.                             csv.addRow({
  920.                                 ModelFile: path.relative(dir, modelPath),
  921.                                 PositionX: model.position[0],
  922.                                 PositionY: model.position[1],
  923.                                 PositionZ: model.position[2],
  924.                                 RotationX: model.rotation[0],
  925.                                 RotationY: model.rotation[1],
  926.                                 RotationZ: model.rotation[2],
  927.                                 ScaleFactor: model.scale / 1024,
  928.                                 ModelId: model.uniqueId,
  929.                                 Type: 'wmo'
  930.                             });
  931.                         } catch {
  932.                             log.write('Failed to export %s [%d]', fileName, fileDataID);
  933.                         }
  934.                     }
  935.                 }
  936.  
  937.                 await csv.write();
  938.             } else {
  939.                 log.write('Skipping model placement export %s (file exists, overwrite disabled)', csvPath);
  940.             }
  941.         }
  942.  
  943.         // Prepare foliage data tables if needed.
  944.         if (config.mapsIncludeFoliage && !hasLoadedFoliage)
  945.             await loadFoliageTables();
  946.  
  947.         // Export foliage.
  948.         if (config.mapsIncludeFoliage && isFoliageAvailable) {
  949.             const foliageExportCache = new Set();
  950.             const foliageDir = path.join(dir, 'foliage');
  951.            
  952.             log.write('Exporting foliage to %s', foliageDir);
  953.  
  954.             for (const chunk of texAdt.texChunks) {
  955.                 // Skip chunks that have no layers?
  956.                 if (!chunk.layers)
  957.                     continue;
  958.  
  959.                 for (const layer of chunk.layers) {
  960.                     // Skip layers with no effect.
  961.                     if (!layer.effectID)
  962.                         continue;
  963.  
  964.                     const groundEffectTexture = dbTextures.getRow(layer.effectID);
  965.                     if (!groundEffectTexture || !Array.isArray(groundEffectTexture.DoodadID))
  966.                         continue;
  967.  
  968.                     for (const doodadEntryID of groundEffectTexture.DoodadID) {
  969.                         // Skip empty fields.
  970.                         if (!doodadEntryID)
  971.                             continue;
  972.  
  973.                         const groundEffectDoodad = dbDoodads.getRow(doodadEntryID);
  974.                         if (groundEffectDoodad) {
  975.                             const modelID = groundEffectDoodad.ModelFileID;
  976.                             if (!modelID || foliageExportCache.has(modelID))
  977.                                 continue;
  978.  
  979.                             const modelName = path.basename(listfile.getByID(modelID));
  980.                             const data = await casc.getFile(modelID);
  981.  
  982.                             const exporter = new M2Exporter(data);
  983.                             const modelPath = ExportHelper.replaceExtension(modelName, '.obj');
  984.                             await exporter.exportAsOBJ(path.join(foliageDir, modelPath));
  985.  
  986.                             foliageExportCache.add(modelID);
  987.                         }
  988.                     }
  989.                 }
  990.             }
  991.         }
  992.     }
  993.  
  994.     /**
  995.      * Clear internal tile-loading cache.
  996.      */
  997.     static clearCache() {
  998.         wdtCache.clear();
  999.     }
  1000. }
  1001.  
  1002. module.exports = ADTExporter;
Add Comment
Please, Sign In to add comment