Three.js creating geometry and mesh freezes animation - javascript

I am trying to create a scrolling text animation, and add more text to the animation while it is running.
This is my function for creating the text geometry and mesh:
function createText(){
textGeo = new THREE.TextGeometry( "Some new text that as quite long", {
size: 20,
height: height,
font: font
});
textMesh1 = new THREE.Mesh( textGeo, new THREE.MeshBasicMaterial( { color: 0x112358 } ) );
textMesh1.position.x = (window.innerWidth / 2) + 100;
textMesh1.position.y = ((window.innerHeight / 2) * -1) + 40;
textMesh1.position.z = 0;
textMesh1.rotation.x = 0;
textMesh1.rotation.y = 0;
group.add( textMesh1 );
}
And this is my animate function:
function animate() {
var fistBox = new THREE.Box3();
requestAnimationFrame( animate );
for(i = 0; i < group.children.length; i++){
group.children[i].position.x -= 2;
}
fistBox.setFromObject(group.children[0]);
if(group.children[0].position.x < ((window.innerWidth / 2) * -1) - fistBox.size().x ){
scene.remove( group.children[0] );
}
render();
}
Basically the animation scrolls all the children of the group, and when the child leaves the screen it is removed.
The problem is that when I call the function that creates the text geometry and mesh (even without adding it to the group), the scroll animation freezes for a few frames.
I have looked at Web Workers, to try and "multithread" the create function, but it cannot pass back the mesh so I can't use that method to resolve the issue.
Any suggestions on how to create the text geometry and mesh, without effecting the animation, would be greatly appreciated! TIA!

You could split your text into chunks (e.g. words are mabye letters) and distribute the creation of word meshes across frames. Something like
function TextBuilder( text, opts, parent ) {
this.words = text.split( /\s/g );
this.index = 0;
this.step = function () {
if ( this.index >= this.words.length ) return;
var word = this.words[ this.index ];
++this.index;
var geo = new THREE.TextGeometry( word, opts );
var mesh = new THREE.Mesh( geo, opts.material );
// need to position mesh according to word length here
parent.add( mesh );
}
}
then create a textbuilder and call textbuilder.step() in animate. The positioning might be an issue though, unless your font is monospace. Otherwise you'll probably have to dig further into FontUtils to see how spacing is done there, and somehow apply that to the textbuilder.

Related

Three.js: Strategies to progressively increase resolution of texture

