Terrain Generation with web-viewer and hoops communicator: how to implement in web-viewer. and place model in geolocation of terrian

import React, { useRef, useEffect } from 'react';
import * as THREE from 'three';
import samepl from "./sample.png"
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';


const Terrain = () => {
  const terrainRef = useRef();

  useEffect(() => {
    // Set up Three.js scene
    const scene = new THREE.Scene();
    const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
    const renderer = new THREE.WebGLRenderer();

    renderer.setSize(window.innerWidth, window.innerHeight);
    terrainRef.current.appendChild(renderer.domElement);

    // Create terrain geometry based on a heightmap
    const geometry = new THREE.PlaneGeometry(10, 10, 99, 99);
    const loader = new THREE.TextureLoader();
    const texture = loader.load('sample.png', () => {
      // Once the texture is loaded, apply height data to the terrain geometry
      const canvas = document.createElement('canvas');
      const context = canvas.getContext('2d');
      canvas.width = texture.image.width;
      canvas.height = texture.image.height;
      context.drawImage(texture.image, 0, 0, canvas.width, canvas.height);

      const positionAttribute = geometry.getAttribute('position');

      for (let i = 0; i < positionAttribute.count; i++) {
        const x = positionAttribute.getX(i);
        const y = positionAttribute.getY(i);
        const dataIndex = Math.floor((y + 0.5) * canvas.width + (x + 0.5)); // Assuming terrain is centered at (0, 0)
        positionAttribute.setZ(i, (context.getImageData(x, y, 1, 1).data[0] / 255) * 2 - 1); // Assuming grayscale image
      }

      positionAttribute.needsUpdate = true; // Update the attribute to reflect the changes
    });

    // Create material and mesh
    const material = new THREE.MeshBasicMaterial({ wireframe: true, side: THREE.DoubleSide });
    const terrainMesh = new THREE.Mesh(geometry, material);

    // Add terrain to the scene
    scene.add(terrainMesh);

    // Set camera position
    camera.position.z = 5;

    // Add OrbitControls
    const controls = new OrbitControls(camera, renderer.domElement);
    controls.enableDamping = true; // an animation loop is required when either damping or auto-rotation are enabled
    controls.dampingFactor = 0.25;
    controls.screenSpacePanning = false;
    controls.maxPolarAngle = Math.PI / 2;

    // Set camera position
    camera.position.z = 5;

    // Animation function
    const animate = () => {
      requestAnimationFrame(animate);

      // Rotate the terrain for visualization
      // terrainMesh.rotation.x += 0.005;
      // terrainMesh.rotation.y += 0.005;

      // Render the scene
      renderer.render(scene, camera);
    };

    animate();

    // Handle window resize
    const handleResize = () => {
      camera.aspect = window.innerWidth / window.innerHeight;
      camera.updateProjectionMatrix();
      renderer.setSize(window.innerWidth, window.innerHeight);
    };

    window.addEventListener('resize', handleResize);

    // Clean up on component unmount
    return () => {
      window.removeEventListener('resize', handleResize);
      // Additional cleanup if needed
    };
  }, []);

  return <div ref={terrainRef} />;
};

export default Terrain;

Hello @raja.r,

Currently, Communicator does not support the integration of GIS topology into the Web Viewer. We already have a feature requested created for that.

Thanks,
Tino

1 Like
const loadImage = (url) => {
    return new Promise((resolve, reject) => {
        const image = new Image();
        image.crossOrigin = '';
        image.onload = () => resolve(image);
        image.onerror = (error) => reject(error);
        image.src = url;
    });
};

const loadTexture = (url) => {
    return loadImage(url).then((image) => {
        const canvas = document.createElement('canvas');
        const context = canvas.getContext('2d');
        canvas.width = image.width;
        canvas.height = image.height;
        context.drawImage(image, 0, 0);
        const imageData = context.getImageData(0, 0, canvas.width, canvas.height).data;
        return { data: imageData, width: canvas.width, height: canvas.height };
    });
};

async function getImages() {
    const heightMapPromise = loadTexture('images/textures/elevation.png');
    const colorMapPromise = loadTexture('images/textures/colors.png');
    const [heightMap, colorMap] = await Promise.all([heightMapPromise, colorMapPromise]);
    return { heightMap, colorMap }
}

// Function to create raw position, uv, and color data for a plane geometry
function createPlaneGeometryData(width, height, widthSegments, heightSegments) {
    const positions = [];
    const uvs = [];
    const colors = [];
    const normals = [];

    for (let y = 0; y < heightSegments; y++) {
        for (let x = 0; x < widthSegments; x++) {
            const u = x / widthSegments;
            const v = y / heightSegments;

            const xPos = u * width - width / 2;
            const yPos = v * height - height / 2;
            const zPos = 0;

            // Push raw position data
            positions.push(xPos, yPos, zPos);

            // Push raw uv data
            uvs.push(u, v);

            // Push raw color data (white)
            colors.push(1, 1, 1, 1);

            // Calculate and push normal vectors (for a flat plane, normal is [0, 0, 1])
            normals.push(0, 0, 1);
        }
    }

    return { positions, uvs, colors, normals };
}


