Change to WebGPU
This commit is contained in:
parent
523cf0945a
commit
4dadaf470c
6 changed files with 170 additions and 160 deletions
|
@ -14,9 +14,10 @@ import {
|
||||||
SceneViewContext,
|
SceneViewContext,
|
||||||
SceneViewContextType,
|
SceneViewContextType,
|
||||||
} from "../providers/scene-view-provider";
|
} from "../providers/scene-view-provider";
|
||||||
import { Mesh, MeshStandardMaterial } from "three";
|
import { Mesh } from "three";
|
||||||
import { CustomEvent } from "../three/SceneView";
|
import { CustomEvent } from "../three/SceneView";
|
||||||
import { RangeSlider } from "./RangeSlider";
|
import { RangeSlider } from "./RangeSlider";
|
||||||
|
import { MeshStandardNodeMaterial } from "three/webgpu";
|
||||||
|
|
||||||
function Toggle({
|
function Toggle({
|
||||||
title,
|
title,
|
||||||
|
@ -263,10 +264,10 @@ export function Form() {
|
||||||
const key = `toggle-visibility-${child.name}`;
|
const key = `toggle-visibility-${child.name}`;
|
||||||
let color = "transparent";
|
let color = "transparent";
|
||||||
if (
|
if (
|
||||||
(child as Mesh).material instanceof MeshStandardMaterial
|
(child as Mesh).material instanceof MeshStandardNodeMaterial
|
||||||
) {
|
) {
|
||||||
color = `#${(
|
color = `#${(
|
||||||
(child as Mesh).material as MeshStandardMaterial
|
(child as Mesh).material as MeshStandardNodeMaterial
|
||||||
).color.getHexString()}`;
|
).color.getHexString()}`;
|
||||||
}
|
}
|
||||||
const visible = (child as Mesh).visible;
|
const visible = (child as Mesh).visible;
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
import {
|
import {
|
||||||
Group,
|
Group,
|
||||||
Material,
|
|
||||||
Mesh,
|
Mesh,
|
||||||
MeshBasicMaterial,
|
MeshBasicMaterial,
|
||||||
MeshPhongMaterial,
|
MeshPhongMaterial,
|
||||||
MeshStandardMaterial,
|
|
||||||
PerspectiveCamera,
|
PerspectiveCamera,
|
||||||
Plane,
|
Plane,
|
||||||
Raycaster,
|
Raycaster,
|
||||||
|
@ -12,7 +10,6 @@ import {
|
||||||
SphereGeometry,
|
SphereGeometry,
|
||||||
Vector2,
|
Vector2,
|
||||||
Vector3,
|
Vector3,
|
||||||
WebGLRenderer,
|
|
||||||
} from "three";
|
} from "three";
|
||||||
import { buildMeshes } from "./utils/build-meshes";
|
import { buildMeshes } from "./utils/build-meshes";
|
||||||
import { Extent, animate, buildScene } from "./utils/build-scene";
|
import { Extent, animate, buildScene } from "./utils/build-scene";
|
||||||
|
@ -39,6 +36,11 @@ import {
|
||||||
} from "geo-three";
|
} from "geo-three";
|
||||||
import { Data, createSVG } from "./utils/create-borehole-svg";
|
import { Data, createSVG } from "./utils/create-borehole-svg";
|
||||||
import { TileData, updateTiles } from "./ShaderMaterial";
|
import { TileData, updateTiles } from "./ShaderMaterial";
|
||||||
|
import {
|
||||||
|
ClippingGroup,
|
||||||
|
MeshStandardNodeMaterial,
|
||||||
|
WebGPURenderer,
|
||||||
|
} from "three/webgpu";
|
||||||
|
|
||||||
export type CustomEvent = CustomEventInit<{
|
export type CustomEvent = CustomEventInit<{
|
||||||
element: SVGSVGElement | null;
|
element: SVGSVGElement | null;
|
||||||
|
@ -46,7 +48,7 @@ export type CustomEvent = CustomEventInit<{
|
||||||
|
|
||||||
export class SceneView extends EventTarget {
|
export class SceneView extends EventTarget {
|
||||||
private _scene: Scene;
|
private _scene: Scene;
|
||||||
private _model: Group;
|
private _model: ClippingGroup;
|
||||||
private _camera: PerspectiveCamera;
|
private _camera: PerspectiveCamera;
|
||||||
private _container: HTMLElement;
|
private _container: HTMLElement;
|
||||||
private _raycaster: Raycaster;
|
private _raycaster: Raycaster;
|
||||||
|
@ -58,17 +60,17 @@ export class SceneView extends EventTarget {
|
||||||
private _callback: EventListenerOrEventListenerObject | null = null;
|
private _callback: EventListenerOrEventListenerObject | null = null;
|
||||||
private _orbitControls: OrbitControls;
|
private _orbitControls: OrbitControls;
|
||||||
private _dragControls: DragControls | null = null;
|
private _dragControls: DragControls | null = null;
|
||||||
private _renderer: WebGLRenderer;
|
private _renderer: WebGPURenderer;
|
||||||
private static _DISPLACEMENT = 2000;
|
private static _DISPLACEMENT = 2000;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
scene: Scene,
|
scene: Scene,
|
||||||
model: Group,
|
model: ClippingGroup,
|
||||||
camera: PerspectiveCamera,
|
camera: PerspectiveCamera,
|
||||||
container: HTMLElement,
|
container: HTMLElement,
|
||||||
extent: Extent,
|
extent: Extent,
|
||||||
orbitControls: OrbitControls,
|
orbitControls: OrbitControls,
|
||||||
renderer: WebGLRenderer
|
renderer: WebGPURenderer
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
this._scene = scene;
|
this._scene = scene;
|
||||||
|
@ -151,7 +153,7 @@ export class SceneView extends EventTarget {
|
||||||
// Set wireframe for model
|
// Set wireframe for model
|
||||||
const model = this._model;
|
const model = this._model;
|
||||||
model.children.forEach((child) => {
|
model.children.forEach((child) => {
|
||||||
const material = (child as Mesh).material as MeshStandardMaterial;
|
const material = (child as Mesh).material as MeshStandardNodeMaterial;
|
||||||
material.wireframe = !material.wireframe;
|
material.wireframe = !material.wireframe;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -162,7 +164,7 @@ export class SceneView extends EventTarget {
|
||||||
|
|
||||||
if (capMeshGroup) {
|
if (capMeshGroup) {
|
||||||
capMeshGroup.children.forEach((mesh) => {
|
capMeshGroup.children.forEach((mesh) => {
|
||||||
const material = (mesh as Mesh).material as MeshStandardMaterial;
|
const material = (mesh as Mesh).material as MeshStandardNodeMaterial;
|
||||||
if (material) {
|
if (material) {
|
||||||
material.wireframe = !material.wireframe;
|
material.wireframe = !material.wireframe;
|
||||||
}
|
}
|
||||||
|
@ -213,7 +215,7 @@ export class SceneView extends EventTarget {
|
||||||
const depthEnd = intersects[i + 1].point.z;
|
const depthEnd = intersects[i + 1].point.z;
|
||||||
const name = intersects[i].object.name;
|
const name = intersects[i].object.name;
|
||||||
const color = `#${(
|
const color = `#${(
|
||||||
(intersects[i].object as Mesh).material as MeshStandardMaterial
|
(intersects[i].object as Mesh).material as MeshStandardNodeMaterial
|
||||||
).color.getHexString()}`;
|
).color.getHexString()}`;
|
||||||
|
|
||||||
// Avoid duplicate entries, just update the depth information
|
// Avoid duplicate entries, just update the depth information
|
||||||
|
@ -339,10 +341,9 @@ export class SceneView extends EventTarget {
|
||||||
// Remove existing cap meshes
|
// Remove existing cap meshes
|
||||||
for (const o in Orientation) {
|
for (const o in Orientation) {
|
||||||
const capMeshGroupName = `cap-mesh-group-${o}`;
|
const capMeshGroupName = `cap-mesh-group-${o}`;
|
||||||
let capMeshGroup = this._scene.getObjectByName(capMeshGroupName);
|
const capMeshGroup = this._scene.getObjectByName(capMeshGroupName);
|
||||||
while (capMeshGroup) {
|
if (capMeshGroup) {
|
||||||
this._scene.remove(capMeshGroup);
|
capMeshGroup.clear();
|
||||||
capMeshGroup = this._scene.getObjectByName(capMeshGroupName);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -352,10 +353,8 @@ export class SceneView extends EventTarget {
|
||||||
this._dragControls = null;
|
this._dragControls = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove clipping planes
|
// Disable clipping group
|
||||||
for (const mesh of this._model.children) {
|
this.model.enabled = false;
|
||||||
((mesh as Mesh).material as Material).clippingPlanes = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset clipping box
|
// Reset clipping box
|
||||||
|
@ -373,10 +372,9 @@ export class SceneView extends EventTarget {
|
||||||
|
|
||||||
this._dragControls = dragControls;
|
this._dragControls = dragControls;
|
||||||
|
|
||||||
// Add clipping planes to the meshes
|
// Add planes to ClippingGroup
|
||||||
for (const mesh of this._model.children) {
|
this.model.clippingPlanes = planes;
|
||||||
((mesh as Mesh).material as Material).clippingPlanes = planes;
|
this.model.enabled = true;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Explode meshes
|
// Explode meshes
|
||||||
|
@ -454,7 +452,8 @@ async function init(container: HTMLElement, modelId = MODEL_ID) {
|
||||||
|
|
||||||
// Build the 3D model
|
// Build the 3D model
|
||||||
const meshes = await buildMeshes(mappedFeatures);
|
const meshes = await buildMeshes(mappedFeatures);
|
||||||
const model = new Group();
|
const model = new ClippingGroup();
|
||||||
|
model.enabled = false;
|
||||||
model.add(...meshes);
|
model.add(...meshes);
|
||||||
model.name = "geologic-model";
|
model.name = "geologic-model";
|
||||||
scene.add(model);
|
scene.add(model);
|
||||||
|
@ -510,7 +509,7 @@ async function init(container: HTMLElement, modelId = MODEL_ID) {
|
||||||
|
|
||||||
function rendererCallback(
|
function rendererCallback(
|
||||||
camera: PerspectiveCamera,
|
camera: PerspectiveCamera,
|
||||||
renderer: WebGLRenderer,
|
renderer: WebGPURenderer,
|
||||||
scene: Scene,
|
scene: Scene,
|
||||||
map: MapView,
|
map: MapView,
|
||||||
extent: Extent,
|
extent: Extent,
|
||||||
|
@ -518,6 +517,7 @@ function rendererCallback(
|
||||||
) {
|
) {
|
||||||
return () => {
|
return () => {
|
||||||
if (topography.visible) {
|
if (topography.visible) {
|
||||||
|
//@ts-expect-error WebGPURenderer is not supported by Geo-Three
|
||||||
map.lod.updateLOD(map, camera, renderer, scene);
|
map.lod.updateLOD(map, camera, renderer, scene);
|
||||||
const tiles: TileData[] = [];
|
const tiles: TileData[] = [];
|
||||||
traverse(map.root, extent, tiles);
|
traverse(map.root, extent, tiles);
|
||||||
|
|
|
@ -1,13 +1,24 @@
|
||||||
import {
|
import {
|
||||||
Color,
|
|
||||||
DataArrayTexture,
|
DataArrayTexture,
|
||||||
LinearFilter,
|
LinearFilter,
|
||||||
RGBAFormat,
|
RGBAFormat,
|
||||||
ShaderChunk,
|
SRGBColorSpace,
|
||||||
ShaderMaterial,
|
|
||||||
Texture,
|
Texture,
|
||||||
Vector4,
|
Vector4,
|
||||||
} from "three";
|
} from "three";
|
||||||
|
import {
|
||||||
|
Break,
|
||||||
|
Fn,
|
||||||
|
If,
|
||||||
|
Loop,
|
||||||
|
oneMinus,
|
||||||
|
positionWorld,
|
||||||
|
texture,
|
||||||
|
uniform,
|
||||||
|
uniformArray,
|
||||||
|
vec4,
|
||||||
|
} from "three/tsl";
|
||||||
|
import { MeshStandardNodeMaterial } from "three/webgpu";
|
||||||
|
|
||||||
export interface TileData {
|
export interface TileData {
|
||||||
xmin: number;
|
xmin: number;
|
||||||
|
@ -26,9 +37,9 @@ const height = 256;
|
||||||
const size = width * height;
|
const size = width * height;
|
||||||
|
|
||||||
const canvas = new OffscreenCanvas(width, height);
|
const canvas = new OffscreenCanvas(width, height);
|
||||||
const ctx = canvas.getContext("2d");
|
const ctx = canvas.getContext("2d", { willReadFrequently: true });
|
||||||
|
|
||||||
const tileBounds = Array(maxTiles).fill(new Vector4(0, 0, 0, 0));
|
const tileBounds: Vector4[] = Array(maxTiles).fill(new Vector4(0, 0, 0, 0));
|
||||||
|
|
||||||
const data = new Uint8Array(4 * size * maxTiles);
|
const data = new Uint8Array(4 * size * maxTiles);
|
||||||
const tileCache: {
|
const tileCache: {
|
||||||
|
@ -42,92 +53,65 @@ dataArrayTexture.format = RGBAFormat;
|
||||||
dataArrayTexture.generateMipmaps = false;
|
dataArrayTexture.generateMipmaps = false;
|
||||||
dataArrayTexture.magFilter = LinearFilter;
|
dataArrayTexture.magFilter = LinearFilter;
|
||||||
dataArrayTexture.minFilter = LinearFilter;
|
dataArrayTexture.minFilter = LinearFilter;
|
||||||
|
dataArrayTexture.colorSpace = SRGBColorSpace;
|
||||||
dataArrayTexture.needsUpdate = true;
|
dataArrayTexture.needsUpdate = true;
|
||||||
|
|
||||||
// Create shader material
|
// Create shader material
|
||||||
export const shaderMaterial = new ShaderMaterial({
|
export const topoNodeMaterial = new MeshStandardNodeMaterial({
|
||||||
clipping: true,
|
alphaToCoverage: true,
|
||||||
uniforms: {
|
});
|
||||||
tileBounds: { value: tileBounds },
|
const tileBoundsUniform = uniformArray(tileBounds);
|
||||||
tileCount: { value: maxTiles },
|
const dataArrayTextureUniform = uniform(dataArrayTexture);
|
||||||
tiles: { value: dataArrayTexture },
|
|
||||||
color: { value: new Color(1, 1, 1) },
|
|
||||||
},
|
|
||||||
vertexShader:
|
|
||||||
ShaderChunk.common +
|
|
||||||
"\n" +
|
|
||||||
ShaderChunk.logdepthbuf_pars_vertex +
|
|
||||||
`
|
|
||||||
varying vec3 vWorldPosition;
|
|
||||||
varying float fragDepth;
|
|
||||||
|
|
||||||
#include <clipping_planes_pars_vertex>
|
const fragmentShader = /*#__PURE__*/ Fn(() => {
|
||||||
|
const color = vec4(191.0 / 255.0, 209.0 / 255.0, 229.0 / 255.0, 1.0).toVar();
|
||||||
|
Loop({ start: 0, end: maxTiles, condition: "<" }, ({ i }) => {
|
||||||
|
const bounds = tileBoundsUniform.element(i);
|
||||||
|
|
||||||
void main() {
|
If(
|
||||||
#include <begin_vertex>
|
positionWorld.x
|
||||||
vWorldPosition = (modelMatrix * vec4(position, 1.0)).xyz;
|
.greaterThanEqual(bounds.x)
|
||||||
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
.and(positionWorld.x.lessThanEqual(bounds.y))
|
||||||
fragDepth = (gl_Position.z / gl_Position.w + 1.0) * 0.5;
|
.and(positionWorld.y.greaterThanEqual(bounds.z))
|
||||||
|
.and(positionWorld.y.lessThanEqual(bounds.w)),
|
||||||
|
() => {
|
||||||
|
let uv = positionWorld.xy
|
||||||
|
.sub(bounds.xz)
|
||||||
|
.div(bounds.yw.sub(bounds.xz))
|
||||||
|
.toVar();
|
||||||
|
uv.y.assign(oneMinus(uv.y));
|
||||||
|
|
||||||
#include <project_vertex>
|
const tile = texture(dataArrayTextureUniform.value, uv);
|
||||||
#include <clipping_planes_vertex>
|
color.assign(tile.depth(i));
|
||||||
|
Break();
|
||||||
` +
|
}
|
||||||
ShaderChunk.logdepthbuf_vertex +
|
);
|
||||||
`
|
});
|
||||||
}
|
|
||||||
`,
|
|
||||||
fragmentShader:
|
|
||||||
ShaderChunk.logdepthbuf_pars_fragment +
|
|
||||||
`
|
|
||||||
uniform vec4 tileBounds[${maxTiles}];
|
|
||||||
uniform int tileCount;
|
|
||||||
uniform sampler2DArray tiles;
|
|
||||||
varying vec3 vWorldPosition;
|
|
||||||
varying float fragDepth;
|
|
||||||
|
|
||||||
#include <clipping_planes_pars_fragment>
|
return color;
|
||||||
|
|
||||||
void main() {
|
|
||||||
#include <clipping_planes_fragment>
|
|
||||||
|
|
||||||
vec4 color = vec4(191.0/255.0, 209.0/255.0, 229.0/255.0, 1.0); // Default color
|
|
||||||
|
|
||||||
for (int i = 0; i < ${maxTiles}; i++) {
|
|
||||||
if (i >= tileCount) break; // Only process available tiles
|
|
||||||
|
|
||||||
vec4 bounds = tileBounds[i];
|
|
||||||
|
|
||||||
if (vWorldPosition.x >= bounds.x && vWorldPosition.x <= bounds.y &&
|
|
||||||
vWorldPosition.y >= bounds.z && vWorldPosition.y <= bounds.w) {
|
|
||||||
|
|
||||||
vec2 uv = (vWorldPosition.xy - bounds.xz) / (bounds.yw - bounds.xz);
|
|
||||||
uv = vec2(uv.x, 1.0 - uv.y);
|
|
||||||
color = texture2D(tiles, vec3(uv, i));
|
|
||||||
|
|
||||||
break; // Stop checking once we find the correct tile
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
gl_FragColor = color;
|
|
||||||
gl_FragDepth = fragDepth;
|
|
||||||
` +
|
|
||||||
ShaderChunk.logdepthbuf_fragment +
|
|
||||||
`
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
topoNodeMaterial.colorNode = fragmentShader();
|
||||||
|
|
||||||
|
let oldKeys: string[] = [];
|
||||||
export function updateTiles(newTiles: TileData[]) {
|
export function updateTiles(newTiles: TileData[]) {
|
||||||
if (newTiles.length > maxTiles) {
|
if (newTiles.length > maxTiles) {
|
||||||
newTiles = newTiles.slice(0, maxTiles);
|
newTiles = newTiles.slice(0, maxTiles);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let i = 0; i < newTiles.length; i++) {
|
const newKeys = newTiles.map((t) => getTileDataKey(t));
|
||||||
updateDataArrayTexture(newTiles[i], i);
|
const update =
|
||||||
}
|
oldKeys.some((k, i) => k !== newKeys[i]) || oldKeys.length === 0;
|
||||||
|
|
||||||
dataArrayTexture.needsUpdate = true;
|
// Only update if tiles changed
|
||||||
|
if (update) {
|
||||||
|
for (let i = 0; i < newTiles.length; i++) {
|
||||||
|
updateDataArrayTexture(newTiles[i], i);
|
||||||
|
}
|
||||||
|
|
||||||
|
dataArrayTexture.needsUpdate = true;
|
||||||
|
oldKeys = newKeys;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update buffer
|
// Update buffer
|
||||||
|
|
|
@ -8,7 +8,6 @@ import {
|
||||||
LineBasicMaterial,
|
LineBasicMaterial,
|
||||||
LineSegments,
|
LineSegments,
|
||||||
Mesh,
|
Mesh,
|
||||||
MeshBasicMaterial,
|
|
||||||
MeshStandardMaterial,
|
MeshStandardMaterial,
|
||||||
Object3DEventMap,
|
Object3DEventMap,
|
||||||
PerspectiveCamera,
|
PerspectiveCamera,
|
||||||
|
@ -17,11 +16,17 @@ import {
|
||||||
Scene,
|
Scene,
|
||||||
Vector2,
|
Vector2,
|
||||||
Vector3,
|
Vector3,
|
||||||
WebGLRenderer,
|
|
||||||
} from "three";
|
} from "three";
|
||||||
import { DragControls, OrbitControls } from "three/examples/jsm/Addons.js";
|
import { DragControls, OrbitControls } from "three/examples/jsm/Addons.js";
|
||||||
import { Extent } from "./build-scene";
|
import { Extent } from "./build-scene";
|
||||||
import earcut from "earcut";
|
import earcut from "earcut";
|
||||||
|
import {
|
||||||
|
ClippingGroup,
|
||||||
|
MeshBasicNodeMaterial,
|
||||||
|
MeshStandardNodeMaterial,
|
||||||
|
WebGPURenderer,
|
||||||
|
} from "three/webgpu";
|
||||||
|
import { Fn, uniform, vec4 } from "three/tsl";
|
||||||
|
|
||||||
export enum Orientation {
|
export enum Orientation {
|
||||||
X = "X",
|
X = "X",
|
||||||
|
@ -32,7 +37,7 @@ export enum Orientation {
|
||||||
NZ = "NZ",
|
NZ = "NZ",
|
||||||
}
|
}
|
||||||
|
|
||||||
type PlaneMesh = Mesh<PlaneGeometry, MeshBasicMaterial, Object3DEventMap>;
|
type PlaneMesh = Mesh<PlaneGeometry, MeshBasicNodeMaterial, Object3DEventMap>;
|
||||||
type EdgeMesh = LineSegments<
|
type EdgeMesh = LineSegments<
|
||||||
EdgesGeometry<PlaneGeometry>,
|
EdgesGeometry<PlaneGeometry>,
|
||||||
LineBasicMaterial,
|
LineBasicMaterial,
|
||||||
|
@ -49,7 +54,7 @@ let currentExtent: Extent;
|
||||||
const BUFFER = 500;
|
const BUFFER = 500;
|
||||||
|
|
||||||
export function buildClippingplanes(
|
export function buildClippingplanes(
|
||||||
renderer: WebGLRenderer,
|
renderer: WebGPURenderer,
|
||||||
camera: PerspectiveCamera,
|
camera: PerspectiveCamera,
|
||||||
orbitControls: OrbitControls,
|
orbitControls: OrbitControls,
|
||||||
extent: Extent,
|
extent: Extent,
|
||||||
|
@ -148,7 +153,7 @@ export function buildClippingplanes(
|
||||||
const planeGeometry = new PlaneGeometry(width, height);
|
const planeGeometry = new PlaneGeometry(width, height);
|
||||||
const planeMesh = new Mesh(
|
const planeMesh = new Mesh(
|
||||||
planeGeometry,
|
planeGeometry,
|
||||||
new MeshBasicMaterial({
|
new MeshBasicNodeMaterial({
|
||||||
visible: true,
|
visible: true,
|
||||||
color: 0xa92a4e,
|
color: 0xa92a4e,
|
||||||
transparent: true,
|
transparent: true,
|
||||||
|
@ -190,6 +195,19 @@ export function buildClippingplanes(
|
||||||
edgeMeshMap[p.orientation] = edges;
|
edgeMeshMap[p.orientation] = edges;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const o in Orientation) {
|
||||||
|
const capMeshGroupName = `cap-mesh-group-${o}`;
|
||||||
|
let capMeshGroup = scene.getObjectByName(capMeshGroupName) as ClippingGroup;
|
||||||
|
if (capMeshGroup) {
|
||||||
|
capMeshGroup.clear();
|
||||||
|
} else {
|
||||||
|
capMeshGroup = new ClippingGroup();
|
||||||
|
capMeshGroup.name = capMeshGroupName;
|
||||||
|
capMeshGroup.clippingPlanes = planes;
|
||||||
|
scene.add(capMeshGroup);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Add meshes to the scene
|
// Add meshes to the scene
|
||||||
const planeMeshGroup = new Group();
|
const planeMeshGroup = new Group();
|
||||||
planeMeshGroup.name = "clipping-planes";
|
planeMeshGroup.name = "clipping-planes";
|
||||||
|
@ -388,29 +406,21 @@ export function buildClippingplanes(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove existing cap meshes
|
// Remove existing cap meshes
|
||||||
const capMeshGroupName = `cap-mesh-group-${object.name}`;
|
const capMeshGroupName = `cap-mesh-group-${orientation}`;
|
||||||
let capMeshGroup = scene.getObjectByName(capMeshGroupName);
|
const capMeshGroup = scene.getObjectByName(
|
||||||
while (capMeshGroup) {
|
capMeshGroupName
|
||||||
scene.remove(capMeshGroup);
|
) as ClippingGroup;
|
||||||
capMeshGroup = scene.getObjectByName(capMeshGroupName);
|
if (capMeshGroup) {
|
||||||
}
|
capMeshGroup.clear();
|
||||||
|
|
||||||
// Generate new cap meshes
|
// Generate new cap meshes
|
||||||
const capMeshes = generateCapMeshes(
|
generateCapMeshes(
|
||||||
meshes,
|
meshes,
|
||||||
plane.clone(),
|
plane.clone(),
|
||||||
planes,
|
orientation,
|
||||||
orientation,
|
scene,
|
||||||
scene
|
capMeshGroup
|
||||||
);
|
);
|
||||||
|
|
||||||
// Add new cap meshes
|
|
||||||
if (capMeshes.length > 0) {
|
|
||||||
const newCapMeshGroup = new Group();
|
|
||||||
|
|
||||||
newCapMeshGroup.add(...capMeshes);
|
|
||||||
newCapMeshGroup.name = capMeshGroupName;
|
|
||||||
scene.add(newCapMeshGroup);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -576,12 +586,10 @@ function resizeClippingPlane(
|
||||||
function generateCapMeshes(
|
function generateCapMeshes(
|
||||||
meshes: Mesh[],
|
meshes: Mesh[],
|
||||||
plane: Plane,
|
plane: Plane,
|
||||||
planes: Plane[],
|
|
||||||
orientation: Orientation,
|
orientation: Orientation,
|
||||||
scene: Scene
|
scene: Scene,
|
||||||
|
capMeshGroup: ClippingGroup
|
||||||
) {
|
) {
|
||||||
const capMeshes: Mesh[] = [];
|
|
||||||
|
|
||||||
// Rescale to local coordinates
|
// Rescale to local coordinates
|
||||||
if (orientation === Orientation.Z || orientation === Orientation.NZ)
|
if (orientation === Orientation.Z || orientation === Orientation.NZ)
|
||||||
plane.constant /= scene.scale.z;
|
plane.constant /= scene.scale.z;
|
||||||
|
@ -640,9 +648,6 @@ function generateCapMeshes(
|
||||||
// Intersection surface can be a multipolygon consisting of disconnected polygons
|
// Intersection surface can be a multipolygon consisting of disconnected polygons
|
||||||
const polygons: Vector3[][] = buildPolygons(edges);
|
const polygons: Vector3[][] = buildPolygons(edges);
|
||||||
|
|
||||||
// Clip cap surfaces with clipping planes
|
|
||||||
const clippingPlanes = planes.filter((p) => !p.normal.equals(plane.normal));
|
|
||||||
|
|
||||||
const offset =
|
const offset =
|
||||||
orientation === Orientation.NX ||
|
orientation === Orientation.NX ||
|
||||||
orientation === Orientation.NY ||
|
orientation === Orientation.NY ||
|
||||||
|
@ -651,24 +656,30 @@ function generateCapMeshes(
|
||||||
: -1;
|
: -1;
|
||||||
|
|
||||||
const color =
|
const color =
|
||||||
mesh.material instanceof MeshStandardMaterial
|
mesh.material instanceof MeshStandardNodeMaterial
|
||||||
? mesh.material.color
|
? mesh.material.color
|
||||||
: new Color(1, 1, 1);
|
: new Color(1, 1, 1);
|
||||||
|
|
||||||
const material = new MeshStandardMaterial({
|
const material = new MeshStandardNodeMaterial({
|
||||||
color,
|
color,
|
||||||
side: DoubleSide,
|
side: DoubleSide,
|
||||||
metalness: 0.0,
|
metalness: 0.1,
|
||||||
roughness: 1.0,
|
roughness: 0.5,
|
||||||
flatShading: true,
|
flatShading: true,
|
||||||
polygonOffset: true,
|
polygonOffset: true,
|
||||||
polygonOffsetFactor: offset,
|
polygonOffsetFactor: offset,
|
||||||
polygonOffsetUnits: offset,
|
polygonOffsetUnits: offset,
|
||||||
clippingPlanes,
|
|
||||||
wireframe: scene.userData.wireframe,
|
wireframe: scene.userData.wireframe,
|
||||||
|
alphaToCoverage: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const localMeshes = polygons.map((polygon) => {
|
const tColor = uniform(new Color(color));
|
||||||
|
const fragmentShader = Fn(() => {
|
||||||
|
return vec4(tColor.r, tColor.g, tColor.b, 1.0);
|
||||||
|
});
|
||||||
|
material.fragmentNode = fragmentShader();
|
||||||
|
|
||||||
|
polygons.forEach((polygon) => {
|
||||||
const geometry = triangulatePolygon(polygon, plane);
|
const geometry = triangulatePolygon(polygon, plane);
|
||||||
|
|
||||||
const capMesh = new Mesh(geometry, material);
|
const capMesh = new Mesh(geometry, material);
|
||||||
|
@ -687,13 +698,11 @@ function generateCapMeshes(
|
||||||
}
|
}
|
||||||
positionAttr.needsUpdate = true;
|
positionAttr.needsUpdate = true;
|
||||||
|
|
||||||
return capMesh;
|
if (capMesh) {
|
||||||
|
capMeshGroup.add(capMesh);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
capMeshes.push(...localMeshes);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return capMeshes;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build polygons by grouping connected intersection edges
|
// Build polygons by grouping connected intersection edges
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
import {
|
import {
|
||||||
BufferAttribute,
|
BufferAttribute,
|
||||||
BufferGeometry,
|
BufferGeometry,
|
||||||
|
Color,
|
||||||
DoubleSide,
|
DoubleSide,
|
||||||
Mesh,
|
Mesh,
|
||||||
MeshStandardMaterial,
|
|
||||||
} from "three";
|
} from "three";
|
||||||
|
|
||||||
import { fetchVertices, fetchTriangleIndices, transform } from "./utils";
|
import { fetchVertices, fetchTriangleIndices, transform } from "./utils";
|
||||||
import { TRIANGLE_INDICES_URL, VERTICES_URL } from "../config";
|
import { TRIANGLE_INDICES_URL, VERTICES_URL } from "../config";
|
||||||
import { shaderMaterial } from "../ShaderMaterial";
|
import { topoNodeMaterial } from "../ShaderMaterial";
|
||||||
|
import { MeshStandardNodeMaterial } from "three/webgpu";
|
||||||
|
import { Fn, uniform, vec3, vec4 } from "three/tsl";
|
||||||
|
|
||||||
interface MappedFeature {
|
interface MappedFeature {
|
||||||
featuregeom_id: number;
|
featuregeom_id: number;
|
||||||
|
@ -24,6 +26,8 @@ export async function buildMeshes(mappedFeatures: MappedFeature[]) {
|
||||||
const mesh = await buildMesh(layerData);
|
const mesh = await buildMesh(layerData);
|
||||||
if (layerData.name === "Topography") {
|
if (layerData.name === "Topography") {
|
||||||
mesh.visible = false;
|
mesh.visible = false;
|
||||||
|
} else {
|
||||||
|
mesh.visible = true;
|
||||||
}
|
}
|
||||||
meshes.push(mesh);
|
meshes.push(mesh);
|
||||||
}
|
}
|
||||||
|
@ -58,19 +62,26 @@ async function buildMesh(layerData: MappedFeature) {
|
||||||
const indices = new BufferAttribute(indexArray, 1);
|
const indices = new BufferAttribute(indexArray, 1);
|
||||||
|
|
||||||
geometry.setIndex(indices);
|
geometry.setIndex(indices);
|
||||||
|
geometry.computeVertexNormals();
|
||||||
|
|
||||||
const material = new MeshStandardMaterial({
|
const material = new MeshStandardNodeMaterial({
|
||||||
color: color,
|
color: color,
|
||||||
metalness: 0.1,
|
metalness: 0.1,
|
||||||
roughness: 0.5,
|
roughness: 0.5,
|
||||||
flatShading: true,
|
flatShading: true,
|
||||||
side: DoubleSide,
|
side: DoubleSide,
|
||||||
wireframe: false,
|
alphaToCoverage: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const tColor = uniform(new Color(color));
|
||||||
|
const fragmentShader = Fn(() => {
|
||||||
|
return vec4(tColor.r, tColor.g, tColor.b, 1.0);
|
||||||
|
});
|
||||||
|
material.colorNode = fragmentShader();
|
||||||
|
|
||||||
const mesh = new Mesh(
|
const mesh = new Mesh(
|
||||||
geometry,
|
geometry,
|
||||||
name === "Topography" ? shaderMaterial : material
|
name === "Topography" ? topoNodeMaterial : material
|
||||||
);
|
);
|
||||||
mesh.name = name;
|
mesh.name = name;
|
||||||
mesh.userData.layerId = geomId;
|
mesh.userData.layerId = geomId;
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import {
|
import {
|
||||||
PerspectiveCamera,
|
PerspectiveCamera,
|
||||||
Scene,
|
Scene,
|
||||||
WebGLRenderer,
|
|
||||||
AmbientLight,
|
AmbientLight,
|
||||||
DirectionalLight,
|
DirectionalLight,
|
||||||
Group,
|
Group,
|
||||||
|
@ -19,6 +18,7 @@ import {
|
||||||
|
|
||||||
import { OrbitControls } from "three/addons/controls/OrbitControls.js";
|
import { OrbitControls } from "three/addons/controls/OrbitControls.js";
|
||||||
import { getCenter3D, getMaxSize } from "./utils";
|
import { getCenter3D, getMaxSize } from "./utils";
|
||||||
|
import { WebGPURenderer } from "three/webgpu";
|
||||||
|
|
||||||
export interface Extent {
|
export interface Extent {
|
||||||
xmin: number;
|
xmin: number;
|
||||||
|
@ -30,7 +30,7 @@ export interface Extent {
|
||||||
}
|
}
|
||||||
|
|
||||||
let controls: OrbitControls;
|
let controls: OrbitControls;
|
||||||
let renderer: WebGLRenderer;
|
let renderer: WebGPURenderer;
|
||||||
let camera: PerspectiveCamera;
|
let camera: PerspectiveCamera;
|
||||||
let scene: Scene;
|
let scene: Scene;
|
||||||
let overlayCamera: OrthographicCamera;
|
let overlayCamera: OrthographicCamera;
|
||||||
|
@ -54,15 +54,15 @@ export function buildScene(container: HTMLElement, extent: Extent) {
|
||||||
camera.lookAt(center);
|
camera.lookAt(center);
|
||||||
|
|
||||||
// Initialize the renderer
|
// Initialize the renderer
|
||||||
renderer = new WebGLRenderer({
|
renderer = new WebGPURenderer({
|
||||||
logarithmicDepthBuffer: true,
|
logarithmicDepthBuffer: true,
|
||||||
|
antialias: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
renderer.setPixelRatio(window.devicePixelRatio);
|
renderer.setPixelRatio(window.devicePixelRatio);
|
||||||
renderer.setSize(width, height);
|
renderer.setSize(width, height);
|
||||||
renderer.localClippingEnabled = true;
|
renderer.localClippingEnabled = true;
|
||||||
renderer.autoClear = false;
|
renderer.autoClear = false;
|
||||||
// renderer.setAnimationLoop(animate);
|
|
||||||
|
|
||||||
// Handle window resize event to adapt the aspect ratio
|
// Handle window resize event to adapt the aspect ratio
|
||||||
window.addEventListener("resize", () => onWindowResize(container));
|
window.addEventListener("resize", () => onWindowResize(container));
|
||||||
|
@ -117,7 +117,7 @@ function onWindowResize(container: HTMLElement) {
|
||||||
camera.updateProjectionMatrix();
|
camera.updateProjectionMatrix();
|
||||||
renderer.setSize(container.clientWidth, container.clientHeight);
|
renderer.setSize(container.clientWidth, container.clientHeight);
|
||||||
|
|
||||||
// required if controls.enableDamping or controls.autoRotate are set to true
|
// Required if controls.enableDamping or controls.autoRotate are set to true
|
||||||
controls.update();
|
controls.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -152,7 +152,12 @@ function renderOverlay() {
|
||||||
);
|
);
|
||||||
|
|
||||||
// Render the overlay scene to the screen (position it in the bottom left)
|
// Render the overlay scene to the screen (position it in the bottom left)
|
||||||
renderer.setViewport(10, 10, UI_WIDTH, UI_HEIGHT);
|
renderer.setViewport(
|
||||||
|
10,
|
||||||
|
renderer.domElement.height - UI_HEIGHT - 10,
|
||||||
|
UI_WIDTH,
|
||||||
|
UI_HEIGHT
|
||||||
|
);
|
||||||
renderer.render(overlayScene, overlayCamera);
|
renderer.render(overlayScene, overlayCamera);
|
||||||
renderer.setViewport(
|
renderer.setViewport(
|
||||||
0,
|
0,
|
||||||
|
@ -179,7 +184,7 @@ function buildDefaultLights(scene: Scene, extent: Extent) {
|
||||||
lights.push(ambient);
|
lights.push(ambient);
|
||||||
|
|
||||||
// Directional lights
|
// Directional lights
|
||||||
const directionalLight = new DirectionalLight(0xffffff, 1.5);
|
const directionalLight = new DirectionalLight(0xffffff, 2);
|
||||||
directionalLight.position.set(
|
directionalLight.position.set(
|
||||||
lightPosition.x,
|
lightPosition.x,
|
||||||
lightPosition.y,
|
lightPosition.y,
|
||||||
|
|
Loading…
Add table
editor.link_modal.header
Reference in a new issue