@openhps/core
Version:
Open Hybrid Positioning System - Core component
342 lines (311 loc) • 11 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
const refreshUniforms = ['alphaMap', 'alphaTest', 'anisotropy', 'anisotropyMap', 'anisotropyRotation', 'aoMap', 'attenuationColor', 'attenuationDistance', 'bumpMap', 'clearcoat', 'clearcoatMap', 'clearcoatNormalMap', 'clearcoatNormalScale', 'clearcoatRoughness', 'color', 'dispersion', 'displacementMap', 'emissive', 'emissiveMap', 'envMap', 'gradientMap', 'ior', 'iridescence', 'iridescenceIOR', 'iridescenceMap', 'iridescenceThicknessMap', 'lightMap', 'map', 'matcap', 'metalness', 'metalnessMap', 'normalMap', 'normalScale', 'opacity', 'roughness', 'roughnessMap', 'sheen', 'sheenColor', 'sheenColorMap', 'sheenRoughnessMap', 'shininess', 'specular', 'specularColor', 'specularColorMap', 'specularIntensity', 'specularIntensityMap', 'specularMap', 'thickness', 'transmission', 'transmissionMap'];
/**
* This class is used by {@link WebGPURenderer} as management component.
* It's primary purpose is to determine whether render objects require a
* refresh right before they are going to be rendered or not.
*/
class NodeMaterialObserver {
/**
* Constructs a new node material observer.
*
* @param {NodeBuilder} builder - The node builder.
*/
constructor(builder) {
/**
* A node material can be used by more than one render object so the
* monitor must maintain a list of render objects.
*
* @type {WeakMap<RenderObject,Object>}
*/
this.renderObjects = new WeakMap();
/**
* Whether the material uses node objects or not.
*
* @type {boolean}
*/
this.hasNode = this.containsNode(builder);
/**
* Whether the node builder's 3D object is animated or not.
*
* @type {boolean}
*/
this.hasAnimation = builder.object.isSkinnedMesh === true;
/**
* A list of all possible material uniforms
*
* @type {Array<string>}
*/
this.refreshUniforms = refreshUniforms;
/**
* Holds the current render ID from the node frame.
*
* @type {number}
* @default 0
*/
this.renderId = 0;
}
/**
* Returns `true` if the given render object is verified for the first time of this observer.
*
* @param {RenderObject} renderObject - The render object.
* @return {boolean} Whether the given render object is verified for the first time of this observer.
*/
firstInitialization(renderObject) {
const hasInitialized = this.renderObjects.has(renderObject);
if (hasInitialized === false) {
this.getRenderObjectData(renderObject);
return true;
}
return false;
}
/**
* Returns monitoring data for the given render object.
*
* @param {RenderObject} renderObject - The render object.
* @return {Object} The monitoring data.
*/
getRenderObjectData(renderObject) {
let data = this.renderObjects.get(renderObject);
if (data === undefined) {
const {
geometry,
material,
object
} = renderObject;
data = {
material: this.getMaterialData(material),
geometry: {
id: geometry.id,
attributes: this.getAttributesData(geometry.attributes),
indexVersion: geometry.index ? geometry.index.version : null,
drawRange: {
start: geometry.drawRange.start,
count: geometry.drawRange.count
}
},
worldMatrix: object.matrixWorld.clone()
};
if (object.center) {
data.center = object.center.clone();
}
if (object.morphTargetInfluences) {
data.morphTargetInfluences = object.morphTargetInfluences.slice();
}
if (renderObject.bundle !== null) {
data.version = renderObject.bundle.version;
}
if (data.material.transmission > 0) {
const {
width,
height
} = renderObject.context;
data.bufferWidth = width;
data.bufferHeight = height;
}
this.renderObjects.set(renderObject, data);
}
return data;
}
/**
* Returns an attribute data structure holding the attributes versions for
* monitoring.
*
* @param {Object} attributes - The geometry attributes.
* @return {Object} An object for monitoring the versions of attributes.
*/
getAttributesData(attributes) {
const attributesData = {};
for (const name in attributes) {
const attribute = attributes[name];
attributesData[name] = {
version: attribute.version
};
}
return attributesData;
}
/**
* Returns `true` if the node builder's material uses
* node properties.
*
* @param {NodeBuilder} builder - The current node builder.
* @return {boolean} Whether the node builder's material uses node properties or not.
*/
containsNode(builder) {
const material = builder.material;
for (const property in material) {
if (material[property] && material[property].isNode) return true;
}
if (builder.renderer.nodes.modelViewMatrix !== null || builder.renderer.nodes.modelNormalViewMatrix !== null) return true;
return false;
}
/**
* Returns a material data structure holding the material property values for
* monitoring.
*
* @param {Material} material - The material.
* @return {Object} An object for monitoring material properties.
*/
getMaterialData(material) {
const data = {};
for (const property of this.refreshUniforms) {
const value = material[property];
if (value === null || value === undefined) continue;
if (typeof value === 'object' && value.clone !== undefined) {
if (value.isTexture === true) {
data[property] = {
id: value.id,
version: value.version
};
} else {
data[property] = value.clone();
}
} else {
data[property] = value;
}
}
return data;
}
/**
* Returns `true` if the given render object has not changed its state.
*
* @param {RenderObject} renderObject - The render object.
* @return {boolean} Whether the given render object has changed its state or not.
*/
equals(renderObject) {
const {
object,
material,
geometry
} = renderObject;
const renderObjectData = this.getRenderObjectData(renderObject);
// world matrix
if (renderObjectData.worldMatrix.equals(object.matrixWorld) !== true) {
renderObjectData.worldMatrix.copy(object.matrixWorld);
return false;
}
// material
const materialData = renderObjectData.material;
for (const property in materialData) {
const value = materialData[property];
const mtlValue = material[property];
if (value.equals !== undefined) {
if (value.equals(mtlValue) === false) {
value.copy(mtlValue);
return false;
}
} else if (mtlValue.isTexture === true) {
if (value.id !== mtlValue.id || value.version !== mtlValue.version) {
value.id = mtlValue.id;
value.version = mtlValue.version;
return false;
}
} else if (value !== mtlValue) {
materialData[property] = mtlValue;
return false;
}
}
if (materialData.transmission > 0) {
const {
width,
height
} = renderObject.context;
if (renderObjectData.bufferWidth !== width || renderObjectData.bufferHeight !== height) {
renderObjectData.bufferWidth = width;
renderObjectData.bufferHeight = height;
return false;
}
}
// geometry
const storedGeometryData = renderObjectData.geometry;
const attributes = geometry.attributes;
const storedAttributes = storedGeometryData.attributes;
const storedAttributeNames = Object.keys(storedAttributes);
const currentAttributeNames = Object.keys(attributes);
if (storedGeometryData.id !== geometry.id) {
storedGeometryData.id = geometry.id;
return false;
}
if (storedAttributeNames.length !== currentAttributeNames.length) {
renderObjectData.geometry.attributes = this.getAttributesData(attributes);
return false;
}
// compare each attribute
for (const name of storedAttributeNames) {
const storedAttributeData = storedAttributes[name];
const attribute = attributes[name];
if (attribute === undefined) {
// attribute was removed
delete storedAttributes[name];
return false;
}
if (storedAttributeData.version !== attribute.version) {
storedAttributeData.version = attribute.version;
return false;
}
}
// check index
const index = geometry.index;
const storedIndexVersion = storedGeometryData.indexVersion;
const currentIndexVersion = index ? index.version : null;
if (storedIndexVersion !== currentIndexVersion) {
storedGeometryData.indexVersion = currentIndexVersion;
return false;
}
// check drawRange
if (storedGeometryData.drawRange.start !== geometry.drawRange.start || storedGeometryData.drawRange.count !== geometry.drawRange.count) {
storedGeometryData.drawRange.start = geometry.drawRange.start;
storedGeometryData.drawRange.count = geometry.drawRange.count;
return false;
}
// morph targets
if (renderObjectData.morphTargetInfluences) {
let morphChanged = false;
for (let i = 0; i < renderObjectData.morphTargetInfluences.length; i++) {
if (renderObjectData.morphTargetInfluences[i] !== object.morphTargetInfluences[i]) {
morphChanged = true;
}
}
if (morphChanged) return true;
}
// center
if (renderObjectData.center) {
if (renderObjectData.center.equals(object.center) === false) {
renderObjectData.center.copy(object.center);
return true;
}
}
// bundle
if (renderObject.bundle !== null) {
renderObjectData.version = renderObject.bundle.version;
}
return true;
}
/**
* Checks if the given render object requires a refresh.
*
* @param {RenderObject} renderObject - The render object.
* @param {NodeFrame} nodeFrame - The current node frame.
* @return {boolean} Whether the given render object requires a refresh or not.
*/
needsRefresh(renderObject, nodeFrame) {
if (this.hasNode || this.hasAnimation || this.firstInitialization(renderObject)) return true;
const {
renderId
} = nodeFrame;
if (this.renderId !== renderId) {
this.renderId = renderId;
return true;
}
const isStatic = renderObject.object.static === true;
const isBundle = renderObject.bundle !== null && renderObject.bundle.static === true && this.getRenderObjectData(renderObject).version === renderObject.bundle.version;
if (isStatic || isBundle) return false;
const notEqual = this.equals(renderObject) !== true;
return notEqual;
}
}
var _default = exports.default = NodeMaterialObserver;