UNPKG

mdx-m3-viewer

Version:

A browser WebGL model viewer. Mainly focused on models of the games Warcraft 3 and Starcraft 2.

335 lines 17.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const path_1 = require("../../../common/path"); const audio_1 = require("../../../common/audio"); const model_1 = require("../../../parsers/mdlx/model"); const isformat_1 = require("../../../parsers/mdlx/isformat"); const mappeddata_1 = require("../../../utils/mappeddata"); const viewer_1 = require("../../viewer"); const model_2 = require("./model"); const texture_1 = require("./texture"); const sd_vert_1 = require("./shaders/sd.vert"); const sd_frag_1 = require("./shaders/sd.frag"); const hd_vert_1 = require("./shaders/hd.vert"); const hd_frag_1 = require("./shaders/hd.frag"); const particles_vert_1 = require("./shaders/particles.vert"); const particles_frag_1 = require("./shaders/particles.frag"); const batch_1 = require("./batch"); const mappedDataCallback = (data) => new mappeddata_1.MappedData(data); const decodedDataCallback = (data) => (0, audio_1.decodeAudioData)(data); exports.default = { load(viewer, pathSolver, reforged = false) { const gl = viewer.gl; const webgl = viewer.webgl; // Bone textures. if (!webgl.ensureExtension('OES_texture_float')) { throw new Error('MDX: No float texture support!'); } // Geometry emitters. if (!webgl.ensureExtension('ANGLE_instanced_arrays')) { throw new Error('MDX: No instanced rendering support!'); } // Shaders. Lots of them. const sdExtendedVert = '#define EXTENDED_BONES\n' + sd_vert_1.default; const sdDiffuse = '#define ONLY_DIFFUSE\n' + sd_frag_1.default; const sdTexcoords = '#define ONLY_TEXCOORDS\n' + sd_frag_1.default; const sdNormals = '#define ONLY_NORMALS\n' + sd_frag_1.default; const hdExtendedVert = '#define EXTENDED_BONES\n' + hd_vert_1.default; const hdSkinVert = '#define SKIN\n' + hd_vert_1.default; const hdDiffuse = '#define ONLY_DIFFUSE\n' + hd_frag_1.default; const hdNormalMap = '#define ONLY_NORMAL_MAP\n' + hd_frag_1.default; const hdOcclusion = '#define ONLY_OCCLUSION\n' + hd_frag_1.default; const hdRoughness = '#define ONLY_ROUGHNESS\n' + hd_frag_1.default; const hdMetallic = '#define ONLY_METALLIC\n' + hd_frag_1.default; const hdTCFactor = '#define ONLY_TC_FACTOR\n' + hd_frag_1.default; const hdEmissive = '#define ONLY_EMISSIVE\n' + hd_frag_1.default; const hdTexCoords = '#define ONLY_TEXCOORDS\n' + hd_frag_1.default; const hdNormals = '#define ONLY_NORMALS\n' + hd_frag_1.default; const hdTangents = '#define ONLY_TANGENTS\n' + hd_frag_1.default; const sdShader = webgl.createShader(sd_vert_1.default, sd_frag_1.default); const sdExtendedShader = webgl.createShader(sdExtendedVert, sd_frag_1.default); const hdShader = webgl.createShader(hd_vert_1.default, hd_frag_1.default); const hdExtendedShader = webgl.createShader(hdExtendedVert, hd_frag_1.default); const hdSkinShader = webgl.createShader(hdSkinVert, hd_frag_1.default); const particlesShader = webgl.createShader(particles_vert_1.default, particles_frag_1.default); const sdDebugShaders = []; const hdDebugShaders = []; let shaders = []; shaders[viewer_1.DebugRenderMode.Diffuse] = webgl.createShader(sd_vert_1.default, sdDiffuse); shaders[viewer_1.DebugRenderMode.TexCoords] = webgl.createShader(sd_vert_1.default, sdTexcoords); shaders[viewer_1.DebugRenderMode.Normals] = webgl.createShader(sd_vert_1.default, sdNormals); sdDebugShaders[batch_1.SkinningType.VertexGroups] = shaders; shaders = []; shaders[viewer_1.DebugRenderMode.Diffuse] = webgl.createShader(sdExtendedVert, sdDiffuse); shaders[viewer_1.DebugRenderMode.TexCoords] = webgl.createShader(sdExtendedVert, sdTexcoords); shaders[viewer_1.DebugRenderMode.Normals] = webgl.createShader(sdExtendedVert, sdNormals); sdDebugShaders[batch_1.SkinningType.ExtendedVertexGroups] = shaders; shaders = []; shaders[viewer_1.DebugRenderMode.Diffuse] = webgl.createShader(hd_vert_1.default, hdDiffuse); shaders[viewer_1.DebugRenderMode.NormalMap] = webgl.createShader(hd_vert_1.default, hdNormalMap); shaders[viewer_1.DebugRenderMode.Occlusion] = webgl.createShader(hd_vert_1.default, hdOcclusion); shaders[viewer_1.DebugRenderMode.Roughness] = webgl.createShader(hd_vert_1.default, hdRoughness); shaders[viewer_1.DebugRenderMode.Metallic] = webgl.createShader(hd_vert_1.default, hdMetallic); shaders[viewer_1.DebugRenderMode.TCFactor] = webgl.createShader(hd_vert_1.default, hdTCFactor); shaders[viewer_1.DebugRenderMode.Emissive] = webgl.createShader(hd_vert_1.default, hdEmissive); shaders[viewer_1.DebugRenderMode.TexCoords] = webgl.createShader(hd_vert_1.default, hdTexCoords); shaders[viewer_1.DebugRenderMode.Normals] = webgl.createShader(hd_vert_1.default, hdNormals); shaders[viewer_1.DebugRenderMode.Tangents] = webgl.createShader('#define ONLY_TANGENTS\n' + hd_vert_1.default, hdTangents); hdDebugShaders[batch_1.SkinningType.VertexGroups] = shaders; shaders = []; shaders[viewer_1.DebugRenderMode.Diffuse] = webgl.createShader(hdExtendedVert, hdDiffuse); shaders[viewer_1.DebugRenderMode.NormalMap] = webgl.createShader(hdExtendedVert, hdNormalMap); shaders[viewer_1.DebugRenderMode.Occlusion] = webgl.createShader(hdExtendedVert, hdOcclusion); shaders[viewer_1.DebugRenderMode.Roughness] = webgl.createShader(hdExtendedVert, hdRoughness); shaders[viewer_1.DebugRenderMode.Metallic] = webgl.createShader(hdExtendedVert, hdMetallic); shaders[viewer_1.DebugRenderMode.TCFactor] = webgl.createShader(hdExtendedVert, hdTCFactor); shaders[viewer_1.DebugRenderMode.Emissive] = webgl.createShader(hdExtendedVert, hdEmissive); shaders[viewer_1.DebugRenderMode.TexCoords] = webgl.createShader(hdExtendedVert, hdTexCoords); shaders[viewer_1.DebugRenderMode.Normals] = webgl.createShader(hdExtendedVert, hdNormals); shaders[viewer_1.DebugRenderMode.Tangents] = webgl.createShader('#define ONLY_TANGENTS\n' + hdExtendedVert, hdTangents); hdDebugShaders[batch_1.SkinningType.ExtendedVertexGroups] = shaders; shaders = []; shaders[viewer_1.DebugRenderMode.Diffuse] = webgl.createShader(hdSkinVert, hdDiffuse); shaders[viewer_1.DebugRenderMode.NormalMap] = webgl.createShader(hdSkinVert, hdNormalMap); shaders[viewer_1.DebugRenderMode.Occlusion] = webgl.createShader(hdSkinVert, hdOcclusion); shaders[viewer_1.DebugRenderMode.Roughness] = webgl.createShader(hdSkinVert, hdRoughness); shaders[viewer_1.DebugRenderMode.Metallic] = webgl.createShader(hdSkinVert, hdMetallic); shaders[viewer_1.DebugRenderMode.TCFactor] = webgl.createShader(hdSkinVert, hdTCFactor); shaders[viewer_1.DebugRenderMode.Emissive] = webgl.createShader(hdSkinVert, hdEmissive); shaders[viewer_1.DebugRenderMode.TexCoords] = webgl.createShader(hdSkinVert, hdTexCoords); shaders[viewer_1.DebugRenderMode.Normals] = webgl.createShader(hdSkinVert, hdNormals); shaders[viewer_1.DebugRenderMode.Tangents] = webgl.createShader('#define ONLY_TANGENTS\n' + hdSkinVert, hdTangents); hdDebugShaders[batch_1.SkinningType.Skin] = shaders; const rectBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, rectBuffer); gl.bufferData(gl.ARRAY_BUFFER, new Uint8Array([0, 1, 2, 0, 2, 3]), gl.STATIC_DRAW); const handlerData = { pathSolver, reforged, // Shaders. sdShader, sdExtendedShader, hdShader, hdExtendedShader, hdSkinShader, particlesShader, sdDebugShaders, hdDebugShaders, // Geometry emitters buffer. rectBuffer, // Team color/glow textures - loaded when the first model that uses team textures is loaded. teamColors: [], teamGlows: [], eventObjectTables: {}, // lutTexture: null, // envDiffuseTexture: null, // envSpecularTexture: null, }; viewer.sharedCache.set('mdx', handlerData); }, isValidSource(object) { if (object instanceof model_1.default) { return true; } return (0, isformat_1.isMdx)(object) || (0, isformat_1.isMdl)(object); }, resource: model_2.default, // async loadEnv(viewer: ModelViewer) { // const mdxHandler = <MdxHandlerObject>viewer.sharedCache.get('mdx'); // if (!mdxHandler.lutTexture) { // mdxHandler.lutTexture = new MdxTexture(0, WrapMode.WrapBoth); // mdxHandler.envDiffuseTexture = new MdxTexture(0, WrapMode.WrapBoth); // mdxHandler.envSpecularTexture = new MdxTexture(0, WrapMode.WrapBoth); // const [lutTexture, diffuseTexture, specularTexture] = await Promise.all([ // viewer.load('env/lut.png'), // viewer.load('env/diffuse-sRGB.png'), // viewer.load('env/specular-sRGB.png'), // ]); // mdxHandler.lutTexture.texture = <Texture>lutTexture; // mdxHandler.envDiffuseTexture.texture = <Texture>diffuseTexture; // mdxHandler.envSpecularTexture.texture = <Texture>specularTexture; // } // }, loadTeamTextures(viewer) { const { pathSolver, reforged, teamColors, teamGlows } = viewer.sharedCache.get('mdx'); if (teamColors.length === 0) { const teams = reforged ? 28 : 16; const ext = reforged ? 'dds' : 'blp'; const params = reforged ? { reforged: true } : undefined; for (let i = 0; i < teams; i++) { const id = `${i}`.padStart(2, '0'); const end = `${id}.${ext}`; const teamColor = new texture_1.default(1, 3 /* WrapBoth */); const teamGlow = new texture_1.default(2, 3 /* WrapBoth */); viewer.load(`ReplaceableTextures\\TeamColor\\TeamColor${end}`, pathSolver, params) .then((texture) => teamColor.texture = texture); viewer.load(`ReplaceableTextures\\TeamGlow\\TeamGlow${end}`, pathSolver, params) .then((texture) => teamGlow.texture = texture); teamColors[i] = teamColor; teamGlows[i] = teamGlow; } } }, getEventObjectSoundFile(file, reforged, isHd, tables) { if (!reforged || (0, path_1.extname)(file) === '.flac') { return file; } for (let i = 1, l = tables.length; i < l; i++) { const raceRow = tables[i].data.getRow(file); if (raceRow) { const flags = raceRow.string('Flags'); const filePath = raceRow.string('Filepath'); if (flags === 'SD_ONLY') { if (!isHd) { return filePath; } } else if (flags === 'HD_ONLY') { if (isHd) { return filePath; } } else { return filePath; } } } return; }, async getEventObjectData(viewer, type, id, isHd) { // Units\Critters\BlackStagMale\BlackStagMale.mdx has an event object named "Point01". if (type !== 'SPN' && type !== 'SPL' && type !== 'UBR' && type !== 'SND') { return; } const { pathSolver, reforged, eventObjectTables } = viewer.sharedCache.get('mdx'); const params = reforged ? { reforged: true } : {}; const safePathSolver = (src, params) => { if (pathSolver) { return pathSolver(src, params); } return src; }; if (!eventObjectTables[type]) { const paths = []; if (type === 'SPN') { paths.push('Splats\\SpawnData.slk'); } else if (type === 'SPL') { paths.push('Splats\\SplatData.slk'); } else if (type === 'UBR') { paths.push('Splats\\UberSplatData.slk'); } else if (type === 'SND') { paths.push('UI\\SoundInfo\\AnimSounds.slk'); // Reforged changed the data layout. if (reforged) { paths.push('UI\\SoundInfo\\DialogueHumanBase.slk', 'UI\\SoundInfo\\DialogueOrcBase.slk', 'UI\\SoundInfo\\DialogueUndeadBase.slk', 'UI\\SoundInfo\\DialogueNightElfBase.slk', 'UI\\SoundInfo\\DialogueNagaBase.slk', 'UI\\SoundInfo\\DialogueDemonBase.slk', 'UI\\SoundInfo\\DialogueCreepsBase.slk'); } else { paths.push('UI\\SoundInfo\\AnimLookups.slk'); } } const promises = paths.map((path) => viewer.loadGeneric(safePathSolver(path, params), 'text', mappedDataCallback)); const resources = await Promise.all(promises); for (const resource of resources) { if (!resource) { return; } } eventObjectTables[type] = resources; } const tables = eventObjectTables[type]; const mappedData = tables[0].data; let row; const promises = []; if (type === 'SND') { // How to get the sound row? // TFT has AnimLookups.slk, which stores a ID->Label. Give it the event object ID, get back the label to look for in AnimSounds.slk. // Reforged removed AnimLookups.slk, and instead has the ID under a new column in AnimSounds.slk called AnimationEventCode. // In addition, Reforged can have SD/HD flags per sound, to determine whether it should load in SD or HD modes. // When a sound has both modes, the path to it in AnimSounds.slk won't be an actual file path (ending with .flac) but rather a label. // This label can be queried in other sound SLKs such as DialogueHumanBase.slk, which contains the full path and the mentioned flags. if (reforged) { row = mappedData.findRow('AnimationEventCode', id); } else { const lookupRow = tables[1].data.getRow(id); if (lookupRow) { row = mappedData.getRow(lookupRow.string('SoundLabel')); } } if (row) { for (const fileName of row.string('FileNames').split(',')) { const file = this.getEventObjectSoundFile(fileName, reforged, isHd, tables); if (file) { promises.push(viewer.loadGeneric(safePathSolver(file, params), 'arrayBuffer', decodedDataCallback)); } } } } else { // Model and texture event objects are simpler than sounds - just get the right model or texture file. row = mappedData.getRow(id); if (row) { if (type === 'SPN') { promises.push(viewer.load(row.string('Model').replace('.mdl', '.mdx'), safePathSolver, params)); } else if (type === 'SPL' || type === 'UBR') { promises.push(viewer.load(`ReplaceableTextures\\Splats\\${row.string('file')}${reforged ? '.dds' : '.blp'}`, safePathSolver, params)); } } } if (row && promises.length) { const resources = await Promise.all(promises); // Make sure the resources actually loaded properly. const filtered = resources.filter((resource) => resource); if (filtered.length) { return { row, resources: filtered }; } } return; }, getBatchShader(viewer, skinningType, isHd) { const mdxCache = viewer.sharedCache.get('mdx'); const debugRenderMode = viewer.debugRenderMode; if (isHd) { if (debugRenderMode !== viewer_1.DebugRenderMode.None) { const shaders = mdxCache.hdDebugShaders[skinningType]; if (shaders) { const shader = shaders[debugRenderMode]; if (shader) { return shader; } } } if (skinningType === batch_1.SkinningType.Skin) { return mdxCache.hdSkinShader; } else if (skinningType === batch_1.SkinningType.VertexGroups) { return mdxCache.hdShader; } else { return mdxCache.hdExtendedShader; } } else { if (debugRenderMode !== viewer_1.DebugRenderMode.None) { const shaders = mdxCache.sdDebugShaders[skinningType]; if (shaders) { const shader = shaders[debugRenderMode]; if (shader) { return shader; } } } if (skinningType === batch_1.SkinningType.VertexGroups) { return mdxCache.sdShader; } else { return mdxCache.sdExtendedShader; } } } }; //# sourceMappingURL=handler.js.map