@here/harp-mapview
Version:
Functionality needed to render a map.
258 lines • 13.3 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TileObjectRenderer = void 0;
/*
* Copyright (C) 2020-2021 HERE Europe B.V.
* Licensed under Apache 2.0, see full license in LICENSE
* SPDX-License-Identifier: Apache-2.0
*/
const harp_datasource_protocol_1 = require("@here/harp-datasource-protocol");
const BackgroundDataSource_1 = require("./BackgroundDataSource");
const SolidLineMesh_1 = require("./geometry/SolidLineMesh");
const MapObjectAdapter_1 = require("./MapObjectAdapter");
const DEFAULT_STENCIL_VALUE = 1;
class TileObjectRenderer {
constructor(m_env, m_renderer) {
this.m_env = m_env;
this.m_renderer = m_renderer;
this.m_renderOrderStencilValues = new Map();
// Valid values start at 1, because the screen is cleared to zero
this.m_stencilValue = DEFAULT_STENCIL_VALUE;
}
render(tile, storageLevel, zoomLevel, cameraPosition, rootNode) {
const worldOffsetX = tile.computeWorldOffsetX();
if (tile.willRender(storageLevel)) {
for (const object of tile.objects) {
const mapObjectAdapter = MapObjectAdapter_1.MapObjectAdapter.get(object);
if (!this.processTileObject(tile, storageLevel, zoomLevel, object, mapObjectAdapter)) {
continue;
}
this.updateStencilRef(object);
object.position.copy(tile.center);
if (object.displacement !== undefined) {
object.position.add(object.displacement);
}
object.position.x += worldOffsetX;
object.position.sub(cameraPosition);
if (tile.localTangentSpace) {
object.setRotationFromMatrix(tile.boundingBox.getRotationMatrix());
}
object.frustumCulled = false;
rootNode.add(object);
}
tile.didRender();
}
}
prepareRender() {
this.m_stencilValue = DEFAULT_STENCIL_VALUE;
this.m_renderOrderStencilValues.clear();
}
/**
* Prepares the sorting of tile objects.
*/
setupRenderer() {
/**
* Custom sorting function to avoid non-deterministic IBCT testcases. It's basically a copy
* of the three.js internal sorting, see:
* https://github.com/mrdoob/three.js/blob/r118/src/renderers/webgl/WebGLRenderLists.js#L5
* but additionally checking tile-id before checking object.id, material.id and program.id
* b/c these ids are generated by incrementing a counter. This means if for two test
* executions the tiles are processed in a different order the ids hence draw call order
* will also be different.
*/
const stableSort = (a, b) => {
if (a.groupOrder !== b.groupOrder) {
return a.groupOrder - b.groupOrder;
}
else if (a.renderOrder !== b.renderOrder) {
return a.renderOrder - b.renderOrder;
}
else if (a.object.userData.tileKey &&
b.object.userData.tileKey &&
a.object.userData.tileKey.mortonCode() !== b.object.userData.tileKey.mortonCode()) {
return (a.object.userData.tileKey.mortonCode() - b.object.userData.tileKey.mortonCode());
}
else if (a.program !== b.program) {
return a.program.id - b.program.id;
}
else if (a.material.id !== b.material.id) {
return a.material.id - b.material.id;
}
else if (a.z !== b.z) {
return a.z - b.z;
}
else {
return a.id - b.id;
}
};
// Custom sorting function which first sorts by the data source order, then by the level,
// then by the function `stableSort` above.
const painterSortStable = (a, b) => {
var _a, _b, _c, _d;
const mapObjectAdapterA = MapObjectAdapter_1.MapObjectAdapter.get(a.object);
const mapObjectAdapterB = MapObjectAdapter_1.MapObjectAdapter.get(b.object);
const dataSourceOrder = (_a = mapObjectAdapterA === null || mapObjectAdapterA === void 0 ? void 0 : mapObjectAdapterA.dataSource) === null || _a === void 0 ? void 0 : _a.dataSourceOrder;
const otherDataSourceOrder = (_b = mapObjectAdapterB === null || mapObjectAdapterB === void 0 ? void 0 : mapObjectAdapterB.dataSource) === null || _b === void 0 ? void 0 : _b.dataSourceOrder;
if (
// We need to check against undefined because if either is 0, it will evaluate false
dataSourceOrder !== undefined &&
otherDataSourceOrder !== undefined &&
dataSourceOrder !== otherDataSourceOrder) {
return dataSourceOrder - otherDataSourceOrder;
}
// Background data source must be sorted by rendorOrder and not level, otherwise
// fallback tiles are useless, because they will be covered by this datasource
if (a.renderOrder === BackgroundDataSource_1.BackgroundDataSource.GROUND_RENDER_ORDER ||
b.renderOrder === BackgroundDataSource_1.BackgroundDataSource.GROUND_RENDER_ORDER) {
return stableSort(a, b);
}
if ((mapObjectAdapterA === null || mapObjectAdapterA === void 0 ? void 0 : mapObjectAdapterA.level) !== undefined && (mapObjectAdapterB === null || mapObjectAdapterB === void 0 ? void 0 : mapObjectAdapterB.level) !== undefined) {
// Extruded buildings may interfere with landmarks, so we need to sort by
// renderOrder, see LandmarkDataSource.computeRenderOrder
const eitherIsBuilding = ((_c = mapObjectAdapterA.kind) === null || _c === void 0 ? void 0 : _c.find(s => s === "building")) !== undefined ||
((_d = mapObjectAdapterB.kind) === null || _d === void 0 ? void 0 : _d.find(s => s === "building")) !== undefined;
const sameLevel = mapObjectAdapterA.level === mapObjectAdapterB.level;
if (sameLevel || eitherIsBuilding) {
return stableSort(a, b);
}
return mapObjectAdapterA.level - mapObjectAdapterB.level;
}
return stableSort(a, b);
};
// Temporary workaround due to incorrect comparator type definition:
// https://github.com/three-types/three-ts-types/issues/41
this.m_renderer.setOpaqueSort(painterSortStable);
}
updateStencilRef(object) {
// TODO: acquire a new style value of if transparent
if (object.renderOrder !== undefined && object instanceof SolidLineMesh_1.SolidLineMesh) {
const material = object.material;
if (Array.isArray(material)) {
material.forEach(mat => (mat.stencilRef = this.getStencilValue(object.renderOrder)));
}
else {
material.stencilRef = this.getStencilValue(object.renderOrder);
}
}
}
allocateStencilValue(renderOrder) {
const stencilValue = this.m_stencilValue++;
this.m_renderOrderStencilValues.set(renderOrder, stencilValue);
return stencilValue;
}
getStencilValue(renderOrder) {
var _a;
return ((_a = this.m_renderOrderStencilValues.get(renderOrder)) !== null && _a !== void 0 ? _a : this.allocateStencilValue(renderOrder));
}
/**
* Process dynamic updates of [[TileObject]]'s style.
*
* @returns `true` if object shall be used in scene, `false` otherwise
*/
processTileObject(tile, storageLevel, zoomLevel, object, mapObjectAdapter) {
if (!object.visible) {
return false;
}
if (!this.processTileObjectFeatures(tile, storageLevel, zoomLevel, object)) {
return false;
}
if (mapObjectAdapter) {
mapObjectAdapter.ensureUpdated(tile.mapView);
if (!mapObjectAdapter.isVisible() &&
!(mapObjectAdapter.pickability === harp_datasource_protocol_1.Pickability.all)) {
return false;
}
}
return true;
}
/**
* Process the features owned by the given `TileObject`.
*
* @param tile - The {@link Tile} owning the `TileObject`'s features.
* @param storageLevel - The storage level of the `Tile` containing the object,
* @param zoomLevel - The current zoom level of `MapView`.
* @param object - The `TileObject` to process.
* @returns `false` if the given `TileObject` should not be added to the scene.
*/
processTileObjectFeatures(tile, storageLevel, zoomLevel, object) {
var _a, _b;
const technique = object.userData.technique;
const minZoomLevel = harp_datasource_protocol_1.getPropertyValue(technique === null || technique === void 0 ? void 0 : technique.minZoomLevel, this.m_env);
const maxZoomLevel = harp_datasource_protocol_1.getPropertyValue(technique === null || technique === void 0 ? void 0 : technique.maxZoomLevel, this.m_env);
if (typeof minZoomLevel === "number" && zoomLevel < minZoomLevel) {
return false;
}
if (typeof maxZoomLevel === "number" && zoomLevel >= maxZoomLevel) {
return false;
}
if ((technique === null || technique === void 0 ? void 0 : technique.enabled) === undefined) {
// Nothing to do, there's no technique.
return true;
}
const feature = object.userData.feature;
if (!feature || !harp_datasource_protocol_1.Expr.isExpr(technique.enabled)) {
return Boolean(harp_datasource_protocol_1.getPropertyValue(technique.enabled, this.m_env));
}
const { starts, objInfos } = feature;
if (!Array.isArray(objInfos) || !Array.isArray(starts)) {
// Nothing to do, the object is missing feature ids and their position
// in the index buffer.
return true;
}
const geometry = object.geometry;
if (!geometry || !geometry.isBufferGeometry) {
// Nothing to do, the geometry is not a [[THREE.BufferGeometry]]
// and we can't generate groups.
return true;
}
// ExtrudeBufferGeometry for example doesn't have an index, hence we get the final index
// from the number of vertices.
const finalIndex = (_b = (_a = geometry.getIndex()) === null || _a === void 0 ? void 0 : _a.count) !== null && _b !== void 0 ? _b : geometry.attributes.position.count;
// clear the groups.
geometry.clearGroups();
// The offset in the index buffer of the end of the last
// pushed group.
let endOfLastGroup;
objInfos.forEach((properties, featureIndex) => {
var _a, _b;
// the id of the current feature.
const featureId = harp_datasource_protocol_1.getFeatureId(properties);
let enabled = true;
if (harp_datasource_protocol_1.Expr.isExpr(technique.enabled)) {
// the state of current feature.
const featureState = tile.dataSource.getFeatureState(featureId);
// create a new {@link @here/harp-datasource-protocol#Env} that can be used
// to evaluate expressions that access the feature state.
const $state = featureState ? new harp_datasource_protocol_1.MapEnv(featureState) : null;
const parentEnv = typeof properties === "object"
? new harp_datasource_protocol_1.MapEnv(properties, this.m_env)
: this.m_env;
const env = new harp_datasource_protocol_1.MapEnv({ $state }, parentEnv);
enabled = Boolean(harp_datasource_protocol_1.getPropertyValue(technique.enabled, env));
}
if (!enabled) {
// skip this feature, it was disabled.
return;
}
// HARP-12247, geometry with no featureStarts would set start to `undefined`, in this
// case, `endOfLastGroup` is also undefined (first execution in this loop), so it would
// try to change the count of a group which hasn't yet been added, `addGroup` wasn't yet
// called, hence we use the `??` operator and fall back to 0. Because featureStarts are
// optional, we need to have a fallback.
const start = (_a = starts[featureIndex]) !== null && _a !== void 0 ? _a : 0;
const end = (_b = starts[featureIndex + 1]) !== null && _b !== void 0 ? _b : finalIndex;
const count = end - start;
if (start === endOfLastGroup) {
// extend the last group
geometry.groups[geometry.groups.length - 1].count += count;
}
else {
geometry.addGroup(start, count);
}
endOfLastGroup = start + count;
});
return geometry.groups.length > 0;
}
}
exports.TileObjectRenderer = TileObjectRenderer;
//# sourceMappingURL=TileObjectsRenderer.js.map