From fb8a697cece8fc9f3b07f314d0988b6e354664bf Mon Sep 17 00:00:00 2001 From: Jules Laplace Date: Sun, 27 Jan 2019 16:48:47 +0100 Subject: splash page init, add obj2ply script --- client/splash/index.js | 3 ++ megapixels/commands/misc/obj2ply.py | 79 +++++++++++++++++++++++++++++++++++++ package.json | 4 +- site/assets/demo/splash/index.html | 46 +++++++++++++++++++++ webpack.config.dev.js | 2 +- webpack.splash.dev.js | 59 +++++++++++++++++++++++++++ webpack.splash.prod.js | 50 +++++++++++++++++++++++ 7 files changed, 241 insertions(+), 2 deletions(-) create mode 100644 client/splash/index.js create mode 100644 megapixels/commands/misc/obj2ply.py create mode 100644 site/assets/demo/splash/index.html create mode 100644 webpack.splash.dev.js create mode 100644 webpack.splash.prod.js diff --git a/client/splash/index.js b/client/splash/index.js new file mode 100644 index 00000000..4fc7609c --- /dev/null +++ b/client/splash/index.js @@ -0,0 +1,3 @@ +/* teaser page */ + +console.log('hey..') diff --git a/megapixels/commands/misc/obj2ply.py b/megapixels/commands/misc/obj2ply.py new file mode 100644 index 00000000..e3e18e54 --- /dev/null +++ b/megapixels/commands/misc/obj2ply.py @@ -0,0 +1,79 @@ +#!/usr/bin/python + +""" +Convert an OBJ 3D model (with vertex color) to a PLY file, which can be read by the draco_encoder. +""" + +import click + +@click.command() +@click.add_argument('--float_colors', action='store_true', help='pass if RGB colors are floats, not ints, in the obj') +@click.add_argument('--unwind', action='store_true', help='pass to reverse winding order on faces (if surface normals are upside down)') +@click.add_argument('--flip_y', action='store_true', help='flip Y axis') +@click.add_argument('-i', '--input_fn', required=True, help='input OBJ filename') +@click.add_argument('-o', '--output_fn', help='output PLY filename') +@click.pass_context +def cli(ctx, float_colors, unwind, flip_y, input_fn, output_fn): + """ + click command for converting OBJ to PLY + """ + + ply_header = """ply + format ascii 1.0 + element vertex {} + property float x + property float y + property float z + property uchar red + property uchar green + property uchar blue + element face {} + property list uchar int vertex_index + end_header + """ + + if output_fn is None: + output_fn = input_fn.replace('.obj', '.ply') + + with open(input_fn, 'r') as f: + i = 0 + vertexes = [] + faces = [] + for line in f.readlines(): + N = line.strip().split(' ') + if N[0] == 'v': + if flip_y: + N[2] = str(float(N[2]) * -1) + if float_colors: + vertexes.append([ + N[1], + N[2], + N[3], + str(int(255 * float(N[4]))), + str(int(255 * float(N[5]))), + str(int(255 * float(N[6]))), + ]) + else: + vertexes.append(N[1:]) + if N[0] == 'f': + if unwind: + faces.append([ + "3", + str(int(N[3]) - 1), + str(int(N[2]) - 1), + str(int(N[1]) - 1), + ]) + else: + faces.append([ + "3", + str(int(N[1]) - 1), + str(int(N[2]) - 1), + str(int(N[3]) - 1), + ]) + + with open(output_fn, 'w') as out_file: + out_file.write(ply_header.format(len(vertexes), len(faces))) + for v in vertexes: + out_file.write(" ".join(v) + "\n") + for f in faces: + out_file.write(" ".join(f) + "\n") diff --git a/package.json b/package.json index d007cf2e..bcd22bd0 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,9 @@ "watch": "NODE_ENV=development webpack --config ./webpack.config.dev.js --colors --watch", "buildDev": "NODE_ENV=development webpack --config ./webpack.config.dev.js --colors", "build": "NODE_ENV=production webpack --config ./webpack.config.prod.js", - "deploy": "NODE_ENV=production webpack --config ./webpack.config.prod.js && git commit -am 'deploy' && git push origin master && ssh vframe@vframe ./restart.sh" + "deploy": "NODE_ENV=production webpack --config ./webpack.config.prod.js && git commit -am 'deploy' && git push origin master && ssh vframe@vframe ./restart.sh", + "watchSplash": "NODE_ENV=development webpack --config ./webpack.splash.dev.js --colors --watch", + "buildSplash": "NODE_ENV=production webpack --config ./webpack.splash.prod.js" }, "repository": { "type": "git", diff --git a/site/assets/demo/splash/index.html b/site/assets/demo/splash/index.html new file mode 100644 index 00000000..292bff28 --- /dev/null +++ b/site/assets/demo/splash/index.html @@ -0,0 +1,46 @@ + + + + MegaPixels + + + + + + + + + + +
+ + +
MegaPixels
+
+ +
+
+ + +
+ + + + \ No newline at end of file diff --git a/webpack.config.dev.js b/webpack.config.dev.js index 4137a948..504e7cc7 100644 --- a/webpack.config.dev.js +++ b/webpack.config.dev.js @@ -11,7 +11,7 @@ module.exports = { }, output: { path: path.resolve(__dirname, 'site/assets/js/dist'), - filename: 'index.js' + filename: 'index.js', }, devtool: 'inline-source-map', resolve: { diff --git a/webpack.splash.dev.js b/webpack.splash.dev.js new file mode 100644 index 00000000..374630a7 --- /dev/null +++ b/webpack.splash.dev.js @@ -0,0 +1,59 @@ +require('dotenv').config() + +// const HtmlWebpackPlugin = require('html-webpack-plugin') +// const CleanWebpackPlugin = require('clean-webpack-plugin') +const webpack = require('webpack') +const path = require('path') + +module.exports = { + entry: { + main: './client/splash/index.js' + }, + output: { + path: path.resolve(__dirname, 'site/assets/js/dist'), + filename: 'splash.js' + }, + devtool: 'inline-source-map', + plugins: [ + // new CleanWebpackPlugin(['dist']), + new webpack.DefinePlugin({ + 'process.env.NODE_ENV': '"development"', + 'process.env.S3_HOST': '"' + process.env.S3_HOST + '"', + 'process.env.API_HOST': '""', + }), + // new HtmlWebpackPlugin({ + // title: 'VFrame Metadata', + // meta: { + // viewport: 'width=device-width,initial-scale=1.0' + // } + // }), + // new webpack.HotModuleReplacementPlugin() + ], + module: { + rules: [ + { + test: /\.css$/, + use: ['style-loader', 'css-loader'] + }, + { + test: /\.js$/, + // include: path.resolve(__dirname, 'client'), + exclude: /(node_modules|bower_components|build)/, + use: { + loader: 'babel-loader', + options: { + presets: ['env'], + plugins: [ + require('babel-plugin-transform-runtime'), + require('babel-plugin-transform-es2015-arrow-functions'), + require('babel-plugin-transform-object-rest-spread'), + require('babel-plugin-transform-class-properties'), + require('babel-plugin-transform-react-jsx'), + require('react-hot-loader/babel') + ] + } + } + } + ] + } +}; diff --git a/webpack.splash.prod.js b/webpack.splash.prod.js new file mode 100644 index 00000000..78359a8c --- /dev/null +++ b/webpack.splash.prod.js @@ -0,0 +1,50 @@ +require('dotenv').config() + +const webpack = require('webpack') +const path = require('path') +const UglifyJsPlugin = require('uglifyjs-webpack-plugin') +// const CleanWebpackPlugin = require('clean-webpack-plugin') + +module.exports = { + entry: { + main: './client/splash/index.js', + }, + output: { + path: path.resolve(__dirname, 'site/assets/js/dist'), + filename: 'splash.js', + }, + plugins: [ + new webpack.DefinePlugin({ + 'process.env.NODE_ENV': '"production"', + 'process.env.S3_HOST': '"' + process.env.S3_HOST + '"', + 'process.env.API_HOST': '""', + }), + new UglifyJsPlugin(), + new webpack.optimize.AggressiveMergingPlugin() + ], + devtool: 'inline-source-map', + module: { + rules: [ + { + test: /\.css$/, + use: ['style-loader', 'css-loader'] + }, + { + test: /\.js$/, + // include: path.resolve(__dirname, 'client'), + exclude: /(node_modules|bower_components|build)/, + loader: 'babel-loader', + options: { + presets: ['env'], + plugins: [ + require('babel-plugin-transform-runtime'), + require('babel-plugin-transform-es2015-arrow-functions'), + require('babel-plugin-transform-object-rest-spread'), + require('babel-plugin-transform-class-properties'), + require('babel-plugin-transform-react-jsx'), + ] + } + } + ] + }, +}; -- cgit v1.2.3-70-g09d2 From c7dbbddf3539efc6d2d321cda9080005de9b6443 Mon Sep 17 00:00:00 2001 From: Jules Laplace Date: Sun, 27 Jan 2019 17:02:32 +0100 Subject: pull in DRACO loader --- client/splash/vendor/DRACOLoader.js | 515 +++++++++++++++++++++ client/splash/vendor/geometry_helper.js | 102 ++++ site/assets/demo/splash/index.html | 1 + site/assets/js/vendor/draco/draco_decoder.js | 32 ++ site/assets/js/vendor/draco/draco_decoder.wasm | Bin 0 -> 331539 bytes site/assets/js/vendor/draco/draco_decoder_gltf.js | 31 ++ .../assets/js/vendor/draco/draco_decoder_gltf.wasm | Bin 0 -> 228970 bytes site/assets/js/vendor/draco/draco_encoder.js | 33 ++ site/assets/js/vendor/draco/draco_wasm_wrapper.js | 119 +++++ .../js/vendor/draco/draco_wasm_wrapper_gltf.js | 119 +++++ 10 files changed, 952 insertions(+) create mode 100644 client/splash/vendor/DRACOLoader.js create mode 100644 client/splash/vendor/geometry_helper.js create mode 100644 site/assets/js/vendor/draco/draco_decoder.js create mode 100644 site/assets/js/vendor/draco/draco_decoder.wasm create mode 100644 site/assets/js/vendor/draco/draco_decoder_gltf.js create mode 100644 site/assets/js/vendor/draco/draco_decoder_gltf.wasm create mode 100644 site/assets/js/vendor/draco/draco_encoder.js create mode 100644 site/assets/js/vendor/draco/draco_wasm_wrapper.js create mode 100644 site/assets/js/vendor/draco/draco_wasm_wrapper_gltf.js diff --git a/client/splash/vendor/DRACOLoader.js b/client/splash/vendor/DRACOLoader.js new file mode 100644 index 00000000..03fecb6b --- /dev/null +++ b/client/splash/vendor/DRACOLoader.js @@ -0,0 +1,515 @@ +// Copyright 2016 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +'use strict'; + +/** + * @param {THREE.LoadingManager} manager + */ +THREE.DRACOLoader = function(manager) { + this.timeLoaded = 0; + this.manager = manager || THREE.DefaultLoadingManager; + this.materials = null; + this.verbosity = 0; + this.attributeOptions = {}; + this.drawMode = THREE.TrianglesDrawMode; + // Native Draco attribute type to Three.JS attribute type. + this.nativeAttributeMap = { + 'position' : 'POSITION', + 'normal' : 'NORMAL', + 'color' : 'COLOR', + 'uv' : 'TEX_COORD' + }; +}; + +THREE.DRACOLoader.prototype = { + + constructor: THREE.DRACOLoader, + + load: function(url, onLoad, onProgress, onError) { + var scope = this; + var loader = new THREE.FileLoader(scope.manager); + loader.setPath(this.path); + loader.setResponseType('arraybuffer'); + if (this.crossOrigin !== undefined) { + loader.crossOrigin = this.crossOrigin; + } + loader.load(url, function(blob) { + scope.decodeDracoFile(blob, onLoad); + }, onProgress, onError); + }, + + setPath: function(value) { + this.path = value; + }, + + setCrossOrigin: function(value) { + this.crossOrigin = value; + }, + + setVerbosity: function(level) { + this.verbosity = level; + }, + + /** + * Sets desired mode for generated geometry indices. + * Can be either: + * THREE.TrianglesDrawMode + * THREE.TriangleStripDrawMode + */ + setDrawMode: function(drawMode) { + this.drawMode = drawMode; + }, + + /** + * Skips dequantization for a specific attribute. + * |attributeName| is the THREE.js name of the given attribute type. + * The only currently supported |attributeName| is 'position', more may be + * added in future. + */ + setSkipDequantization: function(attributeName, skip) { + var skipDequantization = true; + if (typeof skip !== 'undefined') + skipDequantization = skip; + this.getAttributeOptions(attributeName).skipDequantization = + skipDequantization; + }, + + /** + * |attributeUniqueIdMap| specifies attribute unique id for an attribute in + * the geometry to be decoded. The name of the attribute must be one of the + * supported attribute type in Three.JS, including: + * 'position', + * 'color', + * 'normal', + * 'uv', + * 'uv2', + * 'skinIndex', + * 'skinWeight'. + * The format is: + * attributeUniqueIdMap[attributeName] = attributeId + */ + decodeDracoFile: function(rawBuffer, callback, attributeUniqueIdMap, + attributeTypeMap) { + var scope = this; + THREE.DRACOLoader.getDecoderModule() + .then( function ( module ) { + scope.decodeDracoFileInternal( rawBuffer, module.decoder, callback, + attributeUniqueIdMap || {}, attributeTypeMap || {}); + }); + }, + + decodeDracoFileInternal: function(rawBuffer, dracoDecoder, callback, + attributeUniqueIdMap, attributeTypeMap) { + /* + * Here is how to use Draco Javascript decoder and get the geometry. + */ + var buffer = new dracoDecoder.DecoderBuffer(); + buffer.Init(new Int8Array(rawBuffer), rawBuffer.byteLength); + var decoder = new dracoDecoder.Decoder(); + + /* + * Determine what type is this file: mesh or point cloud. + */ + var geometryType = decoder.GetEncodedGeometryType(buffer); + if (geometryType == dracoDecoder.TRIANGULAR_MESH) { + if (this.verbosity > 0) { + console.log('Loaded a mesh.'); + } + } else if (geometryType == dracoDecoder.POINT_CLOUD) { + if (this.verbosity > 0) { + console.log('Loaded a point cloud.'); + } + } else { + var errorMsg = 'THREE.DRACOLoader: Unknown geometry type.' + console.error(errorMsg); + throw new Error(errorMsg); + } + callback(this.convertDracoGeometryTo3JS(dracoDecoder, decoder, + geometryType, buffer, attributeUniqueIdMap, attributeTypeMap)); + }, + + addAttributeToGeometry: function(dracoDecoder, decoder, dracoGeometry, + attributeName, attributeType, attribute, + geometry, geometryBuffer) { + if (attribute.ptr === 0) { + var errorMsg = 'THREE.DRACOLoader: No attribute ' + attributeName; + console.error(errorMsg); + throw new Error(errorMsg); + } + + var numComponents = attribute.num_components(); + var numPoints = dracoGeometry.num_points(); + var numValues = numPoints * numComponents; + var attributeData; + var TypedBufferAttribute; + + switch ( attributeType ) { + + case Float32Array: + attributeData = new dracoDecoder.DracoFloat32Array(); + decoder.GetAttributeFloatForAllPoints( + dracoGeometry, attribute, attributeData); + geometryBuffer[ attributeName ] = new Float32Array( numValues ); + TypedBufferAttribute = THREE.Float32BufferAttribute; + break; + + case Int8Array: + attributeData = new dracoDecoder.DracoInt8Array(); + decoder.GetAttributeInt8ForAllPoints( + dracoGeometry, attribute, attributeData ); + geometryBuffer[ attributeName ] = new Int8Array( numValues ); + TypedBufferAttribute = THREE.Int8BufferAttribute; + break; + + case Int16Array: + attributeData = new dracoDecoder.DracoInt16Array(); + decoder.GetAttributeInt16ForAllPoints( + dracoGeometry, attribute, attributeData); + geometryBuffer[ attributeName ] = new Int16Array( numValues ); + TypedBufferAttribute = THREE.Int16BufferAttribute; + break; + + case Int32Array: + attributeData = new dracoDecoder.DracoInt32Array(); + decoder.GetAttributeInt32ForAllPoints( + dracoGeometry, attribute, attributeData); + geometryBuffer[ attributeName ] = new Int32Array( numValues ); + TypedBufferAttribute = THREE.Int32BufferAttribute; + break; + + case Uint8Array: + attributeData = new dracoDecoder.DracoUInt8Array(); + decoder.GetAttributeUInt8ForAllPoints( + dracoGeometry, attribute, attributeData); + geometryBuffer[ attributeName ] = new Uint8Array( numValues ); + TypedBufferAttribute = THREE.Uint8BufferAttribute; + break; + + case Uint16Array: + attributeData = new dracoDecoder.DracoUInt16Array(); + decoder.GetAttributeUInt16ForAllPoints( + dracoGeometry, attribute, attributeData); + geometryBuffer[ attributeName ] = new Uint16Array( numValues ); + TypedBufferAttribute = THREE.Uint16BufferAttribute; + break; + + case Uint32Array: + attributeData = new dracoDecoder.DracoUInt32Array(); + decoder.GetAttributeUInt32ForAllPoints( + dracoGeometry, attribute, attributeData); + geometryBuffer[ attributeName ] = new Uint32Array( numValues ); + TypedBufferAttribute = THREE.Uint32BufferAttribute; + break; + + default: + var errorMsg = 'THREE.DRACOLoader: Unexpected attribute type.'; + console.error( errorMsg ); + throw new Error( errorMsg ); + + } + + // Copy data from decoder. + for (var i = 0; i < numValues; i++) { + geometryBuffer[attributeName][i] = attributeData.GetValue(i); + } + // Add attribute to THREEJS geometry for rendering. + geometry.addAttribute(attributeName, + new TypedBufferAttribute(geometryBuffer[attributeName], + numComponents)); + dracoDecoder.destroy(attributeData); + }, + + convertDracoGeometryTo3JS: function(dracoDecoder, decoder, geometryType, + buffer, attributeUniqueIdMap, + attributeTypeMap) { + if (this.getAttributeOptions('position').skipDequantization === true) { + decoder.SkipAttributeTransform(dracoDecoder.POSITION); + } + var dracoGeometry; + var decodingStatus; + const start_time = performance.now(); + if (geometryType === dracoDecoder.TRIANGULAR_MESH) { + dracoGeometry = new dracoDecoder.Mesh(); + decodingStatus = decoder.DecodeBufferToMesh(buffer, dracoGeometry); + } else { + dracoGeometry = new dracoDecoder.PointCloud(); + decodingStatus = + decoder.DecodeBufferToPointCloud(buffer, dracoGeometry); + } + if (!decodingStatus.ok() || dracoGeometry.ptr == 0) { + var errorMsg = 'THREE.DRACOLoader: Decoding failed: '; + errorMsg += decodingStatus.error_msg(); + console.error(errorMsg); + dracoDecoder.destroy(decoder); + dracoDecoder.destroy(dracoGeometry); + throw new Error(errorMsg); + } + + var decode_end = performance.now(); + dracoDecoder.destroy(buffer); + /* + * Example on how to retrieve mesh and attributes. + */ + var numFaces; + if (geometryType == dracoDecoder.TRIANGULAR_MESH) { + numFaces = dracoGeometry.num_faces(); + if (this.verbosity > 0) { + console.log('Number of faces loaded: ' + numFaces.toString()); + } + } else { + numFaces = 0; + } + + var numPoints = dracoGeometry.num_points(); + var numAttributes = dracoGeometry.num_attributes(); + if (this.verbosity > 0) { + console.log('Number of points loaded: ' + numPoints.toString()); + console.log('Number of attributes loaded: ' + + numAttributes.toString()); + } + + // Verify if there is position attribute. + var posAttId = decoder.GetAttributeId(dracoGeometry, + dracoDecoder.POSITION); + if (posAttId == -1) { + var errorMsg = 'THREE.DRACOLoader: No position attribute found.'; + console.error(errorMsg); + dracoDecoder.destroy(decoder); + dracoDecoder.destroy(dracoGeometry); + throw new Error(errorMsg); + } + var posAttribute = decoder.GetAttribute(dracoGeometry, posAttId); + + // Structure for converting to THREEJS geometry later. + var geometryBuffer = {}; + // Import data to Three JS geometry. + var geometry = new THREE.BufferGeometry(); + + // Add native Draco attribute type to geometry. + for (var attributeName in this.nativeAttributeMap) { + // The native attribute type is only used when no unique Id is + // provided. For example, loading .drc files. + if (attributeUniqueIdMap[attributeName] === undefined) { + var attId = decoder.GetAttributeId(dracoGeometry, + dracoDecoder[this.nativeAttributeMap[attributeName]]); + if (attId !== -1) { + if (this.verbosity > 0) { + console.log('Loaded ' + attributeName + ' attribute.'); + } + var attribute = decoder.GetAttribute(dracoGeometry, attId); + this.addAttributeToGeometry(dracoDecoder, decoder, dracoGeometry, + attributeName, Float32Array, attribute, geometry, geometryBuffer); + } + } + } + + // Add attributes of user specified unique id. E.g. GLTF models. + for (var attributeName in attributeUniqueIdMap) { + var attributeType = attributeTypeMap[attributeName] || Float32Array; + var attributeId = attributeUniqueIdMap[attributeName]; + var attribute = decoder.GetAttributeByUniqueId(dracoGeometry, + attributeId); + this.addAttributeToGeometry(dracoDecoder, decoder, dracoGeometry, + attributeName, attributeType, attribute, geometry, geometryBuffer); + } + + // For mesh, we need to generate the faces. + if (geometryType == dracoDecoder.TRIANGULAR_MESH) { + if (this.drawMode === THREE.TriangleStripDrawMode) { + var stripsArray = new dracoDecoder.DracoInt32Array(); + var numStrips = decoder.GetTriangleStripsFromMesh( + dracoGeometry, stripsArray); + geometryBuffer.indices = new Uint32Array(stripsArray.size()); + for (var i = 0; i < stripsArray.size(); ++i) { + geometryBuffer.indices[i] = stripsArray.GetValue(i); + } + dracoDecoder.destroy(stripsArray); + } else { + var numIndices = numFaces * 3; + geometryBuffer.indices = new Uint32Array(numIndices); + var ia = new dracoDecoder.DracoInt32Array(); + for (var i = 0; i < numFaces; ++i) { + decoder.GetFaceFromMesh(dracoGeometry, i, ia); + var index = i * 3; + geometryBuffer.indices[index] = ia.GetValue(0); + geometryBuffer.indices[index + 1] = ia.GetValue(1); + geometryBuffer.indices[index + 2] = ia.GetValue(2); + } + dracoDecoder.destroy(ia); + } + } + + geometry.drawMode = this.drawMode; + if (geometryType == dracoDecoder.TRIANGULAR_MESH) { + geometry.setIndex(new(geometryBuffer.indices.length > 65535 ? + THREE.Uint32BufferAttribute : THREE.Uint16BufferAttribute) + (geometryBuffer.indices, 1)); + } + var posTransform = new dracoDecoder.AttributeQuantizationTransform(); + if (posTransform.InitFromAttribute(posAttribute)) { + // Quantized attribute. Store the quantization parameters into the + // THREE.js attribute. + geometry.attributes['position'].isQuantized = true; + geometry.attributes['position'].maxRange = posTransform.range(); + geometry.attributes['position'].numQuantizationBits = + posTransform.quantization_bits(); + geometry.attributes['position'].minValues = new Float32Array(3); + for (var i = 0; i < 3; ++i) { + geometry.attributes['position'].minValues[i] = + posTransform.min_value(i); + } + } + dracoDecoder.destroy(posTransform); + dracoDecoder.destroy(decoder); + dracoDecoder.destroy(dracoGeometry); + + this.decode_time = decode_end - start_time; + this.import_time = performance.now() - decode_end; + + if (this.verbosity > 0) { + console.log('Decode time: ' + this.decode_time); + console.log('Import time: ' + this.import_time); + } + return geometry; + }, + + isVersionSupported: function(version, callback) { + THREE.DRACOLoader.getDecoderModule() + .then( function ( module ) { + callback( module.decoder.isVersionSupported( version ) ); + }); + }, + + getAttributeOptions: function(attributeName) { + if (typeof this.attributeOptions[attributeName] === 'undefined') + this.attributeOptions[attributeName] = {}; + return this.attributeOptions[attributeName]; + } +}; + +THREE.DRACOLoader.decoderPath = './'; +THREE.DRACOLoader.decoderConfig = {}; +THREE.DRACOLoader.decoderModulePromise = null; + +/** + * Sets the base path for decoder source files. + * @param {string} path + */ +THREE.DRACOLoader.setDecoderPath = function ( path ) { + THREE.DRACOLoader.decoderPath = path; +}; + +/** + * Sets decoder configuration and releases singleton decoder module. Module + * will be recreated with the next decoding call. + * @param {Object} config + */ +THREE.DRACOLoader.setDecoderConfig = function ( config ) { + var wasmBinary = THREE.DRACOLoader.decoderConfig.wasmBinary; + THREE.DRACOLoader.decoderConfig = config || {}; + THREE.DRACOLoader.releaseDecoderModule(); + + // Reuse WASM binary. + if ( wasmBinary ) THREE.DRACOLoader.decoderConfig.wasmBinary = wasmBinary; +}; + +/** + * Releases the singleton DracoDecoderModule instance. Module will be recreated + * with the next decoding call. + */ +THREE.DRACOLoader.releaseDecoderModule = function () { + THREE.DRACOLoader.decoderModulePromise = null; +}; + +/** + * Gets WebAssembly or asm.js singleton instance of DracoDecoderModule + * after testing for browser support. Returns Promise that resolves when + * module is available. + * @return {Promise<{decoder: DracoDecoderModule}>} + */ +THREE.DRACOLoader.getDecoderModule = function () { + var scope = this; + var path = THREE.DRACOLoader.decoderPath; + var config = THREE.DRACOLoader.decoderConfig; + var promise = THREE.DRACOLoader.decoderModulePromise; + + if ( promise ) return promise; + + // Load source files. + if ( typeof DracoDecoderModule !== 'undefined' ) { + // Loaded externally. + promise = Promise.resolve(); + } else if ( typeof WebAssembly !== 'object' || config.type === 'js' ) { + // Load with asm.js. + promise = THREE.DRACOLoader._loadScript( path + 'draco_decoder.js' ); + } else { + // Load with WebAssembly. + config.wasmBinaryFile = path + 'draco_decoder.wasm'; + promise = THREE.DRACOLoader._loadScript( path + 'draco_wasm_wrapper.js' ) + .then( function () { + return THREE.DRACOLoader._loadArrayBuffer( config.wasmBinaryFile ); + } ) + .then( function ( wasmBinary ) { + config.wasmBinary = wasmBinary; + } ); + } + + // Wait for source files, then create and return a decoder. + promise = promise.then( function () { + return new Promise( function ( resolve ) { + config.onModuleLoaded = function ( decoder ) { + scope.timeLoaded = performance.now(); + // Module is Promise-like. Wrap before resolving to avoid loop. + resolve( { decoder: decoder } ); + }; + DracoDecoderModule( config ); + } ); + } ); + + THREE.DRACOLoader.decoderModulePromise = promise; + return promise; +}; + +/** + * @param {string} src + * @return {Promise} + */ +THREE.DRACOLoader._loadScript = function ( src ) { + var prevScript = document.getElementById( 'decoder_script' ); + if ( prevScript !== null ) { + prevScript.parentNode.removeChild( prevScript ); + } + var head = document.getElementsByTagName( 'head' )[ 0 ]; + var script = document.createElement( 'script' ); + script.id = 'decoder_script'; + script.type = 'text/javascript'; + script.src = src; + return new Promise( function ( resolve ) { + script.onload = resolve; + head.appendChild( script ); + }); +}; + +/** + * @param {string} src + * @return {Promise} + */ +THREE.DRACOLoader._loadArrayBuffer = function ( src ) { + var loader = new THREE.FileLoader(); + loader.setResponseType( 'arraybuffer' ); + return new Promise( function( resolve, reject ) { + loader.load( src, resolve, undefined, reject ); + }); +}; diff --git a/client/splash/vendor/geometry_helper.js b/client/splash/vendor/geometry_helper.js new file mode 100644 index 00000000..095f7970 --- /dev/null +++ b/client/splash/vendor/geometry_helper.js @@ -0,0 +1,102 @@ +// Copyright 2017 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +/** + * @fileoverview Helper class implementing various utilities for THREE.js + * geometry. + */ + +function GeometryHelper() {} + +GeometryHelper.prototype = { + constructor: GeometryHelper, + + // Computes vertex normals on THREE.js buffer geometry, even when the mesh + // uses triangle strip connectivity. + computeVertexNormals: function (bufferGeometry) { + if (bufferGeometry.drawMode === THREE.TrianglesDrawMode) { + bufferGeometry.computeVertexNormals(); + return; + } else if (bufferGeometry.drawMode === THREE.TriangleStripDrawMode) { + if (bufferGeometry.attributes.position === undefined) { + return; + } + const inPositions = bufferGeometry.attributes.position.array; + if (bufferGeometry.attributes.normal === undefined) { + bufferGeometry.addAttribute( + 'normal', + new THREE.BufferAttribute(new Float32Array(inPositions.length), + 3)); + } else { + // Reset existing normals to zero. + const array = bufferGeometry.attributes.normal.array; + for (let i = 0; i < array.length; ++i) { + array[ i ] = 0; + } + } + let outNormals = bufferGeometry.attributes.normal.array; + + let pos0 = new THREE.Vector3(); + let pos1 = new THREE.Vector3(); + let pos2 = new THREE.Vector3(); + let posDif0 = new THREE.Vector3(), posDif1 = new THREE.Vector3(); + let localNormal = new THREE.Vector3(); + + const stripIndices = bufferGeometry.index.array; + for (let i = 2; i < stripIndices.length; ++i) { + let index0 = stripIndices[i - 2] * 3; + let index1 = stripIndices[i - 1] * 3; + let index2 = stripIndices[i] * 3; + // Skip degenerate triangles. + if (index0 === index1 || index0 === index2 || index1 === index2) { + continue; + } + if ((i & 1) !== 0) { + // Swap index 1 and 0 on odd indexed triangles. + const tmpIndex = index1; + index1 = index2; + index2 = tmpIndex; + } + + // Get position values. + pos0.fromArray(inPositions, index0); + pos1.fromArray(inPositions, index1); + pos2.fromArray(inPositions, index2); + + // Position differences + posDif0.subVectors(pos2, pos0); + posDif1.subVectors(pos1, pos0); + + // Weighted normal. + localNormal.crossVectors(posDif1, posDif0); + + // Update normals on vertices + outNormals[index0] += localNormal.x; + outNormals[index0 + 1] += localNormal.y; + outNormals[index0 + 2] += localNormal.z; + + outNormals[index1] += localNormal.x; + outNormals[index1 + 1] += localNormal.y; + outNormals[index1 + 2] += localNormal.z; + + outNormals[index2] += localNormal.x; + outNormals[index2 + 1] += localNormal.y; + outNormals[index2 + 2] += localNormal.z; + } + bufferGeometry.normalizeNormals(); + bufferGeometry.attributes.normal.needsUpdate = true; + } + }, +}; diff --git a/site/assets/demo/splash/index.html b/site/assets/demo/splash/index.html index 292bff28..371d573e 100644 --- a/site/assets/demo/splash/index.html +++ b/site/assets/demo/splash/index.html @@ -25,6 +25,7 @@
+