src/viewer/scene/model/layer/Layer.js
import {createRTCViewMat, math} from "../../math/index.js";
import {ENTITY_FLAGS} from "../ENTITY_FLAGS.js";
import {createLightSetup, createProgramVariablesState, setupTexture} from '../../webgl/WebGLRenderer.js';
import {LinearEncoding} from "../../constants/constants.js";
import { ColorProgram } from "./programs/ColorProgram.js";
import { ColorTextureProgram } from "./programs/ColorTextureProgram.js";
import { DepthProgram } from "./programs/DepthProgram.js";
import { EdgesProgram } from "./programs/EdgesProgram.js";
import { FlatColorProgram } from "./programs/FlatColorProgram.js";
import { OcclusionProgram } from "./programs/OcclusionProgram.js";
import { PBRProgram } from "./programs/PBRProgram.js";
import { PickDepthProgram } from "./programs/PickDepthProgram.js";
import { PickMeshProgram } from "./programs/PickMeshProgram.js";
import { PickNormalsProgram } from "./programs/PickNormalsProgram.js";
import { ShadowProgram } from "./programs/ShadowProgram.js";
import { SilhouetteProgram } from "./programs/SilhouetteProgram.js";
import { SnapProgram } from "./programs/SnapProgram.js";
const tempVec2 = math.vec2();
const tempVec3 = math.vec3();
const tempVec4 = math.vec4();
const tempMat4 = math.mat4();
const vec3zero = math.vec3([0,0,0]);
const RENDER_PASSES = {
// Skipped - suppress rendering
NOT_RENDERED: 0,
// Normal rendering - mutually exclusive modes
COLOR_OPAQUE: 1,
COLOR_TRANSPARENT: 2,
// Emphasis silhouette rendering - mutually exclusive modes
SILHOUETTE_HIGHLIGHTED: 3,
SILHOUETTE_SELECTED: 4,
SILHOUETTE_XRAYED: 5,
// Edges rendering - mutually exclusive modes
EDGES_COLOR_OPAQUE: 6,
EDGES_COLOR_TRANSPARENT: 7,
EDGES_HIGHLIGHTED: 8,
EDGES_SELECTED: 9,
EDGES_XRAYED: 10,
// Picking
PICK: 11
};
const safeInvVec3 = v => [ math.safeInv(v[0]), math.safeInv(v[1]), math.safeInv(v[2]) ];
export const isPerspectiveMatrix = (m) => `(${m}[2][3] == - 1.0)`;
export const getColSilhEdgePickFlags = (flags, transparent, hasEdges, scene, dst) => {
const visible = !!(flags & ENTITY_FLAGS.VISIBLE);
const xrayed = !!(flags & ENTITY_FLAGS.XRAYED);
const highlighted = !!(flags & ENTITY_FLAGS.HIGHLIGHTED);
const selected = !!(flags & ENTITY_FLAGS.SELECTED);
const edges = !!(flags & ENTITY_FLAGS.EDGES);
const pickable = !!(flags & ENTITY_FLAGS.PICKABLE);
const culled = !!(flags & ENTITY_FLAGS.CULLED);
let colorFlag;
if (!visible || culled || xrayed
|| (highlighted && !scene.highlightMaterial.glowThrough)
|| (selected && !scene.selectedMaterial.glowThrough)) {
colorFlag = RENDER_PASSES.NOT_RENDERED;
} else {
if (transparent) {
colorFlag = RENDER_PASSES.COLOR_TRANSPARENT;
} else {
colorFlag = RENDER_PASSES.COLOR_OPAQUE;
}
}
let silhouetteFlag;
if (!visible || culled) {
silhouetteFlag = RENDER_PASSES.NOT_RENDERED;
} else if (selected) {
silhouetteFlag = RENDER_PASSES.SILHOUETTE_SELECTED;
} else if (highlighted) {
silhouetteFlag = RENDER_PASSES.SILHOUETTE_HIGHLIGHTED;
} else if (xrayed) {
silhouetteFlag = RENDER_PASSES.SILHOUETTE_XRAYED;
} else {
silhouetteFlag = RENDER_PASSES.NOT_RENDERED;
}
let edgeFlag = 0;
if ((!hasEdges) || (!visible) || culled) {
edgeFlag = RENDER_PASSES.NOT_RENDERED;
} else if (selected) {
edgeFlag = RENDER_PASSES.EDGES_SELECTED;
} else if (highlighted) {
edgeFlag = RENDER_PASSES.EDGES_HIGHLIGHTED;
} else if (xrayed) {
edgeFlag = RENDER_PASSES.EDGES_XRAYED;
} else if (edges) {
if (transparent) {
edgeFlag = RENDER_PASSES.EDGES_COLOR_TRANSPARENT;
} else {
edgeFlag = RENDER_PASSES.EDGES_COLOR_OPAQUE;
}
} else {
edgeFlag = RENDER_PASSES.NOT_RENDERED;
}
const pickFlag = (visible && !culled && pickable) ? RENDER_PASSES.PICK : RENDER_PASSES.NOT_RENDERED;
dst[0] = colorFlag;
dst[1] = silhouetteFlag;
dst[2] = edgeFlag;
dst[3] = pickFlag;
};
export const getRenderers = (function() {
const cachedRenderers = { };
return function(scene, layerType, primitive, saoEnabled, pbrSupported, colorTextureSupported, surfaceHasNormals, makeRenderingAttributes) {
const primKey = ((primitive === "points") || (primitive === "lines")) ? primitive : "triangles";
const cacheKey = [ layerType, primKey, !!saoEnabled, !!pbrSupported, !!colorTextureSupported, !!surfaceHasNormals ].join(":");
if (! (cacheKey in cachedRenderers)) {
cachedRenderers[cacheKey] = { };
}
const cache = cachedRenderers[cacheKey];
const sceneId = scene.id;
if (! (sceneId in cache)) {
const gl = scene.canvas.gl;
const wrapRenderer = function(createProgramSetup, subGeometry, isEager) {
const instantiate = function() {
const programVariablesState = createProgramVariablesState();
const programVariables = programVariablesState.programVariables;
const renderingAttributes = makeRenderingAttributes(programVariables, subGeometry);
return createProgramSetup(
programVariables,
renderingAttributes.geometryParameters,
function(programSetup, usePickClipPos) {
const pointsMaterial = scene.pointsMaterial;
const setupPoints = (primitive === "points") && (! subGeometry);
const getHash = () => [ scene._sectionPlanesState.getHash() ].concat(setupPoints ? [ pointsMaterial.hash ] : [ ]).concat(programSetup.getHash ? programSetup.getHash() : [ ]).join(";");
const hash = getHash();
const getValid = () => hash === getHash();
const geometryParameters = renderingAttributes.geometryParameters;
const incrementDrawState = programSetup.incrementDrawState;
const isShadowProgram = programSetup.isShadowProgram;
const attributes = geometryParameters.attributes;
const worldPositionAttribute = attributes.position.world;
const clipPos = "clipPos";
const [ program, errors ] = programVariablesState.buildProgram(
gl,
layerType + " " + primitive + " " + programSetup.programName,
{
appendFragmentOutputs: programSetup.appendFragmentOutputs,
cleanerEdges: programSetup.cleanerEdges,
clipPos: clipPos,
clippableTest: renderingAttributes.clippableTest,
clippingCaps: programSetup.clippingCaps,
crossSections: scene.crossSections,
discardPoints: setupPoints && pointsMaterial.roundPoints,
getGammaFactor: scene.gammaOutput && (() => scene.gammaFactor),
getLogDepth: programSetup.getLogDepth,
getPointSize: (function() {
const addends = [ ];
if (setupPoints) {
const pointSize = programVariables.createUniform("float", "pointSize", (set) => set(pointsMaterial.pointSize));
if (pointsMaterial.perspectivePoints) {
const nearPlaneHeight = programVariables.createUniform("float", "nearPlaneHeight", (set, state) => set(state.legacyFrameCtx.nearPlaneHeight));
const minVal = `${Math.floor(pointsMaterial.minPerspectivePointSize)}.0`;
const maxVal = `${Math.floor(pointsMaterial.maxPerspectivePointSize)}.0`;
addends.push(`clamp((${nearPlaneHeight} * ${pointSize}) / gl_Position.w, ${minVal}, ${maxVal})`);
} else {
addends.push(pointSize);
}
} else if (subGeometry && subGeometry.vertices) {
addends.push("1.0");
}
programSetup.incPointSizeBy10 && (primitive === "points") && addends.push("10.0");
return (addends.length > 0) && (() => addends.join(" + "));
})(),
getVertexData: () => {
const colorA = attributes.color;
const flag = attributes.flags[programSetup.renderPassFlag];
const renderPass = programVariables.createUniform("int", "renderPass", (set, state) => set(state.renderPass));
const flagTest = (isShadowProgram
? `(${flag} <= 0) || (${colorA}.a < 1.0)`
: `${flag} != ${renderPass}`);
const afterFlagsColorLines = [
`if (${flagTest}) {`,
` gl_Position = vec4(${programSetup.vertexCullX || 0.0}, 0.0, 0.0, 0.0);`, // Cull vertex
" return;",
"}"
].concat(
((! renderingAttributes.dontCullOnAlphaZero) && (! programSetup.dontCullOnAlphaZero))
? [
`if (${colorA}.a == 0.0) {`,
" gl_Position = vec4(3.0, 3.0, 3.0, 1.0);", // Cull vertex
" return;",
"}"
]
: [ ]
).concat(
(programSetup.filterIntensityRange && (primitive === "points") && pointsMaterial.filterIntensity)
? (function () {
const intensityRange = programVariables.createUniform("vec2", "intensityRange", (set) => {
tempVec2[0] = pointsMaterial.minIntensity;
tempVec2[1] = pointsMaterial.maxIntensity;
set(tempVec2);
});
return [
`if ((${colorA}.a < ${intensityRange}[0]) || (${colorA}.a > ${intensityRange}[1])) {`,
" gl_Position = vec4(0.0, 0.0, 0.0, 0.0);", // Cull vertex
" return;",
"}"
];
})()
: [ ]);
const vertexData = [ ];
renderingAttributes.ensureColorAndFlagAvailable && renderingAttributes.ensureColorAndFlagAvailable(vertexData);
afterFlagsColorLines.forEach(line => vertexData.push(line));
renderingAttributes.appendVertexData(vertexData);
const viewPosition = geometryParameters.attributes.position.view;
vertexData.push(`vec4 ${viewPosition} = ${geometryParameters.viewMatrix} * ${worldPositionAttribute};`);
vertexData.push(`vec4 ${clipPos} = ${geometryParameters.projMatrix} * ${viewPosition};`);
return vertexData;
},
projMatrix: geometryParameters.projMatrix,
sectionPlanesState: scene._sectionPlanesState,
testPerspectiveForGl_FragDepth: ((primitive !== "points") && (primitive !== "lines")) || subGeometry,
usePickClipPos: usePickClipPos,
worldPositionAttribute: worldPositionAttribute
});
if (errors) {
console.error(errors);
return {
getValid: getValid,
destroy: () => { },
drawLayer: (frameCtx, layer, renderPass) => { }
};
} else {
return {
getValid: getValid,
destroy: () => program.destroy(),
drawLayer: (frameCtx, layer, renderPass) => {
if (frameCtx.lastProgramId !== program.id) {
frameCtx.lastProgramId = program.id;
program.bind();
}
const origin = layer.origin;
const model = layer.model;
const viewParams = frameCtx.viewParams;
const rtcOrigin = tempVec3;
math.transformPoint3(model.matrix, origin, rtcOrigin);
const viewMatrix = viewParams.viewMatrix;
const projMatrix = viewParams.projMatrix;
if (frameCtx.snapPickOrigin) {
frameCtx.snapPickOrigin[0] = rtcOrigin[0];
frameCtx.snapPickOrigin[1] = rtcOrigin[1];
frameCtx.snapPickOrigin[2] = rtcOrigin[2];
}
const state = {
legacyFrameCtx: frameCtx,
layerTextureSet: layer.layerTextureSet,
mesh: {
layerIndex: layer.layerIndex,
origin: rtcOrigin,
renderFlags: { sectionPlanesActivePerLayer: model.renderFlags.sectionPlanesActivePerLayer },
},
renderPass: renderPass,
view: {
eye: viewParams.eye,
far: viewParams.far,
projMatrix: projMatrix,
viewMatrix: math.compareVec3(rtcOrigin, vec3zero) ? viewMatrix : createRTCViewMat(viewMatrix, rtcOrigin, tempMat4),
viewNormalMatrix: viewParams.viewNormalMatrix
}
};
program.inputSetters.setUniforms(frameCtx, state);
layer.drawCalls[subGeometry ? (subGeometry.vertices ? "drawVertices" : "drawEdges") : "drawSurface"](
program.inputSetters.attributesHash,
renderingAttributes.layerTypeInputs,
{
projMatrix: projMatrix,
viewMatrix: math.compareVec3(rtcOrigin, vec3zero) ? viewMatrix : createRTCViewMat(viewMatrix, rtcOrigin, tempMat4),
viewNormalMatrix: viewParams.viewNormalMatrix,
eye: viewParams.eye
});
if (incrementDrawState) {
frameCtx.drawElements++;
}
}
};
}
});
};
let renderer = isEager && instantiate();
return {
drawLayer: (frameCtx, layer, renderPass) => {
if (! renderer) {
renderer = instantiate();
}
renderer.drawLayer(frameCtx, layer, renderPass);
},
revalidate: force => {
if (renderer && (force || (! renderer.getValid()))) {
renderer.destroy();
renderer = isEager && instantiate();
}
}
};
};
// Pre-initialize certain renderers that would otherwise be lazy-initialised on user interaction,
// such as picking or emphasis, so that there is no delay when user first begins interacting with the viewer.
const eager = (createProgramSetup, subGeometry) => wrapRenderer(createProgramSetup, subGeometry, true);
const lazy = (createProgramSetup, subGeometry) => wrapRenderer(createProgramSetup, subGeometry, false);
const makeColorProgram = (vars, geo, lights, sao) => ColorProgram(vars, geo, scene.logarithmicDepthBufferEnabled, lights, sao, primitive);
const makePickDepthProgram = (vars, geo, c) => c(PickDepthProgram(vars, geo, scene.logarithmicDepthBufferEnabled), true);
const makePickMeshProgram = (vars, geo, c) => c(PickMeshProgram(vars, geo, scene.logarithmicDepthBufferEnabled), true);
const makePickNormalsProgram = (vars, geo, c, isFlat) => c(PickNormalsProgram(vars, geo, scene.logarithmicDepthBufferEnabled, isFlat), true);
const makeSnapProgram = (vars, geo, c, isSnapInit, isPoints) => c(SnapProgram(vars, geo, isSnapInit, isPoints), true);
if (primitive === "points") {
cache[sceneId] = {
colorRenderers: { "sao-": { "vertex": lazy((vars, geo, c) => c(makeColorProgram(vars, geo, null, null))) } },
occlusionRenderer: lazy((vars, geo, c) => c(OcclusionProgram(vars, scene.logarithmicDepthBufferEnabled))),
pickDepthRenderer: lazy(makePickDepthProgram),
pickMeshRenderer: lazy(makePickMeshProgram),
// VBOBatchingPointsShadowRenderer has been implemented by 14e973df6268369b00baef60e468939e062ac320,
// but never used (and probably not maintained), as opposed to VBOInstancingPointsShadowRenderer in the same commit
// drawShadow has been nop in VBO point layers
// shadowRenderer: instancing && lazy((vars, geo, c) => c(ShadowProgram(scene.logarithmicDepthBufferEnabled))),
silhouetteRenderer: lazy((vars, geo, c) => c(SilhouetteProgram(vars, geo, scene.logarithmicDepthBufferEnabled, true))),
snapInitRenderer: lazy((vars, geo, c) => makeSnapProgram(vars, geo, c, true, true)),
snapVertexRenderer: lazy((vars, geo, c) => makeSnapProgram(vars, geo, c, false, true), { vertices: true })
};
} else if (primitive === "lines") {
cache[sceneId] = {
colorRenderers: { "sao-": { "vertex": lazy((vars, geo, c) => c(makeColorProgram(vars, geo, null, null))) } },
silhouetteRenderer: lazy((vars, geo, c) => c(SilhouetteProgram(vars, geo, scene.logarithmicDepthBufferEnabled, true))),
snapInitRenderer: lazy((vars, geo, c) => makeSnapProgram(vars, geo, c, true, false)),
snapEdgeRenderer: lazy((vars, geo, c) => makeSnapProgram(vars, geo, c, false, false), { vertices: false }),
snapVertexRenderer: lazy((vars, geo, c) => makeSnapProgram(vars, geo, c, false, false), { vertices: true })
};
} else {
cache[sceneId] = {
colorRenderers: (function() {
// WARNING: Changing `useMaps' to `true' for DTX might have unexpected consequences while binding textures, as the DTX texture binding mechanism doesn't rely on `frameCtx.textureUnit` the way VBO does (see setSAORenderState);
const createLights = vars => createLightSetup(vars, scene._lightsState);
const createSAO = vars => {
const uSAOParams = vars.createUniform("vec4", "uSAOParams", (set, state) => set(state.legacyFrameCtx.saoParams));
const uOcclusionTexture = setupTexture(vars, "sampler2D", "uOcclusionTexture", LinearEncoding, (set, state) => set(state.legacyFrameCtx.occlusionTexture));
return {
getAmbient: (gl_FragCoord) => {
// Doing SAO blend in the main solid fill draw shader just so that edge lines can be drawn over the top
// Would be more efficient to defer this, then render lines later, using same depth buffer for Z-reject
const uv = `${gl_FragCoord}.xy / ${uSAOParams}.xy`; // / viewportWH
const blendCutoff = `${uSAOParams}.z`;
const blendFactor = `${uSAOParams}.w`;
const unPackFactors = "(255. / 256.) / vec4(256. * 256. * 256., 256. * 256., 256., 1.)";
return `(smoothstep(${blendCutoff}, 1.0, dot(${uOcclusionTexture(uv)}, ${unPackFactors})) * ${blendFactor})`;
}
};
};
const saoRenderers = function(useSao) {
const makeColorTextureProgram = (vars, geo, useAlphaCutoff) => ColorTextureProgram(vars, geo, scene, createLights(vars), useSao && createSAO(vars), useAlphaCutoff, scene.gammaOutput); // If gammaOutput set, then it expects that all textures and colors need to be outputted in premultiplied gamma. Default is false.
return {
"PBR": pbrSupported && lazy((vars, geo, c) => c(PBRProgram(vars, geo, scene, createLights(vars), useSao && createSAO(vars)))),
"texture": colorTextureSupported && {
"alphaCutoff-": lazy((vars, geo, c) => c(makeColorTextureProgram(vars, geo, false))),
"alphaCutoff+": lazy((vars, geo, c) => c(makeColorTextureProgram(vars, geo, true)))
},
"vertex": (surfaceHasNormals
? lazy((vars, geo, c) => c(makeColorProgram(vars, geo, createLights(vars), useSao && createSAO(vars))))
: lazy((vars, geo, c) => c(FlatColorProgram(vars, geo, scene.logarithmicDepthBufferEnabled, createLights(vars), useSao && createSAO(vars)))))
};
};
return {
"sao-": saoRenderers(false),
"sao+": saoEnabled && saoRenderers(true)
};
})(),
depthRenderer: lazy((vars, geo, c) => c(DepthProgram(vars, geo, scene.logarithmicDepthBufferEnabled))),
edgesRenderers: {
uniform: lazy((vars, geo, c) => c(EdgesProgram(vars, geo, scene.logarithmicDepthBufferEnabled, true)), { vertices: false }),
vertex: lazy((vars, geo, c) => c(EdgesProgram(vars, geo, scene.logarithmicDepthBufferEnabled, false)), { vertices: false })
},
occlusionRenderer: lazy((vars, geo, c) => c(OcclusionProgram(vars, scene.logarithmicDepthBufferEnabled))),
pickDepthRenderer: eager(makePickDepthProgram),
pickMeshRenderer: eager(makePickMeshProgram),
pickNormalsFlatRenderer: eager((vars, geo, c) => makePickNormalsProgram(vars, geo, c, true)),
pickNormalsRenderer: eager((vars, geo, c) => makePickNormalsProgram(vars, geo, c, false)),
shadowRenderer: lazy((vars, geo, c) => c(ShadowProgram(vars, scene))),
silhouetteRenderer: eager((vars, geo, c) => c(SilhouetteProgram(vars, geo, scene.logarithmicDepthBufferEnabled, false))),
snapInitRenderer: eager((vars, geo, c) => makeSnapProgram(vars, geo, c, true, false)),
snapEdgeRenderer: eager((vars, geo, c) => makeSnapProgram(vars, geo, c, false, false), { vertices: false }),
snapVertexRenderer: eager((vars, geo, c) => makeSnapProgram(vars, geo, c, false, false), { vertices: true })
};
}
const revalidateAll = force => {
(function rec(obj) {
if (obj.revalidate) {
obj.revalidate(force);
} else {
Object.values(obj).forEach(v => v && rec(v));
}
})(cache[sceneId]);
};
const compile = () => revalidateAll(false);
compile();
scene.on("compile", compile);
scene.on("destroyed", () => {
delete cache[sceneId];
});
}
return cache[sceneId];
};
})();
export class Layer {
constructor(model, primitive, origin) {
this.model = model;
this.primitive = primitive;
this.origin = origin;
this._meshes = [];
// The axis-aligned World-space boundary of this Layer's positions.
this._aabb = math.collapseAABB3();
this._aabbDirty = true;
}
finalize() {
if (! this._compiledPortions) {
this._compiledPortions = this.compilePortions();
this.solid = this._compiledPortions.solid;
this.sortId = this._compiledPortions.sortId;
this.layerTextureSet = this._compiledPortions.layerTextureSet;
this.drawCalls = this._compiledPortions.drawCalls;
this._renderers = this._compiledPortions.renderers;
this._setFlags = this._compiledPortions.setFlags;
this.setColor = this._compiledPortions.setColor;
this.setMatrix = this._compiledPortions.setMatrix;
this.setOffset = this._compiledPortions.setOffset;
this.getEachIndex = this._compiledPortions.getEachIndex;
this.getEachVertex = this._compiledPortions.getEachVertex;
this.readGeometryData = this._compiledPortions.readGeometryData;
this.precisionRayPickSurface = this._compiledPortions.precisionRayPickSurface;
// These counts are used to avoid unnecessary render passes
this._countsByFlag = { };
const setupFlagCount = (entityFlag, modelCountProperty) => {
const cnt = {
count: 0,
fromFlags: (flags, onlyIncrement) => {
if (flags & entityFlag) {
cnt.count++;
this.model[modelCountProperty]++;
} else if (! onlyIncrement) {
cnt.count--;
this.model[modelCountProperty]--;
}
}
};
this._countsByFlag[entityFlag] = cnt;
};
setupFlagCount(ENTITY_FLAGS.VISIBLE, "numVisibleLayerPortions");
setupFlagCount(ENTITY_FLAGS.CULLED, "numCulledLayerPortions");
setupFlagCount(ENTITY_FLAGS.PICKABLE, "numPickableLayerPortions");
setupFlagCount(ENTITY_FLAGS.CLIPPABLE, "numClippableLayerPortions");
setupFlagCount(ENTITY_FLAGS.XRAYED, "numXRayedLayerPortions");
setupFlagCount(ENTITY_FLAGS.HIGHLIGHTED, "numHighlightedLayerPortions");
setupFlagCount(ENTITY_FLAGS.SELECTED, "numSelectedLayerPortions");
if (this._renderers.edgesRenderers) {
setupFlagCount(ENTITY_FLAGS.EDGES, "numEdgesLayerPortions");
}
// Optimize initFlags to not require Object.values call each time (adds up a lot)
this._countsByFlagArr = Object.values(this._countsByFlag);
this._numTransparentLayerPortions = 0;
}
}
aabbChanged() { this._aabbDirty = true; }
getAABB() {
if (this._aabbDirty) { // Per-layer AABB for best RTC accuracy
math.collapseAABB3(this._aabb);
this._meshes.forEach(m => math.expandAABB3(this._aabb, m.aabb));
this._aabbDirty = false;
}
return this._aabb;
}
__drawLayer(renderFlags, frameCtx, renderer, pass) {
if ((this._countsByFlag[ENTITY_FLAGS.CULLED].count < this._portions.length) && (this._countsByFlag[ENTITY_FLAGS.VISIBLE].count > 0)) {
const backfacePasses = (this.primitive !== "points") && (this.primitive !== "lines") && [
RENDER_PASSES.COLOR_OPAQUE,
RENDER_PASSES.COLOR_TRANSPARENT,
RENDER_PASSES.PICK,
RENDER_PASSES.SILHOUETTE_HIGHLIGHTED,
RENDER_PASSES.SILHOUETTE_SELECTED,
RENDER_PASSES.SILHOUETTE_XRAYED,
];
if (backfacePasses && backfacePasses.includes(pass)) {
// _updateBackfaceCull
const backfaces = true; // See XCD-230
if (frameCtx.backfaces !== backfaces) {
const gl = this.model.scene.canvas.gl;
if (backfaces) {
gl.disable(gl.CULL_FACE);
} else {
gl.enable(gl.CULL_FACE);
}
frameCtx.backfaces = backfaces;
}
}
frameCtx.textureUnit = 0; // WIP Maybe only for (! snap)?
renderer.drawLayer(frameCtx, this, pass);
}
}
__setVec4FromMaterialColorAlpha(color, alpha, dst) {
dst[0] = color[0];
dst[1] = color[1];
dst[2] = color[2];
dst[3] = alpha;
return dst;
}
// ---------------------- COLOR RENDERING -----------------------------------
__drawColor(renderFlags, frameCtx, renderOpaque) {
if ((renderOpaque ? (this._numTransparentLayerPortions < this._portions.length) : (this._numTransparentLayerPortions > 0))
&&
(this._countsByFlag[ENTITY_FLAGS.XRAYED].count < this._portions.length)) {
const saoRenderer = (renderOpaque && frameCtx.withSAO && this._renderers.colorRenderers["sao+"]) || this._renderers.colorRenderers["sao-"];
const renderer = ((frameCtx.pbrEnabled && saoRenderer["PBR"])
? saoRenderer["PBR"]
: ((frameCtx.colorTextureEnabled && saoRenderer["texture"])
? saoRenderer["texture"][(this.layerTextureSet && (typeof(this.layerTextureSet.alphaCutoff) === "number")) ? "alphaCutoff+" : "alphaCutoff-"]
: saoRenderer["vertex"]));
const pass = renderOpaque ? RENDER_PASSES.COLOR_OPAQUE : RENDER_PASSES.COLOR_TRANSPARENT;
this.__drawLayer(renderFlags, frameCtx, renderer, pass);
}
}
drawColorOpaque(renderFlags, frameCtx) {
this.__drawColor(renderFlags, frameCtx, true);
}
drawColorTransparent(renderFlags, frameCtx) {
this.__drawColor(renderFlags, frameCtx, false);
}
// ---------------------- RENDERING SAO POST EFFECT TARGETS --------------
drawDepth(renderFlags, frameCtx) {
// Assume whatever post-effect uses depth (eg SAO) does not apply to transparent objects
const renderer = this._renderers.depthRenderer;
if (renderer && (this._numTransparentLayerPortions < this._portions.length) && (this._countsByFlag[ENTITY_FLAGS.XRAYED].count < this._portions.length)) {
this.__drawLayer(renderFlags, frameCtx, renderer, RENDER_PASSES.COLOR_OPAQUE);
}
}
// ---------------------- SILHOUETTE RENDERING -----------------------------------
__drawSilhouette(renderFlags, frameCtx, material, renderPass) {
frameCtx.programColor = this.__setVec4FromMaterialColorAlpha(material.fillColor, material.fillAlpha, tempVec4);
this.__drawLayer(renderFlags, frameCtx, this._renderers.silhouetteRenderer, renderPass);
}
drawSilhouetteXRayed(renderFlags, frameCtx) {
if (this._countsByFlag[ENTITY_FLAGS.XRAYED].count > 0) {
this.__drawSilhouette(renderFlags, frameCtx, this.model.scene.xrayMaterial, RENDER_PASSES.SILHOUETTE_XRAYED);
}
}
drawSilhouetteHighlighted(renderFlags, frameCtx) {
if (this._countsByFlag[ENTITY_FLAGS.HIGHLIGHTED].count > 0) {
this.__drawSilhouette(renderFlags, frameCtx, this.model.scene.highlightMaterial, RENDER_PASSES.SILHOUETTE_HIGHLIGHTED);
}
}
drawSilhouetteSelected(renderFlags, frameCtx) {
if (this._countsByFlag[ENTITY_FLAGS.SELECTED].count > 0) {
this.__drawSilhouette(renderFlags, frameCtx, this.model.scene.selectedMaterial, RENDER_PASSES.SILHOUETTE_SELECTED);
}
}
// ---------------------- EDGES RENDERING -----------------------------------
__drawVertexEdges(renderFlags, frameCtx, renderPass) {
const renderer = this._renderers.edgesRenderers && this._renderers.edgesRenderers.vertex;
if (renderer && (this._countsByFlag[ENTITY_FLAGS.EDGES].count > 0)) {
this.__drawLayer(renderFlags, frameCtx, renderer, renderPass);
}
}
drawEdgesColorOpaque(renderFlags, frameCtx) {
if (this._compiledPortions.edgesColorOpaqueAllowed()) {
this.__drawVertexEdges(renderFlags, frameCtx, RENDER_PASSES.EDGES_COLOR_OPAQUE);
}
}
drawEdgesColorTransparent(renderFlags, frameCtx) {
if (this._numTransparentLayerPortions > 0) {
this.__drawVertexEdges(renderFlags, frameCtx, RENDER_PASSES.EDGES_COLOR_TRANSPARENT);
}
}
__drawUniformEdges(renderFlags, frameCtx, material, renderPass) {
const renderer = this._renderers.edgesRenderers && this._renderers.edgesRenderers.uniform;
if (renderer) {
frameCtx.programColor = this.__setVec4FromMaterialColorAlpha(material.edgeColor, material.edgeAlpha, tempVec4);
this.__drawLayer(renderFlags, frameCtx, renderer, renderPass);
}
}
drawEdgesHighlighted(renderFlags, frameCtx) {
if (this._countsByFlag[ENTITY_FLAGS.HIGHLIGHTED].count > 0) {
this.__drawUniformEdges(renderFlags, frameCtx, this.model.scene.highlightMaterial, RENDER_PASSES.EDGES_HIGHLIGHTED);
}
}
drawEdgesSelected(renderFlags, frameCtx) {
if (this._countsByFlag[ENTITY_FLAGS.SELECTED].count > 0) {
this.__drawUniformEdges(renderFlags, frameCtx, this.model.scene.selectedMaterial, RENDER_PASSES.EDGES_SELECTED);
}
}
drawEdgesXRayed(renderFlags, frameCtx) {
if (this._countsByFlag[ENTITY_FLAGS.XRAYED].count > 0) {
this.__drawUniformEdges(renderFlags, frameCtx, this.model.scene.xrayMaterial, RENDER_PASSES.EDGES_XRAYED);
}
}
//---- PICKING ----------------------------------------------------------------------------------------------------
__drawPick(renderFlags, frameCtx, renderer) {
if (renderer) {
this.__drawLayer(renderFlags, frameCtx, renderer, RENDER_PASSES.PICK);
}
}
drawPickMesh(renderFlags, frameCtx) {
this.__drawPick(renderFlags, frameCtx, this._renderers.pickMeshRenderer);
}
drawPickDepths(renderFlags, frameCtx) {
this.__drawPick(renderFlags, frameCtx, this._renderers.pickDepthRenderer);
}
drawPickNormals(renderFlags, frameCtx) {
const renderer = (false // TODO for VBO: this._state.normalsBuf
? this._renderers.pickNormalsRenderer
: this._renderers.pickNormalsFlatRenderer);
this.__drawPick(renderFlags, frameCtx, renderer);
}
drawSnap(renderFlags, frameCtx, isSnapInit) {
frameCtx.snapPickOrigin = [0, 0, 0];
const aabb = this.getAABB();
frameCtx.snapPickCoordinateScale = math.mulVec3Scalar(
safeInvVec3([ aabb[3] - aabb[0], aabb[4] - aabb[1], aabb[5] - aabb[2] ]),
math.MAX_INT);
const renderer = isSnapInit ? this._renderers.snapInitRenderer : ((frameCtx.snapMode === "edge") ? this._renderers.snapEdgeRenderer : this._renderers.snapVertexRenderer);
this.__drawPick(renderFlags, frameCtx, renderer);
frameCtx.snapPickLayerParams.push({
origin: frameCtx.snapPickOrigin.slice(),
coordinateScale: safeInvVec3(frameCtx.snapPickCoordinateScale)
});
}
drawOcclusion(renderFlags, frameCtx) {
const renderer = this._renderers.occlusionRenderer;
if (renderer) {
this.__drawLayer(renderFlags, frameCtx, renderer, RENDER_PASSES.COLOR_OPAQUE);
}
}
drawShadow(renderFlags, frameCtx) {
const renderer = this._renderers.shadowRenderer;
if (renderer) {
this.__drawLayer(renderFlags, frameCtx, renderer, RENDER_PASSES.COLOR_OPAQUE);
}
}
initFlags(portionId, flags, transparent) {
this._countsByFlagArr.forEach(cnt => cnt.fromFlags(flags, true));
if (transparent) {
this._numTransparentLayerPortions++;
this.model.numTransparentLayerPortions++;
}
const deferred = (this.primitive !== "points");
this._setFlags(portionId, flags, transparent, deferred);
this._compiledPortions.setFlags2(portionId, flags, deferred);
}
flushInitFlags() {
this._compiledPortions.setDeferredFlags();
}
setVisible(portionId, flags, transparent) {
this._countsByFlag[ENTITY_FLAGS.VISIBLE].fromFlags(flags);
this._setFlags(portionId, flags, transparent);
}
setCulled(portionId, flags, transparent) {
this._countsByFlag[ENTITY_FLAGS.CULLED].fromFlags(flags);
this._setFlags(portionId, flags, transparent);
}
setPickable(portionId, flags, transparent) {
this._countsByFlag[ENTITY_FLAGS.PICKABLE].fromFlags(flags);
this._setFlags(portionId, flags, transparent);
}
setClippable(portionId, flags) {
this._countsByFlag[ENTITY_FLAGS.CLIPPABLE].fromFlags(flags);
this._compiledPortions.setClippableFlags(portionId, flags);
}
setCollidable(portionId, flags) {
}
setXRayed(portionId, flags, transparent) {
this._countsByFlag[ENTITY_FLAGS.XRAYED].fromFlags(flags);
this._setFlags(portionId, flags, transparent);
}
setHighlighted(portionId, flags, transparent) {
this._countsByFlag[ENTITY_FLAGS.HIGHLIGHTED].fromFlags(flags);
this._setFlags(portionId, flags, transparent);
}
setSelected(portionId, flags, transparent) {
this._countsByFlag[ENTITY_FLAGS.SELECTED].fromFlags(flags);
this._setFlags(portionId, flags, transparent);
}
setEdges(portionId, flags, transparent) {
if (this._countsByFlag[ENTITY_FLAGS.EDGES]) {
this._countsByFlag[ENTITY_FLAGS.EDGES].fromFlags(flags);
this._setFlags(portionId, flags, transparent);
}
}
setTransparent(portionId, flags, transparent) {
if (transparent) {
this._numTransparentLayerPortions++;
this.model.numTransparentLayerPortions++;
} else {
this._numTransparentLayerPortions--;
this.model.numTransparentLayerPortions--;
}
this._setFlags(portionId, flags, transparent);
}
destroy() {
this._compiledPortions && this._compiledPortions.destroy();
}
}