Unable to map texture on to mesh in three.js - javascript

I've been working on putting together a very simple program that will create a height map from an image and texture that image in three.js
After a bit of hacking around I can generate the height map/mesh from an image quite easily but I cannot map a texture to this mesh object. I'm not sure where I'm going wrong although I'm pretty sure it is in the addMesh(); function. This is my first attempt with three.js so it may well be something very obvious.
The error I am thrown is:
THREE.Material: 'map' parameter is undefined.
This is what is rendered:
This is the code I'm using:
// Define variables
var camera, scene, renderer, geometry, material, mesh;
var img = document.getElementById("landscape-image");
var texture = new THREE.TextureLoader().load( "assets/texture.png" )
// Get the pixels by draw the image onto a canvas. From the canvas get the Pixel (R,G,B,A)
function getTerrainPixelData(){
var canvas = document.getElementById("canvas");
canvas.width = img.width;
canvas.height = img.height;
canvas.getContext('2d').drawImage(img, 0, 0, img.width, img.height);
var data = canvas.getContext('2d').getImageData(0,0, img.height, img.width).data;
var normPixels = []
for (var i = 0, n = data.length; i < n; i += 4) {
// get the average value of R, G and B.
normPixels.push((data[i] + data[i+1] + data[i+2]) / 3); //Essentially render image black and white
}
return normPixels;
}
// Create a mesh from pixel data
function addMesh() {
var numSegments = img.width-1; //Equal to number of image pixels - 1
geometry = new THREE.PlaneGeometry(2400, 2400, numSegments, numSegments);
material = new THREE.MeshBasicMaterial({
wireframe: true,
map: texture
});
terrain = getTerrainPixelData();
for (var i = 0, l = geometry.vertices.length; i < l; i++) // For each vertex
{
var terrainValue = terrain[i] / 255;
geometry.vertices[i].z = geometry.vertices[i].z + terrainValue * 800 ; // Define height of vertices against image pixel data
}
mesh = new THREE.Mesh(geometry, material);
scene.add(mesh)
}
// Function to render scene and camera
function render() {
requestAnimationFrame(render);
renderer.render(scene, camera);
}
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.1, 10000); //Field-of-View, Aspect Ratio, Near Render, Far Render
camera.position.z = 3000; // Camera distance
renderer = new THREE.WebGLRenderer();
controls = new THREE.OrbitControls( camera, renderer.domElement );
addMesh();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
render();
body {
margin: 0;
}
#canvas {
display: none;
}
#assets {
display: none;
}
<html>
<head>
<title>Landscape Test</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div id="assets"><img id="landscape-image" src="assets/Heightmap2.png"/></div>
<canvas id="canvas"></canvas>
<script src="js/three.min.js"></script>
<script src="js/OrbitControls.js"></script> <!-- added so user can rotate the scene with the mouse -->
<script src="js/app.js"></script>
</body>
</html>

Related

three.js - how to render scene.background before a mesh?

