ThreeJS image texture on merged objects - three.js

Help! I'm trying to create and merge two different objects in THREEjs. I have one image and I need it to be stretched over the entire merged object once. Currently it is doing it twice, once on the top and once on the bottom, like they are still two separate objects.
var texture = new THREE.TextureLoader().load('//s1.postimg.org/26dva6d58v/peace.png');
texture.alphaTest = 0.9;
texture.needsUpdate = true;
color = new THREE.MeshStandardMaterial( {
side: THREE.DoubleSide,
color: 0x949494,
map: texture
} );
var size = {'top': 1.395, 'bottom': 1.11, 'height': 3.5};
cylinderGeo1 = new THREE.CylinderGeometry( size.top, size.bottom, size.height, 50, null, true );
cylinderGeo1.applyMatrix( new THREE.Matrix4().makeTranslation(0, size.height/2, 0) );
cylinder1 = new THREE.Mesh(cylinderGeo1);
cylinderGeo2 = new THREE.CylinderGeometry( size.top * 2,size.top, size.height, 50, null, true );
cylinderGeo2.applyMatrix( new THREE.Matrix4().makeTranslation(0, (size.height/2) + size.height, 0) );
cylinder2 = new THREE.Mesh(cylinderGeo2);
cupGeo = new THREE.Geometry();;
cylinder1.updateMatrix();
cupGeo.merge(cylinderGeo1, cylinder1.matrix);
cylinder2.updateMatrix();
cupGeo.merge(cylinderGeo2, cylinder2.matrix);
cup = new THREE.Mesh(cupGeo, color);
cup.updateMatrix();
scene.add(cup);
https://jsfiddle.net/dj2k6y8t/16/
[edit] You may need to zoom on the canvas to get it render.

Related

partial texture update in three.js

I upload a large texture to my fragment shader as a map in three.js.
This texture is a 2D canvas on which I draw random shapes.
While drawing, I just want to update the part of the texture that has changed. Updating the whole texture (up to 3000x3000 pixel) is just too slow.
However, I can not get to perform a partial update. texSubImage2D doesn't have any effect in my scene.
No errors in the console - I expect that I am missing a step but can not figure it out yet.
//
// create drawing canvas 2D and related textures
//
this._canvas = document.createElement('canvas');
this._canvas.width = maxSliceDimensionsX;
this._canvas.height = maxSliceDimensionsY;
this._canvasContext = this._canvas.getContext('2d')!;
this._canvasContext.fillStyle = 'rgba(0, 0, 0, 0)';
this._canvasContext.fillRect(0, 0, window.innerWidth, window.innerHeight);
this._texture = new Texture(this._canvas);
this._texture.magFilter = NearestFilter;
this._texture.minFilter = NearestFilter;
// const data = new Uint8Array(maxSliceDimensionsX * maxSliceDimensionsY * 4);
// this._texture2 = new THREE.DataTexture(data, maxSliceDimensionsX, maxSliceDimensionsY, THREE.RGBAFormat);
this._brushMaterial = new MeshBasicMaterial({
map: this._texture,
side: DoubleSide,
transparent: true,
});
...
// in the render loop
renderLoop() {
...
// grab random values as a test
const imageData = this._canvasContext.getImageData(100, 100, 200, 200);
const uint8Array = new Uint8Array(imageData.data.buffer);
// activate texture?
renderer.setTexture2D(this._texture, 0 );
// update subtexture
const context = renderer.getContext();
context.texSubImage2D( context.TEXTURE_2D, 0, 0, 0, 200, 200, context.RGBA, context.UNSIGNED_BYTE, uint8Array);
// updating the whole texture works as expected but is slow
// this._texture.needsUpdate = true;
// Render new scene
renderer.render(scene, this._camera);
}
Thanks,
Nicolas
Right after creating the texture, we must notify three that the texture needs to be uploaded to the GPU.
We just have to set it 1 time at creation time and not in the render loop.
this._texture = new Texture(this._canvas);
this._texture.magFilter = NearestFilter;
this._texture.minFilter = NearestFilter;
// MUST BE ADDED
this._texture.needsUpdate = true;

Three.js - Create new mesh from certain faces/vertices of another mesh

