Is it possible to load texture to specific faces of PlaneGeometry object? What I want to do is to map separate texture to specific 2 triangles of PlaneGeometry object.
Here is some example that color each 2 trianles of PlaneGeometry object with some color and it works:
<!DOCTYPE html>
<html lang="en">
<head>
<title>title</title>
<meta charset="utf-8">
<style>
body {
font-family: Monospace;
background-color: #f0f0f0;
margin: 0px;
overflow: hidden;
}
</style>
</head>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/87/three.min.js"></script>
<script>
var camera, scene, renderer, geometry, material, mesh;
init();
animate();
function init() {
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 10000);
camera.position.z = 500;
scene.add(camera);
// geometry
var thumbnail_width = 32;
var thumbnail_height = 32;
var width_segments = 10;
var height_segments = 10;
var plane_geometry_width = width_segments * thumbnail_width;
var plane_geometry_height = height_segments * thumbnail_height;
var geometry = new THREE.PlaneGeometry(plane_geometry_width, plane_geometry_height, width_segments, height_segments); // faces.length = widthSegments*heightSegments*2
//Draw grid with static colors
// materials
var materials = [];
materials.push(new THREE.MeshBasicMaterial({
color: 0xff0000, side:THREE.DoubleSide
}));
materials.push(new THREE.MeshBasicMaterial({
color: 0x00ff00, side:THREE.DoubleSide
}));
materials.push(new THREE.MeshBasicMaterial({
color: 0x0000ff, side:THREE.DoubleSide
}));
// Add materialIndex to face
var l = geometry.faces.length / 2;
for (var i = 0; i < l; i++) {
var j = 2 * i;
geometry.faces[j].materialIndex = i % 3;
geometry.faces[j + 1].materialIndex = i % 3;
}
// mesh
mesh = new THREE.Mesh(geometry, materials);
scene.add(mesh);
// WebGL renderer
renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
}
function animate() {
requestAnimationFrame(animate);
render();
}
function render() {
renderer.render(scene, camera);
}
</script>
</body>
Here is result:
However when I try to map texture the same way, it maps to all triangles, which is not what I want:
<!DOCTYPE html>
<html lang="en">
<head>
<title>title</title>
<meta charset="utf-8">
<style>
body {
font-family: Monospace;
background-color: #f0f0f0;
margin: 0px;
overflow: hidden;
}
</style>
</head>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/87/three.min.js"></script>
<script>
var camera, scene, renderer, geometry, material, mesh;
init();
animate();
function init() {
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 10000);
camera.position.z = 500;
scene.add(camera);
// geometry
var thumbnail_width = 32;
var thumbnail_height = 32;
var width_segments = 10;
var height_segments = 10;
var plane_geometry_width = width_segments * thumbnail_width;
var plane_geometry_height = height_segments * thumbnail_height;
var geometry = new THREE.PlaneGeometry(plane_geometry_width, plane_geometry_height, width_segments, height_segments); // faces.length = widthSegments*heightSegments*2
var texture = new THREE.TextureLoader().load("image_small/item_1.png")
var materials = [];
materials.push(new THREE.MeshBasicMaterial({map : texture, side: THREE.DoubleSide}));
materials.push(new THREE.MeshBasicMaterial({color: 0xff0000, side:THREE.DoubleSide}));
materials.push(new THREE.MeshBasicMaterial({color: 0x00ff00, side:THREE.DoubleSide}));
// Add materialIndex to face
var l = geometry.faces.length / 2;
for (var i = 0; i < l; i++) {
var j = 2 * i;
geometry.faces[j].materialIndex = i % 3;
geometry.faces[j + 1].materialIndex = i % 3;
}
// mesh
mesh = new THREE.Mesh(geometry, materials);
scene.add(mesh);
// WebGL renderer
renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
}
function animate() {
requestAnimationFrame(animate);
render();
}
function render() {
renderer.render(scene, camera);
}
</script>
</body>
Here is result:
UPDATE:
Part of code for 2nd solution suggested by #WestLangley
var texture = new THREE.TextureLoader().load("image_small/item_1.png")
texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
texture.repeat.set( 10, 10 );
var materials = [];
materials.push(new THREE.MeshBasicMaterial({map : texture, side: THREE.DoubleSide}));
materials.push(new THREE.MeshBasicMaterial({color: 0xff0000, side:THREE.DoubleSide}));
materials.push(new THREE.MeshBasicMaterial({color: 0x00ff00, side:THREE.DoubleSide}));
// Add materialIndex to face
var l = geometry.faces.length / 2;
for (var i = 0; i < l; i++) {
var j = 2 * i;
geometry.faces[j].materialIndex = i % 3;
geometry.faces[j + 1].materialIndex = i % 3;
}
geometry.sortFacesByMaterialIndex();
// Mesh
mesh = new THREE.Mesh(geometry, materials);
scene.add(mesh);
// WebGL renderer
renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
Result:
UPDATE2:
var texture = new THREE.TextureLoader().load("image_small/item_1.png")
texture.flipY = true
var materials = [];
materials.push(new THREE.MeshBasicMaterial({map : texture, side: THREE.DoubleSide}));
for (var i = 0; i < geometry.faces.length; i++) {
geometry.faces[i].materialIndex = 0;
}
var uvs = geometry.faceVertexUvs[ 0 ]; // widthSegments*heightSegments*2
//UV
// (0,1) (1,1)
// (0,0) (1,0)
// For THREE.PlaneGeometry, UV ( 0, 0 ) is located at the bottom left, and ( 1, 1 ) the top right.
for (var i = 0; i < uvs.length; i++) {
if(i % 2 == 0)
{
// (0,1)[2] x
// (0,0)[1] (1,0)[3]
uvs[i][0].x = 0.0;
uvs[i][0].y = 0.0;
uvs[i][1].x = 0.0;
uvs[i][1].y = 1.0;
uvs[i][2].x = 1.0;
uvs[i][2].y = 0.0;
}
else
{
// (0,1)[1] (1,1)[2]
// x (1,0)[3]
uvs[i][0].x = 0.0;
uvs[i][0].y = 1.0;
uvs[i][1].x = 1.0;
uvs[i][1].y = 1.0;
uvs[i][2].x = 1.0;
uvs[i][2].y = 0.0;
}
}
var mesh = new THREE.Mesh(geometry, materials);
scene.add(mesh);
// WebGL renderer
renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
Result:
You have two options.
One is to change the UVs of your geometry so each square has UVs that range from ( 0, 0 ) lower-left to ( 1, 1 ) upper-right.
The other solution is to just set the repeat of your texture:
texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
texture.repeat.set( 10, 10 );
BTW, type renderer.info into the console, and you will see you are generating 100 draw calls.
Add the following to your code after you set the material indices:
geometry.sortFacesByMaterialIndex();
You should now see just 3 draw calls.
three.js r.89
Related
I'm trying to create an ocean for my Three.js application. I took the example from this site:
https://codepen.io/RemiRuc/pen/gJMwOe?fbclid=IwAR2caTQL-AOPE2Gv6x4rzSWBrOmAh2j-raqesOO0XbYQAuSG37imbMszSis
var params = {
res : 32,
speed : 8,
amp : 2,
wireframe : true,
backgroundColor : 0x9c81e3,
planeColor : 0x4a4a4a
}
var scene = new THREE.Scene();
scene.background = new THREE.Color(params.backgroundColor)
var camera = new THREE.PerspectiveCamera( 75, window.innerWidth/window.innerHeight, 0.1, 1000 )
let canvas = document.getElementById("webgl")
var renderer = new THREE.WebGLRenderer({canvas:canvas, antialias: true})
renderer.setSize( window.innerWidth, window.innerHeight )
var simplex = new SimplexNoise()
var light = new THREE.AmbientLight( 0xcccccc ); // soft white light
scene.add( light );
var pointLight = new THREE.PointLight( 0xeeeeee, 1, 100 );
pointLight.position.set( 0, 20, -20 );
scene.add( pointLight );
let geometry, material, plane
createPlane()
camera.position.z = 5;
camera.position.y = 3;
camera.lookAt(new THREE.Vector3( 0, 3, 0 ))
var animate = function () {
requestAnimationFrame( animate );
for (var i = 0; i < geometry.vertices.length; i++) {
var z = (i + Date.now() * params.speed/100000)
geometry.vertices[i].z = simplex.noise4D(z,z,z,z) * params.amp
plane.geometry.verticesNeedUpdate = true;
}
scene.background = new THREE.Color(params.backgroundColor)
material.color = new THREE.Color(params.planeColor)
material.wireframe = params.wireframe
camera.rotation.y += 0.001
renderer.render( scene, camera );
};
animate();
function createPlane(){
geometry = new THREE.PlaneGeometry( 200, 200, params.res,params.res );
material = new THREE.MeshLambertMaterial( {color: params.planeColor, side: THREE.DoubleSide, wireframe: params.wireframe} );
plane = new THREE.Mesh( geometry, material );
scene.add( plane );
plane.rotation.x = Math.PI/2
}
/***RESIZE***/
window.addEventListener('resize', ()=>{
document.querySelector('canvas').style.width = window.innerWidth + "px"
document.querySelector('canvas').style.height = window.innerHeight + "px"
renderer.setSize( window.innerWidth, window.innerHeight )
camera.aspect = window.innerWidth / window.innerHeight
camera.updateProjectionMatrix()
})
var gui = new dat.GUI()
var controller = gui.add(params, "res", 0, 100).name("Plane resolution")
gui.add(params, "speed", 0, 500).name("Wave speed")
gui.add(params, "amp", 0, 20).name("Wave amplitude")
gui.add(params, "wireframe", 0, 20).name("Wireframe")
gui.addColor(params, "backgroundColor").name("Background color")
gui.addColor(params, "planeColor").name("Plane color")
controller.onChange(()=>{
scene.remove(plane)
createPlane()
})
Issue is, I'm using PlaneBufferGeometry instead of PlaneGeometry, and it seems there are some differences
My code in render after creating the waterPlane
for (var i = 0; i < waterGeometry.attributes.position.count; i++) {
var z = (i + Date.now() * params.speed/100000);
waterGeometry.attributes.position[i] = simplex.noise4D(z,z,z,z) * params.amp;
}
waterGeometry.attributes.position.needsUpdate = true;
waterPlaneMesh.attributes.position.needsUpdate = true;
I'm not getting any errors, but no matter what I do, all I get is a flat wireframe plane geometry that doesn't move or anything. I think issue is in the updating of the plane?
This is an example of how you can displace vertices of a buffer geometry, using that SimplexNoise library:
body {
margin: 0;
background-color: #000;
color: #fff;
font-family: Monospace;
font-size: 13px;
line-height: 24px;
overscroll-behavior: none;
}
<script type="module">
import * as THREE from "https://cdn.skypack.dev/three#0.136.0";
import { OrbitControls } from "https://cdn.skypack.dev/three#0.136.0/examples/jsm/controls/OrbitControls";
import { createNoise3D } from "https://cdn.skypack.dev/simplex-noise";
let simplex = createNoise3D();
let scene = new THREE.Scene();
let camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 1, 2000);
camera.position.set(0, 0.5, 1).setLength(12);
let renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setSize(innerWidth, innerHeight);
document.body.appendChild(renderer.domElement);
window.addEventListener("resize", onWindowResize);
//scene.add(new THREE.GridHelper())
let controls = new OrbitControls(camera, renderer.domElement);
let light = new THREE.DirectionalLight(0xffffff, 0.5);
light.position.setScalar(1);
scene.add(light, new THREE.AmbientLight(0xffffff, 0.5));
let v3 = new THREE.Vector3();
let v2 = new THREE.Vector2();
let g = new THREE.PlaneGeometry(200, 200, 100, 100);
g.rotateX(-Math.PI *0.5);
let m = new THREE.MeshLambertMaterial({color: "aqua", wireframe: false});
let o = new THREE.Mesh(g, m);
scene.add(o);
let clock = new THREE.Clock();
renderer.setAnimationLoop(() => {
renderer.render(scene, camera);
let t = clock.getElapsedTime();
for(let i = 0; i < g.attributes.position.count; i++){
v2.fromBufferAttribute(g.attributes.uv, i).addScalar(t * 0.01).multiplyScalar(20);
let h = simplex(v2.x, v2.y, t * 0.1);
g.attributes.position.setY(i, h);
}
g.computeVertexNormals();
g.attributes.position.needsUpdate = true;
});
function onWindowResize() {
camera.aspect = innerWidth / innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(innerWidth, innerHeight);
}
</script>
I've created two 3JS objects and scenes for learning purposes. Both housed in separate PHP files (not in the jsfiddle). However, I can't figure out why by adding a second object the first object stops animating and doesn't run anymore? How can I go about figuring out why my objects are canceling one another out and how to create two different or more multiple threejs items that animate at the same time?
//GRID OBJ is the first object, and the second is //BALL
When I remove the second script calling for 3js ball the grid will animate, when the ball is added the grid stops animating and only the ball animates.
http://jsfiddle.net/tdqh4jno/
//GRID OBJ
var container = document.getElementById('grid')
var vertexHeight = 90,
planeDefinition = 25,
planeSize = 900,
totalObjects = 1,
background = "#002135",
meshColor = "#ff3057";
var camera2 = new THREE.PerspectiveCamera( 110, 1, 5)
camera2.position.y = 500;
var scene2 = new THREE.Scene();
scene2.background = new THREE.Color( 0x08080c);
var renderer = new THREE.WebGLRenderer({alpha: true});
renderer.setClearColor( 0x000000, 0 );
var planeGeo = new THREE.PlaneGeometry(planeSize, planeSize, planeDefinition, planeDefinition);
var plane = new THREE.Mesh(planeGeo, new THREE.MeshBasicMaterial({
color: meshColor,
wireframe: true
}));
plane.rotation.x -= Math.PI * .5;
scene2.add(plane);
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(background, 1);
container.appendChild(renderer.domElement);
function updatePlane() {
for (var i = 0; i < planeGeo.vertices.length; i++) {
planeGeo.vertices[i].z += Math.random() * vertexHeight;
planeGeo.vertices[i]._myZ = planeGeo.vertices[i].z
}
};
var count = 0
function renderGrid() {
var gridSpeed = 0.4;
var gridInterlop = -0.4;
var gridWaveSpeed = 0.003;
var gridWaveHeight = 0.00003;
requestAnimationFrame(renderGrid);
for (var i = 0; i < planeGeo.vertices.length; i++) {
var z = +planeGeo.vertices[i].z;
planeGeo.vertices[i].z = Math.sin(( i + count * gridWaveHeight)) * (planeGeo.vertices[i]._myZ - (planeGeo.vertices[i]._myZ * gridWaveSpeed))
plane.geometry.verticesNeedUpdate = true;
count += gridInterlop
}
renderer.render(scene2, camera2);
}
updatePlane();
renderGrid();
//BALL ITEM
var camera2 = new THREE.PerspectiveCamera(100, window.innerWidth/window.innerHeight, 0.1, 1000);
var renderer = new THREE.WebGLRenderer();
renderer.autoClear = false;
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var sceneTop = new THREE.Scene(); // initialising the scene
sceneTop.background = new THREE.Color( 0x08080c);
var spotLight = new THREE.SpotLight(0xffffff);
spotLight.position.set(100,100,100);
spotLight.castShadow = false;
sceneTop.add(spotLight);
function Mat(){
var material = new THREE.MeshPhongMaterial({
color : new THREE.Color(0xff3266),
emissive : new THREE.Color(0x08080c),
specular : new THREE.Color(0x08080c),
shininess : 0,
wireframe : true,
transparent: 0.5,
opacity : 0.55
});
return material;
}
var geometry = new THREE.IcosahedronGeometry(45 , 1);
var obj = new THREE.Mesh(geometry, Mat());
sceneTop.add(obj);
camera2.position.z = 90;
function myrender(){
renderer.setClearColor( 0x000000, 0 );
obj.rotation.x += 0.0004;
obj.rotation.y += 0.0006;
obj.rotation.z += Math.random() * 0.0005;
renderer.render(sceneTop, camera2);
requestAnimationFrame(myrender);
}
window.addEventListener('resize', onWindowResize, true);
function onWindowResize() {
renderer.setSize(window.innerWidth, window.innerHeight);
camera2.updateProjectionMatrix();
};
myrender();
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/99/three.min.js"></script>
<div id="grid"></div>
You create 2 THREE.WebGLRenderer objects, but both assigned to the variable renderer. This breaks down the animation of the first (grid) object in the function renderGrid.
In your code are created 2 different scenes referenced by 2 different variables (sceneTop, scene2). Create 2 different render objects, referenced by 2 different variables (renderer, renderer2), too:
var scene2 = new THREE.Scene();
scene2.background = new THREE.Color( 0x08080c);
var renderer2 = new THREE.WebGLRenderer({alpha: true});
renderer2.setClearColor( 0x000000, 0 );
// [...]
renderer2.setSize(window.innerWidth, window.innerHeight);
renderer2.setClearColor(background, 1);
container.appendChild(renderer2.domElement);
function renderGrid() {
// [...]
renderer2.render(scene2, camera2);
}
In the following you have to respect, that renderer corresponds to camera respectively sceneTop, but renderer2 corresponds to scene2 and camera2:
var camera = new THREE.PerspectiveCamera(100, window.innerWidth/window.innerHeight, 0.1, 1000);
[...]
camera.position.z = 90;
function myrender(){
// [...]
renderer.render(sceneTop, camera);
requestAnimationFrame(myrender);
}
See the example, where I applied the suggested changes to your original code:
//GRID OBJ
var container = document.getElementById('grid')
var vertexHeight = 90,
planeDefinition = 25,
planeSize = 900,
totalObjects = 1,
background = "#002135",
meshColor = "#ff3057";
var camera2 = new THREE.PerspectiveCamera( 110, 1, 5)
camera2.position.y = 500;
var scene2 = new THREE.Scene();
scene2.background = new THREE.Color( 0x08080c);
var renderer2 = new THREE.WebGLRenderer({alpha: true});
renderer2.setClearColor( 0x000000, 0 );
var planeGeo = new THREE.PlaneGeometry(planeSize, planeSize, planeDefinition, planeDefinition);
var plane = new THREE.Mesh(planeGeo, new THREE.MeshBasicMaterial({
color: meshColor,
wireframe: true
}));
plane.rotation.x -= Math.PI * .5;
scene2.add(plane);
renderer2.setSize(window.innerWidth, window.innerHeight);
renderer2.setClearColor(background, 1);
container.appendChild(renderer2.domElement);
function updatePlane() {
for (var i = 0; i < planeGeo.vertices.length; i++) {
planeGeo.vertices[i].z += Math.random() * vertexHeight;
planeGeo.vertices[i]._myZ = planeGeo.vertices[i].z
}
};
var count = 0
function renderGrid() {
var gridSpeed = 0.4;
var gridInterlop = -0.4;
var gridWaveSpeed = 0.003;
var gridWaveHeight = 0.00003;
requestAnimationFrame(renderGrid);
for (var i = 0; i < planeGeo.vertices.length; i++) {
var z = +planeGeo.vertices[i].z;
planeGeo.vertices[i].z = Math.sin(( i + count * gridWaveHeight)) * (planeGeo.vertices[i]._myZ - (planeGeo.vertices[i]._myZ * gridWaveSpeed))
plane.geometry.verticesNeedUpdate = true;
count += gridInterlop
}
renderer2.render(scene2, camera2);
}
updatePlane();
renderGrid();
//BALL ITEM
var camera = new THREE.PerspectiveCamera(100, window.innerWidth/window.innerHeight, 0.1, 1000);
var renderer = new THREE.WebGLRenderer();
renderer.autoClear = false;
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var sceneTop = new THREE.Scene(); // initialising the scene
sceneTop.background = new THREE.Color( 0x08080c);
var spotLight = new THREE.SpotLight(0xffffff);
spotLight.position.set(100,100,100);
spotLight.castShadow = false;
sceneTop.add(spotLight);
function Mat(){
var material = new THREE.MeshPhongMaterial({
color : new THREE.Color(0xff3266),
emissive : new THREE.Color(0x08080c),
specular : new THREE.Color(0x08080c),
shininess : 0,
wireframe : true,
transparent: 0.5,
opacity : 0.55
});
return material;
}
var geometry = new THREE.IcosahedronGeometry(45 , 1);
var obj = new THREE.Mesh(geometry, Mat());
sceneTop.add(obj);
camera.position.z = 90;
function myrender(){
renderer.setClearColor( 0x000000, 0 );
obj.rotation.x += 0.0004;
obj.rotation.y += 0.0006;
obj.rotation.z += Math.random() * 0.0005;
renderer.render(sceneTop, camera);
requestAnimationFrame(myrender);
}
window.addEventListener('resize', onWindowResize, true);
function onWindowResize() {
renderer.setSize(window.innerWidth, window.innerHeight);
renderer2.setSize(window.innerWidth, window.innerHeight);
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
camera2.aspect = window.innerWidth / window.innerHeight;
camera2.updateProjectionMatrix();
};
myrender();
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/99/three.min.js"></script>
<div id="grid"></div>
Alternatively both scenes can be rendered to different parts of the viewport.
Create 1 THREE.WebGLRenderer, with the .autoClear property set to false:
var renderer = new THREE.WebGLRenderer({alpha: true, preserveDrawingBuffer: true});
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor( 0x000000, 0 );
renderer.autoClear = false;
document.body.appendChild(renderer.domElement);
Render both scenes 2 different parts of the viewport. The viewport rectangle can be changed by .setViewport:
function myrender(){
// [...]
renderer.setViewport(0,0,window.innerWidth, window.innerHeight);
renderer.clear();
renderer.setViewport(window.innerWidth/2,0,window.innerWidth/2, window.innerHeight);
renderer.render(sceneTop, camera, 0, false);
renderer.setViewport(0,0,window.innerWidth/2, window.innerHeight);
renderer.render(scene2, camera2, 0, false);
requestAnimationFrame(myrender);
}
Ensure that the .background property is only set for the THREE.Scene which is rendered first.
See the example:
//GRID OBJ
var container = document.getElementById('grid')
var vertexHeight = 90,
planeDefinition = 25,
planeSize = 900,
totalObjects = 1,
background = "#002135",
meshColor = "#ff3057";
var renderer = new THREE.WebGLRenderer({alpha: true, preserveDrawingBuffer: true});
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor( 0x000000, 0 );
renderer.autoClear = false;
document.body.appendChild(renderer.domElement);
var camera2 = new THREE.PerspectiveCamera( 110, window.innerWidth/2/window.innerHeight, 5)
camera2.position.y = 500;
var scene2 = new THREE.Scene();
var planeGeo = new THREE.PlaneGeometry(planeSize, planeSize, planeDefinition, planeDefinition);
var plane = new THREE.Mesh(planeGeo, new THREE.MeshBasicMaterial({
color: meshColor,
wireframe: true
}));
plane.rotation.x -= Math.PI * .5;
scene2.add(plane);
function updatePlane() {
for (var i = 0; i < planeGeo.vertices.length; i++) {
planeGeo.vertices[i].z += Math.random() * vertexHeight;
planeGeo.vertices[i]._myZ = planeGeo.vertices[i].z
}
};
var count = 0
updatePlane();
//BALL ITEM
var camera = new THREE.PerspectiveCamera(100, window.innerWidth/2/window.innerHeight, 0.1, 1000);
var sceneTop = new THREE.Scene(); // initialising the scene
sceneTop.background = new THREE.Color( 0x08080c);
var spotLight = new THREE.SpotLight(0xffffff);
spotLight.position.set(100,100,100);
spotLight.castShadow = false;
sceneTop.add(spotLight);
function Mat(){
var material = new THREE.MeshPhongMaterial({
color : new THREE.Color(0xff3266),
emissive : new THREE.Color(0x08080c),
specular : new THREE.Color(0x08080c),
shininess : 0,
wireframe : true,
transparent: 0.5,
opacity : 0.55
});
return material;
}
var geometry = new THREE.IcosahedronGeometry(45 , 1);
var obj = new THREE.Mesh(geometry, Mat());
sceneTop.add(obj);
camera.position.z = 90;
function myrender(){
var gridSpeed = 0.4;
var gridInterlop = -0.4;
var gridWaveSpeed = 0.003;
var gridWaveHeight = 0.00003;
for (var i = 0; i < planeGeo.vertices.length; i++) {
var z = +planeGeo.vertices[i].z;
planeGeo.vertices[i].z = Math.sin(( i + count * gridWaveHeight)) * (planeGeo.vertices[i]._myZ - (planeGeo.vertices[i]._myZ * gridWaveSpeed))
plane.geometry.verticesNeedUpdate = true;
count += gridInterlop
}
obj.rotation.x += 0.0004;
obj.rotation.y += 0.0006;
obj.rotation.z += Math.random() * 0.0005;
renderer.setViewport(0,0,window.innerWidth, window.innerHeight);
renderer.clear();
renderer.setViewport(window.innerWidth/2,0,window.innerWidth/2, window.innerHeight);
renderer.render(sceneTop, camera, 0, false);
renderer.setViewport(0,0,window.innerWidth/2, window.innerHeight);
renderer.render(scene2, camera2, 0, false);
requestAnimationFrame(myrender);
}
window.addEventListener('resize', onWindowResize, true);
function onWindowResize() {
renderer.setSize(window.innerWidth, window.innerHeight);
camera.aspect = window.innerWidth / 2 / window.innerHeight;
camera.updateProjectionMatrix();
camera2.aspect = window.innerWidth / 2 / window.innerHeight;
camera2.updateProjectionMatrix();
};
myrender();
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/99/three.min.js"></script>
I am loading in an STL file with the STLLoader() in Three.js and am transforming it into a mesh. Now, I would like to color each cube of my mesh based on its height, as in the image attached here screenshot-color-by-height. Is this possible within Three.js and if so, what would be the best approach? The actual model is here:
https://casagroupproject.github.io/subpage1.html
Just a concept of how you can make it with colors of vertices:
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.set(2, 5, 10);
var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var controls = new THREE.OrbitControls(camera, renderer.domElement);
var light = new THREE.DirectionalLight(0xffffff, 0.5);
light.position.setScalar(10);
scene.add(light);
scene.add(new THREE.AmbientLight(0xffffff, 0.5));
var planeGeom = new THREE.PlaneBufferGeometry(10, 10, 10, 10);
planeGeom.rotateX(-Math.PI * 0.5);
var yMin = 0;
var yMax = 2;
var colors = [];
for (let i = 0; i < planeGeom.attributes.position.count; i++) {
let yVal = THREE.Math.randInt(yMin, yMax);
let yNorm = (yVal - yMin) / (yMax - yMin);
planeGeom.attributes.position.setY(i, yVal);
colors.push(yNorm, yNorm, 1);
}
planeGeom.addAttribute('color', new THREE.BufferAttribute(new Float32Array(colors), 3));
planeGeom.computeVertexNormals();
var mesh = new THREE.Mesh(planeGeom, new THREE.MeshStandardMaterial({
vertexColors: THREE.VertexColors
}));
scene.add(mesh)
render();
function render() {
requestAnimationFrame(render);
renderer.render(scene, camera);
}
body {
overflow: hidden;
margin: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/92/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>
I was doing a Three js tutorial and this code should cast shadows, but when render shadows does not exists over the plane.
What is wrong on code? I'm reading documentation and other codes and I can not find the problem.
Best regards!
EDIT:
If i make a loop with requestAnimationFrame just works... but only in that case... why?
<!DOCTYPE html>
<html>
<head>
<title>Learning THREE JS Basic</title>
<script src="../../libs/three.js"></script>
<script src="../../libs/jquery.js"></script>
<style>
body {
margin : 0;
overflow : hidden;
}
</style>
</head>
<body>
<div id="visor"></div>
<script>
$(function () {
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(45, window.innerWidth/window.innerHeight, 1, 1000);
var renderer = new THREE.WebGLRenderer();
renderer.setClearColor(0xEEEEEE);
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true;
var axes = new THREE.AxisHelper(20);
scene.add(axes);
var planeGeometry = new THREE.PlaneGeometry(60, 20, 1, 1);
var planeMaterial = new THREE.MeshLambertMaterial({
color: 0xFFFFFF
});
var plane = new THREE.Mesh(planeGeometry, planeMaterial);
plane.rotation.x = -0.5*Math.PI;
plane.position.x = 15;
plane.position.y = 0;
plane.position.z = 0;
plane.receiveShadow = true;
scene.add(plane);
var cubeGeometry = new THREE.BoxGeometry(4,4,4);
var cubeMaterial = new THREE.MeshLambertMaterial({
color: 0xFF0000
});
var cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
cube.position.x = -4;
cube.position.y = 3;
cube.position.z = 0;
cube.castShadow = true;
scene.add(cube);
var sphereGeometry = new THREE.SphereGeometry(4, 20, 20);
var sphereMaterial = new THREE.MeshLambertMaterial({
color: 0x7777FF
});
var sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
sphere.position.x = 20;
sphere.position.y = 4;
sphere.position.z = 2;
sphere.castShadow = true;
scene.add(sphere);
var spotLight = new THREE.SpotLight(0xFFFFFF);
spotLight.position.set(-40, 60, -10);
spotLight.castShadow = true;
scene.add(spotLight);
camera.position.x = -30;
camera.position.y = 40;
camera.position.z = 30;
camera.lookAt(scene.position);
$("#visor").append(renderer.domElement);
renderer.render(scene, camera);
});
</script>
</body>
</html>
three.js r.74 and r.75 have a bug in which shadows are not always rendered in the first call to render(). This bug has been fixed in three.js r.76dev.
Your work-around is to call renderer.render( scene, camera ) a 2nd time, or to have an animation loop.
three.js r.75
I'm trying to use FileReader to pass a client side ASCII file to loader.load() but it looks like the file never gets there. The file does appear in the 3D scene if I use loader.load('server path to test_file.stl') instead of loader.load(fileObject).
I included an alert() function to display the file's ASCII text and that tells me the JavaScript is grabbing and processing the file and there is no Chrome security barrier but apparently I'm not properly passing the file to loader.load().
<!DOCTYPE html>
<html>
<head>
<style>
body {
background-color:#fea47c;
}
div {
position:relative;
left:200px;
top:0px;
background-color: #eeeeee;
border:1px solid black;
width:550px;
height:550px;
}
canvas {
width:550px;
height:550px;
}
</style>
</head>
<body>
<script src="https://raw.github.com/mrdoob/three.js/master/build/three.min.js"></script>
<script src="https://raw.github.com/mrdoob/three.js/master/examples/js/loaders/STLLoader.js"></script>
<script src="https://raw.github.com/mrdoob/three.js/master/examples/js/controls/TrackballControls.js"></script>
<input type="file" id="pickfile"></input>
<script>
document.getElementById('pickfile').addEventListener('change', readFile, false);
function readFile (evt)
{
var fileObject = evt.target.files[0];
var reader = new FileReader();
reader.onload = function() {alert(this.result)}; // verifies ASCII file contents were grabbed
reader.readAsText(fileObject)
}
var container, camera, scene, renderer, controls;
init();
animate();
function init() {
container = document.createElement( 'div' );
document.body.appendChild( container );
var width = container.clientWidth;
var height = container.clientHeight;
camera = new THREE.PerspectiveCamera( 35, width / height, .1 , 10000);
camera.position.set( 0, 0, 600);
scene = new THREE.Scene();
controls = new THREE.TrackballControls( camera , container);
controls.addEventListener( 'change', render );
// object
var loader = new THREE.STLLoader();
loader.addEventListener( 'load', function ( event ) {
var geometry = event.content;
var material = new THREE.MeshLambertMaterial( { ambient: 0xff5533, color: 0xff5533 } );
var mesh = new THREE.Mesh( geometry, material );
scene.add( mesh );
} );
loader.load(fileObject);
// lights
scene.add( new THREE.AmbientLight( 0x222222 ) );
var directionalLight = new THREE.DirectionalLight( 0xffffff, 1 );
directionalLight.position = camera.position;
scene.add( directionalLight );
// renderer
renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setSize( width , height );
container.appendChild( renderer.domElement );
window.addEventListener( 'resize', onWindowResize, false );
}
function addLight( x, y, z, color, intensity ) {
var directionalLight = new THREE.DirectionalLight( color, intensity );
directionalLight.position.set( x, y, z )
scene.add( directionalLight );
}
function onWindowResize() {
camera.aspect = width / height;
camera.updateProjectionMatrix();
renderer.setSize( width, height );
}
function animate() {
requestAnimationFrame( animate );
controls.update();
render();
}
function render() {
camera.lookAt( scene.position );
renderer.render( scene, camera );
}
</script>
</body>
</html>
Ok, I tried again this morning, and I think the problem was I was trying to view the loaded results from a bad camera angle or something... anyways, here's an example based on https://raw.github.com/mrdoob/three.js/master/examples/webgl_loader_stl.html
The essential part:
Like I mentioned above, loader.load does not have any overload that would take the actual contents of the file (and it's kinda silly to think it would). It will only take a location for the file... you needed something that creates your model from the file contents. That would be loader.parse.
For example, the following handler adds your model to a scene where scene is in scope of readFile:
function readFile(evt)
{
var fileObject = evt.target.files[0];
var reader = new FileReader();
reader.onload = function ()
{
var loader = new THREE.STLLoader();
//alert(this.result)
var geometry = loader.parse(this.result);
var material = new THREE.MeshPhongMaterial(
{
ambient: 0xff5533,
color: 0xff5533,
specular: 0x111111,
shininess: 200
});
var mesh = new THREE.Mesh(geometry, material);
mesh.castShadow = true;
mesh.receiveShadow = true;
scene.add(mesh);
};
reader.readAsText(fileObject)
}
Whole example:
I'd put this somewhere on the net but as it uses github to host some of the scripts, etc. that's probably not the best idea.
<!DOCTYPE html>
<html>
<head>
<style>
body {
font-family: Monospace;
background-color: #000000;
margin: 0px;
overflow: hidden;
}
#info {
color: #fff;
position: absolute;
top: 10px;
width: 100%;
text-align: center;
z-index: 100;
display:block;
}
#pickfile {
color: #fff;
position: absolute;
top: 40px;
width: 100%;
text-align: center;
z-index: 100;
display:block;
}
a {
color: skyblue
}
</style>
</head>
<body>
<script src="https://raw.github.com/mrdoob/three.js/master/build/three.min.js"></script>
<script src="https://raw.github.com/mrdoob/three.js/master/examples/js/loaders/STLLoader.js"></script>
<script src="https://raw.github.com/mrdoob/three.js/master/examples/js/controls/TrackballControls.js"></script>
<script src="https://raw.github.com/mrdoob/three.js/master/examples/js/Detector.js"></script>
<script src="https://raw.github.com/mrdoob/three.js/master/examples/js/libs/stats.min.js"></script>
<input type="file" id="pickfile"></input>
<script>
document.getElementById('pickfile').addEventListener('change', readFile, false);
function readFile(evt)
{
var fileObject = evt.target.files[0];
var reader = new FileReader();
reader.onload = function ()
{
var loader = new THREE.STLLoader();
//alert(this.result)
var geometry = loader.parse(this.result);
var material = new THREE.MeshPhongMaterial(
{
ambient: 0xff5533,
color: 0xff5533,
specular: 0x111111,
shininess: 200
});
var mesh = new THREE.Mesh(geometry, material);
mesh.castShadow = true;
mesh.receiveShadow = true;
scene.add(mesh);
};
reader.readAsText(fileObject)
}
if (!Detector.webgl) Detector.addGetWebGLMessage();
var container, stats;
var camera, scene, renderer, objects;
init();
animate();
function init()
{
container = document.createElement('div');
document.body.appendChild(container);
camera = new THREE.PerspectiveCamera(35, window.innerWidth / window.innerHeight, 1, 15);
camera.position.set(3, 0.5, 3);
scene = new THREE.Scene();
scene.fog = new THREE.Fog(0xffffff, 2, 15);
scene.fog.color.setHSV(0.06, 0.2, 0.45);
// Grid
var size = 20,
step = 0.25;
var geometry = new THREE.Geometry();
var material = new THREE.LineBasicMaterial(
{
color: 0x000000
});
for (var i = -size; i <= size; i += step)
{
geometry.vertices.push(new THREE.Vector3(-size, -0.04, i));
geometry.vertices.push(new THREE.Vector3(size, -0.04, i));
geometry.vertices.push(new THREE.Vector3(i, -0.04, -size));
geometry.vertices.push(new THREE.Vector3(i, -0.04, size));
}
var line = new THREE.Line(geometry, material, THREE.LinePieces);
line.position.y = -0.46;
scene.add(line);
// Ground
var plane = new THREE.Mesh(new THREE.PlaneGeometry(40, 40), new THREE.MeshPhongMaterial(
{
ambient: 0x999999,
color: 0x999999,
specular: 0x101010
}));
plane.rotation.x = -Math.PI / 2;
plane.position.y = -0.5;
scene.add(plane);
plane.receiveShadow = true;
// Object
// Lights
scene.add(new THREE.AmbientLight(0x777777));
addShadowedLight(1, 1, 1, 0xffffff, 1.35);
addShadowedLight(0.5, 1, -1, 0xffaa00, 1);
// renderer
renderer = new THREE.WebGLRenderer(
{
antialias: true,
clearColor: 0x111111,
clearAlpha: 1,
alpha: false
});
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(scene.fog.color, 1);
renderer.gammaInput = true;
renderer.gammaOutput = true;
renderer.physicallyBasedShading = true;
renderer.shadowMapEnabled = true;
renderer.shadowMapCullFrontFaces = false;
container.appendChild(renderer.domElement);
// stats
stats = new Stats();
stats.domElement.style.position = 'absolute';
stats.domElement.style.top = '0px';
container.appendChild(stats.domElement);
//
window.addEventListener('resize', onWindowResize, false);
}
function addShadowedLight(x, y, z, color, intensity)
{
var directionalLight = new THREE.DirectionalLight(color, intensity);
directionalLight.position.set(x, y, z)
scene.add(directionalLight);
directionalLight.castShadow = true;
//directionalLight.shadowCameraVisible = true;
var d = 1;
directionalLight.shadowCameraLeft = -d;
directionalLight.shadowCameraRight = d;
directionalLight.shadowCameraTop = d;
directionalLight.shadowCameraBottom = -d;
directionalLight.shadowCameraNear = 1;
directionalLight.shadowCameraFar = 4;
directionalLight.shadowMapWidth = 2048;
directionalLight.shadowMapHeight = 2048;
directionalLight.shadowBias = -0.005;
directionalLight.shadowDarkness = 0.15;
}
function onWindowResize()
{
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
//
function animate()
{
requestAnimationFrame(animate);
render();
stats.update();
}
function render()
{
var timer = Date.now() * 0.0005;
camera.position.x = Math.cos(timer) * 5;
camera.position.z = Math.sin(timer) * 5;
camera.lookAt(scene.position);
renderer.render(scene, camera);
}
</script>
</body>
</html>
Thanks a lot for your snippet, this made my day
Though I had a problem implementing your solution at this line :
reader.readAsText(fileObject) ;
I changed it to :
reader.readAsArrayBuffer(fileObject) ;
and it works like a charm...
So for the complete code :
1) I have a button in my html to load the .stl file :
select your stl file <br>
<input type="file" id="pickFile" />
2) in my js file :
if ( ! Detector.webgl ) Detector.addGetWebGLMessage();
var camera, scene, renderer, mesh, controls ;
var group ;
var container = document.getElementById('canvas3D');
// Create default material
material = new THREE.MeshPhongMaterial();
init();
animate();
// file input button
document.getElementById('pickFile').addEventListener('change', openFile, false);
// file load
function openFile (evt) {
var fileObject = evt.target.files[0];
// delete previous objects from scene
while(group.children.length > 0){
group.remove(group.children[0]);
}
var reader = new FileReader();
reader.onload = function ()
{
var loader = new THREE.STLLoader();
// parse the .stl file
var geometry = loader.parse(this.result);
var mesh = new THREE.Mesh(geometry, material);
mesh.castShadow = true;
mesh.receiveShadow = true;
group.add(mesh);
};
// --> update here
reader.readAsArrayBuffer(fileObject) ;
};
// and the rest of my three.js code there : init() and animate() functions ...