function generateTerrain(heightMapData, colorMapData) {
    // Example usage
    const width = 10;
    const height = 10;
    const widthSegments = 99;
    const heightSegments = 99;

    const geometryData = createPlaneGeometryData(width, height, widthSegments, heightSegments);

    // Access the raw data
    const positionAttribute = new Float32Array(geometryData?.positions);
    const uvAttribute = new Float32Array(geometryData?.uvs);
    const colorAttribute = new Uint8Array(geometryData?.colors);
    const normalAttribute = new Float32Array(geometryData?.normals);

    // Find the minimum and maximum values of x and y
    const xMin = Math.min(...positionAttribute.filter((_, index) => index % 3 === 0));
    const xMax = Math.max(...positionAttribute.filter((_, index) => index % 3 === 0));
    const yMin = Math.min(...positionAttribute.filter((_, index) => index % 3 === 1));
    const yMax = Math.max(...positionAttribute.filter((_, index) => index % 3 === 1));

    for (let i = 0; i < positionAttribute.length/3; i++) {
        // const x = (positionAttribute[i * 3] + 5) / 10; // Normalize x coordinate to [0, 1]
        // const y = (positionAttribute[i * 3 + 1] + 5) / 10; // Normalize y coordinate to [0, 1]

        const x = (positionAttribute[i * 3] - xMin) / (xMax - xMin);
        const y = (positionAttribute[i * 3 + 1] - yMin) / (yMax - yMin);
    
        const pixelX = Math.round(x * (heightMapData.width - 1));
        const pixelY = Math.round(y * (heightMapData.height - 1));

        const grayscaleValue = heightMapData.data[pixelY * heightMapData.width * 4 + pixelX * 4] / 255;

        positionAttribute[i * 3 + 2] = grayscaleValue * 2 - 1; // Assuming grayscale image

        const colorPixelX = pixelX % colorMapData.width;
        const colorPixelY = pixelY % colorMapData.height;

        uvAttribute[i * 2] = colorPixelX / colorMapData.width;
        uvAttribute[i * 2 + 1] = colorPixelY / colorMapData.height;

        const colorPixelIndex = (colorPixelY * colorMapData.width + colorPixelX) * 4;

        if (colorPixelIndex >= 0 && colorPixelIndex + 2 < colorMapData.data.length) {
            const colorValueR = colorMapData.data[colorPixelIndex] /// 255; // Red component
            const colorValueG = colorMapData.data[colorPixelIndex + 1] /// 255; // Green component
            const colorValueB = colorMapData.data[colorPixelIndex + 2] /// 255; // Blue component
            const colorValueA = colorMapData.data[colorPixelIndex + 3] /// 255; // Alpa component

            colorAttribute[i * 4] = colorValueR;
            colorAttribute[i * 4 + 1] = colorValueG;
            colorAttribute[i * 4 + 2] = colorValueB;
            colorAttribute[i * 4 + 3] = colorValueA;

        } else {
            console.warn('Color pixel index out of bounds:', colorPixelIndex, i);
        }
    }

    return { vertices: positionAttribute, uvs: uvAttribute, colors: colorAttribute, normals: normalAttribute }

}

const { heightMap, colorMap } = await getImages();
                  const { vertices, uvs, colors, normals } =  generateTerrain(heightMap, colorMap);
                 
                  var meshData = new Communicator.MeshData();
                  // meshData.setFaceWinding(Communicator.FaceWinding.Clockwise);
                  meshData.setBackfacesEnabled(true);
                  meshData.addFaces(vertices, normals, colors, uvs);
           
                  _viewer.model.createMesh(meshData).then((meshId) => {
                    const meshInstanceData = new Communicator.MeshInstanceData(meshId);
                    // meshInstanceData.setFaceColor(Communicator.Color.green());
                    // meshInstanceData.setLineColor(Communicator.Color.green());
                    // meshInstanceData.setPointColor(Communicator.Color.green());
                    return _viewer.model.createMeshInstance(meshInstanceData);
                  });
                  _viewer.view.setBackfacesVisible(true);
                  _setDefaultCamera();

here is how i trying to create it. but is coming not expected can color each vertices and not like trianges

Hello @raja.r,

Based on the screenshot you provided, it does not look like the vertices array that you are passing to meshData.addFaces(vertices, normals, colors, uvs) is forming a contiguous strip of triangles to form the faces. It may be that you are just forming triangles for every triplet set of points. You’ll need to take into account neighboring/shared points to correctly form the triangles of the faces.

To better visualize the vertices data, you can add points by calling meshData.addPoints(vertices);

Another suggestion is to decrease the number of segments of the terrain to maybe just 9 for now:

const widthSegments = 9;
const heightSegments = 9;

Thanks,
Tino

1 Like