From e00ba80561caecda663c6bdb5681b6c890eb4b85 Mon Sep 17 00:00:00 2001 From: Philip Rideout Date: Tue, 16 Oct 2018 13:23:42 -0700 Subject: [PATCH] jsbindings: add JS utilities, enhance testwasm. --- libs/filamentjs/CMakeLists.txt | 20 +++- libs/filamentjs/jsbindings.cpp | 53 ++++++++- libs/filamentjs/jsenums.cpp | 23 ++++ libs/filamentjs/tests/testwasm.html | 1 + libs/filamentjs/tests/testwasm.js | 165 +++++++++++++++++++--------- libs/filamentjs/utilities.js | 155 ++++++++++++++++++++++++++ libs/filamentjs/wasmloader.js | 33 ------ 7 files changed, 356 insertions(+), 94 deletions(-) create mode 100644 libs/filamentjs/utilities.js diff --git a/libs/filamentjs/CMakeLists.txt b/libs/filamentjs/CMakeLists.txt index 2d3433c2ca1..8819c02bcaf 100644 --- a/libs/filamentjs/CMakeLists.txt +++ b/libs/filamentjs/CMakeLists.txt @@ -2,20 +2,34 @@ cmake_minimum_required(VERSION 3.1) project(filamentjs) +set(JAVASCRIPT_SRC + ${CMAKE_CURRENT_SOURCE_DIR}/wasmloader.js + ${CMAKE_CURRENT_SOURCE_DIR}/utilities.js) + +set(CPP_SRC + jsenums.cpp + jsbindings.cpp) + # The emcc options are not documented well, the best place to find them is the source: # https://github.com/kripken/emscripten/blob/master/src/settings.js set(COPTS "${COPTS} -DEMSCRIPTEN_HAS_UNBOUND_TYPE_NAMES=0") set(LOPTS "${LOPTS} --bind") -set(LOPTS "${LOPTS} --post-js ${CMAKE_CURRENT_SOURCE_DIR}/wasmloader.js") set(LOPTS "${LOPTS} -s ALLOW_MEMORY_GROWTH=1") set(LOPTS "${LOPTS} -s MODULARIZE_INSTANCE=1") set(LOPTS "${LOPTS} -s EXPORT_NAME=Filament") +foreach (JS_FILENAME ${JAVASCRIPT_SRC}) + set(LOPTS "${LOPTS} --post-js ${JS_FILENAME}") +endforeach() set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${LOPTS}") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${COPTS}") -add_executable(filamentjs jsenums.cpp jsbindings.cpp) -set_target_properties(filamentjs PROPERTIES OUTPUT_NAME filament) +add_executable(filamentjs ${CPP_SRC}) + +set_target_properties(filamentjs PROPERTIES + LINK_DEPENDS "${JAVASCRIPT_SRC}" + OUTPUT_NAME filament) + target_link_libraries(filamentjs PRIVATE filament math utils image filameshio) diff --git a/libs/filamentjs/jsbindings.cpp b/libs/filamentjs/jsbindings.cpp index e5dd86f1d70..8454436bb6e 100644 --- a/libs/filamentjs/jsbindings.cpp +++ b/libs/filamentjs/jsbindings.cpp @@ -35,6 +35,7 @@ #include #include #include +#include #include #include #include @@ -82,6 +83,7 @@ namespace emscripten { BIND(View) BIND(Scene) BIND(Camera) + BIND(LightManager) BIND(RenderableManager) BIND(TransformManager) BIND(VertexBuffer) @@ -104,11 +106,12 @@ using VertexBuilder = VertexBuffer::Builder; using IndexBuilder = IndexBuffer::Builder; using MatBuilder = Material::Builder; using TexBuilder = Texture::Builder; +using LightBuilder = LightManager::Builder; // We avoid directly exposing driver::BufferDescriptor because embind does not support move // semantics and void* doesn't make sense to JavaScript anyway. This little wrapper class is exposed // to JavaScript as "driver$BufferDescriptor", but clients will normally use our "Filament.Buffer" -// helper function, which is implemented in wasmloader. +// helper function (implemented in utilities.js) struct BufferDescriptor { // This form is used when JavaScript sends a buffer into WASM. BufferDescriptor(val arrdata) { @@ -132,7 +135,7 @@ struct BufferDescriptor { }; // Exposed to JavaScript as "driver$PixelBufferDescriptor", but clients will normally use the -// "Filament.PixelBuffer" helper function, which is implemented in wasmloader. +// "Filament.PixelBuffer" helper function (implemented in utilities.js) struct PixelBufferDescriptor { PixelBufferDescriptor(val arrdata, driver::PixelDataFormat fmt, driver::PixelDataType dtype) { auto byteLength = arrdata["byteLength"].as(); @@ -286,7 +289,13 @@ class_("Camera") .function("setProjection", EMBIND_LAMBDA(void, (Camera* self, Camera::Projection projection, double left, double right, double bottom, double top, double near, double far), { self->setProjection(projection, left, right, bottom, top, near, far); - }), allow_raw_pointers()); + }), allow_raw_pointers()) + .function("setProjectionFov", EMBIND_LAMBDA(void, (Camera* self, + double fovInDegrees, double aspect, double near, double far, Camera::Fov direction), { + self->setProjection(fovInDegrees, aspect, near, far, direction); + }), allow_raw_pointers()) + .function("setExposure", &Camera::setExposure) + .function("lookAt", &Camera::lookAt); class_("RenderableManager$Builder") .function("build", EMBIND_LAMBDA(void, (RenderBuilder* builder, @@ -322,6 +331,39 @@ class_("TransformManager") class_("TransformManager$Instance"); +class_("LightManager$Builder") + .function("build", EMBIND_LAMBDA(void, (LightBuilder* builder, + Engine* engine, utils::Entity entity), { + builder->build(*engine, entity); + }), allow_raw_pointers()) + .BUILDER_FUNCTION("castShadows", LightBuilder, (LightBuilder* builder, bool enable), { + return &builder->castShadows(enable); }) + .BUILDER_FUNCTION("castLight", LightBuilder, (LightBuilder* builder, bool enable), { + return &builder->castLight(enable); }) + .BUILDER_FUNCTION("position", LightBuilder, (LightBuilder* builder, math::float3 value), { + return &builder->position(value); }) + .BUILDER_FUNCTION("direction", LightBuilder, (LightBuilder* builder, math::float3 value), { + return &builder->direction(value); }) + .BUILDER_FUNCTION("color", LightBuilder, (LightBuilder* builder, math::float3 value), { + return &builder->color(value); }) + .BUILDER_FUNCTION("intensity", LightBuilder, (LightBuilder* builder, float value), { + return &builder->intensity(value); }) + .BUILDER_FUNCTION("falloff", LightBuilder, (LightBuilder* builder, float value), { + return &builder->falloff(value); }) + .BUILDER_FUNCTION("spotLightCone", LightBuilder, + (LightBuilder* builder, float inner, float outer), { + return &builder->spotLightCone(inner, outer); }) + .BUILDER_FUNCTION("sunAngularRadius", LightBuilder, + (LightBuilder* builder, float value), { return &builder->sunAngularRadius(value); }) + .BUILDER_FUNCTION("sunHaloSize", LightBuilder, + (LightBuilder* builder, float value), { return &builder->sunHaloSize(value); }) + .BUILDER_FUNCTION("sunHaloFalloff", LightBuilder, + (LightBuilder* builder, float value), { return &builder->sunHaloFalloff(value); }); + +class_("LightManager") + .class_function("Builder", (LightBuilder (*)(LightManager::Type)) [] (LightManager::Type lt) { + return LightBuilder(lt); }); + class_("VertexBuffer$Builder") .function("build", EMBIND_LAMBDA(VertexBuffer*, (VertexBuilder* builder, Engine* engine), { return builder->build(*engine); @@ -386,7 +428,10 @@ class_("MaterialInstance") self->setParameter(name.c_str(), value); }), allow_raw_pointers()) .function("setTextureParameter", EMBIND_LAMBDA(void, (MaterialInstance* self, std::string name, Texture* value, TextureSampler sampler), { - self->setParameter(name.c_str(), value, sampler); }), allow_raw_pointers()); + self->setParameter(name.c_str(), value, sampler); }), allow_raw_pointers()) + .function("setColorParameter", EMBIND_LAMBDA(void, + (MaterialInstance* self, std::string name, RgbType type, math::float3 value), { + self->setParameter(name.c_str(), type, value); }), allow_raw_pointers()); class_("TextureSampler") .constructor(); diff --git a/libs/filamentjs/jsenums.cpp b/libs/filamentjs/jsenums.cpp index b8a2d6ec245..4b100125ae5 100644 --- a/libs/filamentjs/jsenums.cpp +++ b/libs/filamentjs/jsenums.cpp @@ -15,8 +15,10 @@ */ #include +#include #include #include +#include #include #include #include @@ -29,6 +31,16 @@ using namespace filament; EMSCRIPTEN_BINDINGS(jsenums) { +enum_("RgbType") + .value("sRGB", RgbType::sRGB) + .value("LINEAR", RgbType::LINEAR); + +enum_("RgbaType") + .value("sRGB", RgbaType::sRGB) + .value("LINEAR", RgbaType::LINEAR) + .value("PREMULTIPLIED_sRGB", RgbaType::PREMULTIPLIED_sRGB) + .value("PREMULTIPLIED_LINEAR", RgbaType::PREMULTIPLIED_LINEAR); + enum_("VertexAttribute") .value("POSITION", POSITION) .value("TANGENTS", TANGENTS) @@ -70,6 +82,13 @@ enum_("VertexAttribute") .value("USHORT", IndexBuffer::IndexType::USHORT) .value("UINT", IndexBuffer::IndexType::UINT); +enum_("LightManager$Type") + .value("SUN", LightManager::Type::SUN) + .value("DIRECTIONAL", LightManager::Type::DIRECTIONAL) + .value("POINT", LightManager::Type::POINT) + .value("FOCUSED_SPOT", LightManager::Type::FOCUSED_SPOT) + .value("SPOT", LightManager::Type::SPOT); + enum_("RenderableManager$PrimitiveType") .value("POINTS", RenderableManager::PrimitiveType::POINTS) .value("LINES", RenderableManager::PrimitiveType::LINES) @@ -85,6 +104,10 @@ enum_("VertexAttribute") .value("PERSPECTIVE", Camera::Projection::PERSPECTIVE) .value("ORTHO", Camera::Projection::ORTHO); +enum_("Camera$Fov") + .value("VERTICAL", Camera::Fov::VERTICAL) + .value("HORIZONTAL", Camera::Fov::HORIZONTAL); + enum_("Texture$Sampler") // aka driver::SamplerType .value("SAMPLER_2D", Texture::Sampler::SAMPLER_2D) .value("SAMPLER_CUBEMAP", Texture::Sampler::SAMPLER_CUBEMAP) diff --git a/libs/filamentjs/tests/testwasm.html b/libs/filamentjs/tests/testwasm.html index c52da00e7b2..8ebcff0e24e 100644 --- a/libs/filamentjs/tests/testwasm.html +++ b/libs/filamentjs/tests/testwasm.html @@ -12,6 +12,7 @@ + \ No newline at end of file diff --git a/libs/filamentjs/tests/testwasm.js b/libs/filamentjs/tests/testwasm.js index 354cb713974..9a65b40aa0d 100644 --- a/libs/filamentjs/tests/testwasm.js +++ b/libs/filamentjs/tests/testwasm.js @@ -1,51 +1,134 @@ +Filament.loadMathExtensions(); + +var Fov, VertexAttribute, AttributeType, PrimitiveType, IndexType, LightType; + class App { constructor() { + + VertexAttribute = Filament.VertexAttribute; + AttributeType = Filament.VertexBuffer$AttributeType; + PrimitiveType = Filament.RenderableManager$PrimitiveType; + IndexType = Filament.IndexBuffer$IndexType; + Fov = Filament.Camera$Fov; + LightType = Filament.LightManager$Type; + const BAKED_COLOR_PACKAGE = Filament.Buffer(Filament.assets['bakedColor.filamat']); const SANDBOX_LIT_PACKAGE = Filament.Buffer(Filament.assets['sandboxLit.filamat']); const TEXTURED_LIT_PACKAGE = Filament.Buffer(Filament.assets['texturedLit.filamat']); const ALBEDO_KTX = Filament.Buffer(Filament.assets['albedo.ktx']); + const TRIANGLE_POSITIONS = Filament.Buffer(new Float32Array([ 1, 0, -.5, .86, -.5, -.86 ])); + const TRIANGLE_COLORS = Filament.Buffer(new Uint32Array([0xffff0000, 0xff00ff00, 0xff0000ff])); + + let vb, ib; this.canvas = document.getElementsByTagName('canvas')[0]; const engine = this.engine = Filament.Engine.create(this.canvas); this.scene = engine.createScene(); - this.triangle = Filament.EntityManager.get() - .create(); - this.scene.addEntity(this.triangle); - const TRIANGLE_POSITIONS = Filament.Buffer(new Float32Array([ - 1, 0, - Math.cos(Math.PI * 2 / 3), Math.sin(Math.PI * 2 / 3), - Math.cos(Math.PI * 4 / 3), Math.sin(Math.PI * 4 / 3), - ])); - const TRIANGLE_COLORS = Filament.Buffer(new Uint32Array([ - 0xffff0000, - 0xff00ff00, - 0xff0000ff, - ])); - const VertexAttribute = Filament.VertexAttribute; - const AttributeType = Filament.VertexBuffer$AttributeType; - this.vb = Filament.VertexBuffer.Builder() + + //////////////////////////////////////////////////////////////////////////////////////////////// + + this.triangle = Filament.EntityManager.get().create(); + + vb = Filament.VertexBuffer.Builder() .vertexCount(3) .bufferCount(2) .attribute(VertexAttribute.POSITION, 0, AttributeType.FLOAT2, 0, 8) .attribute(VertexAttribute.COLOR, 1, AttributeType.UBYTE4, 0, 4) .normalized(VertexAttribute.COLOR) .build(engine); - this.vb.setBufferAt(engine, 0, TRIANGLE_POSITIONS); - this.vb.setBufferAt(engine, 1, TRIANGLE_COLORS); - this.ib = Filament.IndexBuffer.Builder() + vb.setBufferAt(engine, 0, TRIANGLE_POSITIONS); + vb.setBufferAt(engine, 1, TRIANGLE_COLORS); + + ib = Filament.IndexBuffer.Builder() .indexCount(3) - .bufferType(Filament.IndexBuffer$IndexType.USHORT) + .bufferType(IndexType.USHORT) + .build(engine); + ib.setBuffer(engine, Filament.Buffer(new Uint16Array([0, 1, 2]))); + + Filament.RenderableManager.Builder(1) + .boundingBox([ [-1, -1, -1], [1, 1, 1] ]) + .material(0, engine.createMaterial(BAKED_COLOR_PACKAGE).getDefaultInstance()) + .geometry(0, PrimitiveType.TRIANGLES, vb, ib) + .build(engine, this.triangle); + + //////////////////////////////////////////////////////////////////////////////////////////////// + + this.sphere = new Filament.IcoSphere(5); + const sphere = Filament.EntityManager.get().create(); + + vb = Filament.VertexBuffer.Builder() + .vertexCount(this.sphere.vertices.length / 3) + .bufferCount(2) + .attribute(VertexAttribute.POSITION, 0, AttributeType.FLOAT3, 0, 0) + .attribute(VertexAttribute.TANGENTS, 1, AttributeType.SHORT4, 0, 0) + .normalized(VertexAttribute.TANGENTS) .build(engine); - this.ib.setBuffer(engine, Filament.Buffer(new Uint16Array([0, 1, 2]))); + vb.setBufferAt(engine, 0, Filament.Buffer(this.sphere.vertices)); + vb.setBufferAt(engine, 1, Filament.Buffer(this.sphere.tangents)); + + ib = Filament.IndexBuffer.Builder() + .indexCount(this.sphere.triangles.length) + .bufferType(IndexType.USHORT) + .build(engine); + ib.setBuffer(engine, Filament.Buffer(this.sphere.triangles)); const sandboxMaterial = engine.createMaterial(SANDBOX_LIT_PACKAGE); + const sandboxMatInstance = sandboxMaterial.createInstance(); + Filament.RenderableManager.Builder(1) + .boundingBox([ [-1, -1, -1], [1, 1, 1] ]) + .material(0, sandboxMatInstance) + .geometry(0, PrimitiveType.TRIANGLES, vb, ib) + .build(engine, sphere); + + //////////////////////////////////////////////////////////////////////////////////////////////// + + this.scene.addEntity(sphere); + this.sphereEntity = sphere; + + const sunlight = Filament.EntityManager.get().create(); + Filament.LightManager.Builder(LightType.SUN) + .color([0.98, 0.92, 0.89]) + .intensity(110000.0) + .direction([0.6, -1.0, -0.8]) + .castShadows(true) + .sunAngularRadius(1.9) + .sunHaloSize(10.0) + .sunHaloFalloff(80.0) + .build(engine, sunlight); + this.scene.addEntity(sunlight); + + const albedo = [158 / 255.0, 118 / 255.0, 74 / 255.0]; + sandboxMatInstance.setColorParameter("baseColor", Filament.RgbType.sRGB, albedo); + sandboxMatInstance.setFloatParameter("roughness", 0.6); + sandboxMatInstance.setFloatParameter("metallic", 0.0); + sandboxMatInstance.setFloatParameter("reflectance", 0.5); + sandboxMatInstance.setFloatParameter("clearCoat", 0.7); + sandboxMatInstance.setFloatParameter("clearCoatRoughness", 0.0); + sandboxMatInstance.setFloatParameter("anisotropy", 0.0); + + //////////////////////////////////////////////////////////////////////////////////////////////// + + this.swapChain = engine.createSwapChain(); + this.renderer = engine.createRenderer(); + this.camera = engine.createCamera(); + this.view = engine.createView(); + this.view.setCamera(this.camera); + this.view.setScene(this.scene); + this.view.setClearColor([0.1, 0.2, 0.3, 1.0]); + this.resize(); + this.render = this.render.bind(this); + this.resize = this.resize.bind(this); + window.addEventListener("resize", this.resize); + window.requestAnimationFrame(this.render); + + //////////////////////////////////////////////////////////////////////////////////////////////// + const sandboxInstance = sandboxMaterial.createInstance(); sandboxInstance.setFloatParameter("metallic", 1.0) sandboxInstance.setFloat3Parameter("baseColor", [1.0, 2.0, 3.0]); const ktx = Window.ktx = new Filament.KtxBundle(ALBEDO_KTX) const nmips = ktx.getNumMipLevels(); - const ta = ktx.getBlob([0, 0, 0]).getBytes(); console.assert(11 == nmips); console.assert(1024 == ktx.info().pixelWidth) console.assert(1024 == ktx.info().pixelHeight) @@ -71,41 +154,13 @@ class App { const sampler = new Filament.TextureSampler(Filament.MinFilter.LINEAR_MIPMAP_LINEAR, Filament.MagFilter.LINEAR, Filament.WrapMode.REPEAT); texturedInstance.setTextureParameter("albedo", tex, sampler); - - const mat = engine.createMaterial(BAKED_COLOR_PACKAGE); - const matinst = mat.getDefaultInstance(); - Filament.RenderableManager.Builder(1) - .boundingBox([ - [-1, -1, -1], - [1, 1, 1] - ]) - .material(0, matinst) - .geometry(0, Filament.RenderableManager$PrimitiveType.TRIANGLES, this.vb, this.ib) - .build(engine, this.triangle); - - this.swapChain = engine.createSwapChain(); - this.renderer = engine.createRenderer(); - this.camera = engine.createCamera(); - this.view = engine.createView(); - this.view.setCamera(this.camera); - this.view.setScene(this.scene); - this.view.setClearColor([0.1, 0.2, 0.3, 1.0]); // blue-green background - this.resize(); // adjust the initial viewport - this.render = this.render.bind(this); - this.resize = this.resize.bind(this); - window.addEventListener("resize", this.resize); - window.requestAnimationFrame(this.render); } render() { // Test setting transforms. - const transform = [ - 1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1]; + const transform = mat4.fromTranslation(mat4.create(), [0, 0, -5]); const tcm = this.engine.getTransformManager(); - tcm.setTransform(tcm.getInstance(this.triangle), transform); + tcm.setTransform(tcm.getInstance(this.sphereEntity), transform); // Render the frame. if (this.renderer.beginFrame(this.swapChain)) { @@ -121,9 +176,11 @@ class App { const width = this.canvas.width = window.innerWidth * dpr; const height = this.canvas.height = window.innerHeight * dpr; this.view.setViewport([0, 0, width, height]); + this.camera.setExposure(16.0, 1 / 125.0, 100.0); + const eye = [0, 0, 0], center = [0, 0, -1], up = [0, 1, 0]; + this.camera.lookAt(eye, center, up); const aspect = width / height; - const Projection = Filament.Camera$Projection; - this.camera.setProjection(Projection.ORTHO, -aspect, aspect, -1, 1, 0, 1); + this.camera.setProjectionFov(45, aspect, 0.1, 50.0, aspect < 1 ? Fov.HORIZONTAL : Fov.VERTICAL); } } diff --git a/libs/filamentjs/utilities.js b/libs/filamentjs/utilities.js new file mode 100644 index 00000000000..04a9137122c --- /dev/null +++ b/libs/filamentjs/utilities.js @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * 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. + */ + +// --------------- +// Buffer Wrappers +// --------------- + +// These wrappers make it easy for JavaScript clients to pass large swaths of data to Filament. They +// copy the contents of the given typed array into the WASM heap, then return a low-level buffer +// descriptor object. If the given array was taken from the WASM heap, then they create a temporary +// copy because the input pointer becomes invalidated after allocating heap memory for the buffer +// descriptor. + +Filament.Buffer = function(typedarray) { + console.assert(typedarray.buffer instanceof ArrayBuffer); + console.assert(typedarray.byteLength > 0); + if (Filament.HEAPU32.buffer == typedarray.buffer) { + typedarray = new Uint8Array(typedarray); + } + const ta = typedarray; + const bd = new Filament.driver$BufferDescriptor(ta); + const uint8array = new Uint8Array(ta.buffer, ta.byteOffset, ta.byteLength); + bd.getBytes().set(uint8array); + return bd; +}; + +Filament.PixelBuffer = function(typedarray, format, datatype) { + console.assert(typedarray.buffer instanceof ArrayBuffer); + console.assert(typedarray.byteLength > 0); + if (Filament.HEAPU32.buffer == typedarray.buffer) { + typedarray = new Uint8Array(typedarray); + } + const ta = typedarray; + const bd = new Filament.driver$PixelBufferDescriptor(ta, format, datatype); + const uint8array = new Uint8Array(ta.buffer, ta.byteOffset, ta.byteLength); + bd.getBytes().set(uint8array); + return bd; +}; + +// ------------------ +// Geometry Utilities +// ------------------ + +// These are some lightweight optional functions. Using them requires the presence of gl-matrix, +// which is not bundled into filament.js. + +Filament.IcoSphere = function(nsubdivs) { + const X = .525731112119133606; + const Z = .850650808352039932; + const N = 0.; + this.vertices = new Float32Array([ + -X, +N, +Z, +X, +N, +Z, -X, +N, -Z, +X, +N, -Z , + +N, +Z, +X, +N, +Z, -X, +N, -Z, +X, +N, -Z, -X , + +Z, +X, +N, -Z, +X, +N, +Z, -X, +N, -Z, -X, +N , + ]); + this.triangles = new Uint16Array([ + 1, 4, 0, 4, 9, 0, 4, 5, 9, 8, 5, 4 , 1, 8, 4 , + 1, 10, 8, 10, 3, 8, 8, 3, 5, 3, 2, 5 , 3, 7, 2 , + 3, 10, 7, 10, 6, 7, 6, 11, 7, 6, 0, 11 , 6, 1, 0 , + 10, 1, 6, 11, 0, 9, 2, 11, 9, 5, 2, 9 , 11, 2, 7 , + ]); + if (nsubdivs) { + while (nsubdivs-- > 0) { + this.subdivide(); + } + } + const nverts = this.vertices.length / 3; + this.tangents = new Uint16Array(4 * nverts); + for (var i = 0; i < nverts; ++i) { + const src = this.vertices.subarray(i * 3, i * 3 + 3); + const dst = this.tangents.subarray(i * 4, i * 4 + 4); + const n = vec3.normalize(vec3.create(), src); + const b = vec3.cross(vec3.create(), n, [1, 0, 0]); + const t = vec3.cross(vec3.create(), b, n); + const q = quat.fromMat3(quat.create(), [t[0], t[1], t[2], b[0], b[1], b[2], n[0], n[1], n[2]]); + vec4.packSnorm16(dst, q); + } +} + +Filament.IcoSphere.prototype.subdivide = function() { + const srctris = this.triangles; + const srcverts = this.vertices; + const nsrctris = srctris.length / 3; + const ndsttris = nsrctris * 4; + const nsrcverts = srcverts.length / 3; + const ndstverts = nsrcverts + nsrctris * 3; + const dsttris = new Uint16Array(ndsttris * 3); + const dstverts = new Float32Array(ndstverts * 3); + dstverts.set(srcverts); + var srcind = 0, dstind = 0, i3 = nsrcverts * 3, i4 = i3 + 3, i5 = i4 + 3; + for (var tri = 0; tri < nsrctris; tri++, i3 += 9, i4 += 9, i5 += 9) { + const i0 = srctris[srcind++] * 3; + const i1 = srctris[srcind++] * 3; + const i2 = srctris[srcind++] * 3; + const v0 = srcverts.subarray(i0, i0 + 3); + const v1 = srcverts.subarray(i1, i1 + 3); + const v2 = srcverts.subarray(i2, i2 + 3); + const v3 = dstverts.subarray(i3, i3 + 3); + const v4 = dstverts.subarray(i4, i4 + 3); + const v5 = dstverts.subarray(i5, i5 + 3); + vec3.normalize(v3, vec3.add(v3, v0, v1)); + vec3.normalize(v4, vec3.add(v4, v1, v2)); + vec3.normalize(v5, vec3.add(v5, v2, v0)); + dsttris[dstind++] = i0 / 3; + dsttris[dstind++] = i3 / 3; + dsttris[dstind++] = i5 / 3; + dsttris[dstind++] = i3 / 3; + dsttris[dstind++] = i1 / 3; + dsttris[dstind++] = i4 / 3; + dsttris[dstind++] = i5 / 3; + dsttris[dstind++] = i3 / 3; + dsttris[dstind++] = i4 / 3; + dsttris[dstind++] = i2 / 3; + dsttris[dstind++] = i5 / 3; + dsttris[dstind++] = i4 / 3; + } + this.triangles = dsttris; + this.vertices = dstverts; +} + +// --------------- +// Math Extensions +// --------------- + +function clamp(v, least, most) { + return Math.max(Math.min(most, v), least); +} + +function packSnorm16(v) { + return Math.round(clamp(v, -1.0, 1.0) * 32767.0); +} + +// This function adds new methods to gl-matrix, its usage is optional. +Filament.loadMathExtensions = function() { + vec4.packSnorm16 = function(out, src) { + out[0] = packSnorm16(src[0]); + out[1] = packSnorm16(src[1]); + out[2] = packSnorm16(src[2]); + out[3] = packSnorm16(src[3]); + return out; + } +}; diff --git a/libs/filamentjs/wasmloader.js b/libs/filamentjs/wasmloader.js index 0ca7ef24170..ac8ff02ecb7 100644 --- a/libs/filamentjs/wasmloader.js +++ b/libs/filamentjs/wasmloader.js @@ -63,39 +63,6 @@ Filament.init = function(assets, onready) { }); }; -// This wrapper makes it easy for JavaScript clients to pass large swaths of data to Filament. It -// copies the contents of the given typed array into the WASM heap, then returns a low-level buffer -// descriptor object. -Filament.Buffer = function(typedarray) { - console.assert(typedarray.buffer instanceof ArrayBuffer); - console.assert(typedarray.byteLength > 0); - // If the given array was taken from the WASM heap, then we need to create a copy because its - // pointer will become invalidated after we instantiate the driver$PixelBufferDescriptor. - if (Filament.HEAPU32.buffer == typedarray.buffer) { - typedarray = new Uint8Array(typedarray); - } - const ta = typedarray; - const bd = new Filament.driver$BufferDescriptor(ta); - const uint8array = new Uint8Array(ta.buffer, ta.byteOffset, ta.byteLength); - bd.getBytes().set(uint8array); - return bd; -}; - -Filament.PixelBuffer = function(typedarray, format, datatype) { - console.assert(typedarray.buffer instanceof ArrayBuffer); - console.assert(typedarray.byteLength > 0); - // If the given array was taken from the WASM heap, then we need to create a copy because its - // pointer will become invalidated after we instantiate the driver$PixelBufferDescriptor. - if (Filament.HEAPU32.buffer == typedarray.buffer) { - typedarray = new Uint8Array(typedarray); - } - const ta = typedarray; - const bd = new Filament.driver$PixelBufferDescriptor(ta, format, datatype); - const uint8array = new Uint8Array(ta.buffer, ta.byteOffset, ta.byteLength); - bd.getBytes().set(uint8array); - return bd; -}; - // The postRun method is called by emscripten after it finishes compiling and instancing the // WebAssembly module. The JS classes that correspond to core Filament classes (e.g., Engine) // are not guaranteed to exist until this function is called.