UNPKG

mdx-m3-viewer

Version:

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

361 lines (309 loc) 10.6 kB
/** * Creates a rectangle geometry object. * * @param {number} w The width of the rectangle. * @param {number} d The depth of the rectangle. * @return {object} The geometry object. */ export function createRectangle(w, d) { return { vertices: new Float32Array([-w, d, 0, -w, -d, 0, w, -d, 0, w, d, 0]), uvs: new Float32Array([0, 0, 0, 1, 1, 1, 1, 0]), faces: new Uint8Array([0, 1, 2, 0, 2, 3]), edges: new Uint8Array([0, 1, 1, 2, 2, 3, 3, 0]), boundingRadius: Math.max(w, d), }; } /** * Creates a unit rectangle geometry object. * * @return {object} The geometry object. */ export function createUnitRectangle() { return createRectangle(1, 1); } /** * Creates a cube geometry object. * * @param {number} w The width of the cube. * @param {number} d The depth of the cube. * @param {number} h The height of the cube. * @return {object} The geometry object. */ export function createCube(w, d, h) { return { vertices: new Float32Array([-w, -d, -h, -w, -d, h, -w, d, -h, -w, d, h, w, d, -h, w, d, h, w, -d, -h, w, -d, h]), uvs: new Float32Array([0, 0, 0, 1, 0.25, 0, 0.25, 1, 0.5, 0, 0.5, 1, 0.75, 0, 0.75, 1]), faces: new Uint8Array([0, 1, 2, 1, 3, 2, 2, 3, 4, 3, 5, 4, 4, 5, 6, 5, 7, 6, 6, 7, 0, 7, 1, 0, 0, 2, 4, 0, 4, 6, 1, 5, 3, 1, 7, 5]), edges: new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7, 0, 2, 2, 4, 4, 6, 6, 0, 1, 3, 3, 5, 5, 7, 7, 1]), boundingRadius: Math.max(w, d, h), }; } /** * Creates a unit cube geometry object. * * @return {object} The geometry object. */ export function createUnitCube() { return createCube(1, 1, 1); } /** * Create a typed array for index buffers based on the biggest possible index * * @param {number} size * @param {number} biggestIndex * @return {Uint8Array|Uint16Array|Uint32Array} */ function createIndexArray(size, biggestIndex) { if (biggestIndex < 0xFF) { return new Uint8Array(size); } else if (biggestIndex < 0xFFFF) { return new Uint16Array(size); } else { return new Uint32Array(size); } } /** * Creates a sphere geometry object. * * @param {number} radius The radius of the sphere. * @param {number} stacks The amount of layers from bottom to top. * @param {number} slices The number of divisions around the Y axis. * @return {object} The geometry object. */ export function createSphere(radius, stacks, slices) { let points = (stacks + 1) * (slices + 1); let vertices = new Float32Array(points * 3); let uvs = new Float32Array(points * 2); let faces = createIndexArray(stacks * slices * 6, points); let edges = createIndexArray(stacks * slices * 6, points); for (let stack = 0, vOffset = 0, uOffset = 0; stack <= stacks; stack++) { let theta = stack * Math.PI / stacks; let sinTheta = Math.sin(theta); let cosTheta = Math.cos(theta); for (let slice = 0; slice <= slices; slice += 1, vOffset += 3, uOffset += 2) { let phi = slice * 2 * Math.PI / slices; let sinPhi = Math.sin(phi); let cosPhi = Math.cos(phi); vertices[vOffset + 0] = cosPhi * sinTheta * radius; vertices[vOffset + 1] = sinPhi * sinTheta * radius; vertices[vOffset + 2] = cosTheta * radius; uvs[uOffset + 0] = slice / slices; uvs[uOffset + 1] = 1 - (stack / stacks); } } for (let stack = 0, fOffset = 0; stack < stacks; stack++) { for (let slice = 0; slice < slices; slice += 1, fOffset += 6) { let first = (stack * (slices + 1)) + slice; let second = first + slices + 1; // Faces faces[fOffset + 0] = first; faces[fOffset + 1] = second; faces[fOffset + 2] = first + 1; faces[fOffset + 3] = second; faces[fOffset + 4] = second + 1; faces[fOffset + 5] = first + 1; // Edges edges[fOffset + 0] = first; edges[fOffset + 1] = second; edges[fOffset + 2] = first; edges[fOffset + 3] = first + 1; edges[fOffset + 4] = second; edges[fOffset + 5] = second + 1; } } return { vertices: vertices, uvs: uvs, faces: faces, edges: edges, boundingRadius: radius, }; } /** * Creates a unit sphere geometry object. * * @param {number} stacks The amount of layers from bottom to top. * @param {number} slices The number of divisions around the Y axis. * @return {object} The geometry object. */ export function createUnitSphere(stacks, slices) { return createSphere(1, stacks, slices); } /** * Creates a cylinder geometry object. * * @param {number} radius The radius of the cylinder. * @param {number} height The height of the cylinder. * @param {number} slices The number of divisions around the Y axis. * @return {object} The geometry object. */ export function createCylinder(radius, height, slices) { slices = Math.max(slices, 3); let points = (slices + 1) * 2 + 2; let vertices = new Float32Array(points * 3); let uvs = new Float32Array(points * 2); let faces = createIndexArray(slices * 12, points); let edges = createIndexArray(slices * 10, points); let step = (Math.PI * 2) / slices; let vOffset = 0; let uOffset = 0; for (let slice = 0; slice < slices + 1; slice += 1, vOffset += 6, uOffset += 4) { let x = Math.cos(step * slice) * radius; let y = Math.sin(step * slice) * radius; let u = slice / slices; vertices[vOffset + 0] = x; vertices[vOffset + 1] = y; vertices[vOffset + 2] = height; vertices[vOffset + 3] = x; vertices[vOffset + 4] = y; vertices[vOffset + 5] = -height; uvs[uOffset + 0] = u; uvs[uOffset + 1] = 1; uvs[uOffset + 2] = u; uvs[uOffset + 3] = 0; } // Poles vertices[vOffset + 0] = 0; vertices[vOffset + 1] = 0; vertices[vOffset + 2] = height; vertices[vOffset + 3] = 0; vertices[vOffset + 4] = 0; vertices[vOffset + 5] = -height; uvs[uOffset + 0] = 0; uvs[uOffset + 1] = 1; uvs[uOffset + 2] = 0; uvs[uOffset + 3] = 0; for (let slice = 0, fOffset = 0, eOffset = 0; slice < slices; slice += 1, fOffset += 12, eOffset += 10) { let first = slice * 2; // Faces faces[fOffset + 0] = first + 0; faces[fOffset + 1] = first + 1; faces[fOffset + 2] = (first + 3) % (points - 2); faces[fOffset + 3] = first + 0; faces[fOffset + 4] = (first + 3) % (points - 2); faces[fOffset + 5] = (first + 2) % (points - 2); faces[fOffset + 6] = first + 0; faces[fOffset + 7] = (first + 2) % (points - 2); faces[fOffset + 8] = points - 2; faces[fOffset + 9] = first + 1; faces[fOffset + 10] = (first + 3) % (points - 2); faces[fOffset + 11] = points - 1; // Edges edges[eOffset + 0] = first + 0; edges[eOffset + 1] = first + 1; edges[eOffset + 2] = first + 0; edges[eOffset + 3] = (first + 2) % (points - 2); edges[eOffset + 4] = first + 1; edges[eOffset + 5] = (first + 3) % (points - 2); edges[eOffset + 6] = first + 0; edges[eOffset + 7] = points - 2; edges[eOffset + 8] = first + 1; edges[eOffset + 9] = points - 1; } return { vertices: vertices, uvs: uvs, faces: faces, edges: edges, boundingRadius: Math.max(radius, height), }; } /** * Creates a unit cylinder geometry object. * * @param {number} slices The number of divisions around the Y axis. * @return {object} The geometry object. */ export function createUnitCylinder(slices) { return createCylinder(1, 1, slices); } /** * Creates an height map geometry object. * * @param {Array<Array<number>>} heightmap The height map as an array of arrays of numbers. * @return {object} The geometry object. */ export function createHeightMap(heightmap) { let columns = heightmap[0].length; let rows = heightmap.length; let points = columns * rows; let vertices = new Float32Array(points * 3); let uvs = new Float32Array(points * 2); let faces = new Uint32Array(points * 6); let edges = new Uint32Array((columns - 1) * (rows - 1) * 4 + (columns - 1) * 2 + (rows - 1) * 2); let vOffset = 0; let uOffset = 0; let fOffset = 0; let eOffset = 0; for (let y = 0; y < rows; y++) { for (let x = 0; x < columns; x += 1, vOffset += 3, uOffset += 2) { vertices[vOffset + 0] = x; vertices[vOffset + 1] = y; vertices[vOffset + 2] = heightmap[y][x]; uvs[uOffset + 0] = x / (columns - 1); uvs[uOffset + 1] = y / (rows - 1); } } for (let y = 0; y < rows - 1; y++) { let base = y * columns; for (let x = 0; x < columns - 1; x += 1, fOffset += 6, eOffset += 4) { faces[fOffset + 0] = base + x; faces[fOffset + 1] = base + x + columns; faces[fOffset + 2] = base + x + columns + 1; faces[fOffset + 3] = base + x; faces[fOffset + 4] = base + x + columns + 1; faces[fOffset + 5] = base + x + 1; edges[eOffset + 0] = base + x; edges[eOffset + 1] = base + x + columns; edges[eOffset + 2] = base + x; edges[eOffset + 3] = base + x + 1; } } // Last row for (let x = 0; x < columns - 1; x += 1, eOffset += 2) { edges[eOffset + 0] = columns * (rows - 1) + x; edges[eOffset + 1] = columns * (rows - 1) + x + 1; } // Last column for (let y = 0; y < rows - 1; y += 1, eOffset += 2) { edges[eOffset + 0] = (columns) * y + columns - 1; edges[eOffset + 1] = (columns) * (y + 1) + columns - 1; } return { vertices: vertices, uvs: uvs, faces: faces, edges: edges, }; } /** * Create a furstum geometry. * * @param {number} fieldOfView * @param {number} aspectRatio * @param {number} nearClipPlane * @param {number} farClipPlane * @return {Object} */ export function createFrustum(fieldOfView, aspectRatio, nearClipPlane, farClipPlane) { let tanFov = 2 * Math.tan(fieldOfView / 2); let nearHeight = (tanFov * nearClipPlane) / 2; let nearWidth = (nearClipPlane * aspectRatio) / 2; let farHeight = (tanFov * farClipPlane) / 2; let farWidth = (farClipPlane * aspectRatio) / 2; return { vertices: new Float32Array([ -nearWidth, -nearHeight, nearClipPlane, -nearWidth, nearHeight, nearClipPlane, -farWidth, -farHeight, farClipPlane, -farWidth, farHeight, farClipPlane, farWidth, -farHeight, farClipPlane, farWidth, farHeight, farClipPlane, nearWidth, -nearHeight, nearClipPlane, nearWidth, nearHeight, nearClipPlane]), uvs: new Float32Array([0, 0, 0, 1, 0.25, 0, 0.25, 1, 0.5, 0, 0.5, 1, 0.75, 0, 0.75, 1]), faces: new Uint8Array([0, 1, 2, 1, 3, 2, 2, 3, 4, 3, 5, 4, 4, 5, 6, 5, 7, 6, 6, 7, 0, 7, 1, 0, 0, 2, 4, 0, 4, 6, 1, 5, 3, 1, 7, 5]), edges: new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7, 0, 2, 2, 4, 4, 6, 6, 0, 1, 3, 3, 5, 5, 7, 7, 1]), }; }