molstar
Version:
A comprehensive macromolecular library.
778 lines (777 loc) • 43.7 kB
JavaScript
"use strict";
/**
* Copyright (c) 2018-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author Gianluca Tomasello <giagitom@gmail.com>
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.Renderer = exports.RendererParams = exports.MarkingType = exports.PickType = void 0;
const util_1 = require("../mol-canvas3d/camera/util");
const linear_algebra_1 = require("../mol-math/linear-algebra");
const color_1 = require("../mol-util/color");
const mol_util_1 = require("../mol-util");
const param_definition_1 = require("../mol-util/param-definition");
const misc_1 = require("../mol-math/misc");
const array_1 = require("../mol-util/array");
const interpolate_1 = require("../mol-math/interpolate");
const debug_1 = require("../mol-util/debug");
const frustum3d_1 = require("../mol-math/geometry/primitives/frustum3d");
const plane3d_1 = require("../mol-math/geometry/primitives/plane3d");
var PickType;
(function (PickType) {
PickType[PickType["None"] = 0] = "None";
PickType[PickType["Object"] = 1] = "Object";
PickType[PickType["Instance"] = 2] = "Instance";
PickType[PickType["Group"] = 3] = "Group";
})(PickType || (exports.PickType = PickType = {}));
var MarkingType;
(function (MarkingType) {
MarkingType[MarkingType["None"] = 0] = "None";
MarkingType[MarkingType["Depth"] = 1] = "Depth";
MarkingType[MarkingType["Mask"] = 2] = "Mask";
})(MarkingType || (exports.MarkingType = MarkingType = {}));
exports.RendererParams = {
backgroundColor: param_definition_1.ParamDefinition.Color((0, color_1.Color)(0x000000), { description: 'Background color of the 3D canvas' }),
pickingAlphaThreshold: param_definition_1.ParamDefinition.Numeric(0.5, { min: 0.0, max: 1.0, step: 0.01 }, { description: 'The minimum opacity value needed for an object to be pickable.' }),
interiorDarkening: param_definition_1.ParamDefinition.Numeric(0.5, { min: 0.0, max: 1.0, step: 0.01 }),
interiorColorFlag: param_definition_1.ParamDefinition.Boolean(true, { label: 'Use Interior Color' }),
interiorColor: param_definition_1.ParamDefinition.Color(color_1.Color.fromNormalizedRgb(0.3, 0.3, 0.3)),
colorMarker: param_definition_1.ParamDefinition.Boolean(true, { description: 'Enable color marker' }),
highlightColor: param_definition_1.ParamDefinition.Color(color_1.Color.fromNormalizedRgb(1.0, 0.4, 0.6)),
selectColor: param_definition_1.ParamDefinition.Color(color_1.Color.fromNormalizedRgb(0.2, 1.0, 0.1)),
dimColor: param_definition_1.ParamDefinition.Color(color_1.Color.fromNormalizedRgb(1.0, 1.0, 1.0)),
highlightStrength: param_definition_1.ParamDefinition.Numeric(0.3, { min: 0.0, max: 1.0, step: 0.1 }),
selectStrength: param_definition_1.ParamDefinition.Numeric(0.3, { min: 0.0, max: 1.0, step: 0.1 }),
dimStrength: param_definition_1.ParamDefinition.Numeric(0.0, { min: 0.0, max: 1.0, step: 0.1 }),
markerPriority: param_definition_1.ParamDefinition.Select(1, [[1, 'Highlight'], [2, 'Select']]),
xrayEdgeFalloff: param_definition_1.ParamDefinition.Numeric(1, { min: 0.0, max: 3.0, step: 0.1 }),
celSteps: param_definition_1.ParamDefinition.Numeric(5, { min: 2, max: 16, step: 1 }),
exposure: param_definition_1.ParamDefinition.Numeric(1, { min: 0.0, max: 3.0, step: 0.01 }),
light: param_definition_1.ParamDefinition.ObjectList({
inclination: param_definition_1.ParamDefinition.Numeric(150, { min: 0, max: 180, step: 1 }),
azimuth: param_definition_1.ParamDefinition.Numeric(320, { min: 0, max: 360, step: 1 }),
color: param_definition_1.ParamDefinition.Color(color_1.Color.fromNormalizedRgb(1.0, 1.0, 1.0)),
intensity: param_definition_1.ParamDefinition.Numeric(0.6, { min: 0.0, max: 5.0, step: 0.01 }),
}, o => color_1.Color.toHexString(o.color), { defaultValue: [{
inclination: 150,
azimuth: 320,
color: color_1.Color.fromNormalizedRgb(1.0, 1.0, 1.0),
intensity: 0.6
}] }),
ambientColor: param_definition_1.ParamDefinition.Color(color_1.Color.fromNormalizedRgb(1.0, 1.0, 1.0)),
ambientIntensity: param_definition_1.ParamDefinition.Numeric(0.4, { min: 0.0, max: 2.0, step: 0.01 }),
};
const tmpDir = (0, linear_algebra_1.Vec3)();
const tmpColor = (0, linear_algebra_1.Vec3)();
function getLight(props, light) {
const count = props.length;
const { direction, color } = light || {
direction: (new Array(count * 3)).fill(0),
color: (new Array(count * 3)).fill(0),
};
for (let i = 0; i < count; ++i) {
const p = props[i];
linear_algebra_1.Vec3.directionFromSpherical(tmpDir, (0, misc_1.degToRad)(p.inclination), (0, misc_1.degToRad)(p.azimuth), 1);
linear_algebra_1.Vec3.toArray(tmpDir, direction, i * 3);
linear_algebra_1.Vec3.scale(tmpColor, color_1.Color.toVec3Normalized(tmpColor, p.color), p.intensity);
linear_algebra_1.Vec3.toArray(tmpColor, color, i * 3);
}
return { count, direction, color };
}
var Renderer;
(function (Renderer) {
function create(ctx, props = {}) {
const { gl, state, stats } = ctx;
const p = param_definition_1.ParamDefinition.merge(exports.RendererParams, param_definition_1.ParamDefinition.getDefaultValues(exports.RendererParams), props);
const light = getLight(p.light);
const viewport = (0, util_1.Viewport)();
const drawingBufferSize = linear_algebra_1.Vec2.create(gl.drawingBufferWidth, gl.drawingBufferHeight);
const bgColor = color_1.Color.toVec3Normalized((0, linear_algebra_1.Vec3)(), p.backgroundColor);
let transparentBackground = false;
let isOccluded = null;
const emptyDepthTexture = ctx.resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest');
emptyDepthTexture.define(1, 1);
emptyDepthTexture.load({ array: new Uint8Array([255, 255, 255, 255]), width: 1, height: 1 });
const sharedTexturesList = [
['tDepth', emptyDepthTexture]
];
const view = (0, linear_algebra_1.Mat4)();
const invView = (0, linear_algebra_1.Mat4)();
const modelView = (0, linear_algebra_1.Mat4)();
const invModelView = (0, linear_algebra_1.Mat4)();
const invProjection = (0, linear_algebra_1.Mat4)();
const modelViewProjection = (0, linear_algebra_1.Mat4)();
const invModelViewProjection = (0, linear_algebra_1.Mat4)();
const cameraDir = (0, linear_algebra_1.Vec3)();
const cameraPosition = (0, linear_algebra_1.Vec3)();
const cameraPlane = (0, plane3d_1.Plane3D)();
const viewOffset = (0, linear_algebra_1.Vec2)();
const frustum = (0, frustum3d_1.Frustum3D)();
const ambientColor = (0, linear_algebra_1.Vec3)();
linear_algebra_1.Vec3.scale(ambientColor, color_1.Color.toArrayNormalized(p.ambientColor, ambientColor, 0), p.ambientIntensity);
const globalUniforms = {
uDrawId: mol_util_1.ValueCell.create(0),
uModel: mol_util_1.ValueCell.create(linear_algebra_1.Mat4.identity()),
uView: mol_util_1.ValueCell.create(view),
uInvView: mol_util_1.ValueCell.create(invView),
uModelView: mol_util_1.ValueCell.create(modelView),
uInvModelView: mol_util_1.ValueCell.create(invModelView),
uInvProjection: mol_util_1.ValueCell.create(invProjection),
uProjection: mol_util_1.ValueCell.create((0, linear_algebra_1.Mat4)()),
uModelViewProjection: mol_util_1.ValueCell.create(modelViewProjection),
uInvModelViewProjection: mol_util_1.ValueCell.create(invModelViewProjection),
uIsOrtho: mol_util_1.ValueCell.create(1),
uViewOffset: mol_util_1.ValueCell.create(viewOffset),
uPixelRatio: mol_util_1.ValueCell.create(ctx.pixelRatio),
uViewport: mol_util_1.ValueCell.create(util_1.Viewport.toVec4((0, linear_algebra_1.Vec4)(), viewport)),
uDrawingBufferSize: mol_util_1.ValueCell.create(drawingBufferSize),
uCameraPosition: mol_util_1.ValueCell.create(cameraPosition),
uCameraDir: mol_util_1.ValueCell.create(cameraDir),
uCameraPlane: mol_util_1.ValueCell.create(plane3d_1.Plane3D.toArray(cameraPlane, (0, linear_algebra_1.Vec4)(), 0)),
uNear: mol_util_1.ValueCell.create(1),
uFar: mol_util_1.ValueCell.create(10000),
uFog: mol_util_1.ValueCell.create(true),
uFogNear: mol_util_1.ValueCell.create(1),
uFogFar: mol_util_1.ValueCell.create(10000),
uFogColor: mol_util_1.ValueCell.create(bgColor),
uRenderMask: mol_util_1.ValueCell.create(0),
uMarkingDepthTest: mol_util_1.ValueCell.create(false),
uPickType: mol_util_1.ValueCell.create(PickType.None),
uMarkingType: mol_util_1.ValueCell.create(MarkingType.None),
uTransparentBackground: mol_util_1.ValueCell.create(false),
uLightDirection: mol_util_1.ValueCell.create(light.direction),
uLightColor: mol_util_1.ValueCell.create(light.color),
uAmbientColor: mol_util_1.ValueCell.create(ambientColor),
uPickingAlphaThreshold: mol_util_1.ValueCell.create(p.pickingAlphaThreshold),
uInteriorDarkening: mol_util_1.ValueCell.create(p.interiorDarkening),
uInteriorColorFlag: mol_util_1.ValueCell.create(p.interiorColorFlag),
uInteriorColor: mol_util_1.ValueCell.create(color_1.Color.toVec3Normalized((0, linear_algebra_1.Vec3)(), p.interiorColor)),
uHighlightColor: mol_util_1.ValueCell.create(color_1.Color.toVec3Normalized((0, linear_algebra_1.Vec3)(), p.highlightColor)),
uSelectColor: mol_util_1.ValueCell.create(color_1.Color.toVec3Normalized((0, linear_algebra_1.Vec3)(), p.selectColor)),
uDimColor: mol_util_1.ValueCell.create(color_1.Color.toVec3Normalized((0, linear_algebra_1.Vec3)(), p.dimColor)),
uHighlightStrength: mol_util_1.ValueCell.create(p.highlightStrength),
uSelectStrength: mol_util_1.ValueCell.create(p.selectStrength),
uDimStrength: mol_util_1.ValueCell.create(p.dimStrength),
uMarkerPriority: mol_util_1.ValueCell.create(p.markerPriority),
uMarkerAverage: mol_util_1.ValueCell.create(0),
uXrayEdgeFalloff: mol_util_1.ValueCell.create(p.xrayEdgeFalloff),
uCelSteps: mol_util_1.ValueCell.create(p.celSteps),
uExposure: mol_util_1.ValueCell.create(p.exposure),
};
const globalUniformList = Object.entries(globalUniforms);
let globalUniformsNeedUpdate = true;
const renderObject = (r, variant, flag) => {
var _a, _b, _c;
if (r.state.disposed || !r.state.visible || (!r.state.pickable && variant === 'pick')) {
return;
}
if (!r.values.drawCount.ref.value) {
return;
}
if (!frustum3d_1.Frustum3D.intersectsSphere3D(frustum, r.values.boundingSphere.ref.value)) {
return;
}
const [minDistance, maxDistance] = r.values.uLod.ref.value;
if (minDistance !== 0 || maxDistance !== 0) {
const { center, radius } = r.values.boundingSphere.ref.value;
const d = plane3d_1.Plane3D.distanceToPoint(cameraPlane, center);
if (d + radius < minDistance)
return;
if (d - radius > maxDistance)
return;
}
if (isOccluded !== null && isOccluded(r.values.boundingSphere.ref.value)) {
return;
}
const hasInstanceGrid = r.values.instanceGrid.ref.value.cellSize > 0;
const hasMultipleInstances = r.values.uInstanceCount.ref.value > 1;
if (hasInstanceGrid && (hasMultipleInstances || r.values.lodLevels)) {
r.cull(cameraPlane, frustum, isOccluded, ctx.stats);
}
else {
r.uncull();
}
let needUpdate = false;
if (r.values.dLightCount.ref.value !== light.count) {
mol_util_1.ValueCell.update(r.values.dLightCount, light.count);
needUpdate = true;
}
if (r.values.dColorMarker.ref.value !== p.colorMarker) {
mol_util_1.ValueCell.update(r.values.dColorMarker, p.colorMarker);
needUpdate = true;
}
if (needUpdate)
r.update();
const program = r.getProgram(variant);
if (state.currentProgramId !== program.id) {
// console.log('new program')
globalUniformsNeedUpdate = true;
program.use();
}
if (globalUniformsNeedUpdate) {
// console.log('globalUniformsNeedUpdate')
program.setUniforms(globalUniformList);
program.bindTextures(sharedTexturesList, 0);
globalUniformsNeedUpdate = false;
}
if (r.values.dGeometryType.ref.value === 'directVolume') {
if (variant !== 'color') {
return; // only color supported
}
// culling done in fragment shader
state.disable(gl.CULL_FACE);
state.frontFace(gl.CCW);
}
else if (flag === 1 /* Flag.BlendedFront */) {
state.enable(gl.CULL_FACE);
if ((_a = r.values.dFlipSided) === null || _a === void 0 ? void 0 : _a.ref.value) {
state.frontFace(gl.CW);
state.cullFace(gl.FRONT);
}
else {
state.frontFace(gl.CCW);
state.cullFace(gl.BACK);
}
}
else if (flag === 2 /* Flag.BlendedBack */) {
state.enable(gl.CULL_FACE);
if ((_b = r.values.dFlipSided) === null || _b === void 0 ? void 0 : _b.ref.value) {
state.frontFace(gl.CW);
state.cullFace(gl.BACK);
}
else {
state.frontFace(gl.CCW);
state.cullFace(gl.FRONT);
}
}
else {
if (r.values.uDoubleSided) {
if (r.values.uDoubleSided.ref.value || r.values.hasReflection.ref.value) {
state.disable(gl.CULL_FACE);
}
else {
state.enable(gl.CULL_FACE);
}
}
else {
// webgl default
state.disable(gl.CULL_FACE);
}
if ((_c = r.values.dFlipSided) === null || _c === void 0 ? void 0 : _c.ref.value) {
state.frontFace(gl.CW);
state.cullFace(gl.FRONT);
}
else {
// webgl default
state.frontFace(gl.CCW);
state.cullFace(gl.BACK);
}
}
r.render(variant, sharedTexturesList.length);
};
const update = (camera, scene) => {
mol_util_1.ValueCell.update(globalUniforms.uView, camera.view);
mol_util_1.ValueCell.update(globalUniforms.uInvView, linear_algebra_1.Mat4.invert(invView, camera.view));
mol_util_1.ValueCell.update(globalUniforms.uProjection, camera.projection);
mol_util_1.ValueCell.update(globalUniforms.uInvProjection, linear_algebra_1.Mat4.invert(invProjection, camera.projection));
mol_util_1.ValueCell.updateIfChanged(globalUniforms.uIsOrtho, camera.state.mode === 'orthographic' ? 1 : 0);
mol_util_1.ValueCell.update(globalUniforms.uViewOffset, camera.viewOffset.enabled ? linear_algebra_1.Vec2.set(viewOffset, camera.viewOffset.offsetX * 16, camera.viewOffset.offsetY * 16) : linear_algebra_1.Vec2.set(viewOffset, 0, 0));
mol_util_1.ValueCell.update(globalUniforms.uCameraPosition, linear_algebra_1.Vec3.copy(cameraPosition, camera.state.position));
mol_util_1.ValueCell.update(globalUniforms.uCameraDir, linear_algebra_1.Vec3.normalize(cameraDir, linear_algebra_1.Vec3.sub(cameraDir, camera.state.target, camera.state.position)));
mol_util_1.ValueCell.updateIfChanged(globalUniforms.uFar, camera.far);
mol_util_1.ValueCell.updateIfChanged(globalUniforms.uNear, camera.near);
mol_util_1.ValueCell.updateIfChanged(globalUniforms.uFog, camera.state.fog > 0);
mol_util_1.ValueCell.updateIfChanged(globalUniforms.uFogFar, camera.fogFar);
mol_util_1.ValueCell.updateIfChanged(globalUniforms.uFogNear, camera.fogNear);
mol_util_1.ValueCell.updateIfChanged(globalUniforms.uTransparentBackground, transparentBackground);
frustum3d_1.Frustum3D.fromProjectionMatrix(frustum, camera.projectionView);
plane3d_1.Plane3D.copy(cameraPlane, frustum[5 /* Frustum3D.PlaneIndex.Near */]);
cameraPlane.constant -= plane3d_1.Plane3D.distanceToPoint(cameraPlane, cameraPosition);
mol_util_1.ValueCell.update(globalUniforms.uCameraPlane, plane3d_1.Plane3D.toArray(cameraPlane, globalUniforms.uCameraPlane.ref.value, 0));
mol_util_1.ValueCell.updateIfChanged(globalUniforms.uMarkerAverage, scene.markerAverage);
};
const updateInternal = (group, camera, depthTexture, renderMask, markingDepthTest) => {
(0, array_1.arrayMapUpsert)(sharedTexturesList, 'tDepth', depthTexture || emptyDepthTexture);
mol_util_1.ValueCell.update(globalUniforms.uModel, group.view);
mol_util_1.ValueCell.update(globalUniforms.uModelView, linear_algebra_1.Mat4.mul(modelView, camera.view, group.view));
mol_util_1.ValueCell.update(globalUniforms.uInvModelView, linear_algebra_1.Mat4.invert(invModelView, modelView));
mol_util_1.ValueCell.update(globalUniforms.uModelViewProjection, linear_algebra_1.Mat4.mul(modelViewProjection, modelView, camera.projection));
mol_util_1.ValueCell.update(globalUniforms.uInvModelViewProjection, linear_algebra_1.Mat4.invert(invModelViewProjection, modelViewProjection));
mol_util_1.ValueCell.updateIfChanged(globalUniforms.uRenderMask, renderMask);
mol_util_1.ValueCell.updateIfChanged(globalUniforms.uMarkingDepthTest, markingDepthTest);
state.enable(gl.SCISSOR_TEST);
state.colorMask(true, true, true, true);
const { x, y, width, height } = viewport;
state.viewport(x, y, width, height);
state.scissor(x, y, width, height);
globalUniformsNeedUpdate = true;
state.currentRenderItemId = -1;
};
const checkOpaque = function (r) {
var _a, _b, _c, _d;
// uAlpha is updated in `r.render` so we need to recompute it here
const alpha = (0, interpolate_1.clamp)(r.values.alpha.ref.value * r.state.alphaFactor, 0, 1);
const xrayShaded = ((_a = r.values.dXrayShaded) === null || _a === void 0 ? void 0 : _a.ref.value) === 'on' || ((_b = r.values.dXrayShaded) === null || _b === void 0 ? void 0 : _b.ref.value) === 'inverted';
return ((alpha === 1 &&
r.values.transparencyAverage.ref.value !== 1 &&
r.values.dGeometryType.ref.value !== 'directVolume' &&
((_c = r.values.dPointStyle) === null || _c === void 0 ? void 0 : _c.ref.value) !== 'fuzzy' &&
!xrayShaded) || ((_d = r.values.dTransparentBackfaces) === null || _d === void 0 ? void 0 : _d.ref.value) === 'opaque');
};
const checkTransparent = function (r) {
var _a, _b, _c;
// uAlpha is updated in `r.render` so we need to recompute it here
const alpha = (0, interpolate_1.clamp)(r.values.alpha.ref.value * r.state.alphaFactor, 0, 1);
const xrayShaded = ((_a = r.values.dXrayShaded) === null || _a === void 0 ? void 0 : _a.ref.value) === 'on' || ((_b = r.values.dXrayShaded) === null || _b === void 0 ? void 0 : _b.ref.value) === 'inverted';
return ((alpha < 1 && alpha !== 0) ||
r.values.transparencyAverage.ref.value > 0 ||
r.values.dGeometryType.ref.value === 'directVolume' ||
((_c = r.values.dPointStyle) === null || _c === void 0 ? void 0 : _c.ref.value) === 'fuzzy' ||
r.values.dGeometryType.ref.value === 'text' ||
r.values.dGeometryType.ref.value === 'image' ||
xrayShaded);
};
const renderPick = (group, camera, variant, pickType) => {
if (debug_1.isTimingMode)
ctx.timer.mark('Renderer.renderPick');
state.disable(gl.BLEND);
state.enable(gl.DEPTH_TEST);
state.depthMask(true);
updateInternal(group, camera, null, 0 /* Mask.All */, false);
mol_util_1.ValueCell.updateIfChanged(globalUniforms.uPickType, pickType);
const { renderables } = group;
for (let i = 0, il = renderables.length; i < il; ++i) {
if (!renderables[i].state.colorOnly) {
renderObject(renderables[i], variant, 0 /* Flag.None */);
}
}
if (debug_1.isTimingMode)
ctx.timer.markEnd('Renderer.renderPick');
};
const renderDepth = (group, camera) => {
if (debug_1.isTimingMode)
ctx.timer.mark('Renderer.renderDepth');
state.disable(gl.BLEND);
state.enable(gl.DEPTH_TEST);
state.depthMask(true);
updateInternal(group, camera, null, 0 /* Mask.All */, false);
const { renderables } = group;
for (let i = 0, il = renderables.length; i < il; ++i) {
renderObject(renderables[i], 'depth', 0 /* Flag.None */);
}
if (debug_1.isTimingMode)
ctx.timer.markEnd('Renderer.renderDepth');
};
const renderDepthOpaque = (group, camera) => {
if (debug_1.isTimingMode)
ctx.timer.mark('Renderer.renderDepthOpaque');
state.disable(gl.BLEND);
state.enable(gl.DEPTH_TEST);
state.depthMask(true);
updateInternal(group, camera, null, 1 /* Mask.Opaque */, false);
const { renderables } = group;
for (let i = 0, il = renderables.length; i < il; ++i) {
const r = renderables[i];
if (checkOpaque(r)) {
renderObject(r, 'depth', 0 /* Flag.None */);
}
}
if (debug_1.isTimingMode)
ctx.timer.markEnd('Renderer.renderDepthOpaque');
};
const renderDepthOpaqueBack = (group, camera) => {
if (debug_1.isTimingMode)
ctx.timer.mark('Renderer.renderDepthOpaqueBack');
state.disable(gl.BLEND);
state.enable(gl.DEPTH_TEST);
state.depthMask(true);
state.depthFunc(gl.GREATER);
updateInternal(group, camera, null, 1 /* Mask.Opaque */, false);
const { renderables } = group;
for (let i = 0, il = renderables.length; i < il; ++i) {
const r = renderables[i];
if (checkOpaque(r)) {
renderObject(r, 'depth', 2 /* Flag.BlendedBack */);
}
}
state.depthFunc(gl.LESS);
if (debug_1.isTimingMode)
ctx.timer.markEnd('Renderer.renderDepthOpaqueBack');
};
const renderDepthTransparent = (group, camera, depthTexture) => {
if (debug_1.isTimingMode)
ctx.timer.mark('Renderer.renderDepthTransparent');
state.disable(gl.BLEND);
state.enable(gl.DEPTH_TEST);
state.depthMask(true);
updateInternal(group, camera, depthTexture, 2 /* Mask.Transparent */, false);
const { renderables } = group;
for (let i = 0, il = renderables.length; i < il; ++i) {
const r = renderables[i];
if (checkTransparent(r)) {
renderObject(r, 'depth', 0 /* Flag.None */);
}
}
if (debug_1.isTimingMode)
ctx.timer.markEnd('Renderer.renderDepthTransparent');
};
const renderMarkingDepth = (group, camera) => {
if (debug_1.isTimingMode)
ctx.timer.mark('Renderer.renderMarkingDepth');
state.disable(gl.BLEND);
state.enable(gl.DEPTH_TEST);
state.depthMask(true);
updateInternal(group, camera, null, 0 /* Mask.All */, false);
mol_util_1.ValueCell.updateIfChanged(globalUniforms.uMarkingType, MarkingType.Depth);
const { renderables } = group;
for (let i = 0, il = renderables.length; i < il; ++i) {
const r = renderables[i];
const alpha = (0, interpolate_1.clamp)(r.values.alpha.ref.value * r.state.alphaFactor, 0, 1);
if (alpha !== 0 && r.values.transparencyAverage.ref.value !== 1 && r.values.markerAverage.ref.value !== 1) {
renderObject(renderables[i], 'marking', 0 /* Flag.None */);
}
}
if (debug_1.isTimingMode)
ctx.timer.markEnd('Renderer.renderMarkingDepth');
};
const renderMarkingMask = (group, camera, depthTexture) => {
if (debug_1.isTimingMode)
ctx.timer.mark('Renderer.renderMarkingMask');
state.disable(gl.BLEND);
state.enable(gl.DEPTH_TEST);
state.depthMask(true);
updateInternal(group, camera, depthTexture, 0 /* Mask.All */, !!depthTexture);
mol_util_1.ValueCell.updateIfChanged(globalUniforms.uMarkingType, MarkingType.Mask);
const { renderables } = group;
for (let i = 0, il = renderables.length; i < il; ++i) {
const r = renderables[i];
if (r.values.markerAverage.ref.value > 0) {
renderObject(renderables[i], 'marking', 0 /* Flag.None */);
}
}
if (debug_1.isTimingMode)
ctx.timer.markEnd('Renderer.renderMarkingMask');
};
const renderEmissive = (group, camera) => {
if (debug_1.isTimingMode)
ctx.timer.mark('Renderer.renderEmissive');
state.disable(gl.BLEND);
state.enable(gl.DEPTH_TEST);
state.depthMask(true);
updateInternal(group, camera, null, 1 /* Mask.Opaque */, false);
const { renderables } = group;
for (let i = 0, il = renderables.length; i < il; ++i) {
const r = renderables[i];
if (checkOpaque(r)) {
renderObject(r, 'emissive', 0 /* Flag.None */);
}
}
if (debug_1.isTimingMode)
ctx.timer.markEnd('Renderer.renderEmissive');
};
const renderTracing = (group, camera) => {
if (debug_1.isTimingMode)
ctx.timer.mark('Renderer.renderTracing');
state.disable(gl.BLEND);
state.enable(gl.DEPTH_TEST);
state.depthMask(true);
updateInternal(group, camera, null, 1 /* Mask.Opaque */, false);
const { renderables } = group;
for (let i = 0, il = renderables.length; i < il; ++i) {
const r = renderables[i];
if (checkOpaque(r)) {
renderObject(r, 'tracing', 0 /* Flag.None */);
}
}
if (debug_1.isTimingMode)
ctx.timer.markEnd('Renderer.renderTracing');
};
const renderBlended = (scene, camera) => {
if (scene.hasOpaque) {
renderOpaque(scene, camera);
}
if (scene.opacityAverage < 1) {
renderBlendedTransparent(scene, camera);
}
};
const renderOpaque = (group, camera) => {
if (debug_1.isTimingMode)
ctx.timer.mark('Renderer.renderOpaque');
state.disable(gl.BLEND);
state.enable(gl.DEPTH_TEST);
state.depthMask(true);
updateInternal(group, camera, null, 1 /* Mask.Opaque */, false);
const { renderables } = group;
for (let i = 0, il = renderables.length; i < il; ++i) {
const r = renderables[i];
if (checkOpaque(r)) {
renderObject(r, 'color', 0 /* Flag.None */);
}
}
if (debug_1.isTimingMode)
ctx.timer.markEnd('Renderer.renderOpaque');
};
const renderBlendedTransparent = (group, camera) => {
var _a, _b;
if (debug_1.isTimingMode)
ctx.timer.mark('Renderer.renderBlendedTransparent');
if (transparentBackground) {
state.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
}
else {
state.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
}
state.enable(gl.BLEND);
state.enable(gl.DEPTH_TEST);
state.depthMask(false);
updateInternal(group, camera, null, 2 /* Mask.Transparent */, false);
const { renderables } = group;
for (let i = 0, il = renderables.length; i < il; ++i) {
const r = renderables[i];
if (checkTransparent(r)) {
if ((_a = r.values.uDoubleSided) === null || _a === void 0 ? void 0 : _a.ref.value) {
// render frontfaces and backfaces separately to avoid artefacts
if (((_b = r.values.dTransparentBackfaces) === null || _b === void 0 ? void 0 : _b.ref.value) !== 'opaque') {
renderObject(r, 'color', 2 /* Flag.BlendedBack */);
}
renderObject(r, 'color', 1 /* Flag.BlendedFront */);
}
else {
renderObject(r, 'color', 0 /* Flag.None */);
}
}
}
if (debug_1.isTimingMode)
ctx.timer.markEnd('Renderer.renderBlendedTransparent');
};
const renderVolume = (group, camera, depthTexture) => {
if (debug_1.isTimingMode)
ctx.timer.mark('Renderer.renderVolume');
state.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
state.enable(gl.BLEND);
// depth test done manually in shader against `depthTexture`
state.disable(gl.DEPTH_TEST);
state.depthMask(false);
updateInternal(group, camera, depthTexture, 2 /* Mask.Transparent */, false);
const { renderables } = group;
for (let i = 0, il = renderables.length; i < il; ++i) {
const r = renderables[i];
if (r.values.dGeometryType.ref.value === 'directVolume') {
renderObject(r, 'color', 0 /* Flag.None */);
}
}
if (debug_1.isTimingMode)
ctx.timer.markEnd('Renderer.renderVolume');
};
const renderWboitTransparent = (group, camera, depthTexture) => {
if (debug_1.isTimingMode)
ctx.timer.mark('Renderer.renderWboitTransparent');
updateInternal(group, camera, depthTexture, 2 /* Mask.Transparent */, false);
const { renderables } = group;
for (let i = 0, il = renderables.length; i < il; ++i) {
const r = renderables[i];
if (checkTransparent(r)) {
renderObject(r, 'color', 0 /* Flag.None */);
}
}
if (debug_1.isTimingMode)
ctx.timer.markEnd('Renderer.renderWboitTransparent');
};
const renderDpoitTransparent = (group, camera, depthTexture, dpoitTextures) => {
if (debug_1.isTimingMode)
ctx.timer.mark('Renderer.renderDpoitTransparent');
state.enable(gl.BLEND);
(0, array_1.arrayMapUpsert)(sharedTexturesList, 'tDpoitDepth', dpoitTextures.depth);
(0, array_1.arrayMapUpsert)(sharedTexturesList, 'tDpoitFrontColor', dpoitTextures.frontColor);
(0, array_1.arrayMapUpsert)(sharedTexturesList, 'tDpoitBackColor', dpoitTextures.backColor);
updateInternal(group, camera, depthTexture, 2 /* Mask.Transparent */, false);
const { renderables } = group;
for (let i = 0, il = renderables.length; i < il; ++i) {
const r = renderables[i];
if (checkTransparent(r)) {
renderObject(r, 'color', 0 /* Flag.None */);
}
}
if (debug_1.isTimingMode)
ctx.timer.markEnd('Renderer.renderDpoitTransparent');
};
return {
clear: (toBackgroundColor, ignoreTransparentBackground, forceToTransparency) => {
state.enable(gl.SCISSOR_TEST);
state.enable(gl.DEPTH_TEST);
state.colorMask(true, true, true, true);
state.depthMask(true);
if (forceToTransparency || transparentBackground && !ignoreTransparentBackground) {
state.clearColor(0, 0, 0, 0);
}
else if (toBackgroundColor) {
state.clearColor(bgColor[0], bgColor[1], bgColor[2], 1);
}
else {
state.clearColor(1, 1, 1, 1);
}
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
},
clearDepth: (packed = false) => {
state.enable(gl.SCISSOR_TEST);
state.enable(gl.DEPTH_TEST);
state.depthMask(true);
if (packed) {
state.colorMask(true, true, true, true);
state.clearColor(1, 1, 1, 1);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
}
else {
gl.clear(gl.DEPTH_BUFFER_BIT);
}
},
update,
renderPick,
renderDepth,
renderDepthOpaque,
renderDepthOpaqueBack,
renderDepthTransparent,
renderMarkingDepth,
renderMarkingMask,
renderEmissive,
renderTracing,
renderBlended,
renderOpaque,
renderBlendedTransparent,
renderVolume,
renderWboitTransparent,
renderDpoitTransparent,
setProps: (props) => {
if (props.backgroundColor !== undefined && props.backgroundColor !== p.backgroundColor) {
p.backgroundColor = props.backgroundColor;
color_1.Color.toVec3Normalized(bgColor, p.backgroundColor);
mol_util_1.ValueCell.update(globalUniforms.uFogColor, linear_algebra_1.Vec3.copy(globalUniforms.uFogColor.ref.value, bgColor));
}
if (props.pickingAlphaThreshold !== undefined && props.pickingAlphaThreshold !== p.pickingAlphaThreshold) {
p.pickingAlphaThreshold = props.pickingAlphaThreshold;
mol_util_1.ValueCell.update(globalUniforms.uPickingAlphaThreshold, p.pickingAlphaThreshold);
}
if (props.interiorDarkening !== undefined && props.interiorDarkening !== p.interiorDarkening) {
p.interiorDarkening = props.interiorDarkening;
mol_util_1.ValueCell.update(globalUniforms.uInteriorDarkening, p.interiorDarkening);
}
if (props.interiorColorFlag !== undefined && props.interiorColorFlag !== p.interiorColorFlag) {
p.interiorColorFlag = props.interiorColorFlag;
mol_util_1.ValueCell.update(globalUniforms.uInteriorColorFlag, p.interiorColorFlag);
}
if (props.interiorColor !== undefined && props.interiorColor !== p.interiorColor) {
p.interiorColor = props.interiorColor;
mol_util_1.ValueCell.update(globalUniforms.uInteriorColor, color_1.Color.toVec3Normalized(globalUniforms.uInteriorColor.ref.value, p.interiorColor));
}
if (props.colorMarker !== undefined && props.colorMarker !== p.colorMarker) {
p.colorMarker = props.colorMarker;
}
if (props.highlightColor !== undefined && props.highlightColor !== p.highlightColor) {
p.highlightColor = props.highlightColor;
mol_util_1.ValueCell.update(globalUniforms.uHighlightColor, color_1.Color.toVec3Normalized(globalUniforms.uHighlightColor.ref.value, p.highlightColor));
}
if (props.selectColor !== undefined && props.selectColor !== p.selectColor) {
p.selectColor = props.selectColor;
mol_util_1.ValueCell.update(globalUniforms.uSelectColor, color_1.Color.toVec3Normalized(globalUniforms.uSelectColor.ref.value, p.selectColor));
}
if (props.dimColor !== undefined && props.dimColor !== p.dimColor) {
p.dimColor = props.dimColor;
mol_util_1.ValueCell.update(globalUniforms.uDimColor, color_1.Color.toVec3Normalized(globalUniforms.uDimColor.ref.value, p.dimColor));
}
if (props.highlightStrength !== undefined && props.highlightStrength !== p.highlightStrength) {
p.highlightStrength = props.highlightStrength;
mol_util_1.ValueCell.update(globalUniforms.uHighlightStrength, p.highlightStrength);
}
if (props.selectStrength !== undefined && props.selectStrength !== p.selectStrength) {
p.selectStrength = props.selectStrength;
mol_util_1.ValueCell.update(globalUniforms.uSelectStrength, p.selectStrength);
}
if (props.dimStrength !== undefined && props.dimStrength !== p.dimStrength) {
p.dimStrength = props.dimStrength;
mol_util_1.ValueCell.update(globalUniforms.uDimStrength, p.dimStrength);
}
if (props.markerPriority !== undefined && props.markerPriority !== p.markerPriority) {
p.markerPriority = props.markerPriority;
mol_util_1.ValueCell.update(globalUniforms.uMarkerPriority, p.markerPriority);
}
if (props.xrayEdgeFalloff !== undefined && props.xrayEdgeFalloff !== p.xrayEdgeFalloff) {
p.xrayEdgeFalloff = props.xrayEdgeFalloff;
mol_util_1.ValueCell.update(globalUniforms.uXrayEdgeFalloff, p.xrayEdgeFalloff);
}
if (props.celSteps !== undefined && props.celSteps !== p.celSteps) {
p.celSteps = props.celSteps;
mol_util_1.ValueCell.update(globalUniforms.uCelSteps, p.celSteps);
}
if (props.exposure !== undefined && props.exposure !== p.exposure) {
p.exposure = props.exposure;
mol_util_1.ValueCell.update(globalUniforms.uExposure, p.exposure);
}
if (props.light !== undefined && !(0, mol_util_1.deepEqual)(props.light, p.light)) {
p.light = props.light;
Object.assign(light, getLight(props.light, light));
mol_util_1.ValueCell.update(globalUniforms.uLightDirection, light.direction);
mol_util_1.ValueCell.update(globalUniforms.uLightColor, light.color);
}
if (props.ambientColor !== undefined && props.ambientColor !== p.ambientColor) {
p.ambientColor = props.ambientColor;
linear_algebra_1.Vec3.scale(ambientColor, color_1.Color.toArrayNormalized(p.ambientColor, ambientColor, 0), p.ambientIntensity);
mol_util_1.ValueCell.update(globalUniforms.uAmbientColor, ambientColor);
}
if (props.ambientIntensity !== undefined && props.ambientIntensity !== p.ambientIntensity) {
p.ambientIntensity = props.ambientIntensity;
linear_algebra_1.Vec3.scale(ambientColor, color_1.Color.toArrayNormalized(p.ambientColor, ambientColor, 0), p.ambientIntensity);
mol_util_1.ValueCell.update(globalUniforms.uAmbientColor, ambientColor);
}
},
setViewport: (x, y, width, height) => {
state.viewport(x, y, width, height);
state.scissor(x, y, width, height);
if (x !== viewport.x || y !== viewport.y || width !== viewport.width || height !== viewport.height) {
util_1.Viewport.set(viewport, x, y, width, height);
mol_util_1.ValueCell.update(globalUniforms.uViewport, linear_algebra_1.Vec4.set(globalUniforms.uViewport.ref.value, x, y, width, height));
}
},
setTransparentBackground: (value) => {
transparentBackground = value;
},
setDrawingBufferSize: (width, height) => {
if (width !== drawingBufferSize[0] || height !== drawingBufferSize[1]) {
mol_util_1.ValueCell.update(globalUniforms.uDrawingBufferSize, linear_algebra_1.Vec2.set(drawingBufferSize, width, height));
}
},
setPixelRatio: (value) => {
mol_util_1.ValueCell.update(globalUniforms.uPixelRatio, value);
},
setOcclusionTest: (f) => {
isOccluded = f;
},
props: p,
get stats() {
return {
programCount: ctx.stats.resourceCounts.program,
shaderCount: ctx.stats.resourceCounts.shader,
attributeCount: ctx.stats.resourceCounts.attribute,
elementsCount: ctx.stats.resourceCounts.elements,
framebufferCount: ctx.stats.resourceCounts.framebuffer,
renderbufferCount: ctx.stats.resourceCounts.renderbuffer,
textureCount: ctx.stats.resourceCounts.texture,
vertexArrayCount: ctx.stats.resourceCounts.vertexArray,
drawCount: stats.drawCount,
instanceCount: stats.instanceCount,
instancedDrawCount: stats.instancedDrawCount,
};
},
get light() {
return light;
},
get ambientColor() {
return globalUniforms.uAmbientColor.ref.value;
},
dispose: () => {
// TODO
}
};
}
Renderer.create = create;
})(Renderer || (exports.Renderer = Renderer = {}));