I´ve been several days struggling with a particular Three.js issue, and I cannot find any way to do it. This is my case:
1) I have a floating mesh, formed by several triangled faces. This mesh is created from the geometry returned by a loader, after obtaining its vertices and faces using getAttribute('position'): How to smooth mesh triangles in STL loaded BufferGeometry
2) What I want to do now is to "project" the bottom face agains the floor.
3) Later, with this new face added, create the resulting mesh of filling the space between the 3 vertices of both faces.
I already have troubles in step 2... To create a new face I´m supossed to have its 3 vertices already added to geometry.vertices. I did it, cloning the original face vertices. I use geometry.vertices.push() results to know their new indexes, and later I use that indexes (-1) to finally create the new face. But its shape is weird, also the positions and the size. I think I´m not getting the world/scene/vector position equivalence theory right :P
I tried applying this, with no luck:
How to get the absolute position of a vertex in three.js?
Converting World coordinates to Screen coordinates in Three.js using Projection
http://barkofthebyte.azurewebsites.net/post/2014/05/05/three-js-projecting-mouse-clicks-to-a-3d-scene-how-to-do-it-and-how-it-works
I discovered that if I directly clone the full original face and simply add it to the mesh, the face is added but in the same position, so I cannot then change its vertices to place it on the floor (or at least without modifying the original face vertices!). I mean, I can change their x, y, z properties, but they are in a very small measure that doesn´t match the original mesh dimensions.
Could someone help me get this concept right?
EDIT: source code
// Create geometry
var geo = new THREE.Geometry();
var geofaces = [];
var geovertices = [];
original_geometry.updateMatrixWorld();
for(var index in original_geometry.faces){
// Get original face vertexNormals to know its 3 vertices
var face = original_geometry[index];
var vertexNormals = face.vertexNormals;
// Create 3 new vertices, add it to the array and then create a new face using the vertices indexes
var vertexIndexes = [null, null, null];
for (var i = 0, l = vertexNormals.length; i < l; i++) {
var vectorClone = vertexNormals[i].clone();
vectorClone.applyMatrix4( original_geometry.matrixWorld );
//vectorClone.unproject(camera); // JUST TESTING
//vectorClone.normalize(); // JUST TESTING
var vector = new THREE.Vector3(vectorClone.x, vectorClone.z, vectorClone.y)
//vector.normalize(); // JUST TESTING
//vector.project(camera); // JUST TESTING
//vector.unproject(camera); // JUST TESTING
vertexIndexes[i] = geovertices.push( vector ) - 1;
}
var newFace = new THREE.Face3( vertexIndexes[0], vertexIndexes[1], vertexIndexes[2] );
geofaces.push(newFace);
}
// Assign filled arrays to the geometry
geo.faces = geofaces;
geo.vertices = geovertices;
geo.mergeVertices();
geo.computeVertexNormals();
geo.computeFaceNormals();
// Create a new mesh with resulting geometry and add it to scene (in this case, to the original mesh to keep the positions)
new_mesh = new THREE.Mesh( geo, new THREE.MeshFaceMaterial(material) ); // material is defined elsewhere
new_mesh.position.set(0, -100, 0);
original_mesh.add( new_mesh );
I created a fully operational JSFiddle with the case to try things and see the problem more clear. With this STL (smaller than my local example) I cannot even see the badly cloned faces added to the scene.. Maybe they are too small or out of focus.
Take a look to the calculateProjectedMesh() function, here is where I tried to clone and place the bottom faces (already detected because they have a different materialIndex):
JSFiddle: https://jsfiddle.net/tc39sgo1/
var container;
var stlPath = 'https://dl.dropboxusercontent.com/s/p1xp4lhy4wxmf19/Handle_Tab_floating.STL';
var camera, controls, scene, renderer, model;
var mouseX = 0,
mouseY = 0;
var test = true;
var meshPlane = null, meshStl = null, meshCube = null, meshHang = null;
var windowHalfX = window.innerWidth / 2;
var windowHalfY = window.innerHeight / 2;
/*THREE.FrontSide = 0;
THREE.BackSide = 1;
THREE.DoubleSide = 2;*/
var materials = [];
materials.push( new THREE.MeshPhongMaterial({color : 0x00FF00, side:0, shading: THREE.FlatShading, transparent: true, opacity: 0.9, overdraw : true, wireframe: false}) );
materials.push( new THREE.MeshPhongMaterial({color : 0xFF0000, transparent: true, opacity: 0.8, side:0, shading: THREE.FlatShading, overdraw : true, metal: false, wireframe: false}) );
materials.push( new THREE.MeshPhongMaterial({color : 0x0000FF, side:2, shading: THREE.FlatShading, overdraw : true, metal: false, wireframe: false}) );
var lineMaterial = new THREE.LineBasicMaterial({ color: 0x0000ff, transparent: true, opacity: 0.05 });
init();
animate();
function webglAvailable() {
try {
var canvas = document.createElement('canvas');
return !!(window.WebGLRenderingContext && (
canvas.getContext('webgl') || canvas.getContext('experimental-webgl')));
} catch (e) {
return false;
}
}
function init() {
container = document.createElement('div');
document.body.appendChild(container);
camera = new THREE.PerspectiveCamera(25, window.innerWidth / window.innerHeight, 0.1, 100000000);
camera.position.x = 1500;
camera.position.z = -2000;
camera.position.y = 1000;
controls = new THREE.OrbitControls(camera);
// scene
scene = new THREE.Scene();
var ambient = new THREE.AmbientLight(0x101030); //0x101030
scene.add(ambient);
var directionalLight = new THREE.DirectionalLight(0xffffff, 2);
directionalLight.position.set(0, 3, 0).normalize();
scene.add(directionalLight);
var directionalLight = new THREE.DirectionalLight(0xffffff, 2);
directionalLight.position.set(0, 1, -2).normalize();
scene.add(directionalLight);
if (webglAvailable()) {
renderer = new THREE.WebGLRenderer();
} else {
renderer = new THREE.CanvasRenderer();
}
renderer.setClearColor( 0xCDCDCD, 1 );
// renderer = new THREE.WebGLRenderer();
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
container.appendChild(renderer.domElement);
document.addEventListener('mousemove', onDocumentMouseMove, false);
window.addEventListener('resize', onWindowResize, false);
createPlane(500, 500);
createCube(500);
loadStl();
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function onDocumentMouseMove(event) {
mouseX = (event.clientX - windowHalfX) / 2;
mouseY = (event.clientY - windowHalfY) / 2;
}
function animate() {
requestAnimationFrame(animate);
render();
}
function render() {
renderer.render(scene, camera);
}
function createPlane(width, height) {
var planegeometry = new THREE.PlaneBufferGeometry(width, height, 0, 0);
var material = new THREE.MeshLambertMaterial({
color: 0xFFFFFF,
side: THREE.DoubleSide
});
planegeometry.computeBoundingBox();
planegeometry.center();
meshPlane = new THREE.Mesh(planegeometry, material);
meshPlane.rotation.x = 90 * (Math.PI/180);
//meshPlane.position.y = -height/2;
scene.add(meshPlane);
}
function createCube(size) {
var geometry = new THREE.BoxGeometry( size, size, size );
geometry.computeFaceNormals();
geometry.mergeVertices();
geometry.computeVertexNormals();
geometry.center();
var material = new THREE.MeshPhongMaterial({
color: 0xFF0000,
opacity: 0.04,
transparent: true,
wireframe: true,
side: THREE.DoubleSide
});
meshCube = new THREE.Mesh(geometry, material);
meshCube.position.y = size/2;
scene.add(meshCube);
}
function loadStl() {
var loader = new THREE.STLLoader();
loader.load( stlPath, function ( geometry ) {
// Convert BufferGeometry to Geometry
var geometry = new THREE.Geometry().fromBufferGeometry( geometry );
geometry.computeBoundingBox();
geometry.computeVertexNormals();
geometry.center();
var faces = geometry.faces;
for(var index in faces){
var face = faces[index];
var faceNormal = face.normal;
var axis = new THREE.Vector3(0,-1,0);
var angle = Math.acos(axis.dot(faceNormal));
var angleReal = (angle / (Math.PI/180));
if(angleReal <= 70){
face.materialIndex = 1;
}
else{
face.materialIndex = 0;
}
}
geometry.computeFaceNormals();
geometry.computeVertexNormals();
meshStl = new THREE.Mesh(geometry, new THREE.MeshFaceMaterial(materials));
meshStl.position.x = 0;
meshStl.position.y = 400;
scene.add( meshStl );
// Once loaded, calculate projections mesh
calculateProjectedMesh();
});
}
function calculateProjectedMesh(){
var geometry = meshStl.geometry;
var faces = geometry.faces;
var vertices = geometry.vertices;
var geometry_projected = new THREE.Geometry();
var faces_projected = [];
var vertices_projected = [];
meshStl.updateMatrixWorld();
for(var index in faces){
var face = faces[index];
// This are the faces
if(face.materialIndex == 1){
var vertexIndexes = [face.a, face.b, face.c];
for (var i = 0, l = vertexIndexes.length; i < l; i++) {
var relatedVertice = vertices[ vertexIndexes[i] ];
var vectorClone = relatedVertice.clone();
console.warn(vectorClone);
vectorClone.applyMatrix4( meshStl.matrixWorld );
////////////////////////////////////////////////////////////////
// TEST: draw line
var geometry = new THREE.Geometry();
geometry.vertices.push(new THREE.Vector3(vectorClone.x, vectorClone.y, vectorClone.z));
//geometry.vertices.push(new THREE.Vector3(vectorClone.x, vectorClone.y, vectorClone.z));
geometry.vertices.push(new THREE.Vector3(vectorClone.x, meshPlane.position.y, vectorClone.z));
var line = new THREE.Line(geometry, lineMaterial);
scene.add(line);
console.log("line added");
////////////////////////////////////////////////////////////////
vectorClone.y = 0;
var vector = new THREE.Vector3(vectorClone.x, vectorClone.y, vectorClone.z);
vertexIndexes[i] = vertices_projected.push( vector ) - 1;
}
var newFace = new THREE.Face3( vertexIndexes[0], vertexIndexes[1], vertexIndexes[2] );
newFace.materialIndex = 2;
faces_projected.push(newFace);
}
}
geometry_projected.faces = faces_projected;
geometry_projected.vertices = vertices_projected;
geometry_projected.mergeVertices();
console.info(geometry_projected);
meshHang = new THREE.Mesh(geometry_projected, new THREE.MeshFaceMaterial(materials));
var newY = -(2 * meshStl.position.y) + 0;
var newY = -meshStl.position.y;
meshHang.position.set(0, newY, 0);
meshStl.add( meshHang );
}
EDIT: Finally!! I got it! To clone the original faces I must access their 3 original vertices using "a", "b" and "c" properties, which are indexes referencing Vector3 instances in the "vertices" array of the original geometry.
I cloned the 3 vertices flatting the Z position to zero, use their new indexes to create the new face and add it to the projection mesh (in blue).
I´m also adding lines as a visual union between both faces. Now I´m ready for step 3, but I think this is complex enough to close this question.
Thanks for the updateMatrixWorld clue! It was vital to achieve my goal ;)
try this
original_geometry.updateMatrixWorld();
var vertexIndexes = [null, null, null];
for (var i = 0, l = vertexNormals.length; i < l; i++) {
var position = original_geometry.geometry.vertices[i].clone();
position.applyMatrix4( original_geometry.matrixWorld );
var vector = new THREE.Vector3(position.x, position.y, position.z)
vertexIndexes[i] = geovertices.push( vector ) - 1;
}

