Skip to content
This repository has been archived by the owner on Mar 8, 2023. It is now read-only.

Commit

Permalink
HARP-13329: Move ground plane generation out of TileGeometryCreator.
Browse files Browse the repository at this point in the history
Add unit tests for ground plane generation.
  • Loading branch information
atomicsulfate committed Jan 6, 2021
1 parent dfe8b15 commit fcb32cc
Show file tree
Hide file tree
Showing 10 changed files with 432 additions and 253 deletions.
4 changes: 2 additions & 2 deletions @here/harp-mapview/lib/BackgroundDataSource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";

/**
Expand Down Expand Up @@ -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;
}
Expand Down
196 changes: 196 additions & 0 deletions @here/harp-mapview/lib/geometry/AddGroundPlane.ts
Original file line number Diff line number Diff line change
@@ -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
});
}
}
57 changes: 57 additions & 0 deletions @here/harp-mapview/lib/geometry/RegisterTileObject.ts
Original file line number Diff line number Diff line change
@@ -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();
}
Loading

0 comments on commit fcb32cc

Please sign in to comment.