Files
filament/web/samples/helmet.html
Philip Rideout f4004cf047 JavaScript bindings: fix a frequent 4-byte memory leak.
In retrospect, I should've exposed ints to JS instead of entities.
Making this fix is already a TODO, but it will be a tedious change. For
now, this PR fixes two issues that were causing tiny but frequent memory
leaks, according to the emscripten memory leak widget.

(1) Do not include Entity in the list of classes that should only be
passed around by pointer.

(2) Clients should always call delete() on returned Entities.
2020-08-06 14:45:28 -07:00

175 lines
6.2 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.createIblFromKtx(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);
const skybox = engine.createSkyFromKtx(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;
// Enable shadows on every renderable.
const entities = asset.getEntities();
const rm = engine.getRenderableManager();
for (const entity of entities) {
const instance = rm.getInstance(entity);
rm.setCastShadows(instance, true);
instance.delete();
}
// 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.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();
// Add renderable entities to the scene as they 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.
this.renderer.render(this.swapChain, this.view);
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>