three.js How to render a simple white dot/point/pixel

I'm using THREE.WebGLRenderer and I would like to draw a few same-sized white dots at specific positions in 3D space.
Should I use sprites, calculate the 2D screen coordinates and use SpriteMaterial.useScreenCoordinate?
Should I simply recalculate the size of the sprites using the distance of them to the camera?
Can I use SpriteMaterial.scaleByViewport or SpriteMaterial.sizeAttenuation? Is there any documentation for this?
Is there something like GL_POINTS? It would be nice to just define 1 vertex and get a colored pixel at that position. Should I experiment with PointCloud?
Thanks for any hints!
Edit: All points should have the same size on the screen.
Using .sizeAttenuation and a single-vertex PointCloud works, but it feels a bit… overengineered:
var dotGeometry = new THREE.Geometry();
dotGeometry.vertices.push(new THREE.Vector3( 0, 0, 0));
var dotMaterial = new THREE.PointsMaterial( { size: 1, sizeAttenuation: false } );
var dot = new THREE.Points( dotGeometry, dotMaterial );
scene.add( dot );
For r125
The excerpt is taken from threejs official example. After some modification here how made it to work.
var dotGeometry = new BufferGeometry();
dotGeometry.setAttribute( 'position', new Float32BufferAttribute( [0,0,0], 3 ) );
var dotMaterial = new PointsMaterial( { size: 0.1, color: 0x00ff00 } );
var dot = new Points( dotGeometry, dotMaterial );
scene.add( dot );
Yet another update: The interface for attribute has changed somewhat:
For r139
const dotGeometry = new THREE.BufferGeometry();
dotGeometry.setAttribute('position', new THREE.BufferAttribute(new Float32Array([0,0,0]), 3));
const dotMaterial = new THREE.PointsMaterial({ size: 0.1, color: 0xff0000 });
const dot = new THREE.Points(dotGeometry, dotMaterial);
scene.add(dot);

