I have been trying to find a way to be able to toggle between two scenes in three.js. I am aware that one can load a scene by using sceneLoader / exportScene combo.
Code taken from josdirksen/learning-threejs - loading a scene
var controls = new function () {
this.exportScene = function () {
var exporter = new THREE.SceneExporter();
var sceneJson = JSON.stringify(exporter.parse(scene));
localStorage.setItem('scene', sceneJson);
};
this.clearScene = function () {
scene = new THREE.Scene();
};
this.importScene = function () {
var json = (localStorage.getItem('scene'));
var sceneLoader = new THREE.SceneLoader();
sceneLoader.parse(JSON.parse(json), function (e) {
scene = e.scene;
}, '.');
}
};
From my understanding of the above code you need to have the scene loaded first before you can extract it and save to local storage before you can put it back into the scene. I am also aware that SceneLoader is now deprecated.
For my senario I want to have an initial scene load and by clicking the 'scene2' button I then want to display scene2 only and if I click the 'scene1' button go back to seeing scene1 only (see fiddle below).
A Basic Example setup
I'm not sure where to begin with this, so any pointers suggestions or advice would be helpful.
If you need to just switch to new scene, then why not have two scene object and one main scene. Try following code
/* Buttons to handle scene switch */
$("#scene2").click(function() {
scene = scene2
})
$("#scene1").click(function() {
scene = scene1
})
function init() {
....
/* I dont think you need to add camera to scene for viewing perpose. By doing this, essentially you are adding camera object to scene, and you won't be able to see it because scene is rendered using this camera and camera eye is at same location
*/
scene1 = new THREE.Scene();
// Build scene1
// scene1.add(camera);
scene2 = new THREE.Scene();
// Build scene2
// Choosing default scene as scene1
scene = scene1;
}
function render() {
// Try some checking to update what is necessary
renderer.render(scene, camera);
}
Updated jsfiddle
You can redraw the canvas by removing current scene scene.remove(mesh); and add create new mesh add into scene
Demo http://jsfiddle.net/sumitridhal/x8t801f5/4/
You can add custom controls using dat.GUI library.
<script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.6.4/dat.gui.js"></script>
//
var controls = new function() {
// we need the first child, since it's a multimaterial
this.radius = 10;
this.detail = 0;
this.type = 'Icosahedron';
this.redraw = function() {
// remove the old plane
scene.remove(mesh);
// create a new one
// add it to the scene.
scene.add(mesh);
}
});
var gui = new dat.GUI();
gui.add(controls, 'radius', 0, 40).step(1).onChange(controls.redraw);
gui.add(controls, 'detail', 0, 3).step(1).onChange(controls.redraw);
gui.add(controls, 'type', ['Icosahedron', 'Tetrahedron', 'Octahedron', 'Custom']).onChange(controls.redraw);
Demo http://codepen.io/sumitridhal/pen/NjbGpB
Related
I got a problem updating a material when resizing, with that material being instanced in a different file. I've removed everything I've tried so far, since it's leading nowhere other than more and more console errors...
Right now, I have my 'particles.js' file in which I create the geometry, material and mesh, the 'World.js' file in which I'm importing everything and adding it to the scene, and the 'Resizer.js' file. That material would be created and added to mesh in 'particles.js', but the function to update its size is called is 'World.js'.
Here is the part in which I'm trying to update its size on resizing window :
// Creating the material, with a size depending on the screen ratio
function createParticlesMaterial() {
const particlesMaterial = new ShaderMaterial({
uniforms: {
uPixelRatio: { value: Math.min(window.devicePixelRatio, 2) },
},
vertexShader: vShader,
fragmentShader: fShader,
});
return particlesMaterial;
}
// The function I'm trying to call in World.js
function resizeParticles(...particlesMaterial) {
particlesMaterial.uniforms.uPixelRatio.value = Math.min(
window.devicePixelRatio,
2,
);
return particlesMaterial;
}
export { resizeParticles };
// Creating the mesh to be exported to World.js
function createParticles() {
const particlesGeometry = createParticlesGeometry();
const particlesMaterial = resizeParticles();
const particlesMesh = new Points(particlesGeometry, particlesMaterial);
return particlesMesh;
}
export { createParticles };
Now the part of 'World.js' in which I'm adding the mesh, and trying to resize :
class World {
constructor(container) {
scene = createScene();
// ...
resizer = new Resizer(container, camera, renderer);
// ...
const particlesMesh = createParticles();
scene.add(particlesMesh);
resizer.onResize = () => {
resizeParticles(...particlesMaterial);
};
And my 'Resizer.js' :
const setSize = (container, camera, renderer) => {
camera.aspect = container.clientWidth / container.clientHeight;
camera.updateProjectionMatrix();
renderer.setSize(container.clientWidth, container.clientHeight);
renderer.setPixelRatio(window.devicePixelRatio);
};
class Resizer {
constructor(container, camera, renderer) {
// set initial size on load
setSize(container, camera, renderer);
window.addEventListener('resize', () => {
// set the size again if a resize occurs
setSize(container, camera, renderer);
// perform any custom actions
this.onResize();
});
}
onResize() {}
}
export { Resizer };
I know I need to pass particlesMaterial as an argument, for it to be updated and then sent again in 'particles.js', but it's really confusing for me. I've tried resizing directly in 'Resizer.js' after importing the mesh, passing 'resizer' as an argument, but nothin seems to work. I still don't know how to use it correctly, as I've been introduced to this only recently.
I know I could just export the particles material to 'World.js', then update it and create the mesh here, but I'm trying to organize my project as good as I can keeping everything in a separate file.
Thanks for your help and guidance !
Edit : Also, when I'm doing it this way (maybe it could work ?), it's logging this :
read properties of undefined (reading 'uniforms')
So I guess I need to put the resizing function into the createParticlesMaterial() function, but then I'd not know how to export it...
I have created two scenes on two different canvases:
var scene1 = new THREE.Scene()
var scene2 = new THREE.Scene()
scene1.add(camera1)
scene2.add(camera2)
next i have created two renders with "autoClear: false" :
renderer = new THREE.WebGLRenderer({alpha: true, antialias: true});
renderer.autoClear = false;
$obj3D[0].appendChild(renderer.domElement);
renderer2 = new THREE.WebGLRenderer({alpha: true, antialias: true});
renderer2.autoClear = false;
$obj3D2[0].appendChild(renderer2.domElement);
Now in update function i do (with renderer.clear() and renderer.clearDepth()) :
function update() {
controls.update();
controls2.update();
renderer.clear();
renderer.render(scene, camera);
renderer.clearDepth();
renderer2.clear();
renderer2.render(scene2, camera2);
renderer2.clearDepth();
}
But in this way it only shows the object on the last canvas.
Where am I wrong? I do not understand
Here there is the demo: http://atktest.000webhostapp.com/,
Here there is the code: https://github.com/ab89/3d
I explain better what I want to do:
I need to create a page that shows up to 5 identical objects.
That is, I load the .obj only once but then I will go to customize them.
I tried to do it but only shows me the last one.
I did it this way because the way I know to change the material of the object is this:
OBJ.traverse (function (child) {
if (child.name! == '') {
console.log (child)
var clone_material = child.material [0] .clone ();
clone_material.map = texture;
child.material = clone_material;
}
});
scene.add (OBJ);
so i thought i need to have more scenes to do it.
But I should also have: OBJ2, OBJ3, OBJ4, OBJ5 to do the 'traverse' function
and change the texture of each object
I'm trying to toggle between multiple 3d models (loaded with OBJMTLLoader.js) rendered in my three.js scene. I'm using dat.gui to create a dropdown list of model names and when one is chosen, the scene will add the respective obj model to the scene and remove the original one.
Here, I'm loading 2 separate models and adding the second one to the scene and then setting up the dat.gui controls:
var loader = new THREE.OBJMTLLoader();
loader.load("../assets/models/boardlego.obj", "../assets/models/boardlego.mtl", function (obj2) {
obj2.name = 'lego2';
});
loader.load("../assets/models/boardlego2.obj", "../assets/models/boardlego.mtl", function (obj) {
obj.name = 'lego';
scene.add(obj);
});
camControl = new THREE.OrbitControls(camera, renderer.domElement);
// call the render function
render();
//set up dat.gui controls
control = new function () {
this.Templates = 'shortboard';
}
addControls(control);
}
Then, the addControls function:
function addControls(controlObject) {
var gui = new dat.GUI();
gui.add(controlObject, 'Templates', ['shortboard', 'longboard', 'fish', 'funboard', 'simmons', 'parallel', 'gun']).listen();
Both models are loaded but only one is added to the scene. Is it possible to add the other model when the 'Templates' control is changed? I tried to create a separate function updateboard() and call it in the render function, like this:
function updateboard() {
if(controlObject.Templates === "longboard"){
scene.add(obj2);
}
}
But it didn't work. I also tried to set up an if statement in the render function:
function render() {
renderer.render(scene, camera);
if (scene.getObjectByName('lego')) {
scene.getObjectByName('lego').scale.set(control.Length, control.Thickness, control.Width);
}
if(control.Templates === "longboard"){
scene.add(obj2);
}
But it didn't work either. Any help would be greatly appreciated! Or if you can scout out an example that could help too! Thanks in advance.
Load your objects in array();
var my_models();
var loader = new THREE.OBJMTLLoader();
loader.load("../assets/models/boardlego.obj", "../assets/models/boardlego.mtl", function (obj2) {
obj.name = 'lego2';
my_models.push(obj);
});
scene.add(my_models[0]); // 0 would be the first model 1 is the second
I have a 3D object from Blender: cylinder.obj which I am able to render on the screen using Three.js. I also have the code in place to rotate the object using mouse. All this scene is inside a big div, say 600x600 pixel div.
I have the following code for render logic:
// Renders the scene and updates the render as needed.
function animate() {
// Render the scene.
renderer.render(scene, camera);
requestAnimationFrame(animate);
}
This in my understanding is creating a loop and rendering the object again and again on the page.
Is there a way I can render the object once in the scene and then only redraw when there is any change to the object like texture change or when rotating the object using mouse. Once this texture application is done or the mouse rotate is complete, I want the scene to say intact and not consume any CPU for 3D rendering.
So, in brief, is there a way to render the scene only when needed and then stay idle and not consume a lot of CPU when the scene is behaving like an image.
I am new to three.js. I am testing this application in Chrome and IE11. Let me know if any more information is needed for clarification.
EDIT: Adding full JS code:
// global variables
// Set up the scene, camera, and renderer as global variables.
var scene, camera, renderer;
var WIDTH = $("#myDiv").width(),
HEIGHT = $("#myDiv").height();
// Sets up the scene.
function init() {
// Create the scene and set the scene size.
scene = new THREE.Scene();
// Create a renderer and add it to the DOM.
if (Detector.webgl)
renderer = new THREE.WebGLRenderer({ antialias: true });
else
renderer = new THREE.CanvasRenderer(); //IE10 and below, and may be mobile devices
renderer.setSize(WIDTH, HEIGHT);
renderer.domElement.id = 'mycanvas'; //setting id for canvas element
console.log(renderer);
$("#myDiv").append(renderer.domElement);
// Create a camera, zoom it out from the model a bit, and add it to the scene.
camera = new THREE.PerspectiveCamera(45, WIDTH / HEIGHT, 0.1, 100);
camera.position.set(-3, 4, 12);
scene.add(camera);
// Create an event listener that resizes the renderer with the browser window.
window.addEventListener('resize', function () {
var WIDTH = window.innerWidth,
HEIGHT = window.innerHeight;
renderer.setSize(WIDTH, HEIGHT);
camera.aspect = WIDTH / HEIGHT;
camera.updateProjectionMatrix();
});
// Set the background color of the scene.
renderer.setClearColor(0x333F47, 1);
var light2 = new THREE.PointLight(0xffffff);
light2.position.set(0, 10, -10);
scene.add(light2);
var light3 = new THREE.PointLight(0xffffff);
light3.position.set(1, 5, 10);
scene.add(light3);
// Load in the mesh and add it to the scene.
var objLoader = new THREE.OBJLoader();
objLoader.load('objects/male.obj', function (object) {
object.position.y = 0;
object.scale.x = object.scale.y = object.scale.z = 1;
scene.add(object);
});
}
// Renders the scene and updates the render as needed.
function draw() {
//requestAnimationFrame(draw);
// Render the scene.
renderer.render(scene, camera);
}
init();
draw();
To do something only after something happens is called event-driven programming.
For example:
function OnMouseMove(evt) {
// do some transform update on object
// after done updating - draw!
draw();
}
function textureLoaded() {
// hurray, my texture's now loaded, I'm ready to draw now
draw();
}
function draw() {
renderer.render(scene, camera);
}
So instead drawing with timer, you draw thing only when you need them to, and in your case, after mouse move or after the texture's finished loading.
You could then attach OnMouseMove to an event listener, and textureLoaded as some Three.js's callback function for texture loading.
P. S. GPU is actually doing the drawing, not the CPU.
Figured out the logic using start - stop animation function on my events from link: Prevent requestAnimationFrame from running all the time
I am new to Three.JS and am trying to load a load a very simple (single cube) Sketchup model into Three.JS via the ColladaLoader, I get no errors but nothing is displayed:
var renderer = new THREE.WebGLRenderer();
var loader = new THREE.ColladaLoader();
var scene = new THREE.Scene();
var camera = new THREE.Camera();
loader.load('Test.dae', function (result) {
scene.add(result.scene);
});
camera = new THREE.PerspectiveCamera(35, window.innerWidth / window.innerHeight, 1, 10000 );
camera.position.set(0, 0, 5);
scene.add(camera);
renderer.render(scene,camera);
Can anyone spot any immediate errors? Thanks
Fixed. Whilst I had declared the renderer, I hadn't attached it to the document. The following code works:
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(100, window.innerWidth / window.innerHeight, 0.1, 1000);
var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
camera.position.set(0, 0, 4);
var loader = new THREE.ColladaLoader();
loader.load("test.dae", function (result) {
scene.add(result.scene);
});
function render() {
requestAnimationFrame(render);
renderer.render(scene, camera);
}
render();
You need to rerender the scene after the collada is loaded. Something like
loader.load('Test.dae', function (result) {
scene.add(result.scene);
renderer.render(scene,camera);
});
Additionally:
The camera might be inside your geometry. Faces are one-sided by default, they are invisible when looking from back/inside. You could try changing the camera or object location and/or set material.side = THREE.DoubleSide so the faces are visible from both front and back.
The scale of the model can be way off, so it can be too small or large to show. Try different camera positions (eg. z= -1000, -100, -10, -1, 1, 10, 100, 1000) and use lookAt() to point it to 0,0,0 or, I think colladaloader has a scale setting nowadays, not sure.
Loader defaults to position 0,0,0, so center of the world/scene. That doesn't necessarily mean center of screen, depends on camera. In some cases the Collada model itself can be such that the center is away from visible objects, so when it's placed on the scene it can be effectively "off-center". That's quite unlikely though.
You dont have any lights in the scene.
loader.load('test.dae', function colladaReady(collada) {
localObject = collada.scene;
localObject.scale.x = localObject.scale.y = localObject.scale.z = 2;
localObject.updateMatrix();
scene.add(localObject);
I think you need to add the object in the collada scene, or there might be problem with the scaling of the object that you are adding, scale it and than update the matrix of the object.
If your still having trouble it may be that some versions of IE stop you downloading local files (your Test.dae) so it may be worth trying it using firefox or putting your code up on a server.