I am a novice to Javascript and ThreeJS. I have a 3D rotating cube that appears on top of a static background, but one frustrating property is that the cube typically appears first and then the background image appears. How do I ensure the background is rendered first? Specifically, I always want the background image to appear before the cube.
My code:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>My first three.js app</title>
<style>
body { margin: 0; }
</style>
</head>
<body>
<script src="js/three.js"></script>
<script>
function resize() {
var aspect = window.innerWidth / window.innerHeight;
let texAspect = bgWidth / bgHeight;
let relAspect = aspect / texAspect;
bgTexture.repeat = new THREE.Vector2( Math.max(relAspect, 1), Math.max(1/relAspect,1) );
bgTexture.offset = new THREE.Vector2( -Math.max(relAspect-1, 0)/2, -Math.max(1/relAspect-1, 0)/2 );
renderer.setSize(window.innerWidth, window.innerHeight);
camera.aspect = aspect;
camera.updateProjectionMatrix();
}
const scene = new THREE.Scene();
// Arguments:
// 1) Field of Value (degrees)
// 2) Aspect ratio
// 3) Near clipping plane
// 4) Far clipping plane
const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );
const renderer = new THREE.WebGLRenderer();
// Need to set size of renderer. For performance, may want to reduce size.
// Can also reduce resolution by passing false as third arg to .setSize
renderer.setSize( window.innerWidth, window.innerHeight );
// Add the rendered to the HTML
document.body.appendChild( renderer.domElement );
// A BoxGeometry is an object that contains all points (vertices) and fill (faces)
// of the cube
const geometry = new THREE.BoxGeometry();
// Determines surface color (maybe texture?)
const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } );
// Mesh takes a geometry and applies the material to it
const cube = new THREE.Mesh( geometry, material );
// Add background image
const loader = new THREE.TextureLoader();
bgTexture = loader.load('https://images.pexels.com/photos/1205301/pexels-photo-1205301.jpeg' ,
function(texture) {
// Resize image to fit in window
// Code from https://stackoverflow.com/a/48126806/4570472
var img = texture.image;
var bgWidth = img.width;
var bgHeight = img.height;
resize();
});
scene.background = bgTexture;
// By default, whatever we add to the scene will be at coordinates (0, 0, 0)
scene.add( cube );
camera.position.z = 5;
// This somehow creates a loop that causes the rendered to draw the scene
// every time the screen is refreshed (typically 60fps)
function animate() {
requestAnimationFrame( animate );
cube.rotation.x += 0.01;
cube.rotation.y += 0.01;
renderer.render( scene, camera );
}
animate();
</script>
</body>
</html>
This is happening because the texture is taking longer to load than it takes for Three.js to set up the rest of the scene. You already have a handler for the onLoad callback of TextureLoader.load(), so we can use that to adjust the behavior.
Before scene.add( cube );, add a new line:
cube.visible = false;
Now the cube will still be added to the scene, but it won't be visible. Now after the resize() call at the end of function(texture), add
cube.visible = true;
While testing the problem and solution locally, I ran into a few other, less significant issues with your code. You can see all of the changes I had to make to get it running properly at this Gist.

Where (in an object3d) can I find the changing point coordinates?

This shows one point rotating around the Y axis.
I want to see the changing X coordinate as it rotates.
This shows the starting coordinate, but it doesn't change.
The varying coordinates must be somewhere, right? Where?
I thought "pointa.geometry.vertices[0].x" would be the place.
EDIT: I made the rotating point a rotating Group under a fixed Scene.
<!doctype html>
<html>
<head>
<title> a point </title>
<script src="http://threejs.org/build/three.js"></script>
<script type="text/javascript">
"use strict";
var js3canvas, renderer, camera, world, pointa, geometry, material, datadiv;
var norotate;
window.onload = function() {
"use strict";
js3canvas = document.getElementById("js3canvas");
datadiv = document.getElementById("data");
renderer = new THREE.WebGLRenderer( { canvas:js3canvas } );
camera = new THREE.PerspectiveCamera(50, 1, 1, 20);
camera.position.set(0,5,10);
camera.lookAt(0,0,0);
norotate = new THREE.Scene();
norotate.background = new THREE.Color(0x888888);
world = new THREE.Group();
norotate.add(world);
// make point
geometry = new THREE.Geometry();
geometry.vertices.push(new THREE.Vector3(2.3456,0,0));
material = new THREE.PointsMaterial( { color:0xffffff } );
pointa = new THREE.Points(geometry, material);
world.add(pointa);
animate();
}
function animate() {
datadiv.innerHTML = "X coordinate: " + pointa.geometry.vertices[0].x; // the x coordinate
renderer.render( norotate, camera );
world.rotateY(.03);
requestAnimationFrame(animate);
}
</script>
</head>
<body>
<canvas width="200" height="200" id="js3canvas"></canvas>
<div id="data"></div>
</body>
</html>
When you change rotation or position or scale of an object, you apply just a transform matrix to the points of a geometry, keeping data of the points intact.
Set a temp vector, copy a vertex into this vector, cast from local to world coordinate system:
var js3canvas, renderer, camera, world, pointa, geometry, material, datadiv;
var norotate;
js3canvas = document.getElementById("js3canvas");
datadiv = document.getElementById("data");
renderer = new THREE.WebGLRenderer({
canvas: js3canvas
});
camera = new THREE.PerspectiveCamera(50, 1, 1, 20);
camera.position.set(0, 5, 10);
camera.lookAt(0, 0, 0);
norotate = new THREE.Scene();
norotate.background = new THREE.Color(0x888888);
world = new THREE.Group();
norotate.add(world);
// make point
geometry = new THREE.Geometry();
geometry.vertices.push(new THREE.Vector3(2.3456, 0, 0));
material = new THREE.PointsMaterial({
color: 0xffffff
});
pointa = new THREE.Points(geometry, material);
world.add(pointa);
var tempV3 = new THREE.Vector3();
animate();
function animate() {
pointa.localToWorld(tempV3.copy(pointa.geometry.vertices[0]));
datadiv.innerHTML = "X coordinate: " + tempV3.x; // the x coordinate
renderer.render(norotate, camera);
world.rotateY(.03);
requestAnimationFrame(animate);
}
<canvas width="200" height="200" id="js3canvas"></canvas>
<div id="data"></div>
<script src="http://threejs.org/build/three.js"></script>