Three.js shadow corresponds to bounding box, not actual model shape

I'm trying to get a correct-looking shadow for a dinosaur model (Three.js JSON format).
The shadow displays, but it's rectangular, as though it thinks the dinosaur model is just a simple cuboid (as screengrabbed below).
How can I generate a shadow that corresponds to the actual shape of the dinosaur?
I've checked in Blender that there's no containing box visible in the OBJ model, from which I created the JSON model file by using the Three.js OBJ to JSON converter.
Here's some snippets of the relevant pieces of code:
/* Dinosaur! */
var loader = new THREE.JSONLoader();
var filePath = 'models/trex/trex.js';
loader.load(filePath, function(geometry, materials) {
mesh = new THREE.Mesh( geometry,
new THREE.MeshFaceMaterial( materials ) );
mesh.scale.set(1000, 1000, 1000);
mesh.position.set( 0, -75, 0 );
mesh.rotation.y = Math.PI;
mesh.castShadow = true;
scene.add( mesh );
});
...
/* Lights */
var ambientLight = new THREE.AmbientLight( 0xFFFFFF );
scene.add( ambientLight );
var directionalLight = new THREE.DirectionalLight( 0xeeeeff, 0.5 );
directionalLight.position.set(0, 0, 1);
scene.add( directionalLight );
var spotlight = new THREE.SpotLight(0xFFFFFF, 0.2, 2000);
spotlight.position.set( 50, 100, 0 );
spotlight.target.position.set( 0, 0, 0 );
spotlight.castShadow = true;
scene.add( spotlight );
...
/* Renderer */
renderer = new THREE.WebGLRenderer();
renderer.setClearColor(0xffffff);
renderer.setSize(renderWidth, renderHeight);
renderer.shadowMapEnabled = true;
renderer.shadowMapSoft = true;
container.appendChild(renderer.domElement);
...
/* Terrain */
var img = new Image();
img.onload = function() {
heightData = buildHeightData(img);
var plane = new THREE.PlaneGeometry( 100, 100, HEIGHT_MAP_SIZE-1, HEIGHT_MAP_SIZE-1 );
var l = plane.vertices.length;
for( var i=0; i < l; i++ ) {
// We change z because by default the plane will be placed vertically.
// We rotate it afterwards (so the effect on z will end up being the
// effect on y).
plane.vertices[i].z = heightData[i] * 10;
}
terrainMesh = buildMesh(
{
geometry: plane,
scale: 100,
x: 0,
y: -370,
z: -1050,
material: terrainMaterial
} );
terrainMesh.rotation.x = -Math.PI / 2;
terrainMesh.receiveShadow = true;
scene.add( terrainMesh );
};
img.src = 'img/heightmap.jpg';
p.s. I'm using Three.js v66.
Oops. I had jumped to an incorrect conclusion. It was just because the spotlight was not high enough (see new screengrab, with shadowCameraVisible = true).

