ThreeJS – way to show one object by intersecting another - three.js

I try to write a ThreeJS interaction where the user hovers a sphere and a connected text object will show up. The problem is to show the connected text object with the intersected sphere so every sphere shows its own text.
For now I only could show the same text object for each intersected sphere. I think there's some code in the raycast section missing that picks the right text object.
//create textObjects and add to scene
var selectTitles = [];
for (var i = 0; i < numSpheres; i++) {
var title = 'Title '+i;
var textGeom = new THREE.TextGeometry( title, {size: 0.5,height: 0});
var textMaterial = new THREE.MeshBasicMaterial({color:0x334455,transparent: true, opacity: 0});
var titles = new THREE.Mesh( textGeom, textMaterial );
group.add( titles );
titles = selectTitles[i];
}
//onMouseOver
if (intersects[0].object != INTERSECTED) {
INTERSECTED = intersects[0].object;
// here 'titles' doesn't pick different text objects because of the missing 'intersected' connection
new TWEEN.Tween(titles.material).to({opacity:1},350)
.easing(TWEEN.Easing.Bounce.EaseOut).start();
}

I advise you to stick to the same order, much like in the examples on the site threejs.org:
// global variables
var camera;
var scene;
...
window.onload = function() {
init();
render(); //final output
}
function init() {
...
}
function render() {
...
}
I took the liberty to alter your code. Here is the result. Code here.

Related

Three.js - Can we intersect with material ID of a model using raycaster?

I'have been trying to intersect only one material among 3 materials of the loaded model onclick using their ID's, however, the control continues to the entire model. Let me know if it is possible to intersec on basis of material ID's
var intersects = raycaster.intersectObjects( scene.children );
for ( var i = 0; i < intersects.length; i++ ) {
object.scene.traverse(function(child) {
if ( child instanceof THREE.Mesh ) {
if (child.material.name == "heap") {
child.material.color = new THREE.Color( 0x00ff00 );
}
}
})
}
You can update the material of a raycast object by means of accessing the material property on the object of an intersection result.
Something to keep in mind is that, when you do update the properties of a material object, you need to set needsUpdate = true so threejs knows that the material needs updating internally.
Consider the following code that achieves what you're after:
// If only interested in one intersection, you can use .intersectObject()
var intersection = raycaster.intersectObject( scene.children );
// If intersection found, update material in some way
if(intersection) {
// Extract object and material from intersection
var object = intersection.object;
var material = object.material;
/*
Manipulate your material as you need to
material.color = new THREE.Color( 1, 0, 0 );
*/
// Tell threejs that the material was changed and
// needs to be updated internally
material.needsUpdate = true;
}

How to center a THREE.Group based on the width of its children?

