- For completion, FilamentAsset now has getRenderableEntities(). This is similar to sister methods getLightEntities() and getCameraEntities() except there is no need to store a separate array. - The web helmet demo does not need to enable shadows, they are already enabled. - The ViewerGui populateAsset() method was doing two things that are now decoupled for clarity: setAsset() and populateAsset(). - The updateRootTransform() method is now called only when the autoscale checkbox is toggled, instead of every frame. - The getFooEntities() methods in Java now skip doing work for empty lists. Actually these should not return arrays at all, but let's fix that later, since it will break backwards compatibility.
180 lines
6.3 KiB
HTML
180 lines
6.3 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<title>FlightHelmet</title>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width,user-scalable=no,initial-scale=1">
|
|
<link href="favicon.png" rel="icon" type="image/x-icon" />
|
|
<style>
|
|
html, body { height: 100%; }
|
|
body { margin: 0; overflow: hidden; }
|
|
#container { position: relative; height: 100%; }
|
|
canvas { position: absolute; width: 100%; height: 100%; }
|
|
#messages { position: absolute; width: 100%; height: 100%; padding-left: 10px; color:white; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div id="container">
|
|
<canvas></canvas>
|
|
<pre id="messages"></pre>
|
|
</div>
|
|
<script src="filament.js"></script>
|
|
<script src="gl-matrix-min.js"></script>
|
|
<script src="https://unpkg.com/gltumble"></script>
|
|
<script>
|
|
|
|
const env = 'default_env';
|
|
const ibl_url = `${env}/${env}_ibl.ktx`;
|
|
const sky_url = `${env}/${env}_skybox.ktx`;
|
|
const mesh_url = 'FlightHelmet.gltf';
|
|
|
|
Filament.init([mesh_url, ibl_url, sky_url], () => {
|
|
window.gltfio = Filament.gltfio;
|
|
window.Fov = Filament.Camera$Fov;
|
|
window.LightType = Filament.LightManager$Type;
|
|
window.IndirectLight = Filament.IndirectLight;
|
|
window.app = new App(document.getElementsByTagName('canvas')[0]);
|
|
});
|
|
|
|
class App {
|
|
constructor(canvas) {
|
|
this.canvas = canvas;
|
|
const engine = this.engine = Filament.Engine.create(this.canvas);
|
|
const scene = this.scene = engine.createScene();
|
|
this.trackball = new Trackball(canvas, {startSpin: 0.035});
|
|
|
|
const indirectLight = this.ibl = engine.createIblFromKtx1(ibl_url);
|
|
this.scene.setIndirectLight(indirectLight);
|
|
|
|
const iblDirection = IndirectLight.getDirectionEstimate(indirectLight.shfloats);
|
|
const iblColor = IndirectLight.getColorEstimate(indirectLight.shfloats, iblDirection);
|
|
const iblIntensity = 20000;
|
|
|
|
indirectLight.setIntensity(iblIntensity);
|
|
|
|
// Rotate the IBL so that a bright light is behind the helmet, to show off bloom.
|
|
const mat = [];
|
|
const radians = 3.14;
|
|
mat3.fromRotation(mat, radians, [0, 1, 0]);
|
|
indirectLight.setRotation(mat);
|
|
|
|
const skybox = engine.createSkyFromKtx1(sky_url);
|
|
this.scene.setSkybox(skybox);
|
|
|
|
const sunlight = Filament.EntityManager.get().create();
|
|
Filament.LightManager.Builder(LightType.SUN)
|
|
.color(iblColor.slice(0, 3))
|
|
.intensity(iblColor[3] * iblIntensity)
|
|
.direction(iblDirection)
|
|
.sunAngularRadius(1.9)
|
|
.castShadows(true)
|
|
.sunHaloSize(10.0)
|
|
.sunHaloFalloff(80.0)
|
|
.build(engine, sunlight);
|
|
this.scene.addEntity(sunlight);
|
|
|
|
const loader = this.loader = engine.createAssetLoader();
|
|
|
|
this.allowRefresh = false;
|
|
const asset = this.asset = loader.createAssetFromJson(mesh_url);
|
|
this.assetRoot = this.asset.getRoot();
|
|
|
|
const messages = document.getElementById('messages');
|
|
|
|
// Crudely indicate progress by printing the URI of each resource as it is loaded.
|
|
const onFetched = (uri) => messages.innerText += `Downloaded ${uri}\n`;
|
|
|
|
const onDone = () => {
|
|
this.allowRefresh = true;
|
|
|
|
// Hide the HUD.
|
|
messages.remove();
|
|
};
|
|
asset.loadResources(onDone, onFetched);
|
|
|
|
const cameraEntity = Filament.EntityManager.get().create();
|
|
this.camera = engine.createCamera(cameraEntity);
|
|
|
|
const colorGrading = Filament.ColorGrading.Builder()
|
|
.toneMapping(Filament.ColorGrading$ToneMapping.ACES_LEGACY)
|
|
.build(engine);
|
|
|
|
this.swapChain = engine.createSwapChain();
|
|
this.renderer = engine.createRenderer();
|
|
this.view = engine.createView();
|
|
this.view.setVignetteOptions({ midPoint: 0.8, enabled: true });
|
|
|
|
this.view.setBloomOptions({ strength: 0.2, enabled: true });
|
|
|
|
this.view.setCamera(this.camera);
|
|
this.view.setScene(this.scene);
|
|
this.view.setColorGrading(colorGrading);
|
|
this.resize();
|
|
this.render = this.render.bind(this);
|
|
this.resize = this.resize.bind(this);
|
|
this.refresh = this.refresh.bind(this);
|
|
window.addEventListener('resize', this.resize);
|
|
window.addEventListener('dblclick', this.refresh);
|
|
window.requestAnimationFrame(this.render);
|
|
}
|
|
|
|
// Test for memory leaks by destroying and recreating the asset.
|
|
refresh() {
|
|
if (!this.allowRefresh) {
|
|
console.warn('Refresh not allowed while the model is still loading.');
|
|
return;
|
|
}
|
|
console.info('Refreshing...');
|
|
this.allowRefresh = false;
|
|
this.scene.removeEntities(this.asset.getEntities());
|
|
this.loader.destroyAsset(this.asset);
|
|
this.asset = this.loader.createAssetFromJson(mesh_url);
|
|
const onDone = () => { this.allowRefresh = true; }
|
|
this.asset.loadResources(onDone);
|
|
}
|
|
|
|
render() {
|
|
// Spin the model according to the trackball controller.
|
|
const tcm = this.engine.getTransformManager();
|
|
const inst = tcm.getInstance(this.assetRoot);
|
|
tcm.setTransform(inst, this.trackball.getMatrix());
|
|
inst.delete();
|
|
|
|
// Gradually add renderables to the scene as their textures become ready.
|
|
let entity;
|
|
const popRenderable = () => {
|
|
entity = this.asset.popRenderable();
|
|
return entity.getId() != 0;
|
|
}
|
|
while (popRenderable()) {
|
|
this.scene.addEntity(entity);
|
|
entity.delete();
|
|
}
|
|
entity.delete();
|
|
|
|
// Render the scene and request the next animation frame.
|
|
if (this.renderer.beginFrame(this.swapChain)) {
|
|
this.renderer.renderView(this.view);
|
|
this.renderer.endFrame();
|
|
}
|
|
this.engine.execute();
|
|
|
|
window.requestAnimationFrame(this.render);
|
|
}
|
|
|
|
resize() {
|
|
const dpr = window.devicePixelRatio;
|
|
const width = this.canvas.width = window.innerWidth * dpr;
|
|
const height = this.canvas.height = window.innerHeight * dpr;
|
|
this.view.setViewport([0, 0, width, height]);
|
|
const y = -0.125, eye = [0, y, 2], 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(30, aspect, 1.0, 10.0, fov);
|
|
}
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|