Concentric circles texture on RingGeometry

I am trying to create a flat ring in three.js with a concentric circles texture, like Saturn's rings. I cannot manage to do anything but lines that radiate from the center (like a bicycle wheel), no matter what I put in the image. It seems that textures are applied to RingGeometry in a very different fashion than CircleGeometry.
I could easily apply a concentric circles texture to a CircleGeometry, but a ring (with a hole in the middle) is really what I need. Is anybody aware of a way to have textures on rings do something else than radiate?
I did not find a way in Three.js documentation, nor on the web, to do what I want, as it seems that rings are seldom used by anybody...
Thank you
Go here http://jsfiddle.net/theo/VsWb9/ and replace
geometry = new THREE.CubeGeometry(200, 200, 200);
material = new THREE.MeshNormalMaterial();
mesh = new THREE.Mesh(geometry, material);
with
geometry = new THREE.TorusGeometry( 100, .5 , 50 ,50);
material = new THREE.MeshNormalMaterial();
mesh = new THREE.Mesh( geometry, material );
If you want to change the ring color to say black for instance
change
material = new THREE.MeshNormalMaterial();
to
material = new THREE.MeshBasicMaterial({color:0x000});
Stick any other material changes in that array input argument to the constructor
function THREE.MeshBasicMaterial({arguments here})
I found this for making the geometry. It creates a disk of theataSeegmens triangles
this.RingGeometry = function ( innerRadius, outerRadius, thetaSegments) {
THREE.Geometry.call( this )
innerRadius = innerRadius || 0
outerRadius = outerRadius || 50
thetaSegments = thetaSegments || 8
innerRadius*=Obj.Size*100;
outerRadius*=Obj.Size*100;
var normal = new THREE.Vector3( 0, 0, 1 )
for(var i = 0; i < thetaSegments; i++ ){
var angleLo = (i / thetaSegments) *Math.PI*2
var angleHi = ((i+1) / thetaSegments) *Math.PI*2
var vertex1 = new THREE.Vector3(innerRadius * Math.cos(angleLo), innerRadius * Math.sin(angleLo), 0);
var vertex2 = new THREE.Vector3(outerRadius * Math.cos(angleLo), outerRadius * Math.sin(angleLo), 0);
var vertex3 = new THREE.Vector3(innerRadius * Math.cos(angleHi), innerRadius * Math.sin(angleHi), 0);
var vertex4 = new THREE.Vector3(outerRadius * Math.cos(angleHi), outerRadius * Math.sin(angleHi), 0);
this.vertices.push( vertex1 );
this.vertices.push( vertex2 );
this.vertices.push( vertex3 );
this.vertices.push( vertex4 );
var vertexIdx = i * 4;
// Create the first triangle
var face = new THREE.Face3(vertexIdx + 0, vertexIdx + 1, vertexIdx + 2, normal);
var uvs = []
var uv = new THREE.Vector2(0, 0)
uvs.push(uv)
var uv = new THREE.Vector2(1, 0)
uvs.push(uv)
var uv = new THREE.Vector2(0, 1)
uvs.push(uv)
this.faces.push(face);
this.faceVertexUvs[0].push(uvs);
// Create the second triangle
var face = new THREE.Face3(vertexIdx + 2, vertexIdx + 1, vertexIdx + 3, normal);
var uvs = []
var uv = new THREE.Vector2(0, 1)
uvs.push(uv)
var uv = new THREE.Vector2(1, 0)
uvs.push(uv)
var uv = new THREE.Vector2(1, 1)
uvs.push(uv)
this.faces.push(face);
this.faceVertexUvs[0].push(uvs);
}
//this.computeCentroids();
//this.computeFaceNormals();
this.boundingSphere = new THREE.Sphere( new THREE.Vector3(), outerRadius );
};
this.RingGeometry.prototype = Object.create( THREE.Geometry.prototype );
The above line is important to get it to work.
Here is a sugestion how to set the ring material.assuming that you have two pictures (just a sqare section) that can be used for alphamap and for
the actual ring.
var ringMaterial = new THREE.MeshPhongMaterial(
{
map: SaturnRingColor,
alphaMap:SaturnRingPattern,
color: 0xffffff,
specular: 0x555555,
shininess: 3,
emissive:10,
side: THREE.DoubleSide,
castshadow:true,
transparent : true,
opacity : 0.9,
} );
this.ringMesh = new THREE.Mesh( this.RingGeometry , RingMaterial );

Resources