I'm using three.js and a script similar to OrbitControls as my controller. In my main.js file I have a THREE.Group() that is being passed to the controller as an argument. From there, I'm attempting to rotate the entire group.
Problem 1: once the group is passed to the controller, I can no longer access its properties without making a copy
Problem 2: a copy does not contain the entire THREE.Group(), rather only the first child
I've been working on this for hours now and I've tried about 50 different things including anything relevant on stackoverflow. I'm completely out of ideas on how to combat this problem.
main.js
let container;
let camera;
let controls;
let game;
let renderer;
let scene;
function init() {
container = document.querySelector('#scene-container');
scene = new THREE.Scene();
const fov = 35;
const aspect = container.clientWidth / container.clientHeight;
const near = 0.1;
const far = 100;
camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
//***** This is the important line ******
controls = new THREE.ObjectControls(camera, container, game);
game = new THREE.Group();
scene.add(game);
const geometry = new THREE.BoxGeometry();
const material = new THREE.MeshBasicMaterial();
var mesh1 = new THREE.Mesh(geometry, material);
game.add(mesh1);
var mesh2 = new THREE.Mesh(geometry, material);
mesh2.position.set(0,1,0);
game.add(mesh2);
renderer = new THREE.WebGLRenderer();
renderer.setSize(container.clientWidth, container.clientHeight);
renderer.setPixelRatio(window.devicePixelRatio);
container.appendChild(renderer.domElement);
renderer.setAnimationLoop(() => {
renderer.render(scene, camera);
});
}
init();
ObjectControls.js
THREE.ObjectControls = function (camera, domElement, objectToMove) {
mesh = objectToMove;
domElement.addEventListener('mousemove', mouseMove, false);
function mouseMove(e) {
//** objectToMove is undefined :( **
mesh.rotation.y += 3;
}
};
Expected result is that the entire THREE.Group() game will be rotated, but the result that I get is that only the first child of game is rotated, in this case mesh1.
controls = new THREE.ObjectControls(camera, container, game);
game = new THREE.Group();
There is an error in your code since you pass the undefined variable game to the ctor of ObjectControls. If you assign a new object to game one line later, ObjectControls does not have a reference to this variable.
The idea is to assign the group object to game first and then create ObjectControls. You essentially switch both lines.
three.js R105
Related
i am new to three js, i'm trying to create soft lighting for a gltf model using AmbientLight and DirectionalLight, but the color contrast comes out too sharp
what i'm trying to achieve
What i got
my code
const main = document.querySelector('#main');
let WIDTH = main.offsetWidth;
let HEIGHT = main.offsetHeight;
let scene = new THREE.Scene();
let camera = new THREE.PerspectiveCamera(15, WIDTH / HEIGHT, 0.1, 1000);
camera.position.z = 5;
let renderer = new THREE.WebGLRenderer();
renderer.setClearColor('rgb(99, 115, 107)');
renderer.setSize(WIDTH, HEIGHT);
renderer.domElement.setAttribute("id", "Church3DObj");
main.insertBefore(renderer.domElement, main.firstChild);
let controls = new THREE.OrbitControls(camera, renderer.domElement);
const ambientLight = new THREE.AmbientLight('white', 1);
const mainLight = new THREE.DirectionalLight('white', .8);
mainLight.position.set(10, 10, 10);
scene.add(ambientLight)
scene.add(mainLight)
let loader = new THREE.GLTFLoader();
let obj = null;
loader.load('https://raw.githubusercontent.com/aaadraniki/projects/web-pages/assets/models/fender_br/scene.gltf', function(gltf) {
obj = gltf.scene;
scene.add(obj);
});
renderer.setAnimationLoop(_ => {
renderer.render(scene, camera);
})
You'll usually get the best lighting in three.js by using some kind of environment map. THREE.RoomEnvironment is one of the easier ways to do that, or you can load external .hdr or .exr files. Enabling some tone mapping (e.g. ACES Filmic) may also help, see renderer.toneMapping.
I'm trying to learn typescript with three.js and can't figure out why I'm getting this error. In the init function I'm creating a new THREE.OrbitControls controller. Using https://github.com/pinqy520/three-typescript-starter as setup.
Error recieved: Uncaught TypeError: Cannot read property 'prototype' of undefined
// three.
import * as THREE from 'three';
import * as ColladaLoader from 'three-collada-loader';
import * as OrbitControls from 'three-orbit-controls';
// Set the scene size
const WIDTH: number = window.innerWidth;
const HEIGHT: number = window.innerHeight;
// Set camera attributes
const VIEW_ANGLE: number = 45;
const ASPECT: number = WIDTH / HEIGHT;
const NEAR: number = 0.1;
const FAR: number = 10000;
// Create a WebGL renderer, camera
// and a scene
const renderer: THREE.WebGLRenderer = new THREE.WebGLRenderer();
const camera: THREE.PerspectiveCamera = new THREE.PerspectiveCamera(VIEW_ANGLE, ASPECT, NEAR, FAR);
const scene: THREE.Scene = new THREE.Scene();
// Load a model into the scene
let monster: any;
const loader: ColladaLoader = new ColladaLoader();
loader.options.convertUpAxis = true;
loader.load(
'/src/monster.dae',
(collada: THREE.ColladaModel): void => {
monster = collada.scene;
monster.scale.set(0.001, 0.001, 0.001);
monster.position.set(0, 0.1, 0);
scene.add(monster);
init();
animate()
});
const init = (): void => {
// Get the DOM element to attach to
const container = document.querySelector('#canvas');
// Set the position of the camera
// and add it to the scene
camera.position.set(0, 2, 10)
scene.add(camera);
//
const gridHelper: THREE.GridHelper = new THREE.GridHelper(10, 20);
scene.add(gridHelper);
//
const ambientLight: THREE.AmbientLight = new THREE.AmbientLight(0xcccccc)
scene.add(ambientLight);
const directionalLight: THREE.DirectionalLight = new THREE.DirectionalLight((0xffffff));
directionalLight.position.set(0, 1, -1).normalize;
scene.add(directionalLight);
// Start the renderer
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(WIDTH, HEIGHT);
// Attach the renderer-supplied
// DOM element.
container.appendChild(renderer.domElement);
// Create a controll class around
// the camera
debugger
let controls: THREE.OrbitControls = new OrbitControls(camera, renderer.domElement);
}
// Recursive function
let animate = (): void => {
requestAnimationFrame(animate);
render();
}
// Recursive rendering function
let render = (): void => {
renderer.render(scene, camera);
}
According to this
post on GITHUB
that syntax no longer works. So change OrbitControls like this:
THREE.EventDispatcher.apply( this );
There was a change recent versions of the threejs to the way you use the default exports of a module.
So, instead of using:
import THREE from 'three';
You should use:
import * as THREE from 'three';
I ended up using this:
https://www.npmjs.com/package/orbit-controls-es6
One slight difference to make note of:
Do new OrbitControls instead of new THREE.OrbitControls
import OrbitControls from 'orbit-controls-es6';
const controls = new OrbitControls(camera, renderer.domElement);
I am using the following code to generate two spheres in cannonjs with Three.js as rendering module.
var world, mass, body, shape, timeStep=1/60,
camera, scene, renderer, geometry, material, mesh;
initThree();
initCannon();
animate();
function initCannon() {
world = new CANNON.World();
world.gravity.set(0,0,0);
world.broadphase = new CANNON.NaiveBroadphase();
world.solver.iterations = 10;
geometry = new THREE.SphereGeometry( 3);
material = new THREE.MeshBasicMaterial( { color: 0xff0000, wireframe: true } );
mesh = new THREE.Mesh( geometry, material );
body = new CANNON.Body({mass:0.2});
body.angularVelocity.set(0,5,0);
body.angularDamping = 0.9;
world.add(body);
scene.add(mesh);
geometry = new THREE.SphereGeometry(2);
material = new THREE.MeshBasicMaterial( { color: 0x00ff00, wireframe: true } );
mesh = new THREE.Mesh(geometry,material);
body = new CANNON.Body({mass: 1});
body.angularVelocity.set(0,10,0);
body.angularDamping = 0.5;
world.add(body);
scene.add(mesh);
}
function initThree() {
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 1, 100 );
camera.position.z = 5;
scene.add( camera );
renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
}
function animate() {
requestAnimationFrame( animate );
updatePhysics();
render();
}
function updatePhysics() {
// Step the physics world
world.step(timeStep);
// Copy coordinates from Cannon.js to Three.js
mesh.position.copy(body.position);
mesh.quaternion.copy(body.quaternion);
}
function render() {
renderer.render( scene, camera );
}
As a result, I do get two spheres on display, but only the second body declared in initCannon() animates. The cannonjs body is somehow not associated with the Three.js mesh. I tried "body.mesh=mesh", but it doesn't help.
The declaration for the mesh was originally done in initThree() but I don't figure if that matters anyway.
What am I doing wrong? Please help. Ask for any more details if needed.
You're overwriting variables. In the beginning you declare a variable body, and then you say body = new CANNON.Body... and after that you use the same sentence for the second sphere, so the first one gets overwritten. You should have a variable body2, a variable mesh2 to avoid this. This approach would work ok if you only need two bodies, but if you're planning to have more you need a different approach: Arrays. In an Array you can store all your bodies and then loop through it and handle each one singularly. Example:
Here we create 10 bodies:
var bodies= []; //create the array
for (var i = 0; i < 10; i++) {
bodies.push({ //insert into the array an object that contains the information for each body/mesh
body: new CANNON.Body({mass:0.2}),
mesh = new THREE.Mesh(geometry,material)
})
};
And then we use them:
for (var i=0, l=bodies.length; i < l; i++) { //we loop through each body
var body = bodies[i]; //this will be the current body
//MOVE THE BODY AND UPDATE THE MESH HERE
}
This would be a better approach. It's more maintainable and you can have as many bodies as the browser can handle. If you only need two, your fastest fix would to add a second variable for the second body and handle it separately.
EDIT:
Watch this: https://www.youtube.com/watch?v=tW6pmzd34Hc he stores the circles into an array as well. I hope it makes it a little bit clearer.
i'm new in three.js and have a problem by scaling a object. I already looked at the documentation (https://github.com/mrdoob/three.js/wiki/Updates), but i don't really understand it at all.
My Problem: By changing a HTML-select-element the CubeGeometry should be scaled in the x-direction. That is already working BUT the "old" Cube do not disappear. So i have 2 Cubes. But i want only one Cube with the current size. I hope you can understand my problem ;-)
So here is my Code from the View:
$(document).on('change',".configurator > form select",function(event){
// update 3D-object - is that a nice way???
$.getScript("/javascripts/3D-animation.js.erb", function(){
// here comes the update
OBJECT3D.updateObject();
});
})
And here is my 3D-animation.js.erb:
var drawing_area;
var renderer;
var camera;
var obj;
var scene;
var OBJECT3D = {};
$(function() {
// get drawing_area
drawing_area = document.getElementById("canvas_wrapper");
// initialize renderer
renderer = new THREE.WebGLRenderer();
renderer.setSize(drawing_area.clientWidth, drawing_area.clientHeight);
renderer.setClearColor( 0xffffff, 1);
// add renderer to drawing_area
drawing_area.appendChild(renderer.domElement);
// initialize camera
camera = new THREE.PerspectiveCamera(45, drawing_area.clientWidth/drawing_area.clientHeight, 1, 100000);
camera.position.z = 1000;
camera.position.y = 100;
camera.position.x = 300;//-0.78;
// create texture
var texture = THREE.ImageUtils.loadTexture( "/images/materials/texture_1.jpg" );
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
texture.repeat.set( 1, 1 );
// create object
var obj_form = new THREE.CubeGeometry(250,250,250);
var obj_material = new THREE.MeshLambertMaterial( { map: texture,ambient: 0x999999 } );
OBJECT3D.obj = new THREE.Mesh(obj_form, obj_material);
// so what do i need here?
OBJECT3D.obj.geometry.dynamic = true;
// OBJECT3D.obj.geometry.__dirtyVertices = true;
// OBJECT3D.obj.geometry.__dirtyNormals = true;
// OBJECT3D.obj.geometry.verticesNeedUpdate = true;
// create scene
scene = new THREE.Scene();
scene.add(camera);
scene.add(OBJECT3D.obj);
// create lights
pointLight = new THREE.PointLight(0xFFFFFF);
pointLight.position.x = 400;
pointLight.position.y = 200;
pointLight.position.z = 1300;
scene.add(pointLight);
ambientLight = new THREE.AmbientLight( 0xffffff);
scene.add( ambientLight );
requestAnimationFrame(render);
function render(){
requestAnimationFrame(render);
OBJECT3D.obj.rotation.y += 0.005;
OBJECT3D.obj.rotation.z += 0.005;
renderer.render(scene, camera);
};
// update object
OBJECT3D.updateObject = function () {
console.log("in update");
OBJECT3D.obj.scale.x = 2.5; // SCALE
OBJECT3D.obj.geometry.needsUpdate = true;
//render();
}
});
Sorry, if the Code is not the best one, but i'm really new in this stuff! :) hope you can help me!
Thanks!
I think it is because you are using $.getScript method. This will load your script each time again and create a new instance of OBJECT3D.
I propose that you make sure that your code in 3D-animation.js.erb will be included and called only once upon the load of a page (include it just like any other regular javascript file) and then call your update directly like this:
$(document).on('change',".configurator > form select", function(event) {
OBJECT3D.updateObject();
});
also, I believe you can drop following lines of code:
OBJECT3D.obj.geometry.needsUpdate = true;
and
OBJECT3D.obj.geometry.dynamic = true;
I have been doing some experiments with three.js. However, when I try to do a simple test, I can't get it to display anything. Here's my code. If someone could tell me what I'm missing it would be really helpful.
//Constant declaration
var RENDER_DIST = 1000,
FOV = 75;
var WIDTH = window.innerWidth,
HEIGHT= window.innerHeight;
//Create the scene
var scene = new THREE.Scene();
//Create the camera
var camera = new THREE.PerspectiveCamera(FOV, WIDTH / HEIGHT, 0.1, RENDER_DIST);
//Pull the camera back a bit
camera.z = 100;
//Then add it to the scene
scene.add(camera);
//Create a renderer
renderer = new THREE.WebGLRenderer();
renderer.setSize(WIDTH,HEIGHT);
renderer.domElement.className = "glMain";
//Container is a div
$("#container").html(renderer.domElement);
//Boilerplate done! Celebrate!
(function init() {
var geometry = new THREE.SphereGeometry(50);
var material = new THREE.MeshBasicMaterial({color: 0xff0000});
var sphere = new THREE.Mesh(geometry, material);
scene.add(sphere);
//debugging
console.log("Sphere at " + sphere.position.x + " " + sphere.position.y + " " + sphere.position.z);
})();
//Our main function
(function loopRun() {
requestAnimationFrame(loopRun);
console.log("rendered");
renderer.render(scene, camera);
})();
On the console log I am getting all the "rendered" messages but nothing is displaying. I have checked that the canvas is the right size and it is, so I have no idea what is wrong. I have tried a few different geometries.
EDIT: I am using Chrome, and online three.js examples work fine. There are no errors on the console log.
//Pull the camera back a bit
camera.position.z = 100;
//Container is a div
document.body.appendChild(renderer.domElement);
you can also try :
$("#container").append(renderer.viewElement);