Three.js ply colorless (all black)

I am trying to load my ply file using Three.js. It has worked but I almost don't see any colors. There are some glimpses here and there, but it's mostly black (the first image below). The image opens properly (with colors) in MeshLab (the second image below). I have tried vertexColors: THREE.VertexColors (as suggested in Three.js - ply files - why colorless?) and vertexColors: THREE.FaceColors but nothing seemed to help. I am a three.js newbie, so please tell me what am I doing wrong.
and my code:
<!doctype html>
<html lang="en">
<head>
<title>Icon 7</title>
<meta charset="utf-8">
</head>
<body style="margin: 0;">
<script src="js/three.min.js"></script>
<script src="js/PLYLoader.js"></script>
<script src="js/OrbitControls.js"></script>
<script>
// Set up the scene, camera, and renderer as global variables.
var scene, camera, renderer;
init();
animate();
// Sets up the scene.
function init() {
// Create the scene and set the scene size.
scene = new THREE.Scene();
var WIDTH = window.innerWidth,
HEIGHT = window.innerHeight;
// Create a renderer and add it to the DOM.
renderer = new THREE.WebGLRenderer({antialias:true});
renderer.setSize(WIDTH, HEIGHT);
document.body.appendChild(renderer.domElement);
// Create a camera, zoom it out from the model a bit, and add it to the scene.
camera = new THREE.PerspectiveCamera(35, WIDTH / HEIGHT, 0.1, 100);
camera.position.set(0,0.15,3);
camera.lookAt(new THREE.Vector3(0,0,0));
scene.add(camera);
// Create an event listener that resizes the renderer with the browser window.
window.addEventListener('resize', function() {
var WIDTH = window.innerWidth,
HEIGHT = window.innerHeight;
renderer.setSize(WIDTH, HEIGHT);
camera.aspect = WIDTH / HEIGHT;
camera.updateProjectionMatrix();
});
// Set the background color of the scene.
renderer.setClearColor(0xd3d3d3, 1);
// Create a light, set its position, and add it to the scene.
var light = new THREE.PointLight(0xffffff);
light.position.set(0,200,100);
scene.add(light);
// Load in the mesh and add it to the scene.
var loader = new THREE.PLYLoader();
loader.load( './models/foot.ply', function ( geometry ) {
geometry.computeVertexNormals();
geometry.center();
var material = new THREE.MeshStandardMaterial ({ shininess: 1000,vertexColors: THREE.FaceColors } );
var mesh = new THREE.Mesh( geometry, material );
mesh.position.x = 0;
mesh.position.y = 0;
mesh.position.z = 0;
mesh.castShadow = false;
mesh.receiveShadow = false;
scene.add( mesh );
} );
// Add OrbitControls so that we can pan around with the mouse.
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.userPanSpeed = 0.05;
}
// Renders the scene and updates the render as needed.
function animate() {
// Read more about requestAnimationFrame at http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/
requestAnimationFrame(animate);
// Render the scene.
renderer.render(scene, camera);
controls.update();
}
</script>
</body>
</html>
Edit: as I don't have separate textures, this is not a duplicate question.
Instead of using MeshStandardMaterial use MeshBasicMaterial

