Files
filament/web/filament-js/extensions.js
Pixelflinger dc16eb04f8 rename .blurScale to .cocScale in dof options
.blurScale was in fact a scale factor applied to the circle of
confusion (which makes it indeed a "blur scale", but let's use
a more precise language here). 

Update comments to show how to  use .cocScale to control the DoF
effect independently from the camera aperture, which can be useful for
artistic reasons.
2020-09-14 22:22:52 -07:00

572 lines
23 KiB
JavaScript

/*
* 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.
*/
// Private utility that converts an asset string or Uint8Array into a low-level buffer descriptor.
// Note that the low-level buffer descriptor must be manually deleted.
function getBufferDescriptor(buffer) {
if ('string' == typeof buffer || buffer instanceof String) {
buffer = Filament.assets[buffer];
}
if (buffer.buffer instanceof ArrayBuffer) {
buffer = Filament.Buffer(buffer);
}
return buffer;
}
Filament.vectorToArray = function(vector) {
const result = [];
for (let i = 0; i < vector.size(); i++) {
result.push(vector.get(i));
}
return result;
};
Filament.shadowOptions = function(overrides) {
const options = {
mapSize: 1024,
shadowCascades: 1,
constantBias: 0.001,
normalBias: 1.0,
shadowFar: 0.0,
shadowNearHint: 1.0,
shadowFarHint: 100.0,
stable: false,
polygonOffsetConstant: 0.5,
polygonOffsetSlope: 2.0,
screenSpaceContactShadows: false,
stepCount: 8,
maxShadowDistance: 0.3
};
return Object.assign(options, overrides);
};
Filament.loadClassExtensions = function() {
/// Engine ::core class::
/// create ::static method:: Creates an Engine instance for the given canvas.
/// canvas ::argument:: the canvas DOM element
/// options ::argument:: optional WebGL 2.0 context configuration
/// ::retval:: an instance of [Engine]
Filament.Engine.create = function(canvas, options) {
const defaults = {
majorVersion: 2,
minorVersion: 0,
antialias: false,
depth: true,
alpha: false
};
options = Object.assign(defaults, options);
// Create the WebGL 2.0 context.
const ctx = canvas.getContext("webgl2", options);
Filament.glOptions = options;
Filament.glContext = ctx;
// Enable all desired extensions by calling getExtension on each one.
ctx.getExtension('WEBGL_compressed_texture_s3tc');
ctx.getExtension('WEBGL_compressed_texture_astc');
ctx.getExtension('WEBGL_compressed_texture_etc');
// Register the GL context with emscripten and create the Engine.
return Filament.Engine._create();
};
/// createMaterial ::method::
/// package ::argument:: asset string, or Uint8Array, or [Buffer] with filamat contents
/// ::retval:: an instance of [createMaterial]
Filament.Engine.prototype.createMaterial = function(buffer) {
buffer = getBufferDescriptor(buffer);
const result = this._createMaterial(buffer);
buffer.delete();
return result;
};
/// createTextureFromKtx ::method:: Utility function that creates a [Texture] from a KTX file.
/// buffer ::argument:: asset string, or Uint8Array, or [Buffer] with KTX file contents
/// options ::argument:: Options dictionary.
/// ::retval:: [Texture]
Filament.Engine.prototype.createTextureFromKtx = function(buffer, options) {
buffer = getBufferDescriptor(buffer);
const result = Filament._createTextureFromKtx(buffer, this, options);
buffer.delete();
return result;
};
/// createIblFromKtx ::method:: Utility that creates an [IndirectLight] from a KTX file.
/// NOTE: To prevent a leak, please be sure to destroy the associated reflections texture.
/// buffer ::argument:: asset string, or Uint8Array, or [Buffer] with KTX file contents
/// options ::argument:: Options dictionary.
/// ::retval:: [IndirectLight]
Filament.Engine.prototype.createIblFromKtx = function(buffer, options) {
buffer = getBufferDescriptor(buffer);
const result = Filament._createIblFromKtx(buffer, this, options);
buffer.delete();
return result;
};
/// createSkyFromKtx ::method:: Utility function that creates a [Skybox] from a KTX file.
/// NOTE: To prevent a leak, please be sure to destroy the associated texture.
/// buffer ::argument:: asset string, or Uint8Array, or [Buffer] with KTX file contents
/// options ::argument:: Options dictionary.
/// ::retval:: [Skybox]
Filament.Engine.prototype.createSkyFromKtx = function(buffer, options) {
const skytex = this.createTextureFromKtx(buffer, options);
return Filament.Skybox.Builder().environment(skytex).build(this);
};
/// createTextureFromPng ::method:: Creates a 2D [Texture] from the raw contents of a PNG file.
/// buffer ::argument:: asset string, or Uint8Array, or [Buffer] with PNG file contents
/// options ::argument:: object with optional `srgb`, `noalpha`, and `nomips` keys.
/// ::retval:: [Texture]
Filament.Engine.prototype.createTextureFromPng = function(buffer, options) {
buffer = getBufferDescriptor(buffer);
const result = Filament._createTextureFromImageFile(buffer, this, options);
buffer.delete();
return result;
};
/// createTextureFromJpeg ::method:: Creates a 2D [Texture] from the contents of a JPEG file.
/// buffer ::argument:: asset string, or Uint8Array, or [Buffer] with JPEG file contents
/// options ::argument:: JavaScript object with optional `srgb` and `nomips` keys.
/// ::retval:: [Texture]
Filament.Engine.prototype.createTextureFromJpeg = function(buffer, options) {
buffer = getBufferDescriptor(buffer);
const result = Filament._createTextureFromImageFile(buffer, this, options);
buffer.delete();
return result;
};
/// loadFilamesh ::method:: Consumes the contents of a filamesh file and creates a renderable.
/// buffer ::argument:: asset string, or Uint8Array, or [Buffer] with filamesh contents
/// definstance ::argument:: Optional default [MaterialInstance]
/// matinstances ::argument:: Optional in-out object that gets populated with a \
/// name-to-[MaterialInstance] mapping. Clients can also optionally provide individual \
/// material instances using this argument.
/// ::retval:: JavaScript object with keys `renderable`, `vertexBuffer`, and `indexBuffer`. \
/// These are of type [Entity], [VertexBuffer], and [IndexBuffer].
Filament.Engine.prototype.loadFilamesh = function(buffer, definstance, matinstances) {
buffer = getBufferDescriptor(buffer);
const result = Filament._loadFilamesh(this, buffer, definstance, matinstances);
buffer.delete();
return result;
};
/// createAssetLoader ::method::
/// ::retval:: an instance of [AssetLoader]
/// Clients should create only one asset loader for the lifetime of their app, this prevents
/// memory leaks and duplication of Material objects.
Filament.Engine.prototype.createAssetLoader = function() {
const materials = new Filament.gltfio$UbershaderLoader(this);
return new Filament.gltfio$AssetLoader(this, materials);
};
/// addEntities ::method::
/// entities ::argument:: array of entities
/// This method is equivalent to calling `addEntity` on each item in the array.
Filament.Scene.prototype.addEntities = function(entities) {
const vector = new Filament.EntityVector();
for (const entity of entities) {
vector.push_back(entity);
}
this._addEntities(vector);
};
/// removeEntities ::method::
/// entities ::argument:: array of entities
/// This method is equivalent to calling `remove` on each item in the array.
Filament.Scene.prototype.removeEntities = function(entities) {
const vector = new Filament.EntityVector();
for (const entity of entities) {
vector.push_back(entity);
}
this._removeEntities(vector);
};
/// setShadowOptions ::method::
/// instance ::argument:: Instance of a light component obtained from `getInstance`.
/// overrides ::argument:: Dictionary with one or more of the following properties: \
/// mapSize, shadowCascades, constantBias, normalBias, shadowFar, shadowNearHint, \
/// shadowFarHint, stable, polygonOffsetConstant, polygonOffsetSlope, \
// screenSpaceContactShadows, stepCount, maxShadowDistance.
Filament.LightManager.prototype.setShadowOptions = function(instance, overrides) {
this._setShadowOptions(instance, Filament.shadowOptions(overrides));
};
/// setClearOptions ::method::
/// overrides ::argument:: Dictionary with one or more of the following properties: \
/// clearColor, clear, discard.
Filament.Renderer.prototype.setClearOptions = function(overrides) {
const options = {
clearColor: [0, 0, 0, 0],
clear: false,
discard: true
};
Object.assign(options, overrides);
this._setClearOptions(options);
};
/// setAmbientOcclusionOptions ::method::
/// overrides ::argument:: Dictionary with one or more of the following properties: \
/// radius, power, bias, resolution, intensity, quality.
Filament.View.prototype.setAmbientOcclusionOptions = function(overrides) {
const options = {
radius: 0.3,
power: 1.0,
bias: 0.0005,
resolution: 0.5,
intensity: 1.0,
quality: Filament.View$QualityLevel.LOW
};
Object.assign(options, overrides);
this._setAmbientOcclusionOptions(options);
};
/// setDepthOfFieldOptions ::method::
/// overrides ::argument:: Dictionary with one or more of the following properties: \
/// focusDistance, cocScale, maxApertureDiameter, enabled.
Filament.View.prototype.setDepthOfFieldOptions = function(overrides) {
const options = {
focusDistance: 10.0,
cocScale: 1.0,
maxApertureDiameter: 0.01,
enabled: false
};
Object.assign(options, overrides);
this._setDepthOfFieldOptions(options);
};
/// setBloomOptions ::method::
/// overrides ::argument:: Dictionary with one or more of the following properties: \
/// dirtStrength, strength, resolution, anomorphism, levels, blendMode, threshold, enabled.
/// NOTE: dirt texture is not yet supported in the JavaScript API.
Filament.View.prototype.setBloomOptions = function(overrides) {
const options = {
dirtStrength: 0.2,
strength: 0.10,
resolution: 360,
anamorphism: 1.0,
levels: 6,
blendMode: Filament.View$BloomOptions$BlendMode.ADD,
threshold: true,
enabled: false,
dirt: null
};
Object.assign(options, overrides);
this._setBloomOptions(options);
};
/// setFogOptions ::method::
/// overrides ::argument:: Dictionary with one or more of the following properties: \
/// distance, maximumOpacity, height, heightFalloff, color, density, inScatteringStart,
/// inScatteringSize, fogColorFromIbl, enabled.
Filament.View.prototype.setFogOptions = function(overrides) {
const options = {
distance: 0.0,
maximumOpacity: 1.0,
height: 0.0,
heightFalloff: 1.0,
color: .5,
density: 0.1,
inScatteringStart: 0.0,
inScatteringSize: -1.0,
fogColorFromIbl: false,
enabled: false
};
Object.assign(options, overrides);
this._setFogOptions(options);
};
/// setVignetteOptions ::method::
/// overrides ::argument:: Dictionary with one or more of the following properties: \
/// midPoint, roundness, feather, color, enabled.
Filament.View.prototype.setVignetteOptions = function(overrides) {
const options = {
midPoint: 0.5,
roundness: 0.5,
feather: 0.5,
color: [0, 0, 0, 1],
enabled: false
};
Object.assign(options, overrides);
this._setVignetteOptions(options);
};
/// VertexBuffer ::core class::
/// setBufferAt ::method::
/// engine ::argument:: [Engine]
/// bufferIndex ::argument:: non-negative integer
/// buffer ::argument:: asset string, or Uint8Array, or [Buffer]
/// byteOffset ::argument:: non-negative integer
Filament.VertexBuffer.prototype.setBufferAt = function(engine, bufferIndex, buffer, byteOffset = 0) {
buffer = getBufferDescriptor(buffer);
this._setBufferAt(engine, bufferIndex, buffer, byteOffset);
buffer.delete();
};
/// IndexBuffer ::core class::
/// setBuffer ::method::
/// engine ::argument:: [Engine]
/// buffer ::argument:: asset string, or Uint8Array, or [Buffer]
/// byteOffset ::argument:: non-negative integer
Filament.IndexBuffer.prototype.setBuffer = function(engine, buffer, byteOffset = 0) {
buffer = getBufferDescriptor(buffer);
this._setBuffer(engine, buffer, byteOffset);
buffer.delete();
};
Filament.LightManager$Builder.prototype.shadowOptions = function(overrides) {
return this._shadowOptions(Filament.shadowOptions(overrides));
};
Filament.RenderableManager$Builder.prototype.build =
Filament.LightManager$Builder.prototype.build =
function(engine, entity) {
const result = this._build(engine, entity);
this.delete();
return result;
};
Filament.ColorGrading$Builder.prototype.build =
Filament.RenderTarget$Builder.prototype.build =
Filament.VertexBuffer$Builder.prototype.build =
Filament.IndexBuffer$Builder.prototype.build =
Filament.Texture$Builder.prototype.build =
Filament.IndirectLight$Builder.prototype.build =
Filament.Skybox$Builder.prototype.build =
function(engine) {
const result = this._build(engine);
this.delete();
return result;
};
Filament.KtxBundle.prototype.getBlob = function(index) {
const blob = this._getBlob(index);
const result = blob.getBytes();
blob.delete();
return result;
}
Filament.KtxBundle.prototype.getCubeBlob = function(miplevel) {
const blob = this._getCubeBlob(miplevel);
const result = blob.getBytes();
blob.delete();
return result;
}
Filament.Texture.prototype.setImage = function(engine, level, pbd) {
this._setImage(engine, level, pbd);
pbd.delete();
}
Filament.Texture.prototype.setImageCube = function(engine, level, pbd) {
this._setImageCube(engine, level, pbd);
pbd.delete();
}
Filament.SurfaceOrientation$Builder.prototype.normals = function(buffer, stride = 0) {
buffer = new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength);
this.norPointer = Filament._malloc(buffer.byteLength);
Filament.HEAPU8.set(buffer, this.norPointer);
this._normals(this.norPointer, stride);
};
Filament.SurfaceOrientation$Builder.prototype.uvs = function(buffer, stride = 0) {
buffer = new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength);
this.uvsPointer = Filament._malloc(buffer.byteLength);
Filament.HEAPU8.set(buffer, this.uvsPointer);
this._uvs(this.uvsPointer, stride);
};
Filament.SurfaceOrientation$Builder.prototype.positions = function(buffer, stride = 0) {
buffer = new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength);
this.posPointer = Filament._malloc(buffer.byteLength);
Filament.HEAPU8.set(buffer, this.posPointer);
this._positions(this.posPointer, stride);
};
Filament.SurfaceOrientation$Builder.prototype.triangles16 = function(buffer, stride = 0) {
buffer = new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength);
this.t16Pointer = Filament._malloc(buffer.byteLength);
Filament.HEAPU8.set(buffer, this.t16Pointer);
this._triangles16(this.t16Pointer, stride);
};
Filament.SurfaceOrientation$Builder.prototype.triangles32 = function(buffer, stride = 0) {
buffer = new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength);
this.t32Pointer = Filament._malloc(buffer.byteLength);
Filament.HEAPU8.set(buffer, this.t32Pointer);
this._triangles32(this.t32Pointer, stride);
};
Filament.SurfaceOrientation$Builder.prototype.build = function() {
const result = this._build();
this.delete();
if ('norPointer' in this) Filament._free(this.norPointer);
if ('uvsPointer' in this) Filament._free(this.uvsPointer);
if ('posPointer' in this) Filament._free(this.posPointer);
if ('t16Pointer' in this) Filament._free(this.t16Pointer);
if ('t32Pointer' in this) Filament._free(this.t32Pointer);
return result;
};
Filament.SurfaceOrientation.prototype.getQuats = function(nverts) {
const attribType = Filament.VertexBuffer$AttributeType.SHORT4;
const quatsBufferSize = 8 * nverts;
const quatsBuffer = Filament._malloc(quatsBufferSize);
this._getQuats(quatsBuffer, nverts, attribType);
const arrayBuffer = Filament.HEAPU8.subarray(quatsBuffer, quatsBuffer + quatsBufferSize).slice().buffer;
Filament._free(quatsBuffer);
return new Int16Array(arrayBuffer);
};
Filament.gltfio$AssetLoader.prototype.createAssetFromJson = function(buffer) {
if ('string' == typeof buffer && buffer.endsWith('.glb')) {
console.error('Please use createAssetFromBinary for glb files.');
}
buffer = getBufferDescriptor(buffer);
const result = this._createAssetFromJson(buffer);
buffer.delete();
return result;
};
Filament.gltfio$AssetLoader.prototype.createAssetFromBinary = function(buffer) {
if ('string' == typeof buffer && buffer.endsWith('.gltf')) {
console.error('Please use createAssetFromJson for gltf files.');
}
buffer = getBufferDescriptor(buffer);
const result = this._createAssetFromBinary(buffer);
buffer.delete();
return result;
};
Filament.gltfio$AssetLoader.prototype.createInstancedAsset = function(buffer, instances) {
buffer = getBufferDescriptor(buffer);
const asset = this._createInstancedAsset(buffer, instances.length);
buffer.delete();
const instancesVector = asset._getAssetInstances();
for (let i = 0; i < instancesVector.size(); i++) {
instances[i] = instancesVector.get(i);
}
return asset;
};
// See the C++ documentation for ResourceLoader and AssetLoader. The JavaScript API differs in
// that it takes two optional callbacks:
//
// - onDone is called after all resources have been downloaded and decoded.
// - onFetched is called after each resource has finished downloading.
//
// Takes an optional base path for resolving the URI strings in the glTF file, which is
// typically the path to the parent glTF file. The given base path cannot itself be a relative
// URL, but clients can do the following to resolve a relative URL:
// const basePath = '' + new URL(myRelativeUrl, document.location);
// If the given base path is null, document.location is used as the base.
//
// The optional asyncInterval argument allows clients to control how decoding is amortized
// over time. It represents the number of milliseconds between each texture decoding task.
//
// The optional config argument is an object with boolean fields `normalizeSkinningWeights` and
// `recomputeBoundingBoxes`.
Filament.gltfio$FilamentAsset.prototype.loadResources = function(onDone, onFetched, basePath,
asyncInterval, config) {
const asset = this;
const engine = this.getEngine();
const names = this.getResourceUris();
const interval = asyncInterval || 30;
const defaults = {
normalizeSkinningWeights: true,
recomputeBoundingBoxes: false
};
config = Object.assign(defaults, config || {});
basePath = basePath || document.location;
onFetched = onFetched || ((name) => {});
onDone = onDone || (() => {});
// Construct the set of URI strings to fetch.
const urlset = new Set();
const urlToName = {};
for (let i = 0; i < names.size(); i++) {
const name = names.get(i);
if (name) {
const url = '' + new URL(name, basePath);
urlToName[url] = name;
urlset.add(url);
}
}
// Construct a resource loader and start decoding after all textures are fetched.
const resourceLoader = new Filament.gltfio$ResourceLoader(engine,
config.normalizeSkinningWeights,
config.recomputeBoundingBoxes);
const onComplete = () => {
resourceLoader.asyncBeginLoad(asset);
// NOTE: This decodes in the wasm layer instead of using Canvas2D, which allows Filament
// to have more control (handling of alpha, srgb, etc) and improves parity with native
// platforms. In the future we may wish to offload this to web workers.
// Decode a single PNG or JPG every 30 milliseconds, or at the specified interval.
const timer = setInterval(() => {
resourceLoader.asyncUpdateLoad();
const progress = resourceLoader.asyncGetLoadProgress();
if (progress >= 1) {
clearInterval(timer);
resourceLoader.delete();
onDone();
}
}, interval);
};
if (urlset.size == 0) {
onComplete();
return;
}
// Begin downloading all external resources.
Filament.fetch(Array.from(urlset), onComplete, function(url) {
const buffer = getBufferDescriptor(url);
const name = urlToName[url];
resourceLoader.addResourceData(name, buffer);
buffer.delete();
onFetched(name);
});
};
Filament.gltfio$FilamentAsset.prototype.getEntities = function() {
return Filament.vectorToArray(this._getEntities());
};
Filament.gltfio$FilamentAsset.prototype.getEntitiesByName = function(name) {
return Filament.vectorToArray(this._getEntitiesByName(name));
};
Filament.gltfio$FilamentAsset.prototype.getEntitiesByPrefix = function(prefix) {
return Filament.vectorToArray(this._getEntitiesByPrefix(prefix));
};
Filament.gltfio$FilamentAsset.prototype.getLightEntities = function() {
return Filament.vectorToArray(this._getLightEntities());
};
Filament.gltfio$FilamentAsset.prototype.getCameraEntities = function() {
return Filament.vectorToArray(this._getCameraEntities());
};
};