diff --git a/@here/harp-mapview/lib/BackgroundDataSource.ts b/@here/harp-mapview/lib/BackgroundDataSource.ts index 6838fc05c5..b4475e9ac8 100644 --- a/@here/harp-mapview/lib/BackgroundDataSource.ts +++ b/@here/harp-mapview/lib/BackgroundDataSource.ts @@ -8,7 +8,7 @@ import { FlatTheme, Theme } from "@here/harp-datasource-protocol"; import { TileKey, TilingScheme, webMercatorTilingScheme } from "@here/harp-geoutils"; import { DataSource } from "./DataSource"; -import { TileGeometryCreator } from "./geometry/TileGeometryCreator"; +import { addGroundPlane } from "./geometry/AddGroundPlane"; import { Tile } from "./Tile"; /** @@ -77,7 +77,7 @@ export class BackgroundDataSource extends DataSource { getTile(tileKey: TileKey): Tile | undefined { const tile = new Tile(this, tileKey); tile.forceHasGeometry(true); - TileGeometryCreator.instance.addGroundPlane(tile, BackgroundDataSource.GROUND_RENDER_ORDER); + addGroundPlane(tile, BackgroundDataSource.GROUND_RENDER_ORDER); return tile; } diff --git a/@here/harp-mapview/lib/geometry/AddGroundPlane.ts b/@here/harp-mapview/lib/geometry/AddGroundPlane.ts new file mode 100644 index 0000000000..feae2e5226 --- /dev/null +++ b/@here/harp-mapview/lib/geometry/AddGroundPlane.ts @@ -0,0 +1,196 @@ +/* + * Copyright (C) 2017-2020 HERE Europe B.V. + * Licensed under Apache 2.0, see full license in LICENSE + * SPDX-License-Identifier: Apache-2.0 + */ + +import { GeometryKind } from "@here/harp-datasource-protocol"; +import { + EdgeLengthGeometrySubdivisionModifier, + SubdivisionMode +} from "@here/harp-geometry/lib/EdgeLengthGeometrySubdivisionModifier"; +import { SphericalGeometrySubdivisionModifier } from "@here/harp-geometry/lib/SphericalGeometrySubdivisionModifier"; +import { Projection, ProjectionType } from "@here/harp-geoutils"; +import { MapMeshBasicMaterial, MapMeshStandardMaterial } from "@here/harp-materials"; +import * as THREE from "three"; + +import { Tile } from "../Tile"; +import { LodMesh } from "./LodMesh"; +import { projectTilePlaneCorners } from "./ProjectTilePlaneCorners"; +import { registerTileObject } from "./RegisterTileObject"; + +const tmpV = new THREE.Vector3(); + +/** + * Creates and adds a background plane mesh for the tile. + * @param tile - The tile to which the ground plane belongs. + * @param renderOrder - The plane render order. + * @param materialOrColor - The plane material or a color for a default material. + * @param createTexCoords - Whether to create texture coordinates. + * @param receiveShadow - Whether the plane should receive shadows. + * @param createMultiLod - Whether to generate multiple LODs for sphere projection. + * @internal + */ +export function addGroundPlane( + tile: Tile, + renderOrder: number, + materialOrColor: THREE.Material | THREE.Material[] | number = tile.mapView.clearColor, + createTexCoords: boolean = false, + receiveShadow: boolean = tile.mapView.shadowsEnabled, + createMultiLod: boolean = tile.mapView.enableMixedLod !== false +) { + const mesh = createGroundPlane( + tile, + createTexCoords, + receiveShadow, + materialOrColor, + createMultiLod + ); + mesh.receiveShadow = receiveShadow; + mesh.renderOrder = renderOrder; + registerTileObject(tile, mesh, GeometryKind.Background, { pickable: false }); + tile.objects.push(mesh); +} + +function createGroundPlane( + tile: Tile, + createTexCoords: boolean, + receiveShadow: boolean, + materialOrColor: THREE.Material | THREE.Material[] | number, + createMultiLod: boolean +): THREE.Mesh { + const { dataSource, projection } = tile; + const sourceProjection = dataSource.getTilingScheme().projection; + const shouldSubdivide = projection.type === ProjectionType.Spherical; + const useLocalTargetCoords = !shouldSubdivide; + + const material = + typeof materialOrColor === "number" + ? createGroundPlaneMaterial( + new THREE.Color(materialOrColor), + receiveShadow, + projection.type === ProjectionType.Spherical + ) + : materialOrColor; + + const geometry = createGroundPlaneGeometry( + tile, + useLocalTargetCoords, + createTexCoords, + receiveShadow + ); + + if (!shouldSubdivide) { + return new THREE.Mesh(geometry, material); + } + + const geometries: THREE.BufferGeometry[] = []; + const sphericalModifier = new SphericalGeometrySubdivisionModifier( + THREE.MathUtils.degToRad(10), + sourceProjection + ); + + if (!createMultiLod) { + sphericalModifier.modify(geometry); + toLocalTargetCoords(geometry, sourceProjection, tile); + + return new THREE.Mesh(geometry, material); + } + + // Use a [[LodMesh]] to adapt tesselation of tile depending on zoom level + for (let zoomLevelOffset = 0; zoomLevelOffset < 4; ++zoomLevelOffset) { + const subdivision = Math.pow(2, zoomLevelOffset); + const zoomLevelGeometry = geometry.clone(); + if (subdivision > 1) { + const edgeModifier = new EdgeLengthGeometrySubdivisionModifier( + subdivision, + tile.geoBox, + SubdivisionMode.All, + sourceProjection + ); + edgeModifier.modify(zoomLevelGeometry); + } + sphericalModifier.modify(zoomLevelGeometry); + toLocalTargetCoords(zoomLevelGeometry, sourceProjection, tile); + geometries.push(zoomLevelGeometry); + } + return new LodMesh(geometries, material); +} + +function toLocalTargetCoords(geom: THREE.BufferGeometry, srcProjection: Projection, tile: Tile) { + const attr = geom.getAttribute("position") as THREE.BufferAttribute; + const oldArray = attr.array as Float64Array; + // Convert to single precision before rendering (WebGL does not support double + // precision). + const newArray = new Float32Array(oldArray.length); + for (let i = 0; i < attr.array.length; i += 1) { + tmpV.fromBufferAttribute(attr, i); + tile.projection.reprojectPoint(srcProjection, tmpV, tmpV).sub(tile.center); + tmpV.toArray(newArray, i * 3); + } + attr.array = newArray; + attr.needsUpdate = true; +} + +function createGroundPlaneGeometry( + tile: Tile, + useLocalTargetCoords: boolean, + createTexCoords: boolean, + receiveShadow: boolean +): THREE.BufferGeometry { + const { dataSource, projection } = tile; + const sourceProjection = dataSource.getTilingScheme().projection; + const tmpV = new THREE.Vector3(); + + const geometry = new THREE.BufferGeometry(); + const tileCorners = projectTilePlaneCorners(tile, sourceProjection); + const cornersArray = [tileCorners.sw, tileCorners.se, tileCorners.nw, tileCorners.ne]; + if (useLocalTargetCoords) { + for (const corner of cornersArray) { + projection.reprojectPoint(sourceProjection, corner, corner).sub(tile.center); + } + } + // Use 64bits floats for world coordinates to avoid precision issues on coordinate + // tranformations. The array must be converted to single precision before rendering. + const bufferArray = useLocalTargetCoords ? new Float32Array(12) : new Float64Array(12); + const posAttr = new THREE.BufferAttribute(bufferArray, 3).copyVector3sArray(cornersArray); + geometry.setAttribute("position", posAttr); + + if (receiveShadow) { + // Webmercator needs to have it negated to work correctly. + sourceProjection.surfaceNormal(tileCorners.sw, tmpV).negate(); + const normAttr = new THREE.BufferAttribute(new Float32Array(12), 3).copyVector3sArray( + Array(4).fill(tmpV) + ); + geometry.setAttribute("normal", normAttr); + } + geometry.setIndex(new THREE.BufferAttribute(new Uint16Array([0, 1, 2, 2, 1, 3]), 1)); + + if (createTexCoords) { + const uvAttr = new THREE.BufferAttribute(new Float32Array([0, 0, 1, 0, 0, 1, 1, 1]), 2); + geometry.setAttribute("uv", uvAttr); + } + + return geometry; +} + +function createGroundPlaneMaterial( + color: THREE.Color, + receiveShadow: boolean, + depthWrite: boolean +): THREE.Material { + if (receiveShadow) { + return new MapMeshStandardMaterial({ + color, + visible: true, + depthWrite, + removeDiffuseLight: true + }); + } else { + return new MapMeshBasicMaterial({ + color, + visible: true, + depthWrite + }); + } +} diff --git a/@here/harp-mapview/lib/geometry/RegisterTileObject.ts b/@here/harp-mapview/lib/geometry/RegisterTileObject.ts new file mode 100644 index 0000000000..6d73e5b1cf --- /dev/null +++ b/@here/harp-mapview/lib/geometry/RegisterTileObject.ts @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2017-2020 HERE Europe B.V. + * Licensed under Apache 2.0, see full license in LICENSE + * SPDX-License-Identifier: Apache-2.0 + */ + +import { GeometryKind, GeometryKindSet } from "@here/harp-datasource-protocol"; +import * as THREE from "three"; + +import { MapObjectAdapter, MapObjectAdapterParams } from "../MapObjectAdapter"; +import { Tile } from "../Tile"; + +/** + * Adds a THREE object to the root of the tile and register [[MapObjectAdapter]]. + * + * Sets the owning tiles datasource.name and the `tileKey` in the `userData` property of the + * object, such that the tile it belongs to can be identified during picking. + * + * @param tile - The {@link Tile} to add the object to. + * @param object - The object to add to the root of the tile. + * @param geometryKind - The kind of object. Can be used for filtering. + * @param mapAdapterParams - additional parameters for [[MapObjectAdapter]] + */ +export function registerTileObject( + tile: Tile, + object: THREE.Object3D, + geometryKind: GeometryKind | GeometryKindSet | undefined, + mapAdapterParams?: MapObjectAdapterParams +) { + const kind = + geometryKind instanceof Set + ? Array.from((geometryKind as GeometryKindSet).values()) + : Array.isArray(geometryKind) + ? geometryKind + : [geometryKind]; + + MapObjectAdapter.create(object, { + dataSource: tile.dataSource, + kind, + level: tile.tileKey.level, + ...mapAdapterParams + }); + + // TODO legacy fields, encoded directly in `userData to be removed + if (object.userData === undefined) { + object.userData = {}; + } + + const userData = object.userData; + userData.tileKey = tile.tileKey; + userData.dataSource = tile.dataSource.name; + + userData.kind = kind; + + // Force a visibility check of all objects. + tile.resetVisibilityCounter(); +} diff --git a/@here/harp-mapview/lib/geometry/TileGeometryCreator.ts b/@here/harp-mapview/lib/geometry/TileGeometryCreator.ts index eaba6bbe47..0f0142f15c 100644 --- a/@here/harp-mapview/lib/geometry/TileGeometryCreator.ts +++ b/@here/harp-mapview/lib/geometry/TileGeometryCreator.ts @@ -14,7 +14,6 @@ import { ExtrudedPolygonTechnique, FillTechnique, Geometry, - GeometryKind, GeometryKindSet, getArrayConstructor, getPropertyValue, @@ -48,11 +47,6 @@ import { ExprEvaluatorContext, OperatorDescriptor } from "@here/harp-datasource-protocol/lib/ExprEvaluator"; -import { - EdgeLengthGeometrySubdivisionModifier, - SubdivisionMode -} from "@here/harp-geometry/lib/EdgeLengthGeometrySubdivisionModifier"; -import { SphericalGeometrySubdivisionModifier } from "@here/harp-geometry/lib/SphericalGeometrySubdivisionModifier"; import { EarthConstants, ProjectionType } from "@here/harp-geoutils"; import { EdgeMaterial, @@ -61,7 +55,6 @@ import { FadingFeature, hasExtrusionFeature, isHighPrecisionLineMaterial, - MapMeshBasicMaterial, MapMeshDepthMaterial, MapMeshStandardMaterial, setShaderMaterialDefine, @@ -85,13 +78,12 @@ import { } from "../DepthPrePass"; import { DisplacementMap, TileDisplacementMap } from "../DisplacementMap"; import { MapAdapterUpdateEnv, MapMaterialAdapter } from "../MapMaterialAdapter"; -import { MapObjectAdapter, MapObjectAdapterParams } from "../MapObjectAdapter"; import { MapViewPoints } from "../MapViewPoints"; import { PathBlockingElement } from "../PathBlockingElement"; import { TextElementBuilder } from "../text/TextElementBuilder"; import { Tile, TileFeatureData } from "../Tile"; -import { LodMesh } from "./LodMesh"; -import { projectTilePlaneCorners } from "./ProjectTilePlaneCorners"; +import { addGroundPlane } from "./AddGroundPlane"; +import { registerTileObject } from "./RegisterTileObject"; const logger = LoggerManager.instance.create("TileGeometryCreator"); @@ -311,7 +303,7 @@ export class TileGeometryCreator { // 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. - TileGeometryCreator.instance.addGroundPlane(tile, -1); + addGroundPlane(tile, -1); } } @@ -372,52 +364,6 @@ export class TileGeometryCreator { }); } - /** - * Adds a THREE object to the root of the tile and register [[MapObjectAdapter]]. - * - * Sets the owning tiles datasource.name and the `tileKey` in the `userData` property of the - * object, such that the tile it belongs to can be identified during picking. - * - * @param tile - The {@link Tile} to add the object to. - * @param object - The object to add to the root of the tile. - * @param geometryKind - The kind of object. Can be used for filtering. - * @param mapAdapterParams - additional parameters for [[MapObjectAdapter]] - */ - registerTileObject( - tile: Tile, - object: THREE.Object3D, - geometryKind: GeometryKind | GeometryKindSet | undefined, - mapAdapterParams?: MapObjectAdapterParams - ) { - const kind = - geometryKind instanceof Set - ? Array.from((geometryKind as GeometryKindSet).values()) - : Array.isArray(geometryKind) - ? geometryKind - : [geometryKind]; - - MapObjectAdapter.create(object, { - dataSource: tile.dataSource, - kind, - level: tile.tileKey.level, - ...mapAdapterParams - }); - - // TODO legacy fields, encoded directly in `userData to be removed - if (object.userData === undefined) { - object.userData = {}; - } - - const userData = object.userData; - userData.tileKey = tile.tileKey; - userData.dataSource = tile.dataSource.name; - - userData.kind = kind; - - // Force a visibility check of all objects. - tile.resetVisibilityCounter(); - } - /** * Splits the text paths that contain sharp corners. * @@ -840,7 +786,7 @@ export class TileGeometryCreator { this.addUserData(tile, srcGeometry, technique, depthPassMesh); // Set geometry kind for depth pass mesh so that it gets the displacement map // for elevation overlay. - this.registerTileObject(tile, depthPassMesh, techniqueKind, { + registerTileObject(tile, depthPassMesh, techniqueKind, { technique, pickable: false }); @@ -855,7 +801,7 @@ export class TileGeometryCreator { // register all objects as pickable except solid lines with outlines, in that case // it's enough to make outlines pickable. - this.registerTileObject(tile, object, techniqueKind, { + registerTileObject(tile, object, techniqueKind, { technique, pickable: !hasSolidLinesOutlines }); @@ -942,7 +888,7 @@ export class TileGeometryCreator { addToExtrudedMaterials(edgeObj.material, extrudedMaterials); } - this.registerTileObject(tile, edgeObj, techniqueKind, { + registerTileObject(tile, edgeObj, techniqueKind, { technique, pickable: false }); @@ -1021,7 +967,7 @@ export class TileGeometryCreator { this.addUserData(tile, srcGeometry, technique, outlineObj); - this.registerTileObject(tile, outlineObj, techniqueKind, { + registerTileObject(tile, outlineObj, techniqueKind, { technique, pickable: false }); @@ -1078,7 +1024,7 @@ export class TileGeometryCreator { outlineTechnique.secondaryWidth, outlineTechnique.metricUnit ); - this.registerTileObject(tile, outlineObj, techniqueKind, { technique }); + registerTileObject(tile, outlineObj, techniqueKind, { technique }); const mainMaterialAdapter = MapMaterialAdapter.get(material); const outlineMaterialAdapter = MapMaterialAdapter.create(outlineMaterial, { @@ -1137,171 +1083,6 @@ export class TileGeometryCreator { } } - private createGroundPlane( - tile: Tile, - material: THREE.Material | THREE.Material[], - createTexCoords: boolean, - shadowsEnabled: boolean - ): THREE.Mesh { - const { dataSource, projection, mapView } = tile; - const sourceProjection = dataSource.getTilingScheme().projection; - const shouldSubdivide = projection.type === ProjectionType.Spherical; - const useLocalTargetCoords = !shouldSubdivide; - - const geometry = this.createGroundPlaneGeometry( - tile, - useLocalTargetCoords, - createTexCoords, - shadowsEnabled - ); - - if (!shouldSubdivide) { - return new THREE.Mesh(geometry, material); - } - - function moveTileCenter(geom: THREE.BufferGeometry) { - const attr = geom.getAttribute("position") as THREE.BufferAttribute; - const oldArray = attr.array as Float64Array; - // Convert to single precision before rendering (WebGL does not support double - // precision). - const newArray = new Float32Array(oldArray.length); - for (let i = 0; i < attr.array.length; i += 1) { - tmpVector3.fromBufferAttribute(attr, i); - projection - .reprojectPoint(sourceProjection, tmpVector3, tmpVector3) - .sub(tile.center); - tmpVector3.toArray(newArray, i * 3); - } - attr.array = newArray; - attr.needsUpdate = true; - } - - const geometries: THREE.BufferGeometry[] = []; - const sphericalModifier = new SphericalGeometrySubdivisionModifier( - THREE.MathUtils.degToRad(10), - sourceProjection - ); - - if (mapView.enableMixedLod === false) { - // Use static mesh if mixed LOD is disabled - sphericalModifier.modify(geometry); - moveTileCenter(geometry); - - return new THREE.Mesh(geometry, material); - } - - // Use a [[LodMesh]] to adapt tesselation of tile depending on zoom level - for (let zoomLevelOffset = 0; zoomLevelOffset < 4; ++zoomLevelOffset) { - const subdivision = Math.pow(2, zoomLevelOffset); - const zoomLevelGeometry = geometry.clone(); - if (subdivision > 1) { - const edgeModifier = new EdgeLengthGeometrySubdivisionModifier( - subdivision, - tile.geoBox, - SubdivisionMode.All, - sourceProjection - ); - edgeModifier.modify(zoomLevelGeometry); - } - sphericalModifier.modify(zoomLevelGeometry); - moveTileCenter(zoomLevelGeometry); - geometries.push(zoomLevelGeometry); - } - return new LodMesh(geometries, material); - } - - /** - * Creates and add a background plane for the tile. - * @param tile - The tile to which the ground plane belongs. - * @param material - The plane material. - * @param renderOrder - The plane render order. - * @param createTexCoords - Whether to create texture coordinates. - * @param shadowsEnabled - Whether to enable shadows. - */ - addGroundPlane( - tile: Tile, - renderOrder: number, - material?: THREE.Material, - createTexCoords: boolean = false, - shadowsEnabled: boolean = tile.mapView.shadowsEnabled - ) { - if (!material) { - material = this.createGroundPlaneMaterial( - new THREE.Color(tile.mapView.clearColor), - tile.mapView.shadowsEnabled, - tile.mapView.projection.type === ProjectionType.Spherical - ); - } - const mesh = this.createGroundPlane(tile, material, createTexCoords, shadowsEnabled); - mesh.receiveShadow = shadowsEnabled; - mesh.renderOrder = renderOrder; - this.registerTileObject(tile, mesh, GeometryKind.Background, { pickable: false }); - tile.objects.push(mesh); - } - - private createGroundPlaneGeometry( - tile: Tile, - useLocalTargetCoords: boolean, - createTexCoords: boolean, - shadowsEnabled: boolean - ): THREE.BufferGeometry { - const { dataSource, projection } = tile; - const sourceProjection = dataSource.getTilingScheme().projection; - const tmpV = new THREE.Vector3(); - - const geometry = new THREE.BufferGeometry(); - const tileCorners = projectTilePlaneCorners(tile, sourceProjection); - const cornersArray = [tileCorners.sw, tileCorners.se, tileCorners.nw, tileCorners.ne]; - if (useLocalTargetCoords) { - for (const corner of cornersArray) { - projection.reprojectPoint(sourceProjection, corner, corner).sub(tile.center); - } - } - // Use 64bits floats for world coordinates to avoid precision issues on coordinate - // tranformations. The array must be converted to single precision before rendering. - const bufferArray = useLocalTargetCoords ? new Float32Array(12) : new Float64Array(12); - const posAttr = new THREE.BufferAttribute(bufferArray, 3).copyVector3sArray(cornersArray); - geometry.setAttribute("position", posAttr); - - if (shadowsEnabled) { - // Webmercator needs to have it negated to work correctly. - sourceProjection.surfaceNormal(tileCorners.sw, tmpV).negate(); - const normAttr = new THREE.BufferAttribute(new Float32Array(12), 3).copyVector3sArray( - Array(4).fill(tmpV) - ); - geometry.setAttribute("normal", normAttr); - } - geometry.setIndex(new THREE.BufferAttribute(new Uint16Array([0, 1, 2, 2, 1, 3]), 1)); - - if (createTexCoords) { - const uvAttr = new THREE.BufferAttribute(new Float32Array([0, 0, 1, 0, 0, 1, 1, 1]), 2); - geometry.setAttribute("uv", uvAttr); - } - - return geometry; - } - - private createGroundPlaneMaterial( - color: THREE.Color, - shadowsEnabled: boolean, - depthWrite: boolean - ): THREE.Material { - if (shadowsEnabled) { - return new MapMeshStandardMaterial({ - color, - visible: true, - depthWrite, - removeDiffuseLight: true - }); - } else { - return new MapMeshBasicMaterial({ - color, - visible: true, - depthWrite - }); - } - } - /** * Gets the attachments of the given {@link @here/harp-datasource-protocol#DecodedTile}. * diff --git a/@here/harp-mapview/test/AddGroundPlaneTest.ts b/@here/harp-mapview/test/AddGroundPlaneTest.ts new file mode 100644 index 0000000000..092af004e9 --- /dev/null +++ b/@here/harp-mapview/test/AddGroundPlaneTest.ts @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2017-2020 HERE Europe B.V. + * Licensed under Apache 2.0, see full license in LICENSE + * SPDX-License-Identifier: Apache-2.0 + */ + +import { MapEnv } from "@here/harp-datasource-protocol"; +import { + mercatorProjection, + sphereProjection, + TileKey, + TilingScheme, + webMercatorTilingScheme +} from "@here/harp-geoutils"; +import { assert, expect } from "chai"; +import * as sinon from "sinon"; +import * as THREE from "three"; + +import { DataSource } from "../lib/DataSource"; +import { addGroundPlane } from "../lib/geometry/AddGroundPlane"; +import { LodMesh } from "../lib/geometry/LodMesh"; +import { MapObjectAdapter } from "../lib/MapObjectAdapter"; +import { MapView } from "../lib/MapView"; +import { Tile } from "../lib/Tile"; + +class MockDataSource extends DataSource { + /** @override */ + getTilingScheme(): TilingScheme { + return webMercatorTilingScheme; + } + + /** @override */ + getTile(tileKey: TileKey): Tile | undefined { + return undefined; + } +} + +describe("addGroundPlaneTest", function() { + let tile: Tile; + let mapView: MapView; + let mockDataSource: sinon.SinonStubbedInstance; + + before(() => { + mockDataSource = sinon.createStubInstance(MockDataSource); + mockDataSource.getTilingScheme.callsFake(() => webMercatorTilingScheme); + }); + + beforeEach(() => { + mapView = ({ + clearColor: 0, + shadowsEnabled: false, + enableMixedLod: false + } as any) as MapView; + + sinon.stub(mockDataSource, "mapView").get(() => { + return mapView; + }); + tile = new Tile((mockDataSource as any) as DataSource, new TileKey(0, 0, 2)); + }); + + function getPlaneMesh(tile: Tile): THREE.Mesh { + assert.equal(tile.objects.length, 1); + const object = tile.objects[0]; + assert.instanceOf(object, THREE.Mesh); + return object as THREE.Mesh; + } + + describe("mercator", () => { + before(() => { + sinon.stub(mockDataSource, "projection").get(() => mercatorProjection); + }); + + it("background geometry is registered as non-pickable", () => { + addGroundPlane(tile, 0); + const adapter = MapObjectAdapter.get(getPlaneMesh(tile)); + expect(adapter).not.equals(undefined); + expect(adapter!.isPickable(new MapEnv({}))).to.equal(false); + }); + + it("plane mesh properties are correctly set", () => { + const renderOrder = 42; + const receiveShadow = true; + addGroundPlane(tile, renderOrder, 0, false, receiveShadow); + + const mesh = getPlaneMesh(tile); + expect(mesh.receiveShadow).equals(receiveShadow); + expect(mesh.renderOrder).equals(renderOrder); + }); + + it("uses color for default material", () => { + const color = 42; + addGroundPlane(tile, 0, color); + + const mesh = getPlaneMesh(tile); + expect(mesh.material).exist.and.has.property("color"); + expect(((mesh.material as any).color as THREE.Color).getHex()).equals(color); + }); + + it("creates uv coordinates if requested", () => { + addGroundPlane(tile, 0, 0, true); + + const mesh = getPlaneMesh(tile); + expect(mesh.geometry).exist.and.is.instanceOf(THREE.BufferGeometry); + const geometry = mesh.geometry as THREE.BufferGeometry; + expect(geometry.hasAttribute("uv")).to.be.true; + }); + + it("creates normals if plane must receive shadow", () => { + addGroundPlane(tile, 0, 0, false, true); + + const mesh = getPlaneMesh(tile); + expect(mesh.geometry).exist.and.is.instanceOf(THREE.BufferGeometry); + const geometry = mesh.geometry as THREE.BufferGeometry; + expect(geometry.hasAttribute("normal")).to.be.true; + }); + }); + + describe("sphere", () => { + before(() => { + sinon.stub(mockDataSource, "projection").get(() => sphereProjection); + }); + + it("subdivides geometry", () => { + addGroundPlane(tile, 0, 0, false, false, false); + + const mesh = getPlaneMesh(tile); + expect(mesh.geometry).exist.and.is.instanceOf(THREE.BufferGeometry); + const geometry = mesh.geometry as THREE.BufferGeometry; + expect(geometry.hasAttribute("position")).to.be.true; + + const posAttr = geometry.getAttribute("position"); + expect(posAttr.array.length).to.be.greaterThan(4); + }); + + it("creates mesh with mutiple LOD if requested", () => { + addGroundPlane(tile, 0, 0, false, false, true); + + const mesh = getPlaneMesh(tile); + expect(mesh).to.be.instanceOf(LodMesh); + }); + }); +}); diff --git a/@here/harp-mapview/test/ProjectTilePlaneCornersTest.ts b/@here/harp-mapview/test/ProjectTilePlaneCornersTest.ts index a759c4f8d1..dd9aeb0d7c 100644 --- a/@here/harp-mapview/test/ProjectTilePlaneCornersTest.ts +++ b/@here/harp-mapview/test/ProjectTilePlaneCornersTest.ts @@ -9,9 +9,9 @@ import { MercatorConstants, mercatorProjection, TileKey, + TilingScheme, webMercatorTilingScheme } from "@here/harp-geoutils"; -import { TileDataSource } from "@here/harp-mapview-decoder"; import { assert } from "chai"; import * as sinon from "sinon"; import * as THREE from "three"; @@ -20,9 +20,21 @@ import { DataSource } from "../lib/DataSource"; import { projectTilePlaneCorners } from "../lib/geometry/ProjectTilePlaneCorners"; import { Tile } from "../lib/Tile"; +class MockDataSource extends DataSource { + /** @override */ + getTilingScheme(): TilingScheme { + return webMercatorTilingScheme; + } + + /** @override */ + getTile(tileKey: TileKey): Tile | undefined { + return undefined; + } +} + describe("projectTilePlaneCorners", function() { it("generates tile corners ", () => { - const mockDatasource = sinon.createStubInstance(TileDataSource); + const mockDatasource = sinon.createStubInstance(MockDataSource); mockDatasource.getTilingScheme.callsFake(() => webMercatorTilingScheme); sinon.stub(mockDatasource, "projection").get(() => { return mercatorProjection; diff --git a/@here/harp-mapview/test/TileGeometryCreatorTest.ts b/@here/harp-mapview/test/TileGeometryCreatorTest.ts index c8fda08862..f208293c53 100644 --- a/@here/harp-mapview/test/TileGeometryCreatorTest.ts +++ b/@here/harp-mapview/test/TileGeometryCreatorTest.ts @@ -277,14 +277,6 @@ describe("TileGeometryCreator", () => { }); } describe("pickable geometry", () => { - it("background geometry is registered as non-pickable", () => { - tgc.addGroundPlane(newTile, 0); - assert.equal(newTile.objects.length, 1); - const adapter = MapObjectAdapter.get(newTile.objects[0]); - expect(adapter).not.equals(undefined); - expect(adapter!.isPickable(new MapEnv({}))).to.equal(false); - }); - it("extruded polygon depth prepass and edges geometries are registered as non-pickable", () => { const decodedTile: DecodedTile = getExtrudedPolygonTile(); tgc.createObjects(newTile, decodedTile); diff --git a/@here/harp-webtile-datasource/lib/WebTileLoader.ts b/@here/harp-webtile-datasource/lib/WebTileLoader.ts index a0afbe962f..290a9199d1 100644 --- a/@here/harp-webtile-datasource/lib/WebTileLoader.ts +++ b/@here/harp-webtile-datasource/lib/WebTileLoader.ts @@ -5,7 +5,7 @@ */ import { BaseTileLoader, Tile, TileLoaderState } from "@here/harp-mapview"; -import { TileGeometryCreator } from "@here/harp-mapview/lib/geometry/TileGeometryCreator"; +import { addGroundPlane } from "@here/harp-mapview/lib/geometry/AddGroundPlane"; import { enableBlending } from "@here/harp-materials"; import * as THREE from "three"; @@ -66,7 +66,7 @@ export class WebTileLoader extends BaseTileLoader { if (this.dataSource.transparent) { enableBlending(material); } - TileGeometryCreator.instance.addGroundPlane( + addGroundPlane( this.tile, this.dataSource.renderOrder, // Remove, as `renderOrder` will be deprecated. material, diff --git a/@here/harp-webtile-datasource/test/WebTileTest.ts b/@here/harp-webtile-datasource/test/WebTileTest.ts index da044c307a..88751e195e 100644 --- a/@here/harp-webtile-datasource/test/WebTileTest.ts +++ b/@here/harp-webtile-datasource/test/WebTileTest.ts @@ -5,7 +5,7 @@ */ import { mercatorProjection, TileKey } from "@here/harp-geoutils"; import { CopyrightInfo, MapView, Tile } from "@here/harp-mapview"; -import { TileGeometryCreator } from "@here/harp-mapview/lib/geometry/TileGeometryCreator"; +import { addGroundPlane } from "@here/harp-mapview/lib/geometry/AddGroundPlane"; import { LoggerManager } from "@here/harp-utils"; import { expect } from "chai"; import * as sinon from "sinon"; @@ -121,7 +121,7 @@ describe("WebTileDataSource", function() { return fakeMapView; }); - const creatorSpy = sinon.spy(TileGeometryCreator.instance, "addGroundPlane"); + const creatorSpy = sinon.spy(addGroundPlane); const tileKey = TileKey.fromRowColumnLevel(0, 0, 0); const tile = webTileDataSource.getTile(tileKey);