Three js - Google map layer picking

I am using the following library: google-maps-api-threejs-layer
Now I want to add, a picking feature.
So, this is my modified code:
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Three.js Layer - Google Maps API</title>
<style>
html, body, #map-div {
margin: 0;
padding: 0;
height: 100%;
}
</style>
<script src="http://maps.googleapis.com/maps/api/js?sensor=false"></script>
<script src="data.js"></script>
<script src="styles.js"></script>
<script src="../lib/detector.js"></script>
<script src="../lib/dat.gui.js"></script>
<script src="../lib/three.js"></script>
<script src="../threejs-layer.js"></script>
<script>
var vector = new THREE.Vector2();
var raycaster = new THREE.Raycaster();
var myLayer;
function init() {
var container = document.getElementById('map-div');
var map = new google.maps.Map(container, {
zoom: 3,
mapTypeControl: false,
center: new google.maps.LatLng(10, 0),
mapTypeId: google.maps.MapTypeId.TERRAIN,
styles: styles
});
// if you add renderertype:'Canvas' to the options for ThreejsLayer, you can force the usage of CanvasRenderer
myLayer = new ThreejsLayer({ map: map }, function(layer){
if (layer.renderertype=='Canvas' || !Detector.webgl) {
texture = new THREE.Texture(generateSprite());
particles = new THREE.Object3D();
material = new THREE.SpriteMaterial({
size: 20,
opacity: 1,
depthTest: false,
transparent: false
});
photos.forEach(function (photo) {
var particle = new THREE.Sprite(material);
var location = new google.maps.LatLng(photo[0], photo[1]),
vertex = layer.fromLatLngToVertex(location);
particle.position.set(vertex.x, vertex.y, 0);
particle.scale.x = particle.scale.y = 20;
particles.add(particle);
material.size = 20;
});
} else {
var geometry = new THREE.Geometry(),
texture = new THREE.Texture(generateSprite()),
material, particles;
photos.forEach(function(photo){
var location = new google.maps.LatLng(photo[0], photo[1]),
vertex = layer.fromLatLngToVertex(location);
geometry.vertices.push( vertex );
});
texture.needsUpdate = true;
material = new THREE.PointCloudMaterial({
size: 20,
opacity: 0.3,
blending: THREE.AdditiveBlending,
depthTest: false,
transparent: false
});
particles = new THREE.PointCloud( geometry, material );
}
layer.add( particles );
gui = new dat.GUI();
function update(){
if (layer.renderertype=='Canvas' || !Detector.webgl) material.map = new THREE.Texture(generateSprite(material.size));
layer.render();
}
gui.add(material, 'size', 2, 100).onChange(update);
gui.add(material, 'opacity', 0.1, 1).onChange(update);
});
}
function onClick( event ) {
vector.x = ( (event.screenX - myLayer.renderer.domElement.offsetLeft) / myLayer.renderer.domElement.clientWidth ) * 2 - 1;
vector.y = - ( (event.screenY - myLayer.renderer.domElement.offsetTop) / myLayer.renderer.domElement.clientHeight ) * 2 + 1;
raycaster.setFromCamera(vector, myLayer.camera);
var intersects = raycaster.intersectObjects(myLayer.scene.children, true);
if(intersects.length > 0){
console.log(vector.x, vector.y);
}
}document.addEventListener('click', onClick, false);
function generateSprite(size) {
var canvas = document.createElement('canvas'),
context = canvas.getContext('2d'),
gradient;
size = size || 20;
canvas.width = size;
canvas.height = size;
gradient = context.createRadialGradient(
canvas.width / 2, canvas.height / 2, 0,
canvas.width / 2, canvas.height / 2, canvas.width / 2
);
gradient.addColorStop(1.0, 'rgba(255,255,255,0)');
gradient.addColorStop(0.0, 'rgba(255,255,255,1)');
context.fillStyle = gradient;
context.fillRect(0, 0, canvas.width, canvas.height);
return canvas;
}
document.addEventListener('DOMContentLoaded', init, false);
</script>
</head>
<body>
<div id="map-div"></div>
</body>
</html>
As you can see, I am using the raycaster. So, maybe you want an updated three js library: link.
OK, so the problem is when I click, near the geometries (outside the boxes) I can intersect elements, so intersects.length is greater than zero. I only want to intersect pixels inside the geometries in the map. How can I fix it?
EDIT:
I created a repo: https://github.com/FacundoGFlores/google-maps-api-threejs-layer
In your example you are using a PointCloud to render the points on the map. As you cannot reasonably expect any raycasting to precisely hit any single point, three.js uses a treshold-value for the raycaster that might need to get adjusted depending on your use-case.

