UNPKG

@here/harp-mapview

Version:

Functionality needed to render a map.

846 lines (845 loc) 46 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.TileGeometryCreator = void 0; /* * Copyright (C) 2019-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 harp_geoutils_1 = require("@here/harp-geoutils"); const harp_materials_1 = require("@here/harp-materials"); const harp_utils_1 = require("@here/harp-utils"); const THREE = require("three"); const DecodedTileHelpers_1 = require("../DecodedTileHelpers"); const DepthPrePass_1 = require("../DepthPrePass"); const MapMaterialAdapter_1 = require("../MapMaterialAdapter"); const PathBlockingElement_1 = require("../PathBlockingElement"); const TextElementBuilder_1 = require("../text/TextElementBuilder"); const AddGroundPlane_1 = require("./AddGroundPlane"); const RegisterTileObject_1 = require("./RegisterTileObject"); const logger = harp_utils_1.LoggerManager.instance.create("TileGeometryCreator"); const tmpVector3 = new THREE.Vector3(); const tmpVector2 = new THREE.Vector2(); class AttachmentCache { constructor() { this.bufferAttributes = new Map(); this.interleavedAttributes = new Map(); } } class MemoCallExpr extends harp_datasource_protocol_1.CallExpr { constructor(expr) { super("memo", [expr]); this.m_cachedProperties = []; this.m_deps = Array.from(expr.dependencies().properties); this.descriptor = this; } call(context) { let changed = false; this.m_deps.forEach((d, i) => { const newValue = context.env.lookup(d); if (!changed && newValue !== this.m_cachedProperties[i]) { changed = true; } if (changed) { this.m_cachedProperties[i] = newValue; } }); if (changed || this.m_cachedValue === undefined) { this.m_cachedValue = context.evaluate(this.args[0]); } return this.m_cachedValue; } } class AttachmentInfo { constructor(geometry, info, cache) { this.geometry = geometry; this.info = info; this.cache = cache; } getBufferAttribute(description) { if (this.cache.bufferAttributes.has(description)) { return this.cache.bufferAttributes.get(description); } const attribute = DecodedTileHelpers_1.getBufferAttribute(description); this.cache.bufferAttributes.set(description, attribute); return attribute; } getInterleavedBufferAttributes(description) { const interleavedAttributes = this.cache.interleavedAttributes.get(description); if (interleavedAttributes) { return interleavedAttributes; } const ArrayCtor = harp_datasource_protocol_1.getArrayConstructor(description.type); const buffer = new ArrayCtor(description.buffer); const interleavedBuffer = new THREE.InterleavedBuffer(buffer, description.stride); const attrs = description.attributes.map(interleavedAttr => { const attribute = new THREE.InterleavedBufferAttribute(interleavedBuffer, interleavedAttr.itemSize, interleavedAttr.offset, false); const name = interleavedAttr.name; return { name, attribute }; }); this.cache.interleavedAttributes.set(description, attrs); return attrs; } } function addToExtrudedMaterials(material, extrudedMaterials) { if (Array.isArray(material)) { const materials = material; extrudedMaterials.push(...materials); } else { extrudedMaterials.push(material); } } /** * Support class to create geometry for a {@link Tile} from a {@link @here/harp-datasource-protocol#DecodedTile}. * @internal */ class TileGeometryCreator { /** * Creates an instance of TileGeometryCreator. Access is allowed only through `instance`. */ constructor() { // } /** * The `instance` of the `TileGeometryCreator`. * * @returns TileGeometryCreator */ static get instance() { return this.m_instance || (this.m_instance = new TileGeometryCreator()); } /** * Apply `enabledKinds` and `disabledKinds` to all techniques in the `decodedTile`. If a * technique is identified as disabled, its property `enabled` is set to `false`. * * @param decodedTile - The decodedTile containing the actual tile map data. * @param enabledKinds - Optional [[GeometryKindSet]] used to specify which object kinds should be * created. * @param disabledKinds - Optional [[GeometryKindSet]] used to filter objects that should not be * created. */ initDecodedTile(decodedTile, enabledKinds, disabledKinds) { for (const technique of decodedTile.techniques) { const kind = technique.kind; // No info about kind, no way to filter it. if (kind === undefined || (kind instanceof Set && kind.size === 0)) { technique._kindState = true; continue; } // Technique is enabled only if enabledKinds is defined and technique belongs to that set or // if that's not the case, disabledKinds must be undefined or technique does not belong to it. technique._kindState = !(disabledKinds !== undefined && disabledKinds.hasOrIntersects(kind)) || (enabledKinds !== undefined && enabledKinds.hasOrIntersects(kind)); } for (const srcGeometry of decodedTile.geometries) { for (const group of srcGeometry.groups) { group.createdOffsets = []; } } } /** * Called after the `Tile` has been decoded. It is required to call `initDecodedTile` before * calling this method. * * @see [[TileGeometryCreator#initDecodedTile]] * * @param tile - The {@link Tile} to process. * @param decodedTile - The decodedTile containing the actual tile map data. * @returns Promise resolved when all textures are ready to render. */ createAllGeometries(tile, decodedTile) { const filter = (technique) => { return technique._kindState !== false; }; let texturesReady = Promise.resolve(); const onNewTexture = (texturePromise) => { texturesReady = Promise.all([ texturesReady, texturePromise .then(texture => { tile.addOwnedTexture(texture); if (!texture.image) { return Promise.resolve(); } return new Promise(resolve => { texture.onUpdate = () => { texture.onUpdate = null; resolve(); }; tile.mapView.renderer.initTexture(texture); }); }) .catch(() => { }) // Keep waiting for the other textures even if one fails. ]); }; this.createObjects(tile, decodedTile, onNewTexture, filter); this.preparePois(tile, decodedTile); // TextElements do not get their geometry created by Tile, but are managed on a // higher level. const textFilter = (technique) => { if (!harp_datasource_protocol_1.isPoiTechnique(technique) && !harp_datasource_protocol_1.isLineMarkerTechnique(technique) && !harp_datasource_protocol_1.isTextTechnique(technique)) { return false; } return filter(technique); }; this.createTextElements(tile, decodedTile, textFilter); this.createLabelRejectionElements(tile, decodedTile); // HARP-7899, disable ground plane for globe if (tile.dataSource.addGroundPlane && tile.projection.type === harp_geoutils_1.ProjectionType.Planar) { // The ground plane is required for when we zoom in and we fall back to the parent // (whilst the new tiles are loading), in that case the ground plane ensures that the // parent's geometry doesn't show through. AddGroundPlane_1.addGroundPlane(tile, -1); } return texturesReady; } createLabelRejectionElements(tile, decodedTile) { if (decodedTile.pathGeometries === undefined) { return; } for (const path of decodedTile.pathGeometries) { tile.addBlockingElement(new PathBlockingElement_1.PathBlockingElement(path.path)); } } /** * Processes the given tile and assign default values for geometry kinds, * render orders and label priorities. * * @param {Tile} tile * @param {(GeometryKindSet | undefined)} enabledKinds * @param {(GeometryKindSet | undefined)} disabledKinds */ processTechniques(tile, enabledKinds, disabledKinds) { const decodedTile = tile.decodedTile; if (decodedTile === undefined) { return; } // Speedup and simplify following code: Test all techniques if they intersect with // enabledKinds and disabledKinds, in which case they are flagged. The disabledKinds can be // ignored hereafter. this.initDecodedTile(decodedTile, enabledKinds, disabledKinds); // compile the dynamic expressions. const exprPool = tile.dataSource.exprPool; decodedTile.techniques.forEach((technique) => { for (const propertyName in technique) { if (!technique.hasOwnProperty(propertyName)) { continue; } const value = technique[propertyName]; if (harp_datasource_protocol_1.isJsonExpr(value) && propertyName !== "kind") { // "kind" is reserved. try { let expr = harp_datasource_protocol_1.Expr.fromJSON(value); if (expr.dependencies().volatile !== true) { expr = new MemoCallExpr(harp_datasource_protocol_1.Expr.fromJSON(value)); } technique[propertyName] = expr.intern(exprPool); } catch (error) { logger.error("Failed to compile expression:", error); } } } }); } /** * Splits the text paths that contain sharp corners. * * @param tile - The {@link Tile} to process paths on. * @param textPathGeometries - The original path geometries that may have defects. * @param textFilter -: Optional filter. Should return true for any text technique that is * applicable. */ prepareTextPaths(textPathGeometries, decodedTile, textFilter) { const processedPaths = new Array(); const newPaths = textPathGeometries.slice(); while (newPaths.length > 0) { const textPath = newPaths.pop(); if (textPath === undefined) { break; } const technique = decodedTile.techniques[textPath.technique]; if (!harp_datasource_protocol_1.isTextTechnique(technique) || (textFilter !== undefined && !textFilter(technique))) { continue; } processedPaths.push(textPath); } return processedPaths; } /** * Creates {@link TextElement} objects from the decoded tile and list of materials specified. The * priorities of the {@link TextElement}s are updated to simplify label placement. * * @param tile - The {@link Tile} to create the testElements on. * @param decodedTile - The {@link @here/harp-datasource-protocol#DecodedTile}. * @param textFilter -: Optional filter. Should return true for any text technique that is * applicable. */ createTextElements(tile, decodedTile, textFilter) { var _a; const mapView = tile.mapView; const worldOffsetX = tile.computeWorldOffsetX(); const discreteZoomLevel = Math.floor(mapView.zoomLevel); const discreteZoomEnv = new harp_datasource_protocol_1.MapEnv({ $zoom: discreteZoomLevel }, mapView.env); const textElementBuilder = new TextElementBuilder_1.TextElementBuilder(discreteZoomEnv, tile.textStyleCache, tile.dataSource.dataSourceOrder); if (decodedTile.textPathGeometries !== undefined) { const textPathGeometries = this.prepareTextPaths(decodedTile.textPathGeometries, decodedTile, textFilter); for (const textPath of textPathGeometries) { const technique = decodedTile.techniques[textPath.technique]; if (technique._kindState === false || !harp_datasource_protocol_1.isTextTechnique(technique) || (textFilter !== undefined && !textFilter(technique))) { continue; } const path = []; for (let i = 0; i < textPath.path.length; i += 3) { path.push(new THREE.Vector3(textPath.path[i] + worldOffsetX, textPath.path[i + 1], textPath.path[i + 2])); } const textElement = textElementBuilder .withTechnique(technique) .build(textPath.text, path, tile.offset, tile.dataSource.name, tile.dataSource.dataSourceOrder, textPath.objInfos, textPath.pathLengthSqr); tile.addTextElement(textElement); } } if (decodedTile.textGeometries !== undefined) { for (const text of decodedTile.textGeometries) { if (text.technique === undefined || text.stringCatalog === undefined) { continue; } const technique = decodedTile.techniques[text.technique]; if (technique._kindState === false || !harp_datasource_protocol_1.isTextTechnique(technique) || (textFilter !== undefined && !textFilter(technique))) { continue; } const positions = new THREE.BufferAttribute(new Float64Array(text.positions.buffer), text.positions.itemCount); const numPositions = positions.count; if (numPositions < 1) { continue; } textElementBuilder.withTechnique(technique); for (let i = 0; i < numPositions; ++i) { const x = positions.getX(i) + worldOffsetX; const y = positions.getY(i); const z = positions.getZ(i); const label = text.stringCatalog[text.texts[i]]; if (label === undefined) { // skip missing labels continue; } const attributes = (_a = text.objInfos) === null || _a === void 0 ? void 0 : _a[i]; const point = new THREE.Vector3(x, y, z); const textElement = textElementBuilder.build(label, point, tile.offset, tile.dataSource.name, tile.dataSource.dataSourceOrder, attributes); tile.addTextElement(textElement); } } } } /** * Creates `Tile` objects from the decoded tile and list of materials specified. * * @param tile - The {@link Tile} to create the geometry on. * @param decodedTile - The {@link @here/harp-datasource-protocol#DecodedTile}. * @param onTextureCreated - Callback for each texture created, getting a promise that will be * resolved once the texture is loaded. Texture is not uploaded to GPU. * @param techniqueFilter -: Optional filter. Should return true for any technique that is * applicable. */ createObjects(tile, decodedTile, onTextureCreated, techniqueFilter) { var _a, _b, _c, _d, _e, _f; const mapView = tile.mapView; const materials = []; const extrudedMaterials = []; const animatedExtrusionHandler = mapView.animatedExtrusionHandler; const discreteZoomLevel = Math.floor(mapView.zoomLevel); const discreteZoomEnv = new harp_datasource_protocol_1.MapEnv({ $zoom: discreteZoomLevel }, mapView.env); const objects = tile.objects; const viewRanges = mapView.viewRanges; const elevationEnabled = mapView.elevationProvider !== undefined; for (const attachment of this.getAttachments(decodedTile)) { const srcGeometry = attachment.geometry; const groups = attachment.info.groups; const groupCount = groups.length; for (let groupIndex = 0; groupIndex < groupCount;) { const group = groups[groupIndex++]; const start = group.start; const techniqueIndex = group.technique; const technique = decodedTile.techniques[techniqueIndex]; if (group.createdOffsets === undefined) { group.createdOffsets = []; } if (group.createdOffsets.includes(tile.offset) || technique._kindState === false || (techniqueFilter !== undefined && !techniqueFilter(technique))) { continue; } let count = group.count; group.createdOffsets.push(tile.offset); // compress consecutive groups for (; groupIndex < groupCount && groups[groupIndex].technique === techniqueIndex; ++groupIndex) { if (start + count !== groups[groupIndex].start) { break; } count += groups[groupIndex].count; // Mark this group as created, so it does not get processed again. groups[groupIndex].createdOffsets.push(tile.offset); } if (!DecodedTileHelpers_1.usesObject3D(technique)) { continue; } const extrusionAnimationEnabled = (_a = animatedExtrusionHandler === null || animatedExtrusionHandler === void 0 ? void 0 : animatedExtrusionHandler.setAnimationProperties(technique, discreteZoomEnv)) !== null && _a !== void 0 ? _a : false; let material = materials[techniqueIndex]; if (material === undefined) { material = DecodedTileHelpers_1.createMaterial(mapView.renderer.capabilities, { technique, env: mapView.env, fog: mapView.scene.fog !== null, shadowsEnabled: mapView.shadowsEnabled }, onTextureCreated); if (material === undefined) { continue; } if (extrusionAnimationEnabled && harp_materials_1.hasExtrusionFeature(material)) { addToExtrudedMaterials(material, extrudedMaterials); } materials[techniqueIndex] = material; } const techniqueKind = technique.kind; // Modify the standard textured shader to support height-based coloring. if (harp_datasource_protocol_1.isTerrainTechnique(technique)) { this.setupTerrainMaterial(technique, material, tile.mapView.clearColor); } const bufferGeometry = new THREE.BufferGeometry(); (_b = srcGeometry.vertexAttributes) === null || _b === void 0 ? void 0 : _b.forEach(vertexAttribute => { const buffer = attachment.getBufferAttribute(vertexAttribute); bufferGeometry.setAttribute(vertexAttribute.name, buffer); }); (_c = srcGeometry.interleavedVertexAttributes) === null || _c === void 0 ? void 0 : _c.forEach(attr => { attachment .getInterleavedBufferAttributes(attr) .forEach(({ name, attribute }) => bufferGeometry.setAttribute(name, attribute)); }); const index = (_d = attachment.info.index) !== null && _d !== void 0 ? _d : srcGeometry.index; if (index) { bufferGeometry.setIndex(attachment.getBufferAttribute(index)); } if (!bufferGeometry.getAttribute("normal") && harp_datasource_protocol_1.needsVertexNormals(technique)) { bufferGeometry.computeVertexNormals(); } bufferGeometry.addGroup(start, count); if (harp_datasource_protocol_1.isSolidLineTechnique(technique)) { // TODO: Unify access to shader defines via SolidLineMaterial setters harp_utils_1.assert(!harp_materials_1.isHighPrecisionLineMaterial(material)); const lineMaterial = material; if (technique.clipping === true && tile.projection.type === harp_geoutils_1.ProjectionType.Planar) { tile.boundingBox.getSize(tmpVector3); tmpVector2.set(tmpVector3.x, tmpVector3.y); lineMaterial.clipTileSize = tmpVector2; } if (bufferGeometry.getAttribute("color")) { harp_materials_1.setShaderMaterialDefine(lineMaterial, "USE_COLOR", true); } } // Add the solid line outlines as a separate object. const hasSolidLinesOutlines = harp_datasource_protocol_1.isSolidLineTechnique(technique) && technique.secondaryWidth !== undefined; // When the source geometry is split in groups, we // should create objects with an array of materials. const hasFeatureGroups = harp_datasource_protocol_1.Expr.isExpr(technique.enabled) && srcGeometry.featureStarts && srcGeometry.featureStarts.length > 0; const object = DecodedTileHelpers_1.buildObject(technique, bufferGeometry, hasFeatureGroups ? [material] : material, tile, elevationEnabled); object.renderOrder = harp_datasource_protocol_1.getPropertyValue(technique.renderOrder, mapView.env); if (attachment.info.uuid !== undefined) { object.uuid = attachment.info.uuid; object.userData.geometryId = attachment.info.uuid; } if ((harp_datasource_protocol_1.isCirclesTechnique(technique) || harp_datasource_protocol_1.isSquaresTechnique(technique)) && technique.enablePicking !== undefined) { object.enableRayTesting = technique.enablePicking; } if (harp_datasource_protocol_1.isLineTechnique(technique) || harp_datasource_protocol_1.isSegmentsTechnique(technique)) { const fadingParams = this.getFadingParams(discreteZoomEnv, technique); harp_materials_1.FadingFeature.addRenderHelper(object, viewRanges, fadingParams.fadeNear, fadingParams.fadeFar, false); } if (harp_datasource_protocol_1.isSolidLineTechnique(technique)) { const fadingParams = this.getFadingParams(discreteZoomEnv, technique); harp_materials_1.FadingFeature.addRenderHelper(object, viewRanges, fadingParams.fadeNear, fadingParams.fadeFar, false); } if (harp_datasource_protocol_1.isExtrudedLineTechnique(technique)) { // extruded lines are normal meshes, and need transparency only when fading or // dynamic properties is defined. if (technique.fadeFar !== undefined) { const fadingParams = this.getFadingParams(mapView.env, technique); harp_materials_1.FadingFeature.addRenderHelper(object, viewRanges, fadingParams.fadeNear, fadingParams.fadeFar, true); } } this.addUserData(tile, srcGeometry, technique, object); if (harp_datasource_protocol_1.isExtrudedPolygonTechnique(technique)) { object.castShadow = mapView.shadowsEnabled; object.receiveShadow = mapView.shadowsEnabled; } else if (harp_datasource_protocol_1.isStandardTechnique(technique) || harp_datasource_protocol_1.isFillTechnique(technique)) { object.receiveShadow = mapView.shadowsEnabled; } if (harp_datasource_protocol_1.isExtrudedPolygonTechnique(technique) || harp_datasource_protocol_1.isStandardTechnique(technique) || harp_datasource_protocol_1.isFillTechnique(technique)) { // filled polygons are normal meshes, and need transparency only when fading or // dynamic properties is defined. if (technique.fadeFar !== undefined) { const fadingParams = this.getFadingParams(discreteZoomEnv, technique); harp_materials_1.FadingFeature.addRenderHelper(object, viewRanges, fadingParams.fadeNear, fadingParams.fadeFar, true); } } const renderDepthPrePass = harp_datasource_protocol_1.isExtrudedPolygonTechnique(technique) && DepthPrePass_1.isRenderDepthPrePassEnabled(technique, discreteZoomEnv); if (renderDepthPrePass) { const depthPassMesh = DepthPrePass_1.createDepthPrePassMesh(object); this.addUserData(tile, srcGeometry, technique, depthPassMesh); // Set geometry kind for depth pass mesh so that it gets the displacement map // for elevation overlay. RegisterTileObject_1.registerTileObject(tile, depthPassMesh, techniqueKind, { technique, pickability: harp_datasource_protocol_1.Pickability.transient }); objects.push(depthPassMesh); if (extrusionAnimationEnabled) { addToExtrudedMaterials(depthPassMesh.material, extrudedMaterials); } DepthPrePass_1.setDepthPrePassStencil(depthPassMesh, object); } const techniquePickability = harp_datasource_protocol_1.transientToPickability(harp_datasource_protocol_1.getPropertyValue(technique.transient, mapView.env)); // register all objects as pickable except solid lines with outlines, in that case // it's enough to make outlines pickable. RegisterTileObject_1.registerTileObject(tile, object, techniqueKind, { technique, pickability: hasSolidLinesOutlines ? harp_datasource_protocol_1.Pickability.transient : techniquePickability }); objects.push(object); // Add the extruded polygon edges as a separate geometry. if (harp_datasource_protocol_1.isExtrudedPolygonTechnique(technique) && attachment.info.edgeIndex !== undefined) { // When the source geometry is split in groups, we // should create objects with an array of materials. const hasEdgeFeatureGroups = harp_datasource_protocol_1.Expr.isExpr(technique.enabled) && srcGeometry.edgeFeatureStarts && srcGeometry.edgeFeatureStarts.length > 0; const buildingTechnique = technique; const edgeGeometry = new THREE.BufferGeometry(); edgeGeometry.setAttribute("position", bufferGeometry.getAttribute("position")); const colorAttribute = bufferGeometry.getAttribute("color"); if (colorAttribute !== undefined) { edgeGeometry.setAttribute("color", colorAttribute); } const extrusionAttribute = bufferGeometry.getAttribute("extrusionAxis"); if (extrusionAttribute !== undefined) { edgeGeometry.setAttribute("extrusionAxis", extrusionAttribute); } const normalAttribute = bufferGeometry.getAttribute("normal"); if (normalAttribute !== undefined) { edgeGeometry.setAttribute("normal", normalAttribute); } const uvAttribute = bufferGeometry.getAttribute("uv"); if (uvAttribute !== undefined) { edgeGeometry.setAttribute("uv", uvAttribute); } edgeGeometry.setIndex(attachment.getBufferAttribute(attachment.info.edgeIndex)); // Read the uniforms from the technique values (and apply the default values). const extrudedPolygonTechnique = technique; const fadingParams = this.getPolygonFadingParams(discreteZoomEnv, extrudedPolygonTechnique); // Configure the edge material based on the theme values. const materialParams = { color: fadingParams.color, colorMix: fadingParams.colorMix, fadeNear: fadingParams.lineFadeNear, fadeFar: fadingParams.lineFadeFar, extrusionRatio: extrusionAnimationEnabled ? 0 : undefined, vertexColors: bufferGeometry.getAttribute("color") ? true : false, rendererCapabilities: mapView.renderer.capabilities }; const edgeMaterial = new harp_materials_1.EdgeMaterial(materialParams); const edgeObj = new THREE.LineSegments(edgeGeometry, hasEdgeFeatureGroups ? [edgeMaterial] : edgeMaterial); this.addUserData(tile, srcGeometry, technique, edgeObj); // Set the correct render order. edgeObj.renderOrder = object.renderOrder + 0.1; harp_materials_1.FadingFeature.addRenderHelper(edgeObj, viewRanges, fadingParams.lineFadeNear, fadingParams.lineFadeFar, false); if (extrusionAnimationEnabled) { addToExtrudedMaterials(edgeObj.material, extrudedMaterials); } RegisterTileObject_1.registerTileObject(tile, edgeObj, techniqueKind, { technique, pickability: harp_datasource_protocol_1.Pickability.transient }); MapMaterialAdapter_1.MapMaterialAdapter.create(edgeMaterial, { color: buildingTechnique.lineColor, objectColor: buildingTechnique.color, opacity: buildingTechnique.opacity, lineWidth: (frameMapView) => { // lineWidth for ExtrudedPolygonEdges only supports 0 or 1 const value = harp_datasource_protocol_1.getPropertyValue(buildingTechnique.lineWidth, frameMapView.env); if (typeof value === "number") { return THREE.MathUtils.clamp(value, 0, 1); } else { return 0; } } }); objects.push(edgeObj); } // animate the extrusion of buildings if (harp_datasource_protocol_1.isExtrudedPolygonTechnique(technique) && extrusionAnimationEnabled) { object.customDepthMaterial = new harp_materials_1.MapMeshDepthMaterial({ depthPacking: THREE.RGBADepthPacking }); addToExtrudedMaterials(object.customDepthMaterial, extrudedMaterials); } // Add the fill area edges as a separate geometry. if (harp_datasource_protocol_1.isFillTechnique(technique) && attachment.info.edgeIndex) { const hasEdgeFeatureGroups = harp_datasource_protocol_1.Expr.isExpr(technique.enabled) && srcGeometry.edgeFeatureStarts && srcGeometry.edgeFeatureStarts.length > 0; const outlineGeometry = new THREE.BufferGeometry(); outlineGeometry.setAttribute("position", bufferGeometry.getAttribute("position")); outlineGeometry.setIndex(attachment.getBufferAttribute(attachment.info.edgeIndex)); const fillTechnique = technique; const fadingParams = this.getPolygonFadingParams(mapView.env, fillTechnique); // Configure the edge material based on the theme values. const materialParams = { color: fadingParams.color, colorMix: fadingParams.colorMix, fadeNear: fadingParams.lineFadeNear, fadeFar: fadingParams.lineFadeFar, vertexColors: bufferGeometry.getAttribute("color") ? true : false, rendererCapabilities: mapView.renderer.capabilities }; const outlineMaterial = new harp_materials_1.EdgeMaterial(materialParams); const outlineObj = new THREE.LineSegments(outlineGeometry, hasEdgeFeatureGroups ? [outlineMaterial] : outlineMaterial); outlineObj.renderOrder = object.renderOrder + 0.1; harp_materials_1.FadingFeature.addRenderHelper(outlineObj, viewRanges, fadingParams.lineFadeNear, fadingParams.lineFadeFar, false); this.addUserData(tile, srcGeometry, technique, outlineObj); RegisterTileObject_1.registerTileObject(tile, outlineObj, techniqueKind, { technique, pickability: techniquePickability }); MapMaterialAdapter_1.MapMaterialAdapter.create(outlineMaterial, { color: fillTechnique.lineColor, objectColor: fillTechnique.color, opacity: fillTechnique.opacity }); objects.push(outlineObj); } // Add the fill area edges as a separate geometry. if (hasSolidLinesOutlines) { const outlineTechnique = technique; const outlineMaterial = material.clone(); DecodedTileHelpers_1.applyBaseColorToMaterial(outlineMaterial, outlineMaterial.color, outlineTechnique, (_e = outlineTechnique.secondaryColor) !== null && _e !== void 0 ? _e : 0x000000, discreteZoomEnv); if (outlineTechnique.secondaryCaps !== undefined) { outlineMaterial.caps = harp_datasource_protocol_1.getPropertyValue(outlineTechnique.secondaryCaps, mapView.env); } const outlineObj = DecodedTileHelpers_1.buildObject(technique, bufferGeometry, outlineMaterial, tile, elevationEnabled); outlineObj.renderOrder = ((_f = harp_datasource_protocol_1.getPropertyValue(outlineTechnique.secondaryRenderOrder, mapView.env)) !== null && _f !== void 0 ? _f : 0) - 0.0000001; this.addUserData(tile, srcGeometry, technique, outlineObj); const fadingParams = this.getFadingParams(discreteZoomEnv, technique); harp_materials_1.FadingFeature.addRenderHelper(outlineObj, viewRanges, fadingParams.fadeNear, fadingParams.fadeFar, false); const secondaryWidth = DecodedTileHelpers_1.buildMetricValueEvaluator(outlineTechnique.secondaryWidth, outlineTechnique.metricUnit); RegisterTileObject_1.registerTileObject(tile, outlineObj, techniqueKind, { technique }); const mainMaterialAdapter = MapMaterialAdapter_1.MapMaterialAdapter.get(material); const outlineMaterialAdapter = MapMaterialAdapter_1.MapMaterialAdapter.create(outlineMaterial, { color: outlineTechnique.secondaryColor, opacity: outlineTechnique.opacity, caps: outlineTechnique.secondaryCaps, // Still handled above lineWidth: (frameMapView) => { if (!mainMaterialAdapter) { return; } mainMaterialAdapter.ensureUpdated(frameMapView); const mainLineWidth = mainMaterialAdapter.currentStyledProperties.lineWidth; const secondaryLineWidth = harp_datasource_protocol_1.getPropertyValue(secondaryWidth, mapView.env); const opacity = outlineMaterialAdapter.currentStyledProperties .opacity; if (typeof mainLineWidth === "number" && typeof secondaryLineWidth === "number") { if (secondaryLineWidth <= mainLineWidth && (opacity === null || opacity === undefined || opacity === 1)) { // We could mark object as invisible somehow, not sure how // objectAdapter.markInvisible(); return 0; } else { return secondaryLineWidth; } } else { return 0; } } }); objects.push(outlineObj); } } } if (extrudedMaterials.length > 0) { mapView.animatedExtrusionHandler.add(tile, extrudedMaterials); } } /** * Prepare the {@link Tile}s pois. Uses the {@link PoiManager} in {@link MapView}. */ preparePois(tile, decodedTile) { if (decodedTile.poiGeometries !== undefined) { tile.mapView.poiManager.addPois(tile, decodedTile); } } /** * Gets the attachments of the given {@link @here/harp-datasource-protocol#DecodedTile}. * * @param decodedTile - The {@link @here/harp-datasource-protocol#DecodedTile}. */ *getAttachments(decodedTile) { const cache = new AttachmentCache(); for (const geometry of decodedTile.geometries) { // the main attachment const mainAttachment = { index: geometry.index, edgeIndex: geometry.edgeIndex, uuid: geometry.uuid, groups: geometry.groups }; yield new AttachmentInfo(geometry, mainAttachment, cache); if (geometry.attachments) { // the additional attachments for (const info of geometry.attachments) { yield new AttachmentInfo(geometry, info, cache); } } } } setupTerrainMaterial(technique, material, terrainColor) { if (!technique.map || !technique.displacementMap) { // Render terrain using the given color. const stdMaterial = material; stdMaterial.color.set(terrainColor); // Remove displacement map, otherwise it would elevate terrain geometry and make it // twice as high as it should be. harp_materials_1.setDisplacementMapToMaterial(null, stdMaterial); return; } // Render terrain using height-based colors. material.onBeforeCompile = (shader) => { shader.fragmentShader = shader.fragmentShader.replace("#include <map_pars_fragment>", `#include <map_pars_fragment> uniform sampler2D displacementMap; uniform float displacementScale; uniform float displacementBias;`); shader.fragmentShader = shader.fragmentShader.replace("#include <map_fragment>", `#ifdef USE_MAP float minElevation = ${harp_geoutils_1.EarthConstants.MIN_ELEVATION.toFixed(1)}; float maxElevation = ${harp_geoutils_1.EarthConstants.MAX_ELEVATION.toFixed(1)}; float elevationRange = maxElevation - minElevation; float disp = texture2D( displacementMap, vUv ).x * displacementScale + displacementBias; vec4 texelColor = texture2D( map, vec2((disp - minElevation) / elevationRange, 0.0) ); texelColor = mapTexelToLinear( texelColor ); diffuseColor *= texelColor; #endif`); // We remove the displacement map from manipulating the vertices, it is // however still required for the pixel shader, so it can't be directly // removed. shader.vertexShader = shader.vertexShader.replace("#include <displacementmap_vertex>", ""); }; material.displacementMap.needsUpdate = true; } addUserData(tile, srcGeometry, technique, object) { if (harp_datasource_protocol_1.isTerrainTechnique(technique)) { harp_utils_1.assert(Object.keys(object.userData).length === 0, "Unexpected user data in terrain object"); harp_utils_1.assert(typeof srcGeometry.objInfos[0] === "object", "Wrong attribute map type for terrain geometry"); const displacementMap = srcGeometry.objInfos[0]; const tileDisplacementMap = { tileKey: tile.tileKey, texture: new THREE.DataTexture(displacementMap.buffer, displacementMap.xCountVertices, displacementMap.yCountVertices, THREE.LuminanceFormat, THREE.FloatType), displacementMap, geoBox: tile.geoBox }; object.userData = tileDisplacementMap; } else { // Set the feature data for picking with `MapView.intersectMapObjects()` except for // solid-line which uses tile-based picking. const isOutline = object.type === "LineSegments" && (harp_datasource_protocol_1.isExtrudedPolygonTechnique(technique) || harp_datasource_protocol_1.isFillTechnique(technique)); const featureData = { geometryType: srcGeometry.type, starts: isOutline ? srcGeometry.edgeFeatureStarts : srcGeometry.featureStarts, objInfos: srcGeometry.objInfos }; object.userData.feature = featureData; object.userData.technique = technique; } } /** * Gets the fading parameters for several kinds of objects. */ getFadingParams(env, technique) { const fadeNear = technique.fadeNear !== undefined ? harp_datasource_protocol_1.getPropertyValue(technique.fadeNear, env) : harp_materials_1.FadingFeature.DEFAULT_FADE_NEAR; const fadeFar = technique.fadeFar !== undefined ? harp_datasource_protocol_1.getPropertyValue(technique.fadeFar, env) : harp_materials_1.FadingFeature.DEFAULT_FADE_FAR; return { fadeNear, fadeFar }; } /** * Gets the fading parameters for several kinds of objects. */ getPolygonFadingParams(env, technique) { let color; let colorMix = harp_materials_1.EdgeMaterial.DEFAULT_COLOR_MIX; if (technique.lineColor !== undefined) { color = harp_datasource_protocol_1.getPropertyValue(technique.lineColor, env); if (harp_datasource_protocol_1.isExtrudedPolygonTechnique(technique)) { const extrudedPolygonTechnique = technique; colorMix = extrudedPolygonTechnique.lineColorMix !== undefined ? extrudedPolygonTechnique.lineColorMix : harp_materials_1.EdgeMaterial.DEFAULT_COLOR_MIX; } } const fadeNear = technique.fadeNear !== undefined ? harp_datasource_protocol_1.getPropertyValue(technique.fadeNear, env) : harp_materials_1.FadingFeature.DEFAULT_FADE_NEAR; const fadeFar = technique.fadeFar !== undefined ? harp_datasource_protocol_1.getPropertyValue(technique.fadeFar, env) : harp_materials_1.FadingFeature.DEFAULT_FADE_FAR; const lineFadeNear = technique.lineFadeNear !== undefined ? harp_datasource_protocol_1.getPropertyValue(technique.lineFadeNear, env) : fadeNear; const lineFadeFar = technique.lineFadeFar !== undefined ? harp_datasource_protocol_1.getPropertyValue(technique.lineFadeFar, env) : fadeFar; if (color === undefined) { color = harp_materials_1.EdgeMaterial.DEFAULT_COLOR; } return { color, colorMix, fadeNear, fadeFar, lineFadeNear, lineFadeFar }; } } exports.TileGeometryCreator = TileGeometryCreator; //# sourceMappingURL=TileGeometryCreator.js.map