This repository has been archived by the owner on Mar 8, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 197
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
HARP-13329: Move ground plane generation out of TileGeometryCreator.
Add unit tests for ground plane generation.
- Loading branch information
1 parent
fe46488
commit 82dbee1
Showing
9 changed files
with
423 additions
and
243 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
} |
Oops, something went wrong.