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
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