diff options
Diffstat (limited to 'chromium/third_party/webxr_test_pages/webxr-samples/js/cottontail/src/loaders/gltf2.js')
-rw-r--r-- | chromium/third_party/webxr_test_pages/webxr-samples/js/cottontail/src/loaders/gltf2.js | 427 |
1 files changed, 427 insertions, 0 deletions
diff --git a/chromium/third_party/webxr_test_pages/webxr-samples/js/cottontail/src/loaders/gltf2.js b/chromium/third_party/webxr_test_pages/webxr-samples/js/cottontail/src/loaders/gltf2.js new file mode 100644 index 00000000000..8429adbebf9 --- /dev/null +++ b/chromium/third_party/webxr_test_pages/webxr-samples/js/cottontail/src/loaders/gltf2.js @@ -0,0 +1,427 @@ +// Copyright 2018 The Immersive Web Community Group +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +import {PbrMaterial} from '../materials/pbr.js'; +import {Node} from '../core/node.js'; +import {Primitive, PrimitiveAttribute} from '../core/primitive.js'; +import {ImageTexture, ColorTexture} from '../core/texture.js'; + +const GL = WebGLRenderingContext; // For enums + +const GLB_MAGIC = 0x46546C67; +const CHUNK_TYPE = { + JSON: 0x4E4F534A, + BIN: 0x004E4942, +}; + +function isAbsoluteUri(uri) { + let absRegEx = new RegExp('^'+window.location.protocol, 'i'); + return !!uri.match(absRegEx); +} + +function isDataUri(uri) { + let dataRegEx = /^data:/; + return !!uri.match(dataRegEx); +} + +function resolveUri(uri, baseUrl) { + if (isAbsoluteUri(uri) || isDataUri(uri)) { + return uri; + } + return baseUrl + uri; +} + +function getComponentCount(type) { + switch (type) { + case 'SCALAR': return 1; + case 'VEC2': return 2; + case 'VEC3': return 3; + case 'VEC4': return 4; + default: return 0; + } +} + +/** + * Gltf2SceneLoader + * Loads glTF 2.0 scenes into a renderable node tree. + */ + +export class Gltf2Loader { + constructor(renderer) { + this.renderer = renderer; + this._gl = renderer._gl; + } + + loadFromUrl(url) { + return fetch(url) + .then((response) => { + let i = url.lastIndexOf('/'); + let baseUrl = (i !== 0) ? url.substring(0, i + 1) : ''; + + if (url.endsWith('.gltf')) { + return response.json().then((json) => { + return this.loadFromJson(json, baseUrl); + }); + } else if (url.endsWith('.glb')) { + return response.arrayBuffer().then((arrayBuffer) => { + return this.loadFromBinary(arrayBuffer, baseUrl); + }); + } else { + throw new Error('Unrecognized file extension'); + } + }); + } + + loadFromBinary(arrayBuffer, baseUrl) { + let headerView = new DataView(arrayBuffer, 0, 12); + let magic = headerView.getUint32(0, true); + let version = headerView.getUint32(4, true); + let length = headerView.getUint32(8, true); + + if (magic != GLB_MAGIC) { + throw new Error('Invalid magic string in binary header.'); + } + + if (version != 2) { + throw new Error('Incompatible version in binary header.'); + } + + let chunks = {}; + let chunkOffset = 12; + while (chunkOffset < length) { + let chunkHeaderView = new DataView(arrayBuffer, chunkOffset, 8); + let chunkLength = chunkHeaderView.getUint32(0, true); + let chunkType = chunkHeaderView.getUint32(4, true); + chunks[chunkType] = arrayBuffer.slice(chunkOffset + 8, chunkOffset + 8 + chunkLength); + chunkOffset += chunkLength + 8; + } + + if (!chunks[CHUNK_TYPE.JSON]) { + throw new Error('File contained no json chunk.'); + } + + let decoder = new TextDecoder('utf-8'); + let jsonString = decoder.decode(chunks[CHUNK_TYPE.JSON]); + let json = JSON.parse(jsonString); + return this.loadFromJson(json, baseUrl, chunks[CHUNK_TYPE.BIN]); + } + + loadFromJson(json, baseUrl, binaryChunk) { + if (!json.asset) { + throw new Error('Missing asset description.'); + } + + if (json.asset.minVersion != '2.0' && json.asset.version != '2.0') { + throw new Error('Incompatible asset version.'); + } + + let buffers = []; + if (binaryChunk) { + buffers[0] = new Gltf2Resource({}, baseUrl, binaryChunk); + } else { + for (let buffer of json.buffers) { + buffers.push(new Gltf2Resource(buffer, baseUrl)); + } + } + + let bufferViews = []; + for (let bufferView of json.bufferViews) { + bufferViews.push(new Gltf2BufferView(bufferView, buffers)); + } + + let images = []; + if (json.images) { + for (let image of json.images) { + images.push(new Gltf2Resource(image, baseUrl)); + } + } + + let textures = []; + if (json.textures) { + for (let texture of json.textures) { + let image = images[texture.source]; + let glTexture = image.texture(bufferViews); + if (texture.sampler) { + let sampler = sampler[texture.sampler]; + glTexture.sampler.minFilter = sampler.minFilter; + glTexture.sampler.magFilter = sampler.magFilter; + glTexture.sampler.wrapS = sampler.wrapS; + glTexture.sampler.wrapT = sampler.wrapT; + } + textures.push(glTexture); + } + } + + function getTexture(textureInfo) { + if (!textureInfo) { + return null; + } + return textures[textureInfo.index]; + } + + let materials = []; + if (json.materials) { + for (let material of json.materials) { + let glMaterial = new PbrMaterial(); + let pbr = material.pbrMetallicRoughness || {}; + + glMaterial.baseColorFactor.value = pbr.baseColorFactor || [1, 1, 1, 1]; + glMaterial.baseColor.texture = getTexture(pbr.baseColorTexture); + glMaterial.metallicRoughnessFactor.value = [ + pbr.metallicFactor || 1.0, + pbr.roughnessFactor || 1.0, + ]; + glMaterial.metallicRoughness.texture = getTexture(pbr.metallicRoughnessTexture); + glMaterial.normal.texture = getTexture(json.normalTexture); + glMaterial.occlusion.texture = getTexture(json.occlusionTexture); + glMaterial.occlusionStrength.value = (json.occlusionTexture && json.occlusionTexture.strength) ? + json.occlusionTexture.strength : 1.0; + glMaterial.emissiveFactor.value = material.emissiveFactor || [0, 0, 0]; + glMaterial.emissive.texture = getTexture(json.emissiveTexture); + if (!glMaterial.emissive.texture && json.emissiveFactor) { + glMaterial.emissive.texture = new ColorTexture(1.0, 1.0, 1.0, 1.0); + } + + switch (material.alphaMode) { + case 'BLEND': + glMaterial.state.blend = true; + break; + case 'MASK': + // Not really supported. + glMaterial.state.blend = true; + break; + default: // Includes 'OPAQUE' + glMaterial.state.blend = false; + } + + // glMaterial.alpha_mode = material.alphaMode; + // glMaterial.alpha_cutoff = material.alphaCutoff; + glMaterial.state.cullFace = !(material.doubleSided); + + materials.push(glMaterial); + } + } + + let accessors = json.accessors; + + let meshes = []; + for (let mesh of json.meshes) { + let glMesh = new Gltf2Mesh(); + meshes.push(glMesh); + + for (let primitive of mesh.primitives) { + let material = null; + if ('material' in primitive) { + material = materials[primitive.material]; + } else { + // Create a "default" material if the primitive has none. + material = new PbrMaterial(); + } + + let attributes = []; + let elementCount = 0; + /* let glPrimitive = new Gltf2Primitive(primitive, material); + glMesh.primitives.push(glPrimitive); */ + + let min = null; + let max = null; + + for (let name in primitive.attributes) { + let accessor = accessors[primitive.attributes[name]]; + let bufferView = bufferViews[accessor.bufferView]; + elementCount = accessor.count; + + let glAttribute = new PrimitiveAttribute( + name, + bufferView.renderBuffer(this.renderer, GL.ARRAY_BUFFER), + getComponentCount(accessor.type), + accessor.componentType, + bufferView.byteStride || 0, + accessor.byteOffset || 0 + ); + glAttribute.normalized = accessor.normalized || false; + + if (name == 'POSITION') { + min = accessor.min; + max = accessor.max; + } + + attributes.push(glAttribute); + } + + let glPrimitive = new Primitive(attributes, elementCount, primitive.mode); + + if ('indices' in primitive) { + let accessor = accessors[primitive.indices]; + let bufferView = bufferViews[accessor.bufferView]; + + glPrimitive.setIndexBuffer( + bufferView.renderBuffer(this.renderer, GL.ELEMENT_ARRAY_BUFFER), + accessor.byteOffset || 0, + accessor.componentType + ); + glPrimitive.indexType = accessor.componentType; + glPrimitive.indexByteOffset = accessor.byteOffset || 0; + glPrimitive.elementCount = accessor.count; + } + + if (min && max) { + glPrimitive.setBounds(min, max); + } + + // After all the attributes have been processed, get a program that is + // appropriate for both the material and the primitive attributes. + glMesh.primitives.push( + this.renderer.createRenderPrimitive(glPrimitive, material)); + } + } + + let sceneNode = new Node(); + let scene = json.scenes[json.scene]; + for (let nodeId of scene.nodes) { + let node = json.nodes[nodeId]; + sceneNode.addNode( + this.processNodes(node, json.nodes, meshes)); + } + + return sceneNode; + } + + processNodes(node, nodes, meshes) { + let glNode = new Node(); + glNode.name = node.name; + + if ('mesh' in node) { + let mesh = meshes[node.mesh]; + for (let primitive of mesh.primitives) { + glNode.addRenderPrimitive(primitive); + } + } + + if (node.matrix) { + glNode.matrix = new Float32Array(node.matrix); + } else if (node.translation || node.rotation || node.scale) { + if (node.translation) { + glNode.translation = new Float32Array(node.translation); + } + + if (node.rotation) { + glNode.rotation = new Float32Array(node.rotation); + } + + if (node.scale) { + glNode.scale = new Float32Array(node.scale); + } + } + + if (node.children) { + for (let nodeId of node.children) { + let node = nodes[nodeId]; + glNode.addNode(this.processNodes(node, nodes, meshes)); + } + } + + return glNode; + } +} + +class Gltf2Mesh { + constructor() { + this.primitives = []; + } +} + +class Gltf2BufferView { + constructor(json, buffers) { + this.buffer = buffers[json.buffer]; + this.byteOffset = json.byteOffset || 0; + this.byteLength = json.byteLength || null; + this.byteStride = json.byteStride; + + this._viewPromise = null; + this._renderBuffer = null; + } + + dataView() { + if (!this._viewPromise) { + this._viewPromise = this.buffer.arrayBuffer().then((arrayBuffer) => { + return new DataView(arrayBuffer, this.byteOffset, this.byteLength); + }); + } + return this._viewPromise; + } + + renderBuffer(renderer, target) { + if (!this._renderBuffer) { + this._renderBuffer = renderer.createRenderBuffer(target, this.dataView()); + } + return this._renderBuffer; + } +} + +class Gltf2Resource { + constructor(json, baseUrl, arrayBuffer) { + this.json = json; + this.baseUrl = baseUrl; + + this._dataPromise = null; + this._texture = null; + if (arrayBuffer) { + this._dataPromise = Promise.resolve(arrayBuffer); + } + } + + arrayBuffer() { + if (!this._dataPromise) { + if (isDataUri(this.json.uri)) { + let base64String = this.json.uri.replace('data:application/octet-stream;base64,', ''); + let binaryArray = Uint8Array.from(atob(base64String), (c) => c.charCodeAt(0)); + this._dataPromise = Promise.resolve(binaryArray.buffer); + return this._dataPromise; + } + + this._dataPromise = fetch(resolveUri(this.json.uri, this.baseUrl)) + .then((response) => response.arrayBuffer()); + } + return this._dataPromise; + } + + texture(bufferViews) { + if (!this._texture) { + let img = new Image(); + this._texture = new ImageTexture(img); + + if (this.json.uri) { + if (isDataUri(this.json.uri)) { + img.src = this.json.uri; + } else { + img.src = `${this.baseUrl}${this.json.uri}`; + } + } else { + let view = bufferViews[this.json.bufferView]; + view.dataView().then((dataView) => { + let blob = new Blob([dataView], {type: this.json.mimeType}); + img.src = window.URL.createObjectURL(blob); + }); + } + } + return this._texture; + } +} |