diff --git a/app/components/Form.tsx b/app/components/Form.tsx index f1fd145..ac354df 100644 --- a/app/components/Form.tsx +++ b/app/components/Form.tsx @@ -259,9 +259,9 @@ export function Form() {
{sceneView?.model.children.map((child) => { const key = `toggle-visibility-${child.name}`; - const color = `#${( - (child as Mesh).material as MeshStandardMaterial - ).color.getHexString()}`; + //const color = `#${( + // (child as Mesh).material as MeshStandardMaterial + //).color.getHexString()}`; const visible = (child as Mesh).visible; return ( @@ -272,7 +272,7 @@ export function Form() { { - // Pass uniforms from userData to the + // Pass uniforms from userData for (const i in material.userData) { shader.uniforms[i] = material.userData[i]; } diff --git a/app/three/SceneView.ts b/app/three/SceneView.ts index 5d262c4..8cf6c9e 100644 --- a/app/three/SceneView.ts +++ b/app/three/SceneView.ts @@ -3,7 +3,9 @@ import { Material, Mesh, MeshBasicMaterial, + MeshPhongMaterial, MeshStandardMaterial, + Object3D, PerspectiveCamera, Plane, Raycaster, @@ -32,12 +34,16 @@ import { } from "three/examples/jsm/Addons.js"; import { LODFrustum, + LODRaycast, + MapPlaneNode, MapTilerProvider, MapView, OpenStreetMapsProvider, + UnitsUtils, } from "geo-three"; import { CustomMapHeightNodeShader } from "./CustomMapHeightNodeShader"; import { Data, createSVG } from "./utils/create-borehole-svg"; +import { TileData, updateTiles } from "./ShaderMaterial"; export type CustomEvent = CustomEventInit<{ element: SVGSVGElement | null; @@ -489,18 +495,55 @@ async function init(container: HTMLElement, modelId = MODEL_ID) { ); // Create the map view for OSM topography - const lod = new LODFrustum(); + const lod = new LODRaycast(); - // @ts-expect-error Type definition for MapView is incorrect - missing parameter for lod - const map = new MapView(MapView.PLANAR, provider, heightProvider, lod); - const customNode = new CustomMapHeightNodeShader(undefined, map); - map.setRoot(customNode); + const map = new MapView(MapView.PLANAR, provider); + map.lod = lod; + // const customNode = new CustomMapHeightNodeShader(undefined, map); + // map.setRoot(customNode); map.rotateX(Math.PI / 2); map.name = "topography"; map.visible = false; scene.add(map); + controls.addEventListener("change", () => { + const tiles: TileData[] = []; + function traverse(node: MapPlaneNode) { + if (node.isMesh) { + const bounds = UnitsUtils.tileBounds(node.level, node.x, node.y); + + const xmin = bounds[0]; + const ymin = bounds[2]; + const xmax = xmin + bounds[1]; + const ymax = ymin + bounds[3]; + + if ( + (extent.xmax >= xmin && extent.ymax >= ymin) || + (extent.xmin <= xmax && extent.ymax >= ymin) || + (extent.xmin <= xmax && extent.ymin <= ymax) || + (extent.xmax >= xmin && extent.ymin <= ymax) + ) { + tiles.push({ + xmin, + ymin, + xmax, + ymax, + texture: (node.material as MeshPhongMaterial).map, + }); + } + } + for (const c of node.children) { + traverse(c as MapPlaneNode); + } + } + + map.lod.updateLOD(map, camera, renderer, scene); + traverse(map.root); + + updateTiles(tiles.reverse()); + }); + return { scene, model, diff --git a/app/three/ShaderMaterial.ts b/app/three/ShaderMaterial.ts new file mode 100644 index 0000000..18ba003 --- /dev/null +++ b/app/three/ShaderMaterial.ts @@ -0,0 +1,98 @@ +import { ShaderMaterial, Texture, Vector4 } from "three"; + +export interface TileData { + xmin: number; + ymin: number; + xmax: number; + ymax: number; + texture: Texture | null; +} + +const maxTiles = 16; + +// Initialize empty texture slots +const dummyTexture = new Texture(); +dummyTexture.image = document.createElement("canvas"); +dummyTexture.needsUpdate = true; + +// Create shader material +export const shaderMaterial = new ShaderMaterial({ + uniforms: { + tileTextures: { value: Array(maxTiles).fill(dummyTexture) }, + tileBounds: { value: Array(maxTiles).fill(new Vector4(0, 0, 0, 0)) }, + tileCount: { value: 0 }, + }, + vertexShader: ` + varying vec3 vWorldPosition; + void main() { + vWorldPosition = (modelMatrix * vec4(position, 1.0)).xyz; + gl_Position = projectionMatrix * viewMatrix * vec4(vWorldPosition, 1.0); + } + `, + fragmentShader: ` + uniform sampler2D tileTextures[${maxTiles}]; + uniform vec4 tileBounds[${maxTiles}]; + uniform int tileCount; + varying vec3 vWorldPosition; + + void main() { + vec4 color = vec4(1.0, 1.0, 1.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); + switch (i) { + case 0: color = texture2D(tileTextures[0], uv); break; + case 1: color = texture2D(tileTextures[1], uv); break; + case 2: color = texture2D(tileTextures[2], uv); break; + case 3: color = texture2D(tileTextures[3], uv); break; + case 4: color = texture2D(tileTextures[4], uv); break; + case 5: color = texture2D(tileTextures[5], uv); break; + case 6: color = texture2D(tileTextures[6], uv); break; + case 7: color = texture2D(tileTextures[7], uv); break; + case 8: color = texture2D(tileTextures[8], uv); break; + case 9: color = texture2D(tileTextures[9], uv); break; + case 10: color = texture2D(tileTextures[10], uv); break; + case 11: color = texture2D(tileTextures[11], uv); break; + case 12: color = texture2D(tileTextures[12], uv); break; + case 13: color = texture2D(tileTextures[13], uv); break; + case 14: color = texture2D(tileTextures[14], uv); break; + case 15: color = texture2D(tileTextures[15], uv); break; + } + + break; // Stop checking once we find the correct tile + } + } + + gl_FragColor = color; + } + `, +}); + +export function updateTiles(newTiles: TileData[]) { + if (newTiles.length > maxTiles) { + newTiles = newTiles.slice(0, maxTiles); + } + + const textures = newTiles.map((t) => t.texture); + const bounds = newTiles.map( + (t) => new Vector4(t.xmin, t.xmax, t.ymin, t.ymax) + ); + + // Fill remaining slots with dummy data to maintain uniform array size + while (textures.length < maxTiles) { + textures.push(dummyTexture); + bounds.push(new Vector4(0, 0, 0, 0)); + } + + // Update shader uniforms + shaderMaterial.uniforms.tileTextures.value = textures; + shaderMaterial.uniforms.tileBounds.value = bounds; + shaderMaterial.uniforms.tileCount.value = newTiles.length; +} diff --git a/app/three/utils/build-meshes.ts b/app/three/utils/build-meshes.ts index 26ec0a4..7fddd1d 100644 --- a/app/three/utils/build-meshes.ts +++ b/app/three/utils/build-meshes.ts @@ -61,7 +61,7 @@ async function buildMesh(layerData: MappedFeature) { const material = new MeshStandardMaterial({ color: color, metalness: 0.0, - roughness: 1.0, + roughness: 5.0, flatShading: true, side: DoubleSide, wireframe: false,