onchange in dropdown triggers function but no change in three.js output

I am trying to make the page display either a green or a brown floor using three.js depending on the selection from a drop down list. However, I see that the floor images do not change although control does go to the function.
JS fiddle here (I could not upload images though)
The code is below.
<!DOCTYPE html>
<html>
<head>
<title>Floor change</title>
<style>
body {
margin: 0;
overflow: hidden;
}
</style>
</head>
<script>
// global variables
var renderer;
var scene;
var camera;
var container;
//controls
var controls;
//html elements
var colorselection = "green";
function init() {
var SCREEN_WIDTH = window.innerWidth, SCREEN_HEIGHT = window.innerHeight;
SCREEN_WIDTH-=200;
// SCREEN_HEIGHT -= 100;
// create a scene, that will hold all our elements such as objects, cameras and lights.
scene = new THREE.Scene();
// create a camera, which defines where we're looking at.
camera = new THREE.PerspectiveCamera(45, SCREEN_WIDTH / SCREEN_HEIGHT, 0.1, 1000);
// create a render, sets the background color and the size
renderer = new THREE.WebGLRenderer();
renderer.setClearColor(0x000000, 1.0);
renderer.setSize(SCREEN_WIDTH, SCREEN_HEIGHT);
// position and point the camera to the center of the scene
camera.position.x = 0;
camera.position.y = 30;
camera.position.z = 40;
camera.lookAt(scene.position);
// add the output of the renderer to the html element
document.body.appendChild(renderer.domElement);
// attach div element to variable to contain the renderer
container = document.getElementById( 'ThreeJS' );
// attach renderer to the container div
container.appendChild( renderer.domElement );
}
function floor()
{
///////////
// FLOOR //
///////////
if(colorselection == "green")
var floorTexture = new THREE.ImageUtils.loadTexture( 'green.jpg' );
else if(colorselection == "brown")/*go with 2 for now*/
var floorTexture = new THREE.ImageUtils.loadTexture( 'brown.png' );
floorTexture.wrapS = floorTexture.wrapT = THREE.RepeatWrapping;
floorTexture.repeat.set( 20, 20 );
// DoubleSide: render texture on both sides of mesh
var floorMaterial = new THREE.MeshBasicMaterial( { map: floorTexture, side: THREE.DoubleSide } );
var floorGeometry = new THREE.PlaneGeometry(110, 110, 1, 1);
var floor = new THREE.Mesh(floorGeometry, floorMaterial);
floor.position.y = -0.5;
floor.rotation.x = Math.PI / 2;
scene.add(floor);
animate();
}
//scheduler loop
function animate() {
renderer.render(scene,camera)
requestAnimationFrame(animate)
}
function myfunction()
{
colorselection = document.getElementById("mydropdownlist").value;
console.log("clicked on '"+ colorselection + "'")
floor();
}
// calls the init function when the window is done loading.
window.onload = init;
</script>
<body>
<script src="js/Three.js"></script>
<div id="ThreeJS" style="z-index: 1; position: absolute; left:0px; top:0px"></div>
<select id="mydropdownlist" onchange="myfunction()">
<option value="green">green</option>
<option value="brown">brown</option>
</select>
</body>
<style>
#mydropdownlist
{
position:absolute;
left:1200px;
top:20px
}
</style>
</html>
I have uploaded the images brown.png and green.jpg used above.
I copied your code to a local .html file, put it in the example directory for 3js, stuck the pictures there, and then updated the path to three.js to the default one
src="../build/Three.js"
and ran it in chrome. It worked, the floor colors changed when i used the drop-down. It also works in firefox.
I do see a problem however. You add the new floor mesh each time to the scene, but do not remove the old one. I expect to see a scene.remove(floor) before you make a new one so you dont get a bunch piled up in the scene. I also noticed that you have a function called floor and a variable called floor which can cause confusion.
Also, if you are using chrome, you need to use --disable-web-security as a command-line switch if you want to see the textures when the files are on your local drive instead of a web-server.
<!DOCTYPE html>
<html>
<head>
<title>Floor change</title>
<style>
body {
margin: 0;
overflow: hidden;
}
</style>
</head>
<script>
// global variables
var renderer;
var scene;
var camera;
var container;
var floormesh=null;
//controls
var controls;
//html elements
var colorselection = "green";
function init() {
var SCREEN_WIDTH = window.innerWidth, SCREEN_HEIGHT = window.innerHeight;
SCREEN_WIDTH-=200;
// SCREEN_HEIGHT -= 100;
// create a scene, that will hold all our elements such as objects, cameras and lights.
scene = new THREE.Scene();
// create a camera, which defines where we're looking at.
camera = new THREE.PerspectiveCamera(45, SCREEN_WIDTH / SCREEN_HEIGHT, 0.1, 1000);
// create a render, sets the background color and the size
renderer = new THREE.WebGLRenderer();
renderer.setClearColor(0x000000, 1.0);
renderer.setSize(SCREEN_WIDTH, SCREEN_HEIGHT);
// position and point the camera to the center of the scene
camera.position.x = 0;
camera.position.y = 30;
camera.position.z = 40;
camera.lookAt(scene.position);
// add the output of the renderer to the html element
document.body.appendChild(renderer.domElement);
// attach div element to variable to contain the renderer
container = document.getElementById( 'ThreeJS' );
// attach renderer to the container div
container.appendChild( renderer.domElement );
}
function floor()
{
///////////
// FLOOR //
///////////
if(colorselection == "green")
var floorTexture = new THREE.ImageUtils.loadTexture( 'green.jpg' );
else if(colorselection == "brown")/*go with 2 for now*/
var floorTexture = new THREE.ImageUtils.loadTexture( 'brown.png' );
floorTexture.wrapS = floorTexture.wrapT = THREE.RepeatWrapping;
floorTexture.repeat.set( 20, 20 );
// DoubleSide: render texture on both sides of mesh
var floorMaterial = new THREE.MeshBasicMaterial( { map: floorTexture, side: THREE.DoubleSide } );
var floorGeometry = new THREE.PlaneGeometry(110, 110, 1, 1);
if(floormesh)
scene.remove(floormesh);
floormesh = new THREE.Mesh(floorGeometry, floorMaterial);
floormesh.position.y = -0.5;
floormesh.rotation.x = Math.PI / 2;
scene.add(floormesh);
animate();
}
//scheduler loop
function animate() {
renderer.render(scene,camera)
requestAnimationFrame(animate)
}
function myfunction()
{
colorselection = document.getElementById("mydropdownlist").value;
console.log("clicked on '"+ colorselection + "'")
floor();
}
// calls the init function when the window is done loading.
window.onload = init;
</script>
<body>
<script src="../build/Three.js"></script>
<div id="ThreeJS" style="z-index: 1; position: absolute; left:0px; top:0px"></div>
<select id="mydropdownlist" onchange="myfunction()">
<option value="green">green</option>
<option value="brown">brown</option>
</select>
</body>
<style>
#mydropdownlist
{
position:absolute;
left:1200px;
top:20px
}
</style>
</html>

Categories

Resources