1.
I am trying to set up multiple canvases on a page like the given examples on threejs.org.
My basic code is like this:
var scene, camera, controls, renderer, pointLight, geometry, material;
var container, position, dimensions, apps = [];
var windowWidth = window.innerWidth;
var windowHeight = window.innerHeight;
function preView( id ){
apps.push( new App( id ) );
//animate(); // if i call this here, all canvases renders once
function App( id ) {
container = $('#preView_' + id);
dimensions = { width: container.width(), height: container.height()};
camera = new THREE.PerspectiveCamera(45, dimensions.width/dimensions.height, 1, 5 * radius);
camera.position.x = 0;
camera.position.y = 0;
camera.position.z = 100;
scene = new THREE.Scene();
/* add meshes */
/* ======================= */
renderer = new THREE.WebGLRenderer();
renderer.setSize(dimensions.width, dimensions.height);
container.append(renderer.domElement);
this.animate = function() {
if( camera.position.z > -(1/3) * 100 )
{
/* simple fly through the scene */
camera.position.x += 0.05;
camera.position.y += 0.05;
camera.position.z -= 0.1;
}
camera.lookAt(new THREE.Vector3(0,0,0));
render();
};
}
}
function animate(){
for ( var i = 0; i < apps.length; ++i ) {
apps[ i ].animate();
}
requestAnimationFrame(animate);
}
function render(){
renderer.render(scene, camera);
}
The strange thing what happends is, that only the last canvas renderes (at all) if i call animate(); after all canvases are drawn. And if i call animate(); in the preView(); Function, all sences are rendered once but only the last canvas renderes the 'camera fly through'. But a console.log(apps[i]); in the animate(); function go through all apps, but dont render the scene.
What do i do wrong here?
2.
Also i try to achieve this shader effect for every object which i declare as 'light', nomatter which position it has in the scene.
I tried to play a little with all position values in the shaders with absolutly no effect.
The only effect was in the VolumetericLightShader on line 333.
I hope for any Hints here.
Put all the variables, except apps=[], in App( id ) function. Thus you'll make them local for App. In your case now, every time you call
new App( id )
you put information in global variables which you created once. So in those variables you have the data you've stored there since last call of App( id ).
It means that you re-write the data in global variables. The same about the render() method. Put it inside the App() function too. As you mentioned about the example from Threejs.org, you had to notice where this method is stored. It's inside the App() function there. Sample jsfiddle
Maybe it would be easier to use the technique of lens flares. https://threejs.org/examples/webgl_lensflares.html
Related
I am trying to add touch controls to a three.js scene. I want to move the camera in whatever direction the user touches. It works great using the keyboard because you can press and hold the button and the camera moves continuously. But when I try the same thing using touchstart, you have to keep tapping the screen over and over to move, you can't just hold your finger down like on a keyboard or mouse.
I looked at touchmove, but if you just tap and hold without moving, there are no new touches.
Is there something similar to holding down the keyboard or mousekey using touch events?
There is no builtin callback for a touch event which fires repeatedly like the keyboard. You can, however, simply track the start and end of the touch and then call the move method at a set interval.
First, subscribe to the correct events and set a bool to track the state:
var isTouching = false;
window.addEventListener("touchstart", () => isTouching = true);
window.addEventListener("touchend", () => isTouching = false);
In Three.js you will most likely already have a render loop (e.g. a function called "animate"). Check the state variable at every iteration and apply the movement each time. You may need to also factor in deltaTime (the duration of the last frame), to make movement framerate independent.
function animate() {
requestAnimationFrame(animate);
mesh.rotation.x += 0.005;
mesh.rotation.y += 0.01;
if (isTouching) {
console.log("move camera");
}
renderer.render(scene, camera);
}
Here is a snippet which shows the basic approach. Click and hold in the left or right half of the output window to move the camera.
var camera, scene, renderer, mesh, material, clock;
init();
animate();
var isTouching = false;
var mousePositionX;
window.addEventListener("mousedown", (e) => {
isTouching = true;
mousePositionX = e.clientX;
});
window.addEventListener("mouseup", (e) => isTouching = false);
function init() {
renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
clock = new THREE.Clock();
camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.z = 400;
scene = new THREE.Scene();
material = new THREE.MeshPhongMaterial();
var geometry = new THREE.BoxGeometry(200, 200, 200);
mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
var light = new THREE.AmbientLight(0x404040);
scene.add(light);
var directionalLight = new THREE.DirectionalLight(0xffffff);
directionalLight.position.set(1, 1, 1).normalize();
scene.add(directionalLight);
window.addEventListener('resize', onWindowResize, false);
}
function animate() {
requestAnimationFrame(animate);
mesh.rotation.x += 0.005;
mesh.rotation.y += 0.01;
let deltaTime = clock.getDelta();
if (isTouching) {
let speed = 200; // px per second
let movement = speed * deltaTime;
if (mousePositionX > window.innerWidth / 2) {
camera.translateX(-movement);
} else {
camera.translateX(movement);
}
}
renderer.render(scene, camera);
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
body {
padding: 0;
margin: 0;
}
canvas {
display: block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/93/three.min.js"></script>
I originally had an animate function in place for my three.js scene that is loaded within an AngularJS Modal, but found that after closing the Modal, the animation keeps going, and that is unneeded since I don't require constant animation like a video game would have.
At this point, I switched it to only render when someone uses the OrbitControls to move the simple box in my example, and have an initial call to render the scene so that users can see the box instead of a big blacked out square.
However, upon initial render, the texture does not appear to be applied until I use the orbit controls and move the box, at which point they appear. This is odd, since both my initial call and the listener tied to the OrbitControls are to the same function. How do I get the initial load to show the texture?
$scope.generate3D = function () {
// 3D OBJECT - Variables
var texture0 = baseBlobURL + 'Texture_0.png';
var boxDAE = baseBlobURL + 'Box.dae';
var scene;
var camera;
var renderer;
var box;
var controls;
var newtexture;
// Update texture
newtexture = THREE.ImageUtils.loadTexture(texture0);
//Instantiate a Collada loader
var loader = new THREE.ColladaLoader();
loader.options.convertUpAxis = true;
loader.load(boxDAE, function (collada) {
box = collada.scene;
box.traverse(function (child) {
if (child instanceof THREE.SkinnedMesh) {
var animation = new THREE.Animation(child, child.geometry.animation);
animation.play();
}
});
box.scale.x = box.scale.y = box.scale.z = .2;
box.updateMatrix();
init();
// Initial call to render scene, from this point, Orbit Controls render the scene per the event listener
render();
});
function init() {
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
renderer = new THREE.WebGLRenderer();
renderer.setClearColor(0xdddddd);
//renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setSize(500, 500);
// Load the box file
scene.add(box);
// Lighting
var light = new THREE.AmbientLight();
scene.add(light);
// Camera
camera.position.x = 40;
camera.position.y = 40;
camera.position.z = 40;
camera.lookAt(scene.position);
// Rotation Controls
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.addEventListener('change', render);
controls.rotateSpeed = 5.0;
controls.zoomSpeed = 5;
controls.noZoom = false;
controls.noPan = false;
var myEl = angular.element(document.querySelector('#webGL-container'));
myEl.append(renderer.domElement);
}
function render() {
renderer.render(scene, camera);
console.log('loaded');
}
}
You are using ColladaLoader and you want to force a call to render() when the model and all the textures are loaded.
If you add the model to the scene in the loader callback, there is still a chance that even though the model has loaded, the textures may not have.
One thing you can do is add the following before instantiating the loader:
THREE.DefaultLoadingManager.onLoad = function () {
// console.log( 'everything loaded' ); // debug
render();
};
Or alternatively,
THREE.DefaultLoadingManager.onProgress = function ( item, loaded, total ) {
// console.log( item, loaded, total ); // debug
if ( loaded === total ) render();
};
three.js r.72
I set up my scene as follows:
document.addEventListener('mousedown', onDocumentMouseDown, false);
var container = document.getElementById("myCanvas");
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );
renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth*0.99, window.innerHeight*0.99 );
container.appendChild( renderer.domElement );
room_material = new THREE.MeshBasicMaterial({color: 0x00ff00});
room_material.side = THREE.DoubleSide;
objects = [];
camera.position.z = 19;
camera.position.x = 5;
camera.position.y = 30;
I have an array of objects that i'm trying to detect if a click intersects with them, defined as follows:
var thing0 = new THREE.Shape();
thing0.moveTo(-12.1321728566, 35.3935535858);
thing0.lineTo(7.10021556487,35.3935535858);
thing0.lineTo(7.10021556487,19.7039735578);
thing0.lineTo(5.12636517425,19.7166264449);
thing0.lineTo(5.12636517425,33.6221493891);
thing0.lineTo(-12.1377356984,33.6439534769);
var thing0Geom = new THREE.ShapeGeometry(thing0);
var thing0Mesh = new THREE.Mesh( thing0Geom, room_material );
thing0Mesh.name = "abcd";
scene.add(thing0Mesh);
objects.push(thing0Mesh);
I then render the scene with the following code:
renderer.render(scene, camera);
requestAnimationFrame(render);
And lastly I use the following code for the mouse click event:
function onDocumentMouseDown(event) {
var vector = new THREE.Vector3(( event.clientX / window.innerWidth ) * 2 - 1, -( event.clientY / window.innerHeight ) * 2 + 1, 0.5);
vector = vector.unproject(camera);
var raycaster = new THREE.Raycaster(camera.position, vector.sub(camera.position).normalize());
var intersects = raycaster.intersectObjects(objects, true);
alert("well, you clicked!");
if (intersects.length > 0) {
alert("wow, it worked");
}
}
However, no matter what I do the alert never gets called when it follows raycaster.intersectObjects(objects, true); However it does get called when it is placed anywhere before it. It seems that raycaster.intersectObjects(objects, true); is a bit of a black hole in this case?
I assume I simply have something wrong in my setup? Any help would be appreciated!
There are two things I tried that worked:
1) Make sure you're using Three.DoubleSide if your mesh face is not pointing towards origin of the ray. This is directly from the documentation:
"Note that for meshes, faces must be pointed towards the origin of the ray in order to be detected; intersections of the ray passing through the back of a face will not be detected. To raycast against both faces of an object, you'll want to set the material's side property to THREE.DoubleSide."
2) Use mesh.updateMatrixWorld() prior to raycasting. This comes from another stackoverflow post: threejs raycasting does not work
mesh.updateMatrixWorld(); // add this
raycaster.set(from, direction);
I think i have found the problem. I have to add the meshes to an extra array. An intersectObjects over the scene.children don't work, because there are other objects in there with the meshes.
So when i give the intersectObjects( mesh[] ) an mesh array than it works.
For more code detail see https://github.com/mrdoob/three.js/issues/8081
This is an old question, but I was running into a similar issue and thought what I learned might help someone else. My code setup is very similar to JohnnyDevNull's, so I won't repeat it.
The Problem
In my case, calling intersectObjects with scene.children doesn't work because it picks up other objects like ambientLighting, etc. I believe this is what the OP mentions in his own answer.
My Solution
The function below takes an empty array (intersects). It searches each child in scene for THREE Group objects and Mesh objects and adds Mesh objects to the array. For Groups, it sets recursive to true, which applies .intersectObject to each child. Once the array is built up, it calls your supplied callback function on the array.
function raycastMeshes(intersects, callback, theScene, theRaycaster) {
var scene = theScene || scene || new THREE.Scene();
var raycaster = theRaycaster || raycaster || new THREE.Raycaster();
for (var i in scene.children) {
if (scene.children[i] instanceof THREE.Group) {
intersects = raycaster.intersectObjects(scene.children[i].children, true);
} else if (scene.children[i] instanceof THREE.Mesh) {
intersects.push(raycaster.intersectObject(scene.children[i]));
}
}
if (intersects.length > 0) {
return callback(intersects);
} else {
return null;
}
}
Adapting to your Use Case
This is obviously a fairly naive and specific use case, but should serve as a launching board for you if you're having a similar problem.
I am new to WebGL and Three.js. I am trying to visualize a large grid of circles changing colors at once.
As I increase the number of instances, it gets noticeably slower, where it takes seconds to update. What are some suggestions for improving my code? Can I update 4000 circles at once?
Here is my existing implementation:
<html>
<head>
<title>My first Three.js app</title>
<style></style>
</head>
<body>
<script src="./three.js"></script>
<script>
var ROWS = 40
var COLS = 100
var SEGMENTS = 10;
var windowWidth = window.innerWidth, windowHeight = window.innerHeight;
var camera, scene, renderer;
var group, text, plane;
function init() {
// create and append container/canvas
container = document.createElement( 'div' );
document.body.appendChild( container );
// create camera
camera = new THREE.PerspectiveCamera(100, windowWidth / windowHeight, 0.1, 1000 );
// set position of camera
camera.position.z = 500;
camera.position.x = windowWidth/2
camera.position.y = windowHeight/2
// Create a scene
scene = new THREE.Scene();
renderer = new THREE.CanvasRenderer();
renderer.setClearColor( 0xf0f0f0 );
renderer.setSize( windowWidth, windowHeight );
renderer.sortElements = false;
container.appendChild( renderer.domElement );
document.addEventListener( 'mousedown', onDocumentMouseDown, false );
}
function addCircle(color, x, y, z, s , radius) {
var geometry = new THREE.CircleGeometry(radius, SEGMENTS, SEGMENTS)
var material = new THREE.MeshBasicMaterial( { color: color, overdraw: true } );
var mesh = new THREE.Mesh( geometry, material );
mesh.position.set( x, y, z );
mesh.scale.set( s, s, s );
scene.add( mesh );
}
function toHex(d) {
var valueStr = d.toString(16);
valueStr = valueStr.length < 2 ? "0"+valueStr : valueStr;
var fillColor = "0x00" + valueStr + "00";
return parseInt(fillColor);
}
function drawData(data) {
var rows = data.length;
var cols = data[0].length;
distanceBetweenCircles = Math.min(windowWidth/(cols), windowHeight/(rows));
var radius = distanceBetweenCircles/2.0
for(var i = 0; i < data.length; i++) {
for (var j = 0; j < data[0].length; j++) {
var color = toHex(data[i][j])
var x = distanceBetweenCircles*j - radius
var y = distanceBetweenCircles*i - radius
addCircle( color, x, y, 0, 1 , radius-3);
}
}
}
function newData(){
var newData = []
for (var i = 0; i < ROWS; i++) {
var row = [];
for (var j = 0; j < COLS; j ++) {
row.push(Math.floor(Math.random()*255));
}
newData.push(row);
}
return newData;
}
function onDocumentMouseDown ( event ) {
event.preventDefault();
// Update circles
var randomData = newData()
drawData(randomData);
}
var render = function() {
requestAnimationFrame(render);
renderer.render(scene, camera);
}
init();
render();
</script>
</body>
</html>
To add a CircleGeometry object to a scene requires the circle to be drawn. Adding an image/texture of a circle to a scene requires the circle to be printed, so to speak.
Multiply that by 4000 and the drawing becomes quite expensive.
It would be faster to maintain an array of the meshes and update their properties, rather than creating a new set of geometries, materials and meshes every mouse click.
Memory management is extremely important in software design. There is a cost to every variable you introduce, especially those who's instantiation invokes a cascade of allocations behind some API call. This is the down side of using layerings like THREE which hide complexity but also hide ramifications of using their calls. Short of avoiding THREE and doing all the WebGL plumbing yourself (which is always a good first step before ignoring the plumbing and just using a shim like THREE), do some homework to identify what is getting created as you make calls to any API, like THREE. Rip out of inner loops variable creation for objects which should be reused across calls. To your question, yes you can easily update 4000 circles across each animation event loop time slice once your architecture is carefully thought through especially if you use shaders to craft your objects and avoid such computation back in the CPU
For pure speed I suggest you learn graphics by writing OpenGL/WebGL by hand instead of the higher level abstraction library Three.js ... the price of ease of use too often is higher computational load of unnecessary logic which can be cut out if written by hand
Here is a WebGL toy I built which has no Three.js ... it does real-time updates to geometry of 10's of thousands of objects as well as rendering audio using Web Audio API
https://github.com/scottstensland/webgl-3d-animation
I have recently started to play around with Haxe and Three.js. How do I load a 3D object using JSONLoader. I’m very new to the Haxe way of doing things and haven’t wrapped my head around the whole extern thing.
I’m making use of this lib to simplify things:
https://github.com/rjanicek/three.js-haXe
Most of the Three.js Classes are abstracted in the lib except for JSONLoader or any loader for that matter. How can I load the json model I exported from Blender in Haxe?
It seems like I was using the wrong lib :)
This is a better abstraction:
https://github.com/labe-me/haxe-three.js
To load a 3D model, you’ll go about it this way:
package co.za.anber;
import js.three.Three;
import js.Lib;
class Main
{
static private var scene:Scene;
static function main()
{
//Get the dimensions of the scene
var w = Lib.window.innerWidth;
var h = Lib.window.innerHeight;
scene = new Scene();
//add some light
var pointLight = new PointLight(0xffffff, 1, 0);
pointLight.position.set(10, 50, 130);
scene.add(pointLight);
//add a camera
var camera = new PerspectiveCamera(70, w/h, 1, 1000);
camera.position.z = 500;
scene.add(camera);
//setup renderer in the document
var renderer = new WebGLRenderer(null);
renderer.setSize(w, h);
Lib.document.body.appendChild(renderer.domElement);
//Load the Blender exported Mesh.
//This is where we load the Mesh and setup the onload handler. This was the part I wasn't so sure about.
var loader:JSONLoader = new JSONLoader(true);
//I don't like in-line functions. You need to make the returning function into a Dynamic type.
var callbackModel:Dynamic = function( geometry:Dynamic ){createScene(geometry); };
loader.load("Suzanne.js", callbackModel);
//Listen for mouse move. In-line function from somewhere else.
var mouseX = 0, mouseY = 0;
untyped Lib.document.addEventListener('mousemove', function(event){
mouseX = (event.clientX - Lib.window.innerWidth/2);
mouseY = (event.clientY - Lib.window.innerHeight/2);
}, false);
//Render the scene #60 frames per second. Inline function from somewhere else.
var timer = new haxe.Timer(Math.round(1000/60));
timer.run = function(){
camera.position.x += (mouseX - camera.position.x) * 0.05;
camera.position.y += (-mouseY - camera.position.y) * 0.05;
camera.lookAt(scene.position);
renderer.render(scene, camera);
}
}
/**
* Onload complete handler. Here we can add our Mesh.
* #param geometry
*/
static function createScene( geometry:Dynamic):Void{
var mesh:Mesh = new Mesh( geometry, new MeshLambertMaterial( { color: 0x00FF00 } ) );
//We scale it up to be visible!
mesh.scale.set( 150.15, 150.5, 150.5 );
scene.add( mesh );
}
}
Hope this help someone.
Look at this, it should help you.