I'm working on a Three.js chart that basically represents a bunch of images in a 2D plane.
Right now the individual images are each 32px by 32px segments of larger 2048px by 2048px image atlas files. I want to increase the size of those individual images when users zoom in to particular regions of the scene. For example, if users start to zoom in on the images in the far right region of the space, I plan to update the 32px by 32px individual images in that region with 64px by 64px images with the same content (to show more detail).
My question is: what's the Three.js way to accomplish this goal?
My flat-footed plan is to load the higher-resolution assets, map them to the proper geometry coordinates, then simply delete the old mesh with 32px subimages and add the new mesh with 64px subimages. I originally thought I could just update the texture/material for an extant geometry, but I've read that one shouldn't use textures larger than 2048px by 2048px, and a geometry with n points won't allow me to continually increase the fidelity of the images in that geometry without surpassing that maximum texture size.
I would be very grateful for any insight Three.js veterans can offer on how they would approach this task!
Full code:
/**
* Globals
**/
// Identify data endpoint
var dataUrl = 'https://s3.amazonaws.com/duhaime/blog/tsne-webgl/data/';
// Create global stores for image and atlas sizes
var image, atlas;
// Create a store for image position information
var imagePositions = null;
// Create a store for the load progress. Data structure:
// {atlas0: percentLoaded, atlas1: percentLoaded}
var loadProgress = {};
// Create a store for the image atlas materials. Data structure:
// {subImageSize: {atlas0: material, atlas1: material}}
var materials = {32: {}, 64: {}};
// Create a store for meshes
var meshes = [];
/**
* Create Scene
**/
// Create the scene and a camera to view it
var scene = new THREE.Scene();
/**
* Camera
**/
// Specify the portion of the scene visiable at any time (in degrees)
var fieldOfView = 75;
// Specify the camera's aspect ratio
var aspectRatio = window.innerWidth / window.innerHeight;
/*
Specify the near and far clipping planes. Only objects
between those planes will be rendered in the scene
(these values help control the number of items rendered
at any given time)
*/
var nearPlane = 100;
var farPlane = 50000;
// Use the values specified above to create a camera
var camera = new THREE.PerspectiveCamera(
fieldOfView, aspectRatio, nearPlane, farPlane
);
// Finally, set the camera's position
camera.position.z = 12000;
camera.position.y = -2000;
/**
* Lights
**/
// Add a point light with #fff color, .7 intensity, and 0 distance
var light = new THREE.PointLight( 0xffffff, 1, 0 );
// Specify the light's position
light.position.set( 1, 1, 100 );
// Add the light to the scene
scene.add(light)
/**
* Renderer
**/
// Create the canvas with a renderer
var renderer = new THREE.WebGLRenderer({ antialias: true });
// Add support for retina displays
renderer.setPixelRatio( window.devicePixelRatio );
// Specify the size of the canvas
renderer.setSize( window.innerWidth, window.innerHeight );
// Add the canvas to the DOM
document.body.appendChild( renderer.domElement );
/**
* Load External Data
**/
// Load the image position JSON file
var fileLoader = new THREE.FileLoader();
var url = dataUrl + 'image_tsne_projections.json';
fileLoader.load(url, function(data) {
imagePositions = JSON.parse(data);
conditionallyBuildGeometries(32)
})
/**
* Load Atlas Textures
**/
// List of all textures to be loaded, the size of subimages
// in each, and the total count of atlas files for each size
var textureSets = {
32: { size: 32, count: 5 },
64: { size: 64, count: 20 }
}
// Create a texture loader so we can load our image files
var textureLoader = new AjaxTextureLoader();
function loadTextures(size, onProgress) {
setImageAndAtlasSize(size)
for (var i=0; i<textureSets[size].count; i++) {
var url = dataUrl + 'atlas_files/' + size + 'px/atlas-' + i + '.jpg';
if (onProgress) {
textureLoader.load(url,
handleTexture.bind(null, size, i),
onProgress.bind(null, size, i));
} else {
textureLoader.load(url, handleTexture.bind(null, size, i));
}
}
}
function handleProgress(size, idx, xhr) {
loadProgress[idx] = xhr.loaded / xhr.total;
var sum = 0;
Object.keys(loadProgress).forEach(function(k) { sum += loadProgress[k]; })
var progress = sum/textureSets[size].count;
var loader = document.querySelector('#loader');
progress < 1
? loader.innerHTML = parseInt(progress * 100) + '%'
: loader.style.display = 'none';
}
// Create a material from the new texture and call
// the geometry builder if all textures have loaded
function handleTexture(size, idx, texture) {
var material = new THREE.MeshBasicMaterial({ map: texture });
materials[size][idx] = material;
conditionallyBuildGeometries(size, idx)
}
// If the textures and the mapping from image idx to positional information
// are all loaded, create the geometries
function conditionallyBuildGeometries(size, idx) {
if (size === 32) {
var nLoaded = Object.keys(materials[size]).length;
var nRequired = textureSets[size].count;
if (nLoaded === nRequired && imagePositions) {
// Add the low-res textures and load the high-res textures
buildGeometry(size);
loadTextures(64)
}
} else {
// Add the new high-res texture to the scene
updateMesh(size, idx)
}
}
loadTextures(32, handleProgress)
/**
* Build Image Geometry
**/
// Iterate over the textures in the current texture set
// and for each, add a new mesh to the scene
function buildGeometry(size) {
for (var i=0; i<textureSets[size].count; i++) {
// Create one new geometry per set of 1024 images
var geometry = new THREE.Geometry();
geometry.faceVertexUvs[0] = [];
for (var j=0; j<atlas.cols*atlas.rows; j++) {
var coords = getCoords(i, j);
geometry = updateVertices(geometry, coords);
geometry = updateFaces(geometry);
geometry = updateFaceVertexUvs(geometry, j);
if ((j+1)%1024 === 0) {
var idx = (i*textureSets[size].count) + j;
buildMesh(geometry, materials[size][i], idx);
var geometry = new THREE.Geometry();
}
}
}
}
// Get the x, y, z coords for the subimage at index position j
// of atlas in index position i
function getCoords(i, j) {
var idx = (i * atlas.rows * atlas.cols) + j;
var coords = imagePositions[idx];
coords.x *= 2200;
coords.y *= 1200;
coords.z = (-200 + j/10);
return coords;
}
// Add one vertex for each corner of the image, using the
// following order: lower left, lower right, upper right, upper left
function updateVertices(geometry, coords) {
// Retrieve the x, y, z coords for this subimage
geometry.vertices.push(
new THREE.Vector3(
coords.x,
coords.y,
coords.z
),
new THREE.Vector3(
coords.x + image.shownWidth,
coords.y,
coords.z
),
new THREE.Vector3(
coords.x + image.shownWidth,
coords.y + image.shownHeight,
coords.z
),
new THREE.Vector3(
coords.x,
coords.y + image.shownHeight,
coords.z
)
);
return geometry;
}
// Create two new faces for a given subimage, then add those
// faces to the geometry
function updateFaces(geometry) {
// Add the first face (the lower-right triangle)
var faceOne = new THREE.Face3(
geometry.vertices.length-4,
geometry.vertices.length-3,
geometry.vertices.length-2
)
// Add the second face (the upper-left triangle)
var faceTwo = new THREE.Face3(
geometry.vertices.length-4,
geometry.vertices.length-2,
geometry.vertices.length-1
)
// Add those faces to the geometry
geometry.faces.push(faceOne, faceTwo);
return geometry;
}
function updateFaceVertexUvs(geometry, j) {
// Identify the relative width and height of the subimages
// within the image atlas
var relativeW = image.width / atlas.width;
var relativeH = image.height / atlas.height;
// Identify this subimage's offset in the x dimension
// An xOffset of 0 means the subimage starts flush with
// the left-hand edge of the atlas
var xOffset = (j % atlas.cols) * relativeW;
// Identify this subimage's offset in the y dimension
// A yOffset of 0 means the subimage starts flush with
// the bottom edge of the atlas
var yOffset = 1 - (Math.floor(j/atlas.cols) * relativeH) - relativeH;
// Determine the faceVertexUvs index position
var faceIdx = 2 * (j%1024);
// Use the xOffset and yOffset (and the knowledge that
// each row and column contains only 32 images) to specify
// the regions of the current image. Use .set() if the given
// faceVertex is already defined, due to a bug in updateVertexUvs:
// https://github.com/mrdoob/three.js/issues/7179
if (geometry.faceVertexUvs[0][faceIdx]) {
geometry.faceVertexUvs[0][faceIdx][0].set(xOffset, yOffset)
geometry.faceVertexUvs[0][faceIdx][1].set(xOffset + relativeW, yOffset)
geometry.faceVertexUvs[0][faceIdx][2].set(xOffset + relativeW, yOffset + relativeH)
} else {
geometry.faceVertexUvs[0][faceIdx] = [
new THREE.Vector2(xOffset, yOffset),
new THREE.Vector2(xOffset + relativeW, yOffset),
new THREE.Vector2(xOffset + relativeW, yOffset + relativeH)
]
}
// Map the region of the image described by the lower-left,
// upper-right, and upper-left vertices to `faceTwo`
if (geometry.faceVertexUvs[0][faceIdx+1]) {
geometry.faceVertexUvs[0][faceIdx+1][0].set(xOffset, yOffset)
geometry.faceVertexUvs[0][faceIdx+1][1].set(xOffset + relativeW, yOffset + relativeH)
geometry.faceVertexUvs[0][faceIdx+1][2].set(xOffset, yOffset + relativeH)
} else {
geometry.faceVertexUvs[0][faceIdx+1] = [
new THREE.Vector2(xOffset, yOffset),
new THREE.Vector2(xOffset + relativeW, yOffset + relativeH),
new THREE.Vector2(xOffset, yOffset + relativeH)
]
}
return geometry;
}
function buildMesh(geometry, material, idx) {
// Convert the geometry to a BuferGeometry for additional performance
//var geometry = new THREE.BufferGeometry().fromGeometry(geometry);
// Combine the image geometry and material into a mesh
var mesh = new THREE.Mesh(geometry, material);
// Store this image's index position in the mesh
mesh.userData.idx = idx;
// Set the position of the image mesh in the x,y,z dimensions
mesh.position.set(0,0,0)
// Add the image to the scene
scene.add(mesh);
// Save this mesh
meshes.push(mesh);
return mesh;
}
/**
* Update Geometries with new VertexUvs and materials
**/
function updateMesh(size, idx) {
// Update the appropriate material
meshes[idx].material = materials[size][idx];
meshes[idx].material.needsUpdate = true;
// Update the facevertexuvs
for (var j=0; j<atlas.cols*atlas.rows; j++) {
meshes[idx].geometry = updateFaceVertexUvs(meshes[idx].geometry, j);
}
meshes[idx].geometry.uvsNeedUpdate = true;
meshes[idx].geometry.verticesNeedUpdate = true;
}
/**
* Helpers
**/
function setImageAndAtlasSize(size) {
// Identify the subimage size in px (width/height) and the
// size of the image as it will be displayed in the map
image = { width: size, height: size, shownWidth: 64, shownHeight: 64 };
// Identify the total number of cols & rows in the image atlas
atlas = { width: 2048, height: 2048, cols: 2048/size, rows: 2048/size };
}
/**
* Add Controls
**/
var controls = new THREE.TrackballControls(camera, renderer.domElement);
/**
* Add Raycaster
**/
var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();
function onMouseMove( event ) {
// Calculate mouse position in normalized device coordinates
// (-1 to +1) for both components
mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
}
function onClick( event ) {
// Determine which image is selected (if any)
var selected = raycaster.intersectObjects( scene.children );
// Intersecting elements are ordered by their distance (increasing)
if (!selected) return;
if (selected.length) {
selected = selected[0];
console.log('clicked', selected.object.userData.idx)
}
}
window.addEventListener('mousemove', onMouseMove)
window.addEventListener('click', onClick)
/**
* Handle window resizes
**/
window.addEventListener('resize', function() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
controls.handleResize();
});
/**
* Render!
**/
// The main animation function that re-renders the scene each animation frame
function animate() {
requestAnimationFrame( animate );
raycaster.setFromCamera( mouse, camera );
renderer.render( scene, camera );
controls.update();
}
animate();
* {
margin: 0;
padding: 0;
background: #000;
color: #fff;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/88/three.min.js"></script>
<script src="https://s3.amazonaws.com/duhaime/blog/tsne-webgl/assets/js/texture-loader.js"></script>
<script src="https://s3.amazonaws.com/duhaime/blog/tsne-webgl/assets/js/trackball-controls.js"></script>
<div id='loader'>0%</div>
You can potentially use mutli-materials and geometry groups (or in your case, material indices).
This depends on your texture dimensions scaling 1::1. In other words, if your first resolution as dimensions 32x64, then double that resolution should have dimensions of 64x128. UVs are percentage-based, so moving from an image at one resolution to the same image at another resolution "just works".
At this point, you really only need to change the texture image source. But it sounds like you don't want to do that. So instead, we need to assign ALL of your textures to the same Mesh at once. Three.js makes this really easy...
var myMesh = new THREE.Mesh(myGeometry, [ material1, material2, material3 ]);
Notice that the material parameters is defined as an array. Each material has a different texture, which in your case are the different resolution images.
Now, debug into your Mesh. Under the goemetry property, you'll see an a property called faces, which is an array of Face3 objects. Each face has a property named materialIndex. This is the face's reference to the array of materials.
When you reach a point where you want to trigger a change (such as your camera being a certain distance from a mesh), you can change the material index, then trigger the mesh to change its material:
var distance = camera.position.distanceTo(myMesh.position);
if(distance < 50){
myMesh.faces.forEach(function(face){
face.materialIndex = 2;
});
}
else if(distance => 50 && distance < 100){
myMesh.faces.forEach(function(face){
face.materialIndex = 1;
});
}
else{
myMesh.faces.forEach(function(face){
face.materialIndex = 0;
});
}
myMesh.groupsNeedUpdate = true;
The last line (myMesh.groupsNeedUpdate = true;) tells the renderer that the material indices changed, so it will need to update the materials for the render.
Perhaps you could use THREE.LOD. It basically allows you to define different meshes for a range of distances. The meshes would be the same Quads, but you could change their materials to use different textures...
Here is the LOD example in the THREE.js web.
Hope it helps!!

Three JS Keep Label Size On Zoom

I'm working on a solar system in three.js and am curious if there is an easy way to make the labels for the planets I have below all show up the same size regardless of how far they are from the camera? I can't seem to find a solution to this. I figure you could calculate the distance from each label to the camera then come up with some sort of scaling factor based on that. Seems like there would be an easier way to accomplish this?
Thanks!
Updated with answer from prisoner849. Works excellent!
I figure you could calculate the distance from each label to the camera then come up with some sort of scaling factor based on that.
And it's very simple. Let's say, a THREE.Sprite() object (label) is a child of a THREE.Mesh() object (planet), then in your animation loop you need to do
var scaleVector = new THREE.Vector3();
var scaleFactor = 4;
var sprite = planet.children[0];
var scale = scaleVector.subVectors(planet.position, camera.position).length() / scaleFactor;
sprite.scale.set(scale, scale, 1);
I've made a very simple example of the Solar System, using this technique.
For the benefit of future visitors, the transform controls example does exactly this:
https://threejs.org/examples/misc_controls_transform.html
Here's how its done in the example code:
var factor;
if ( this.camera.isOrthographicCamera ) {
factor = ( this.camera.top - this.camera.bottom ) / this.camera.zoom;
} else {
factor = this.worldPosition.distanceTo( this.cameraPosition ) * Math.min( 1.9 * Math.tan( Math.PI * this.camera.fov / 360 ) / this.camera.zoom, 7 );
}
handle.scale.set( 1, 1, 1 ).multiplyScalar( factor * this.size / 7 );
Finally I found the answer to your question:
First, create a DOM Element:
<div class="element">Not Earth</div>
Then set CSS styles for it:
.element {position: absolute; top:0; left:0; color: white}
// |-------------------------------| |-----------|
// make the element on top of canvas is
// the canvas black, so text
// must be white
After that, create moveDom() function and run it every time you render the scene requestAnimationFrame()
geometry is the geometry of the mesh
cube is the mesh you want to create label
var moveDom = function(){
vector = geometry.vertices[0].clone();
vector.applyMatrix4(cube.matrix);
vector.project(camera);
vector.x = (vector.x * innerWidth/2) + innerWidth/2;
vector.y = -(vector.y * innerHeight/2) + innerHeight/2;
//Get the DOM element and apply transforms on it
document.querySelectorAll(".element")[0].style.webkitTransform = "translate("+vector.x+"px,"+vector.y+"px)";
document.querySelectorAll(".element")[0].style.transform = "translate("+vector.x+"px,"+vector.y+"px)";
};
You can create a for loop to set label for all the mesh in your scene.
Because this trick only set 2D position of DOM Element, the size of label is the same even if you zoom (the label is not part of three.js scene).
Full test case: https://jsfiddle.net/0L1rpayz/1/
var renderer, scene, camera, cube, vector, geometry;
var ww = window.innerWidth,
wh = window.innerHeight;
function init(){
renderer = new THREE.WebGLRenderer({canvas : document.getElementById('scene')});
renderer.setSize(ww,wh);
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(50,ww/wh, 0.1, 10000 );
camera.position.set(0,0,500);
scene.add(camera);
light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set( 0, 0, 500 );
scene.add(light);
//Vector use to get position of vertice
vector = new THREE.Vector3();
//Generate Not Earth
geometry = new THREE.BoxGeometry(50,50,50);
var material = new THREE.MeshLambertMaterial({color: 0x00ff00});
cube = new THREE.Mesh(geometry, material);
scene.add(cube);
//Render my scene
render();
}
var moveDom = function(){
vector = geometry.vertices[0].clone();
vector.applyMatrix4(cube.matrix);
vector.project(camera);
vector.x = (vector.x * ww/2) + ww/2;
vector.y = -(vector.y * wh/2) + wh/2;
//Get the DOM element and apply transforms on it
document.querySelectorAll(".element")[0].style.webkitTransform = "translate("+vector.x+"px,"+vector.y+"px)";
document.querySelectorAll(".element")[0].style.transform = "translate("+vector.x+"px,"+vector.y+"px)";
};
var counter = 0;
var render = function (a) {
requestAnimationFrame(render);
counter++;
//Move my cubes
cube.position.x = Math.cos((counter+1*150)/200)*(ww/6+1*80);
cube.position.y = Math.sin((counter+1*150)/200)*(70+1*80);
cube.rotation.x += .001*1+.002;
cube.rotation.y += .001*1+.02;
//Move my dom elements
moveDom();
renderer.render(scene, camera);
};
init();
body,html, canvas{width:100%;height:100%;padding:0;margin:0;overflow: hidden;}
.element{color:white;position:absolute;top:0;left:0}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r79/three.min.js"></script>
<!-- My scene -->
<canvas id="scene"></canvas>
<div class="element">
<h1>Not Earth</h1>
</div>
If you downvote this, please tell me why. I will try my best to improve my posts.
If you are using spriteMaterial to present your text, you could try to set the sizeAttenuation attribute to false.
var spriteMaterial = new THREE.SpriteMaterial( { map: spriteMap, color: 0xffffff, sizeAttenuation:false } );
See more information from here:
https://threejs.org/docs/index.html#api/en/materials/SpriteMaterial.sizeAttenuation

Three.js move as group

It seems like this should be easy, but I have spent nearly a week on this trying every possible combination of how to drag multiple items as a group in Three.js. It started out simple, I used this example https://jsfiddle.net/mz7Lv9dt/1/ to get the ball working. I thought I could just add some TextGeometry, which of course had some major API changes in the last couple releases rendering most examples obsolete.
After finally getting it to work as a single line, I wanted to add in wordwrap and move it as a group, but I can't seem to do so.
Here you can see it working just fine with the ball, but you can't drag the text https://jsfiddle.net/ajhalls/h05v48wd/
By swapping around three lines of code (location line 93-99), I can get it to where you can drag the individual lines around, which you can see here: https://jsfiddle.net/ajhalls/t0e2se3x/
function addText(text, fontSize, boundingWidth) {
var wrapArray;
wrapArray = text.wordwrap(10,2);
var loader = new THREE.FontLoader();
loader.load( 'https://cdn.coursesaver.com/three.js-74/examples/fonts/helvetiker_bold.typeface.js',
function ( font ) {
group = new THREE.Group();
group.name = "infoTag";
for (var i = 0; i < wrapArray.length; i++) {
var objectID=i;
var line = wrapArray[objectID];
var textGeo = new THREE.TextGeometry( line, {font: font,size: fontSize,height: 10,curveSegments: 12,bevelThickness: 0.02,bevelSize: 0.05,bevelEnabled: true});
textGeo.computeBoundingBox();
var centerOffset = -0.5 * ( textGeo.boundingBox.max.x - textGeo.boundingBox.min.x );
var textMaterial = new THREE.MeshPhongMaterial( { color: 0xff0000, specular: 0xffffff } );
var mesh = new THREE.Mesh( textGeo, textMaterial );
mesh.position.x = centerOffset +200;
mesh.position.y = i*fontSize*-1+11;
mesh.position.z = 280;
mesh.castShadow = true;
mesh.receiveShadow = true;
mesh.geometry.center();
mesh.lookAt(camera.position);
mesh.name = i;
group.add( mesh ); //this should work - comment out and swap for other two lines to see
scene.add(mesh); // this should work- comment out and swap for other two lines to see
//objects.push(mesh);//works on individual lines if you uncomment this
//scene.add(mesh); //works on individual lines if you uncomment this
}
objects.push( group ); // this should work- comment out and swap for other two lines to see
});
}
That "should" be working according to everything I had researched over the last week. I had one moment where it was "working" but because of the size of the group object, the pivot points were wrong, the setLength function didn't work, and it flipped the object away from the camera. All in all it was a mess.
I did try using 2d objects such as canvases and sprites, but for reasons detailed here Three.js TextGeometry Wordwrap - drag as group couldn't get it working.
Please, someone help me!
The issue ended up being with the group. Previously I was creating it, adding objects to it with a position.z which increased the size of the box around the group, then after doing that I moved the box to in front of the camera and did a group.lookAt which meant that when I was dragging it everything including pivot point and looking at it from the back was wrong. The right way was to create the group, position it, face the camera, then add the text.
function addText(text, fontSize, wrapWidth, tagColor, positionX, positionY, positionZ) {
var wrapArray;
wrapArray = text.wordwrap(wrapWidth,2);
var loader = new THREE.FontLoader();
loader.load( '/js/fonts/helvetiker_bold.typeface.js', function ( font ) {
group = new THREE.Group();
group.position.x=positionX;
group.position.y=positionY;
group.position.z=positionZ;
group.lookAt(camera.position);
group.tourType = "infoTag";
group.name = "infoTag-" + objects.length;
group.dataID=objects.length;
group.textData=text;
for (var i = 0; i < wrapArray.length; i++) {
var objectID=i;
var line = wrapArray[objectID];
var textGeo = new THREE.TextGeometry( line, {
font: font,
size: fontSize,
height: 1,
curveSegments: 12,
bevelThickness: 0.02,
bevelSize: 0.05,
bevelEnabled: true
});
textGeo.computeBoundingBox();
var centerOffset = -0.5 * ( textGeo.boundingBox.max.x - textGeo.boundingBox.min.x );
var textMaterial = new THREE.MeshPhongMaterial( { color: tagColor, specular: 0xffffff } );
var mesh = new THREE.Mesh( textGeo, textMaterial );
mesh.dataID=objects.length;
mesh.position.x = 0;
mesh.position.y = (i*mesh.geometry.boundingBox.max.y*-1)*1.15;
mesh.position.z = 0;
mesh.castShadow = true;
mesh.receiveShadow = true;
mesh.geometry.center();
//mesh.lookAt(camera.position);
mesh.name = "infoTag-mesh-" + objects.length;
group.add( mesh );
}
scene.add(group);
objects.push( group );
});
}
Of course there were some changes to be made in the mouse events to take into account that you want to move the parent of the object, which looks something like this:
function onDocumentMouseDown(event) {
event.preventDefault();
raycaster.setFromCamera(mouse, camera);
var intersects = raycaster.intersectObjects(objects, true);
if (intersects.length > 0) {
if (intersects[0].object.parent.tourType == 'infoTag') {
var manipulatingInfoTag = true;
SELECTED = intersects[0].object.parent;
}else{
SELECTED = intersects[0].object;
}
var intersects = raycaster.intersectObject(plane);
if (intersects.length > 0) {
offset.copy(intersects[0].point).sub(plane.position);
}
container.style.cursor = 'move';
}
isUserInteracting = true;
onPointerDownPointerX = event.clientX; onPointerDownPointerY = event.clientY; onPointerDownLon = lon; onPointerDownLat = lat;
}

Meshes are in Scene however nothing but the renderer's clear color appears

So I'm working with Three.js and jQuery to create a small visual application. At the moment all I want is for all the meshes I have, to appear on screen.
The Problem: None of the meshes appear on screen whatsoever.
Exceptions: The renderer's clear color appears (0x00bfff) and console.log(scene) confirms that all the meshes are in the scene.
Attempts to Fix: Use THREE.Projector, THREE.Raycaster, change camera positioning, and many more attempts.
I'm still very new to Three.js and programming in general so please be very critical of my work. Anything helps! Thanks!
WORLD.JS
$(document).ready(function() {
initialize();
animate();
});
var initialize = function() {
clock = new THREE.Clock(); // timer used to calculate time between rendering frames
scene = new THREE.Scene(); // list of objects that are to be "read" (rendered)
camera = new THREE.PerspectiveCamera(35, // FOV
window.innerWidth / window.innerHeight, // Aspect Ratio
.1, // Near
10000); // Far
camera.position.set( 25, 25, 125 );
camera.lookAt( scene.position );
setupEnvironment();
setupAI();
renderer = new THREE.WebGLRenderer(); // renderer will draw as WebGL rather than HTML5 Canvas
renderer.setSize( window.innerWidth, window.innerHeight ); // size of the canvas that renderer will draw on
renderer.setClearColor( 0x00bfff, 1 );
document.body.appendChild( renderer.domElement ); // adds the canvas to the document
};
var animate = function() { // animates the scene with frames
requestAnimationFrame(animate); // works recursively
render(); // update and display
}
var render = function() {
var delta = clock.getDelta() // gets the seconds passed since the last call to this method
// AI collision needed
// AI update needed
renderer.render( scene, camera ) // repaint
}
var setupEnvironment = function() {
ground = new BoxMesh( 10, 0.1, 10, 0x6C4319, 1 );
positionThenAdd( ground, [[ 0, 0 ]] );
light1 = new THREE.PointLight( 0xFFFFFF, .5 );
light1.position.set( 10, 10, 10 );
scene.add( light1 );
light2 = new THREE.PointLight( 0xFFFFFF, 1 );
light2.position.set( -10, -10, 10 );
scene.add( light2 );
};
var setupAI = function() {
sheep = new BoxMesh( 1, 1, 1, 0xFFFFFF, 3 );
positionThenAdd( sheep, [[ 0, 0 ],
[ 4.5, 0 ],
[ 9.5, 0 ]]);
sheepHerder = new BoxMesh( 1, 1, 1, 0x996633, 1 );
positionThenAdd( sheepHerder, [[ 4.5, 7.5 ]] );
};
function BoxMesh( width, height, depth, hexColor, amount ) { // creates one or more box meshes
this.width = width;
this.height = height;
this.depth = depth;
this.hexColor = hexColor;
this.amount = amount; // amount of box meshes to be made
boxSize = new THREE.BoxGeometry( width, height, depth );
boxMaterial = new THREE.MeshLambertMaterial( { color: hexColor } );
var all = []; // will contain all of the box meshes
for(var n = 1; n <= amount; n++) { // adds a new box mesh to the end of the all array
all.push(new THREE.Mesh( boxSize, boxMaterial )); // uses the attributes given by the BoxMesh constructor's parameters
}
return all; // returns all of the created box meshes as an array;
}
var positionThenAdd = function( varMesh, posArrXByZ ) { // positions an object and then adds it to the scene
this.varMesh = varMesh; // variable name of the mesh(es) array
this.posArrXByZ = posArrXByZ; // posArrXByZ stands for "array of positions in the format of X-by-Z"
// posArrXByZ is a 2 dimensional array where the first dimension is for the specific mesh to be positioned...
// and the second dimension is the positional coordinates.
// posArrXByZ = [ [x0,z0], [x1,z1], ...[xn,zn] ]
for(var mesh = 0; mesh < varMesh.length; mesh++) { // mesh accesses the varMesh array
varMesh[mesh].position.set( varMesh[mesh].geometry.parameters.width/2 + posArrXByZ[mesh][0], // the x coordinate, varMesh[mesh].width/2 makes the x coordinate act upon the closest side
varMesh[mesh].geometry.parameters.height/2 + ground.height, // the y coordinate, which is pre-set to rest on top of the ground
varMesh[mesh].geometry.parameters.depth/2 + posArrXByZ[mesh][1] ); // the z coordinate, varMesh[mesh].height/2 makes the y coordinate act upon the closest side
scene.add( varMesh[mesh] ); // adds the specific mesh that was just positioned
}
};
HTML FILE
<!DOCTYPE html>
<html>
<head>
<title>Taro's World</title>
<style>
body {
margin: 0;
padding: 0;
border: 0;
}
</style>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<script src="mrdoob-three.js-d6384d2/build/Three.js"></script>
<script src="mrdoob-three.js-d6384d2/examples/js/renderers/Projector.js"></script>
<script src="world.js"></script>
</head>
<body></body>
</html>
Two things are broken in your script :
in your positionThenAdd function, at position.set(...), you wrote somewhere ground.height. ground is an array, you probably meant varMesh[mesh].geometry.parameters.height.
your console should print that positionThenAdd is not a function. While you declared previous functions writing function myFunction(){....} you declared this one that way : var positionThenAdd = function () { ... };. The difference in javascript is that, as any variable, positionThenAdd will then be reachable in the script order. Since you write it at the end, nothing can reach it. You just have to modify its declaration to function positionThenAdd(){...}. See var functionName = function() {} vs function functionName() {}
Your scene : http://jsfiddle.net/ba8vvkyg/1/

threejs raycasting - intersections between camera and a loaded obj model

I'm moving a camera through a scene that contains an obj I've loaded as a mesh, and I want to detect if the camera has collided with any of the walls of my obj.
I've based my code off this threejs example: http://threejs.org/examples/misc_controls_pointerlock.html, and tried to apply your example here: http://stemkoski.github.io/Three.js/Collision-Detection.html - but I can't seem to get a collision.
My example is here
and the relevant javascript is here:
If anyone can point me in the right direction on how to detect a collision, I'd be grateful. Here's the relevant piece of code:
var objects = [];
var oLoader = new THREE.OBJLoader();
//oLoader.load('models/chair.obj', function(object, materials) {
oLoader.load('models/model-for-threejs.obj', function(object, materials) {
// var material = new THREE.MeshFaceMaterial(materials);
var material = new THREE.MeshLambertMaterial({ color: 0x000000 });
//var material = new THREE.MeshBasicMaterial({wireframe: true});
object.traverse( function(child) {
if (child instanceof THREE.Mesh) {
//objects.push(child); //MH - not sure if the object needs to be added here or not, but if I do, this really bogs down things down
}
});
object.position.x = 0;
object.position.y = 12;
object.position.z = 0;
scene.add(object);
objects.push(object);
});
}
var curPos = controls.getObject().position; //gives the position of my camera
raycaster.ray.origin.copy( curPos );
//raycaster.ray.origin.y -= 10;
raycaster.ray.origin.z +=10; //guessing here, but since I'm moving in 2d space (z / x), maybe I should be moving my ray ahead in z space?
var intersections = raycaster.intersectObjects( objects ); //
var isOnObject = intersections.length > 0;
if (isOnObject){ console.log('collide' }; //MH - nothing happening here
Raycaster's intersect object takes an Object3D with children, and has a flag for recursion.
https://github.com/mrdoob/three.js/blob/master/src/core/Raycaster.js#L33
So it should look like this:
var intersections = raycaster.intersectObjects( yourRootObject3D, true );

Categories