UNPKG

@here/harp-mapview

Version:

Functionality needed to render a map.

258 lines 13.3 kB
"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