I am using Three.js (r86) to render a group of spheres. I'm doing this by calling a createSphereGroup function from an external library:
// External library code.
function createSphereGroup(sphereCount) [
var group = new THREE.Group();
for (var i = 0; i < sphereCount; i++) {
var geometry = new THREE.SphereGeometry(50);
var material = new THREE.MeshPhongMaterial({ color: 0xFF0000 });
var sphere = new THREE.Mesh(geometry, material);
sphere.position.x = i * 150;
group.add(sphere);
}
return group;
}
(Assume that the external library is opaque and I can't modify any code in it.)
// My code.
var group = createSphereGroup(5);
scene.add(group);
...which looks like this:
As you can see, the group of spheres is off-center. I would like the group to be centered, with the 3rd sphere to be at the point where the two lines intersect (x=0).
So is there some way that I can center the group? Maybe by computing the width of the group and then positioning it at x=-width/2? I know you can call computeBoundingBox on a geometry to determine its width, but a THREE.Group doesn't have a geometry, so I'm not sure how to do this...
If you want to center an object (or group) and its children, you can do so by setting the object's position like so:
new THREE.Box3().setFromObject( object ).getCenter( object.position ).multiplyScalar( - 1 );
scene.add( object );
three.js r.92
If all the objects in the group are rather the same size, as in your example, you can just take the average of the children's position :
function computeGroupCenter(count) {
var center = new THREE.Vector3();
var children = group.children;
var count = children.length;
for (var i = 0; i < count; i++) {
center.add(children[i].position);
}
center.divideScalar(count);
return center;
}
Another (more robust) way to do this is to create a bounding box containing the bounding boxes of every child of the group.
There are some important things to consider :
A THREE.Group can contain others THREE.Group, therefore, the algorithm should be recursive
Bounding boxes are computed in local space, however, the object may be translated with respect to its parent so we need to work in world space.
The group itself might be translated to! So we have to jump back from world space to the group's local space to have the center defined in the group's space.
You can achieve this easily with THREE.Object3D.traverse, THREE.BufferGeometry.computeBoundingBox and THREE.Box3.ApplyMatrix4 :
/**
* Compute the center of a THREE.Group by creating a bounding box
* containing every children's bounding box.
* #param {THREE.Group} group - the input group
* #param {THREE.Vector3=} optionalTarget - an optional output point
* #return {THREE.Vector3} the center of the group
*/
var computeGroupCenter = (function () {
var childBox = new THREE.Box3();
var groupBox = new THREE.Box3();
var invMatrixWorld = new THREE.Matrix4();
return function (group, optionalTarget) {
if (!optionalTarget) optionalTarget = new THREE.Vector3();
group.traverse(function (child) {
if (child instanceof THREE.Mesh) {
if (!child.geometry.boundingBox) {
child.geometry.computeBoundingBox();
childBox.copy(child.geometry.boundingBox);
child.updateMatrixWorld(true);
childBox.applyMatrix4(child.matrixWorld);
groupBox.min.min(childBox.min);
groupBox.max.max(childBox.max);
}
}
});
// All computations are in world space
// But the group might not be in world space
group.matrixWorld.getInverse(invMatrixWorld);
groupBox.applyMatrix4(invMatrixWorld);
groupBox.getCenter(optionalTarget);
return optionalTarget;
}
})();
Here's a fiddle.

Attach text above 3D Object

I'm wondering if it is possible to attach a text above a 3D Object?
If so, how would I do it?
So far I'm doing the following below to load a mesh with its material and lastly adding it to a THREE.Object3D(); and adding it to the scene. Works great without any problems.
Next step is I want to show a nice text above its this object that is always fixed and can be seen from every angle.
loader.load('assets/' + enemyUrl, function (geometry, materials) {
material = new THREE.MeshFaceMaterial( materials );
model = new THREE.SkinnedMesh( geometry, material );
var mats = model.material.materials;
for (var i = 0,length = mats.length; i < length; i++) {
var m = mats[i];
m.skinning = true;
}
ensureLoop(geometry.animations[0]);
function ensureLoop( tmp ) {
for ( var i = 0; i < tmp.hierarchy.length; i ++ ) {
var bone = tmp.hierarchy[ i ];
var first = bone.keys[ 0 ];
var last = bone.keys[ bone.keys.length - 1 ];
last.pos = first.pos;
last.rot = first.rot;
last.scl = first.scl;
}
}
model.scale.set(2.5,2.5,2.5);
// TODO: Randomize where to put it in the world
yawObject.position.y = spawnPosition.y;
yawObject.position.x = spawnPosition.x;
yawObject.position.z = spawnPosition.z;
yawObject.add(model);
scene.add(yawObject);
});
Something like this:
This is what my game looks like now:
sure its possible. you can create a canvas with text on it, which you use as a texture on a plane that always looks at the camera, you could also do it with a single particle, but im not quite sure how it would work since particle materials have a size parameter. something like:
var name = 'Rovdjuret';
var canvas = document.createElement('canvas');
var ctx = canvas.getContext("2d");
ctx.font="20px Georgia";
ctx.fillText(name,10,50);
var texture = new THREE.Texture(canvas);
texture.needsUpdate = true; //just to make sure it's all up to date.
var label = new THREE.Mesh(new THREE.PlaneGeometry, new THREE.MeshBasicMaterial({map:texture}));
and inside the render/animation loop you make all the labels look at the camera:
label.lookAt(camera.position);

Need to render textures uploaded by user in input files with Canvas renderer three.js

I've loaded a Blender model using the three.js library and want to allow the users to change the texture of some faces through an input field in a form. I don't have any problem when I use the WebGLRenderer, and it works fine in Chrome, but it doesn't work with the canvas renderer when the texture coming from the input is in data:image... format. Seems if I load a full path image from the server it works fine. Does anybody know if there's a way to load textures this way and render them with the canvasrenderer?
Thank you.
I add here the code after I set the camera, lights and detect it the browswer detects webgl or not to use the WebGLRenderer or the CanvasRenderer.
First I load the model from blender:
var loader = new THREE.JSONLoader();
loader.load('assets/models/mimaquina9.js', function (geometry, mat) {
//I set the overdraw property to 1 for each material like i show here for 16
mat[16].overdraw = 1;
mesh = new THREE.Mesh(geometry, new THREE.MeshFaceMaterial(mat) );
mesh.scale.x = 5;
mesh.scale.y = 5;
mesh.scale.z = 5;
scene.add(mesh);
}, 'assets/images');
render();
//To render, I try to make an animation in case WebGL is available and just render one frame in case of using the canvas renderer.
function render() {
if(webgl){
if (mesh) {
mesh.rotation.y += 0.02;
}
// render using requestAnimationFrame
requestAnimationFrame(render);
webGLRenderer.render(scene, camera);
}
else if(canvas){
camera.position.x = 30;
camera.position.y = 20;
camera.position.z = 40;
camera.lookAt(new THREE.Vector3(0, 10, 0));
setTimeout(function (){
//something you want delayed
webGLRenderer.render(scene, camera);
}, 1000);
}
}
$('#datafile').change(function(e)
{
e.preventDefault();
var f = e.target.files[0];
if(f && window.FileReader)
{
var reader = new FileReader();
reader.onload = function(evt) {
console.log(evt);
mesh.material.materials[16].map = THREE.ImageUtils.loadTexture(evt.target.result);
if(canvas && !webgl){
//I read that might be a problem of using Lambert materials, so I tried this commented line without success
//mesh.material.materials[16] = new THREE.MeshBasicMaterial( { map: THREE.ImageUtils.loadTexture(evt.target.result)});
//If I uncomment the next line, it displays the texture fine when rendering after.
//mesh.material.materials[16].map = THREE.ImageUtils.loadTexture("assets/images/foto.jpg");
render();
}
}
reader.readAsDataURL(f);
}
});
Thanks once more.
evt.target.result is a DataURL so you should assign that to a image.src. Something like this should work:
var image = document.createElement( 'img' );
image.src = evt.target.result;
mesh.material.materials[16].map = new THREE.Texture( image );

THREEjs cant get a sphree to render to screen

hii i am trying to get a sphree to render into the screen. here is my js file.
function Earth()
{
this.getEarth = init();
function init()
{
var map = {map:THREE.ImageUtils.loadTexture("images/earth_surface_2048.jpg")};
var material = new THREE.MeshBasicMaterial(map);
var geometry = new THREE.SphereGeometry(1,32,32);
return new THREE.Mesh(geometry, material);
}
function update()
{
getEarth.rotation.x += .01;
}
}
and here is the script that is in the html file
threejs is included to the page.
<script>
var renderer = null;
var scene = null;
var camera = null;
var mesh = null;
var earth = null;
$(document).ready(
function() {
var container = document.getElementById("container");
renderer = new THREE.WebGLRenderer({ antialias: true } );
renderer.setSize(container.offsetWidth,container.offsetHeight);
container.appendChild(renderer.domElement)
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera( 45,
container.offsetWidth / container.offsetHeight, 1, 4000 );
earth = new Earth();
scene.add(camera);
scene.add(earth.getEarth);
renderer.render( scene, camera );
}
);
</script>
i get no erros from the debugger, so any ideas ?
earth.getEarth is probably null or a function. I remember scene.add() silently ignoring stuff it can't take, like undefined values and so on.
How about changing var getEarth = init(); to this.getEarth = init(); so that code outside of your class scope will see that variable in your earth object. Althought I would advice against using a method-like name for a variable containing an object.

Resources