diff --git a/docs/viewer/filament-viewer.js b/docs/viewer/filament-viewer.js
deleted file mode 120000
index bf4479c2be6..00000000000
--- a/docs/viewer/filament-viewer.js
+++ /dev/null
@@ -1 +0,0 @@
-../../web/filament-js/filament-viewer.js
\ No newline at end of file
diff --git a/docs/viewer/filament-viewer.js b/docs/viewer/filament-viewer.js
new file mode 100644
index 00000000000..652d55d8ae4
--- /dev/null
+++ b/docs/viewer/filament-viewer.js
@@ -0,0 +1,447 @@
+/*
+* Copyright (C) 2021 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.
+*/
+
+// If you are bundling this with rollup, webpack, or esbuild, the following URL should be trimmed.
+import { LitElement, html, css } from "https://unpkg.com/lit@2.8.0?module";
+
+// This little utility checks if the Filament module is ready for action.
+// If so, it immediately calls the given function. If not, it asks the Filament
+// loader to call it as soon as the module becomes ready.
+class FilamentTasks {
+ add(callback) {
+ if (Filament.isReady) {
+ callback();
+ } else {
+ Filament.init([], callback);
+ }
+ }
+}
+
+// FilamentViewer is a fairly limited web component for showing glTF models with Filament.
+//
+// To embed a 3D viewer in your web page, simply add something like the following to your HTML,
+// similar to an element.
+//
+//
+//
+// In addition to the src URL, attributes can be used to set up an optional IBL and skybox.
+// The documentation for these attributes is in the FilamentViewer constructor.
+//
+// MISSING FEATURES
+// ----------------
+// None of the following features are implemented. They would be easy to add.
+// - Replace gltumble and Trackball with camutils Manipulator (i.e. enable scroll-to-zoom)
+// - Write a documentation page (might be neat if the doc page has instances of the actual viewer)
+// - Fix the import at the top of the file to support webpack / rollup / esbuild
+// - Expose more animation properties (e.g. enable / disable, selected index)
+// - Expose camera properties, glTF camera selection, and clear color
+// - Expose more IBL properties
+// - Expose directional light properties
+// - Optional turntable animation
+//
+class FilamentViewer extends LitElement {
+ constructor() {
+ super();
+
+ // LitElement properties:
+ this.src = null; // Path to glTF file.
+ this.alt = null; // Alternate canvas content.
+ this.ibl = null; // Path to image based light ktx.
+ this.sky = null; // Path to skybox ktx.
+ this.enableDrop = null; // Enables drag and drop.
+ this.intensity = 30000; // Intensity of the image based light.
+ this.materialVariant = 0; // Index of material variant.
+
+ // Private properties:
+ this.filamentTasks = new FilamentTasks();
+ this.canvasId = "filament-viewer-canvas";
+ this.overlayId = "filament-viewer-overlay";
+ this.srcBlob = null;
+ }
+
+ static get properties() {
+ return {
+ src: { type: String },
+ alt: { type: String },
+ ibl: { type: String },
+ sky: { type: String },
+ enableDrop: { type: Boolean },
+ intensity: { type: Number },
+ materialVariant: { type: Number },
+ }
+ }
+
+ firstUpdated() {
+ // At this point in the lit-element lifecycle, the "render" has taken place, which simply
+ // means the canvas element now exists. However the Filament wasm module may or may not be
+ // fully loaded, which is why we use the task manager.
+ const canvas = this.shadowRoot.getElementById(this.canvasId);
+ if (canvas.parentNode.host.parentElement.tagName === "FILAMENT-VIEWER") {
+ console.error("Do not nest FilamentViewer, this is unsupported.");
+ console.error("Try placing each viewer in a wrapper element.");
+ return;
+ }
+ this.filamentTasks.add(this._startFilament.bind(this));
+
+ const overlay = this.shadowRoot.getElementById(this.overlayId);
+ ["dragenter", "dragover", "dragleave", "drop"].forEach(eventName => {
+ overlay.addEventListener(eventName, e => { e.preventDefault(); e.stopPropagation() }, false)
+ });
+ if (this.enableDrop) {
+ overlay.addEventListener("drop", this._dropHandler.bind(this), false);
+ }
+ }
+
+ updated(props) {
+ if (props.has("src")) this.filamentTasks.add(this._loadAsset.bind(this));
+ if (props.has("ibl")) this.filamentTasks.add(this._loadIbl.bind(this));
+ if (props.has("sky")) this.filamentTasks.add(this._loadSky.bind(this));
+ if (props.has("enableDrop")) this._updateOverlay();
+ if (props.has("intensity") && this.indirectLight) {
+ this.indirectLight.setIntensity(this.intensity);
+ }
+ if (props.has("materialVariant") && this.asset) this._applyMaterialVariant();
+ }
+
+ static get styles() {
+ return css`
+ :host {
+ display: inline-block;
+ width: 300px;
+ height: 300px;
+ border: solid 1px black;
+ position: relative;
+ }
+ canvas {
+ background: #D9D9D9; /* This is consistent with the default clear color. */
+ }
+ canvas, .overlay {
+ width: 100%;
+ height: 100%;
+ position: absolute;
+ }
+ .overlay {
+ text-align: center;
+ padding: 10px;
+ }`;
+ }
+
+ render() {
+ return html`
+
+
+ `;
+ }
+
+ _dropHandler(dragEvent) {
+ if (!dragEvent.dataTransfer) return;
+ this.srcBlob = null;
+ this.srcBlobResources = {};
+ for (const file of dragEvent.dataTransfer.files) {
+ if (file.name.endsWith(".glb") || file.name.endsWith(".gltf")) {
+ this.srcBlob = file;
+ } else {
+ this.srcBlobResources[file.name] = file;
+ }
+ }
+ if (this.srcBlob) {
+ this._loadAsset();
+ } else {
+ console.error("Please include a glTF file.");
+ }
+ };
+
+ _updateOverlay() {
+ const overlay = this.shadowRoot.getElementById(this.overlayId);
+ if (!this.enableDrop || this.asset) {
+ overlay.innerHTML = "";
+ } else {
+ overlay.innerHTML = "Drop glb or file set here.";
+ }
+ }
+
+ _startFilament() {
+ const LightType = Filament.LightManager$Type;
+
+ const canvas = this.shadowRoot.getElementById(this.canvasId);
+ const overlay = this.shadowRoot.getElementById(this.overlayId);
+
+ this.engine = Filament.Engine.create(canvas);
+ this.scene = this.engine.createScene();
+ this.sunlight = Filament.EntityManager.get().create();
+ this.scene.addEntity(this.sunlight);
+ this.loader = this.engine.createAssetLoader();
+ this.cameraEntity = Filament.EntityManager.get().create();
+ this.camera = this.engine.createCamera(this.cameraEntity);
+ this.swapChain = this.engine.createSwapChain();
+ this.renderer = this.engine.createRenderer();
+ this.view = this.engine.createView();
+ this.view.setVignetteOptions({ midPoint: 0.7, enabled: true });
+ this.view.setCamera(this.camera);
+ this.view.setScene(this.scene);
+
+ // If gltumble has been loaded, use it.
+ if (window.Trackball) {
+ this.trackball = new Trackball(overlay, { startSpin: 0.0 });
+ }
+
+ // This color is consistent with the default CSS background color.
+ this.renderer.setClearOptions({ clearColor: [0.8, 0.8, 0.8, 1.0], clear: true });
+
+ Filament.LightManager.Builder(LightType.SUN)
+ .direction([0, -.7, -.7])
+ .sunAngularRadius(1.9)
+ .castShadows(true)
+ .sunHaloSize(10.0)
+ .sunHaloFalloff(80.0)
+ .build(this.engine, this.sunlight);
+
+ // TODO: if ResizeObserver is not supported, then we should call _onResized and
+ // pass (canvas.clientWidth * window.devicePixelRatio) for the dimensions.
+ var ro = new ResizeObserver(entries => {
+ const width = entries[0].devicePixelContentBoxSize[0].inlineSize;
+ const height = entries[0].devicePixelContentBoxSize[0].blockSize;
+ this._onResized(width, height);
+ });
+ ro.observe(canvas);
+
+ window.requestAnimationFrame(this._renderFrame.bind(this));
+ }
+
+ _onResized(width, height) {
+ const Fov = Filament.Camera$Fov;
+ const canvas = this.shadowRoot.getElementById(this.canvasId);
+ canvas.width = width;
+ canvas.height = height;
+ this.view.setViewport([0, 0, width, height]);
+ const y = -0.125, eye = [0, y, 1.5], center = [0, y, 0], up = [0, 1, 0];
+ this.camera.lookAt(eye, center, up);
+ const aspect = width / height;
+ const fov = aspect < 1 ? Fov.HORIZONTAL : Fov.VERTICAL;
+ this.camera.setProjectionFov(25, aspect, 1.0, 10.0, fov);
+ }
+
+ _loadIbl() {
+ if (!this.ibl) {
+ return;
+ }
+ if (this.indirectLight) {
+ console.info("FilamentViewer does not allow the IBL to be changed.");
+ return;
+ }
+ fetch(this.ibl).then(response => {
+ return response.arrayBuffer();
+ }).then(arrayBuffer => {
+ const ktxData = new Uint8Array(arrayBuffer);
+ this.indirectLight = this.engine.createIblFromKtx1(ktxData);
+ this.indirectLight.setIntensity(this.intensity);
+ this.scene.setIndirectLight(this.indirectLight);
+ });
+ }
+
+ _loadSky() {
+ if (!this.sky) {
+ return;
+ }
+ if (this.skybox) {
+ console.info("FilamentViewer does not allow the skybox to be changed.");
+ return;
+ }
+ fetch(this.sky).then(response => {
+ return response.arrayBuffer();
+ }).then(arrayBuffer => {
+ const ktxData = new Uint8Array(arrayBuffer);
+ this.skybox = this.engine.createSkyFromKtx1(ktxData);
+ this.scene.setSkybox(this.skybox);
+ });
+ }
+
+ _loadAsset() {
+ const zoffset = 4;
+
+ if (this.asset) {
+ this.scene.removeEntities(this.asset.getEntities());
+ this.animator = null;
+ this.asset = null;
+ }
+
+ // If we have neither a URL nor a dropped file, leave early.
+ if (!this.src && !this.srcBlob) {
+ this._updateOverlay();
+ return;
+ }
+
+ // Dropping a glb file is simple because there are no external resources.
+ if (this.srcBlob && this.srcBlob.name.endsWith(".glb")) {
+ this.srcBlob.arrayBuffer().then(buffer => {
+ this.asset = this.loader.createAsset(new Uint8Array(buffer));
+ const aabb = this.asset.getBoundingBox();
+ this.assetRoot = this.asset.getRoot();
+ this.unitCubeTransform = Filament.fitIntoUnitCube(aabb, zoffset);
+ this.asset.loadResources();
+ this.animator = this.asset.getInstance().getAnimator();
+ this.animationStartTime = Date.now();
+ this._updateOverlay();
+ });
+ return;
+ }
+
+ // Dropping a fileset requires pushing each resource to ResourceLoader.
+ if (this.srcBlob && this.srcBlob.name.endsWith(".gltf")) {
+
+ const config = {
+ normalizeSkinningWeights: true,
+ asyncInterval: 30
+ };
+
+ const doneAddingResources = (resourceLoader, stbProvider, ktx2Provider) => {
+ this.srcBlobResources = {};
+ resourceLoader.asyncBeginLoad(this.asset);
+ const timer = setInterval(() => {
+ resourceLoader.asyncUpdateLoad();
+ const progress = resourceLoader.asyncGetLoadProgress();
+ if (progress >= 1) {
+ clearInterval(timer);
+ resourceLoader.delete();
+ stbProvider.delete();
+ ktx2Provider.delete();
+ this.animator = this.asset.getInstance().getAnimator();
+ this.animationStartTime = Date.now();
+ }
+ }, config.asyncInterval);
+ };
+
+ this.srcBlob.arrayBuffer().then(buffer => {
+ this.asset = this.loader.createAsset(new Uint8Array(buffer));
+ const aabb = this.asset.getBoundingBox();
+ this.assetRoot = this.asset.getRoot();
+ this.unitCubeTransform = Filament.fitIntoUnitCube(aabb, zoffset);
+
+ const resourceLoader = new Filament.gltfio$ResourceLoader(this.engine,
+ config.normalizeSkinningWeights);
+
+ const stbProvider = new Filament.gltfio$StbProvider(this.engine);
+ const ktx2Provider = new Filament.gltfio$Ktx2Provider(this.engine);
+
+ resourceLoader.addStbProvider("image/jpeg", stbProvider);
+ resourceLoader.addStbProvider("image/png", stbProvider);
+ resourceLoader.addKtx2Provider("image/ktx2", ktx2Provider);
+
+ let remaining = Object.keys(this.srcBlobResources).length;
+ for (const name in this.srcBlobResources) {
+ this.srcBlobResources[name].arrayBuffer().then(buffer => {
+ const desc = getBufferDescriptor(new Uint8Array(buffer));
+ resourceLoader.addResourceData(name, getBufferDescriptor(desc));
+ if (--remaining === 0) {
+ doneAddingResources(resourceLoader, stbProvider, ktx2Provider);
+ }
+ });
+ }
+
+ this._updateOverlay();
+ });
+
+ return;
+ }
+
+ // If we reach this point, we are loading from a src URL rather than drag-and-drop.
+
+ fetch(this.src).then(response => {
+ return response.arrayBuffer();
+ }).then(arrayBuffer => {
+ const modelData = new Uint8Array(arrayBuffer);
+ this.asset = this.loader.createAsset(modelData);
+ const aabb = this.asset.getBoundingBox();
+ this.assetRoot = this.asset.getRoot();
+ this.unitCubeTransform = Filament.fitIntoUnitCube(aabb, zoffset);
+
+ const basePath = '' + new URL(this.src, document.location);
+
+ this.asset.loadResources(() => {
+ this.animator = this.asset.getInstance().getAnimator();
+ this.animationStartTime = Date.now();
+ this._applyMaterialVariant();
+ }, null, basePath);
+
+ this._updateOverlay();
+ });
+ }
+
+ _updateAsset() {
+ // Invoke the first glTF animation if it exists.
+ if (this.animator) {
+ if (this.animator.getAnimationCount() > 0) {
+ const ms = Date.now() - this.animationStartTime;
+ this.animator.applyAnimation(0, ms / 1000);
+ }
+ this.animator.updateBoneMatrices();
+ }
+
+ // Apply the root transform of the model.
+ const tcm = this.engine.getTransformManager();
+ const inst = tcm.getInstance(this.assetRoot);
+ let rootTransform = this.unitCubeTransform;
+ if (this.trackball) {
+ rootTransform = Filament.multiplyMatrices(rootTransform, this.trackball.getMatrix());
+ }
+ tcm.setTransform(inst, rootTransform);
+ inst.delete();
+
+ // Add renderable entities to the scene as they become ready.
+ while (true) {
+ const entity = this.asset.popRenderable();
+ if (entity.getId() == 0) {
+ entity.delete();
+ break;
+ }
+ this.scene.addEntity(entity);
+ entity.delete();
+ }
+ }
+
+ _renderFrame() {
+ // Apply transforms and add entities to the scene.
+ if (this.asset) {
+ this._updateAsset();
+ }
+
+ // Render the view and update the Filament engine.
+ if (this.renderer.beginFrame(this.swapChain)) {
+ this.renderer.renderView(this.view);
+ this.renderer.endFrame();
+ }
+ this.engine.execute();
+
+ window.requestAnimationFrame(this._renderFrame.bind(this));
+ }
+
+ _applyMaterialVariant() {
+ if (!this.hasAttribute("materialVariant")) {
+ return;
+ }
+ const instance = this.asset.getInstance();
+ const names = instance.getMaterialVariantNames();
+ const index = this.materialVariant;
+ if (index < 0 || index >= names.length) {
+ console.error(`Material variant ${index} does not exist in this asset.`);
+ return;
+ }
+ console.info(this.src, `Applying material variant: ${names[index]}`);
+ instance.applyMaterialVariant(index);
+ }
+}
+
+customElements.define("filament-viewer", FilamentViewer);
diff --git a/web/filament-js/filament-viewer.js b/web/filament-js/filament-viewer.js
deleted file mode 100644
index 652d55d8ae4..00000000000
--- a/web/filament-js/filament-viewer.js
+++ /dev/null
@@ -1,447 +0,0 @@
-/*
-* Copyright (C) 2021 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.
-*/
-
-// If you are bundling this with rollup, webpack, or esbuild, the following URL should be trimmed.
-import { LitElement, html, css } from "https://unpkg.com/lit@2.8.0?module";
-
-// This little utility checks if the Filament module is ready for action.
-// If so, it immediately calls the given function. If not, it asks the Filament
-// loader to call it as soon as the module becomes ready.
-class FilamentTasks {
- add(callback) {
- if (Filament.isReady) {
- callback();
- } else {
- Filament.init([], callback);
- }
- }
-}
-
-// FilamentViewer is a fairly limited web component for showing glTF models with Filament.
-//
-// To embed a 3D viewer in your web page, simply add something like the following to your HTML,
-// similar to an element.
-//
-//
-//
-// In addition to the src URL, attributes can be used to set up an optional IBL and skybox.
-// The documentation for these attributes is in the FilamentViewer constructor.
-//
-// MISSING FEATURES
-// ----------------
-// None of the following features are implemented. They would be easy to add.
-// - Replace gltumble and Trackball with camutils Manipulator (i.e. enable scroll-to-zoom)
-// - Write a documentation page (might be neat if the doc page has instances of the actual viewer)
-// - Fix the import at the top of the file to support webpack / rollup / esbuild
-// - Expose more animation properties (e.g. enable / disable, selected index)
-// - Expose camera properties, glTF camera selection, and clear color
-// - Expose more IBL properties
-// - Expose directional light properties
-// - Optional turntable animation
-//
-class FilamentViewer extends LitElement {
- constructor() {
- super();
-
- // LitElement properties:
- this.src = null; // Path to glTF file.
- this.alt = null; // Alternate canvas content.
- this.ibl = null; // Path to image based light ktx.
- this.sky = null; // Path to skybox ktx.
- this.enableDrop = null; // Enables drag and drop.
- this.intensity = 30000; // Intensity of the image based light.
- this.materialVariant = 0; // Index of material variant.
-
- // Private properties:
- this.filamentTasks = new FilamentTasks();
- this.canvasId = "filament-viewer-canvas";
- this.overlayId = "filament-viewer-overlay";
- this.srcBlob = null;
- }
-
- static get properties() {
- return {
- src: { type: String },
- alt: { type: String },
- ibl: { type: String },
- sky: { type: String },
- enableDrop: { type: Boolean },
- intensity: { type: Number },
- materialVariant: { type: Number },
- }
- }
-
- firstUpdated() {
- // At this point in the lit-element lifecycle, the "render" has taken place, which simply
- // means the canvas element now exists. However the Filament wasm module may or may not be
- // fully loaded, which is why we use the task manager.
- const canvas = this.shadowRoot.getElementById(this.canvasId);
- if (canvas.parentNode.host.parentElement.tagName === "FILAMENT-VIEWER") {
- console.error("Do not nest FilamentViewer, this is unsupported.");
- console.error("Try placing each viewer in a wrapper element.");
- return;
- }
- this.filamentTasks.add(this._startFilament.bind(this));
-
- const overlay = this.shadowRoot.getElementById(this.overlayId);
- ["dragenter", "dragover", "dragleave", "drop"].forEach(eventName => {
- overlay.addEventListener(eventName, e => { e.preventDefault(); e.stopPropagation() }, false)
- });
- if (this.enableDrop) {
- overlay.addEventListener("drop", this._dropHandler.bind(this), false);
- }
- }
-
- updated(props) {
- if (props.has("src")) this.filamentTasks.add(this._loadAsset.bind(this));
- if (props.has("ibl")) this.filamentTasks.add(this._loadIbl.bind(this));
- if (props.has("sky")) this.filamentTasks.add(this._loadSky.bind(this));
- if (props.has("enableDrop")) this._updateOverlay();
- if (props.has("intensity") && this.indirectLight) {
- this.indirectLight.setIntensity(this.intensity);
- }
- if (props.has("materialVariant") && this.asset) this._applyMaterialVariant();
- }
-
- static get styles() {
- return css`
- :host {
- display: inline-block;
- width: 300px;
- height: 300px;
- border: solid 1px black;
- position: relative;
- }
- canvas {
- background: #D9D9D9; /* This is consistent with the default clear color. */
- }
- canvas, .overlay {
- width: 100%;
- height: 100%;
- position: absolute;
- }
- .overlay {
- text-align: center;
- padding: 10px;
- }`;
- }
-
- render() {
- return html`
-
-
- `;
- }
-
- _dropHandler(dragEvent) {
- if (!dragEvent.dataTransfer) return;
- this.srcBlob = null;
- this.srcBlobResources = {};
- for (const file of dragEvent.dataTransfer.files) {
- if (file.name.endsWith(".glb") || file.name.endsWith(".gltf")) {
- this.srcBlob = file;
- } else {
- this.srcBlobResources[file.name] = file;
- }
- }
- if (this.srcBlob) {
- this._loadAsset();
- } else {
- console.error("Please include a glTF file.");
- }
- };
-
- _updateOverlay() {
- const overlay = this.shadowRoot.getElementById(this.overlayId);
- if (!this.enableDrop || this.asset) {
- overlay.innerHTML = "";
- } else {
- overlay.innerHTML = "Drop glb or file set here.";
- }
- }
-
- _startFilament() {
- const LightType = Filament.LightManager$Type;
-
- const canvas = this.shadowRoot.getElementById(this.canvasId);
- const overlay = this.shadowRoot.getElementById(this.overlayId);
-
- this.engine = Filament.Engine.create(canvas);
- this.scene = this.engine.createScene();
- this.sunlight = Filament.EntityManager.get().create();
- this.scene.addEntity(this.sunlight);
- this.loader = this.engine.createAssetLoader();
- this.cameraEntity = Filament.EntityManager.get().create();
- this.camera = this.engine.createCamera(this.cameraEntity);
- this.swapChain = this.engine.createSwapChain();
- this.renderer = this.engine.createRenderer();
- this.view = this.engine.createView();
- this.view.setVignetteOptions({ midPoint: 0.7, enabled: true });
- this.view.setCamera(this.camera);
- this.view.setScene(this.scene);
-
- // If gltumble has been loaded, use it.
- if (window.Trackball) {
- this.trackball = new Trackball(overlay, { startSpin: 0.0 });
- }
-
- // This color is consistent with the default CSS background color.
- this.renderer.setClearOptions({ clearColor: [0.8, 0.8, 0.8, 1.0], clear: true });
-
- Filament.LightManager.Builder(LightType.SUN)
- .direction([0, -.7, -.7])
- .sunAngularRadius(1.9)
- .castShadows(true)
- .sunHaloSize(10.0)
- .sunHaloFalloff(80.0)
- .build(this.engine, this.sunlight);
-
- // TODO: if ResizeObserver is not supported, then we should call _onResized and
- // pass (canvas.clientWidth * window.devicePixelRatio) for the dimensions.
- var ro = new ResizeObserver(entries => {
- const width = entries[0].devicePixelContentBoxSize[0].inlineSize;
- const height = entries[0].devicePixelContentBoxSize[0].blockSize;
- this._onResized(width, height);
- });
- ro.observe(canvas);
-
- window.requestAnimationFrame(this._renderFrame.bind(this));
- }
-
- _onResized(width, height) {
- const Fov = Filament.Camera$Fov;
- const canvas = this.shadowRoot.getElementById(this.canvasId);
- canvas.width = width;
- canvas.height = height;
- this.view.setViewport([0, 0, width, height]);
- const y = -0.125, eye = [0, y, 1.5], center = [0, y, 0], up = [0, 1, 0];
- this.camera.lookAt(eye, center, up);
- const aspect = width / height;
- const fov = aspect < 1 ? Fov.HORIZONTAL : Fov.VERTICAL;
- this.camera.setProjectionFov(25, aspect, 1.0, 10.0, fov);
- }
-
- _loadIbl() {
- if (!this.ibl) {
- return;
- }
- if (this.indirectLight) {
- console.info("FilamentViewer does not allow the IBL to be changed.");
- return;
- }
- fetch(this.ibl).then(response => {
- return response.arrayBuffer();
- }).then(arrayBuffer => {
- const ktxData = new Uint8Array(arrayBuffer);
- this.indirectLight = this.engine.createIblFromKtx1(ktxData);
- this.indirectLight.setIntensity(this.intensity);
- this.scene.setIndirectLight(this.indirectLight);
- });
- }
-
- _loadSky() {
- if (!this.sky) {
- return;
- }
- if (this.skybox) {
- console.info("FilamentViewer does not allow the skybox to be changed.");
- return;
- }
- fetch(this.sky).then(response => {
- return response.arrayBuffer();
- }).then(arrayBuffer => {
- const ktxData = new Uint8Array(arrayBuffer);
- this.skybox = this.engine.createSkyFromKtx1(ktxData);
- this.scene.setSkybox(this.skybox);
- });
- }
-
- _loadAsset() {
- const zoffset = 4;
-
- if (this.asset) {
- this.scene.removeEntities(this.asset.getEntities());
- this.animator = null;
- this.asset = null;
- }
-
- // If we have neither a URL nor a dropped file, leave early.
- if (!this.src && !this.srcBlob) {
- this._updateOverlay();
- return;
- }
-
- // Dropping a glb file is simple because there are no external resources.
- if (this.srcBlob && this.srcBlob.name.endsWith(".glb")) {
- this.srcBlob.arrayBuffer().then(buffer => {
- this.asset = this.loader.createAsset(new Uint8Array(buffer));
- const aabb = this.asset.getBoundingBox();
- this.assetRoot = this.asset.getRoot();
- this.unitCubeTransform = Filament.fitIntoUnitCube(aabb, zoffset);
- this.asset.loadResources();
- this.animator = this.asset.getInstance().getAnimator();
- this.animationStartTime = Date.now();
- this._updateOverlay();
- });
- return;
- }
-
- // Dropping a fileset requires pushing each resource to ResourceLoader.
- if (this.srcBlob && this.srcBlob.name.endsWith(".gltf")) {
-
- const config = {
- normalizeSkinningWeights: true,
- asyncInterval: 30
- };
-
- const doneAddingResources = (resourceLoader, stbProvider, ktx2Provider) => {
- this.srcBlobResources = {};
- resourceLoader.asyncBeginLoad(this.asset);
- const timer = setInterval(() => {
- resourceLoader.asyncUpdateLoad();
- const progress = resourceLoader.asyncGetLoadProgress();
- if (progress >= 1) {
- clearInterval(timer);
- resourceLoader.delete();
- stbProvider.delete();
- ktx2Provider.delete();
- this.animator = this.asset.getInstance().getAnimator();
- this.animationStartTime = Date.now();
- }
- }, config.asyncInterval);
- };
-
- this.srcBlob.arrayBuffer().then(buffer => {
- this.asset = this.loader.createAsset(new Uint8Array(buffer));
- const aabb = this.asset.getBoundingBox();
- this.assetRoot = this.asset.getRoot();
- this.unitCubeTransform = Filament.fitIntoUnitCube(aabb, zoffset);
-
- const resourceLoader = new Filament.gltfio$ResourceLoader(this.engine,
- config.normalizeSkinningWeights);
-
- const stbProvider = new Filament.gltfio$StbProvider(this.engine);
- const ktx2Provider = new Filament.gltfio$Ktx2Provider(this.engine);
-
- resourceLoader.addStbProvider("image/jpeg", stbProvider);
- resourceLoader.addStbProvider("image/png", stbProvider);
- resourceLoader.addKtx2Provider("image/ktx2", ktx2Provider);
-
- let remaining = Object.keys(this.srcBlobResources).length;
- for (const name in this.srcBlobResources) {
- this.srcBlobResources[name].arrayBuffer().then(buffer => {
- const desc = getBufferDescriptor(new Uint8Array(buffer));
- resourceLoader.addResourceData(name, getBufferDescriptor(desc));
- if (--remaining === 0) {
- doneAddingResources(resourceLoader, stbProvider, ktx2Provider);
- }
- });
- }
-
- this._updateOverlay();
- });
-
- return;
- }
-
- // If we reach this point, we are loading from a src URL rather than drag-and-drop.
-
- fetch(this.src).then(response => {
- return response.arrayBuffer();
- }).then(arrayBuffer => {
- const modelData = new Uint8Array(arrayBuffer);
- this.asset = this.loader.createAsset(modelData);
- const aabb = this.asset.getBoundingBox();
- this.assetRoot = this.asset.getRoot();
- this.unitCubeTransform = Filament.fitIntoUnitCube(aabb, zoffset);
-
- const basePath = '' + new URL(this.src, document.location);
-
- this.asset.loadResources(() => {
- this.animator = this.asset.getInstance().getAnimator();
- this.animationStartTime = Date.now();
- this._applyMaterialVariant();
- }, null, basePath);
-
- this._updateOverlay();
- });
- }
-
- _updateAsset() {
- // Invoke the first glTF animation if it exists.
- if (this.animator) {
- if (this.animator.getAnimationCount() > 0) {
- const ms = Date.now() - this.animationStartTime;
- this.animator.applyAnimation(0, ms / 1000);
- }
- this.animator.updateBoneMatrices();
- }
-
- // Apply the root transform of the model.
- const tcm = this.engine.getTransformManager();
- const inst = tcm.getInstance(this.assetRoot);
- let rootTransform = this.unitCubeTransform;
- if (this.trackball) {
- rootTransform = Filament.multiplyMatrices(rootTransform, this.trackball.getMatrix());
- }
- tcm.setTransform(inst, rootTransform);
- inst.delete();
-
- // Add renderable entities to the scene as they become ready.
- while (true) {
- const entity = this.asset.popRenderable();
- if (entity.getId() == 0) {
- entity.delete();
- break;
- }
- this.scene.addEntity(entity);
- entity.delete();
- }
- }
-
- _renderFrame() {
- // Apply transforms and add entities to the scene.
- if (this.asset) {
- this._updateAsset();
- }
-
- // Render the view and update the Filament engine.
- if (this.renderer.beginFrame(this.swapChain)) {
- this.renderer.renderView(this.view);
- this.renderer.endFrame();
- }
- this.engine.execute();
-
- window.requestAnimationFrame(this._renderFrame.bind(this));
- }
-
- _applyMaterialVariant() {
- if (!this.hasAttribute("materialVariant")) {
- return;
- }
- const instance = this.asset.getInstance();
- const names = instance.getMaterialVariantNames();
- const index = this.materialVariant;
- if (index < 0 || index >= names.length) {
- console.error(`Material variant ${index} does not exist in this asset.`);
- return;
- }
- console.info(this.src, `Applying material variant: ${names[index]}`);
- instance.applyMaterialVariant(index);
- }
-}
-
-customElements.define("filament-viewer", FilamentViewer);
diff --git a/web/filament-js/filament-viewer.js b/web/filament-js/filament-viewer.js
new file mode 120000
index 00000000000..82babce978e
--- /dev/null
+++ b/web/filament-js/filament-viewer.js
@@ -0,0 +1 @@
+../../docs/viewer/filament-viewer.js
\ No newline at end of file