I am loading FBX models for my project. I Want to create multiple objects of the same FBX model but loading each separately is very inefficient so I would like to save one loaded model and clone it. This concept is working with the .OBJ format.
Everything works as expected but then I use "model.clone();" nothing really happens.
The model is no longer visible but I get no errors. If you compare the cloned model with the original they look exactly the same so I don't know why it's not showing up.
This is how it looks when it's working:
When it's not working it's just a blank canvas.
Here is some simplified working source code:
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<script src="https://vitalkia.com/jsLibraries/zlib.min.js"></script>
<script src="https://threejs.org/build/three.js"></script>
<script src="https://threejs.org/examples/js/loaders/FBXLoader.js">
</script>
<script>
var aspectRatio = 240 / 135;
var scene;
var renderer;
var loaderFBX;
var camera = new THREE.PerspectiveCamera(45, aspectRatio, 1, 10000);
//Actual code
//Setus up scene and renderer
sceneSetup();
//Load fbx and render
//IGNORE ERROR ABOUT NOT FINDING THE RIGHT TEXTURE. It still works
loaderFBX.load("https://threejs.org/examples/models/fbx/Samba Dancing.fbx", function(model){
//Sending the object "model" works
//But using model.clone() does not. It doesn't give any errors or anything, it's just not visible
//If you compare the cloned model with the original they look exactly the same.
var sendModel = model;//Works
//var sendModel = model.clone();//Doesn't works
console.log("Original: ");
console.log(model);
console.log("Cloned: ");
console.log(model.clone());
calcDistance(sendModel);
scene.add(sendModel);
renderer.render(scene, camera);
});
function calcDistance(model){
var bbox = new THREE.Box3().setFromObject(model);
var camDistance = bbox.max.z;
if(bbox.max.x > camDistance){
camDistance = bbox.max.x;
}
if(bbox.max.y*aspectRatio > camDistance){
camDistance = bbox.max.y*aspectRatio;
}
camera.position.z = camDistance;
model.position.x -= bbox.getCenter().x;
model.position.y -= bbox.getCenter().y;
}
function sceneSetup(){
loaderFBX = new THREE.FBXLoader();
scene = new THREE.Scene();
renderer = new THREE.WebGLRenderer({antialias: true, alpha: true});
renderer.setSize(240, 135);
document.body.appendChild(renderer.domElement);
var light1 = new THREE.PointLight(0xffffff, 1, 100000000);
light1.position.set(0, 0, 15);
scene.add(light1);
var newLightPoint = new THREE.HemisphereLight(0x8be2ff, 0xfff9cf, 0.55);
scene.add(newLightPoint);
camera.position.set(0, 0, 100);
scene.add(camera);
}
</script>
Is this how you should do it to reuse models instead of having to reload them for every object? Or is there a better way?
Related
I'm a beginner to three.js.I'm trying to build something similar to this https://virtualshowroom.nissan.in/car-selected.html?selectedCar=ext360_deep_blue_pearl. I built everything using three.js, but I'm not able to figure out how to create a hotspot(like the red dot in the above link) and show pop up when you click on it. below is my project code, let me know if anything else is required.
<html>
<head>
<title>My first three.js app</title>
<style>
body { margin: 0; }
canvas { display: block; }
</style>
</head>
<body>
<h1></h1>
<script src="./three.js"></script>
<script type="module">
import { GLTFLoader } from 'https://threejs.org/examples/jsm/loaders/GLTFLoader.js';
import { OrbitControls } from 'https://threejs.org/examples/jsm/controls/OrbitControls.js';
var renderer,scene,camera;
scene = new THREE.Scene();
scene.background = new THREE.Color(0xfff6e6)
camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );
renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
var loader = new GLTFLoader();
var hlight = new THREE.AmbientLight(0x404040, 100)
scene.add(hlight)
var directionalLight = new THREE.DirectionalLight(0xffffff, 100)
directionalLight.position.set(0,1,0)
directionalLight.castShadow = true
scene.add(directionalLight)
var light = new THREE.PointLight(0xffffff, 10)
light.position.set(0, 300, 500)
scene.add(light)
var light2 = new THREE.PointLight(0xffffff, 10)
light2.position.set(500, 100, 0)
scene.add(light2)
var light3 = new THREE.PointLight(0xffffff, 10)
light3.position.set(0, 100, -500)
scene.add(light3)
var light4 = new THREE.PointLight(0xffffff, 10)
light4.position.set(-5000, 300, 0)
scene.add(light4)
var controls = new OrbitControls(camera, renderer.domElement);
document.body.appendChild(renderer.domElement)
var loader = new GLTFLoader();
loader.load( './scene.gltf', function ( gltf )
{
scene.add( gltf.scene );
}, undefined, function ( error ) { console.error( error ); } );
// load a image resource
camera.position.z = 5;
var animate = function () {
requestAnimationFrame( animate );
renderer.render( scene, camera );
};
animate();
</script>
</body>
</html>
Those “hotspots” as you call them are Annotations where the annotation content is basically pure HTML.
The tutorial in the link is probably the best step-by-step readiness you can follow to learn how to do it in your scene.
I can give a walkthrough on the steps required to get the desired effect since I have done it a few times myself.
define a 3d point in your scene where the hotspot should be. You can optionally nest this in a an other Object3D to make sure it scales, moves and rotates with the model / parent.
Add a plane to this point load a image texture to this plane. and there you have your visible hotspot
update the hotspots to make sure they are always looking at the camera by using the lookAt function.
when the user clicks the screen cast a raycast against all the hotspots you have in your scene. Easiest way to do this is by storing all your hotspots in an array.
When the raycast hits a hotspot get the location either of the hitpoint or the hotspots location. Transform that to screen coordinates. Search on stackoverflow how to do this. I am sure there is a post about this.
Final step display your html on the correct location you obtained from the previous step.
The advantage of this method is that the hotspot will integrate nicely with the model in your scene. Since html based hotspots will always be on top of the scene.
That is about all that is to it. Let me know if you need any further clarification!
I'm trying to display an OBJ 3D model as a wireframe that can be moved with OrbitControls using Three.js. I'm new with three.js, so I apologize if I'm missing something "obvious".
I've been able to get a cube to show as a wireframe with OrbitControls.
In a different setup, I was able to display the OBJ 3D model as a wireframe. Therefore, the issue isn't related to the 3D model.
The issue happens when I try to mix both approaches together.
I've tried to go about this in two ways:
I tried to insert the OBJLoader into the cube environment. (most promising)
I tried to add OrbitControls into the working, but unmovable, wireframe OBJ environment. (didn't really work at all)
Most of the other answers focus on the order of scripts: yes, I have tried to move the three.min.js, OrbitControls.js, OBJLoader.js, and my main working model.js into order and disorder, in every possible way.
Here's my code:
<head>
<style media="screen"> html, body { margin: 0; } #model_container { background-color: #333333; margin: 50px; } </style>
<title>OBJ Wireframe</title>
<script defer src="script/three/build/three.min.js"></script>
<script defer src="script/three/examples/jsm/controls/OrbitControls.js"></script>
<script defer src="script/three/examples/jsm/loaders/OBJLoader.js"></script>
<script defer src="script/model.js"></script>
</head>
<body>
<div id="model_container"></div>
</body>
const globalWidth = window.innerWidth - 100;
const globalHeight = window.innerHeight - 100;
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, globalWidth / globalHeight, 0.1, 1000);
camera.position.x = 300;
camera.position.y = -6;
camera.position.z = 1;
const renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true
});
renderer.setClearColor( 0xffffff, 0);
renderer.setSize(globalWidth, globalHeight);
const canvas = document.getElementById("model_container");
canvas.appendChild( renderer.domElement );
const ambient = new THREE.AmbientLight(0xffffff);
scene.add(ambient);
const loader = new THREE.OBJLoader();
loader.load("assets/castle.obj", function(object) {
const geometry = object.children[0].geometry;
THREE.GeometryUtils.center(geometry);
const material = new THREE.MeshLambertMaterial({});
const mesh = new THREE.Mesh(geometry, material);
mesh.material.wireframe = true;
scene.add(mesh);
})
const controls = new THREE.OrbitControls(camera);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
controls.rotateSpeed = 0.1;
controls.target.set(0, 0, 0);
var animate = function () {
requestAnimationFrame(animate);
controls.update();
renderer.render(scene, camera);
};
animate();
Here are the errors I can't fix after 3 days of searching and trying everything I could:
OrbitControls.js:9: Uncaught SyntaxError: Unexpected token {
OBJLoader.js:5: Uncaught SyntaxError: Unexpected token {
Uncaught TypeError: THREE.OBJLoader is not a constructor at model.js:23
The errors seem to be rooted in the THREE.js files themselves, which I find very strange. I'm all out of ideas and documentation to go through.
I'm running this with MAMP, but have also tried uploading to a server to see if the issue is related to being local files or anything similar.
<script defer src="script/three/examples/jsm/controls/OrbitControls.js"></script>
<script defer src="script/three/examples/jsm/loaders/OBJLoader.js"></script>
It seems you are loading the files from the wrong path. Files from the jsm directory are ES6 modules which are imported via ES6 import syntax. Try to include the files from the js directory:
<script defer src="script/three/examples/js/controls/OrbitControls.js"></script>
<script defer src="script/three/examples/js/loaders/OBJLoader.js"></script>
three.js R106
I'm trying out Three.js, I followed a tutorial step-by-step. In the code editor I'm using( Visual Studio Code 2019) everything seems normal, but when I test it, nothing appears on the page.
the editor I'm using, used the desktop as the place to locate my code, since it is a .html file I could run it. When I did that, the only thing that appeared was the navbar I programmed, nothing else
This is the entire three.js code:
<script src="three.js-dev/build/three.min.js"> </script>
<script>
var scene = new THREE.scene();
var camera = new THREE.PerspectiveCamera( 75, window.innerWidth/window.innerHeight);
var renderer = new THREE.WebGLRenderer({antialias = true});
renderer.setSize( window.innerWidth, window.innerHeight);
$('body').append( renderer.domElement);
var geometry = new THREE.BoxGeometry( 1, 1, 1 );
var material = new THREE.MeshBasicMaterial ( { color = 0xff0000 });
var cube = new THREE.Mesh( geometry, material) ;
scene.add( cube );
cube.position.z = -5;
var animate = function () {
cube.rotation.x += 0.01;
renderer.render(scene, camera);
requestAnimationFrame( animate );
};
animate();
and this the code before it:
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8" />
<script
src="https://code.jquery.com/jquery-2.1.4.js"
integrity="sha256-siFczlgw4jULnUICcdm9gjQPZkw/YPDqhQ9+nAOScE4="
crossorigin="anonymous"></script>
</head>
<body>
<div id="navbar"><span>Three.js Tut</span></div>
I expected a red cube rotating, but nothing appeared. ¿Is there something I'm doing wrong?
You have a few errors in your code:
THREE.scene should be THREE.Scene
{antialias = true} should be {antialias: true}
{ color = 0xff0000 } should be { color: 0xff0000 }
Live demo with your code: https://jsfiddle.net/so736vxj/
BTW: If you are using VSCode, I'm a bit irritated that no errors are highlighted. Especially the last two syntax errors should be reported since it is no valid JavaScript.
three.js R105
I´ve started programming using three.js a little time ago, and I would like to know how to make a menu on a page before it loaded the program using three itself.So, for instance I would like before running my game to have two options:"start" and "music control" being desplayed before it loaded itself.
Is this made using three, or do you use html, javascript files?(Sorry, I really don´t know how to even start it).
If possible, I would like some tutorials or even videos about it.
I would like something like this for example:
http://www.dilladimension.com/
http://lights.helloenjoy.com/
You probably do want to use html to display the menu and js to control the instantiation of your scene.
Rather than use the page load event to set up your renderer, etc, put them inside a function, and use js events to begin the function:
function start() {
var renderer = new THREE.WebGLRenderer();
...
// more setup
...
renderer.render(scene, camera);
};
Then activate this method with a button or a link:
<button onclick="start()">
Start Scene
</button>
Here's a test implementation:
function start() {
var renderer = new THREE.WebGLRenderer();
renderer.setSize(800, 600);
document.body.appendChild(renderer.domElement);
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(
35, // Field of view
800 / 600, // Aspect ratio
0.1, // Near plane
10000 // Far plane
);
camera.position.set(-15, 10, 10);
camera.lookAt(scene.position);
var geometry = new THREE.BoxGeometry(5, 5, 5);
var material = new THREE.MeshLambertMaterial({
color: 0xFF0000
});
var mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
var light = new THREE.PointLight(0xFFFF00);
light.position.set(10, 0, 10);
scene.add(light);
renderer.setClearColor(0xdddddd, 1);
renderer.render(scene, camera);
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r77/three.min.js"></script>
<button onclick="start()">
Start Scene
</button>
I'm trying to load obj files using three.js and webGL. Currently I can load one object by directly changing the code, but I want users to be able to submit their own .obj files.
So far, this is the code that I have. I tried using various examples but I can't really grasp how to use it correctly. I think I might have to write a function to update, but would that require redoing everything in init? (By "redo" I pretty much mean copy/paste.) Is there an easier way of doing it?
relevant parts of html file:
<form id="upload" method="get">
.obj Upload: <input type="text" size="20" name="objfile" id="objfile">
<!-- <input type="button" id="objsubmit" value="Send"> -->
</form>
<script type="text/javascript" src="cubemain.js"></script>
cubemain.js:
var scene, camera, renderer;
var container;
var objFile;
window.onload=function() {
init();
render();
}
function init() {
// upload
objFile = document.getElementById("objfile");
// set up scene, camera, renderer
scene = new THREE.Scene();
var FOV = 70;
var ASPECT = window.innerWidth/window.innerHeight;
var NEAR = 1;
var FAR = 1000;
camera = new THREE.PerspectiveCamera(FOV, ASPECT, NEAR, FAR);
// move camera back otherwise in same position as cube
camera.position.z = 5;
var light = new THREE.AmbientLight(0x101030);
scene.add(light);
var directionalLight = new THREE.DirectionalLight( 0xffeedd );
directionalLight.position.set( 0, 0, 1 );
scene.add( directionalLight );
// use OBJLoader
var loader = new THREE.OBJLoader();
loader.load(objFile.value + ".obj", function(object) {
scene.add(object);
});
renderer = new THREE.WebGLRenderer();
renderer.setSize(.70*window.innerWidth, .75*window.innerHeight);
document.body.appendChild(renderer.domElement);
renderer.setClearColor(0xCC99FF, 1);
var controls = new THREE.OrbitControls(camera, renderer.domElement);
}
function render() {
requestAnimationFrame(render);
renderer.render(scene, camera);
}
It really depends on various aspects. What I did was simple.
//In the html.html
The object: <input type="text" id="inputFile">
<input type="button" id="submitBtn" onclick="loadTheFile()">
Now when the button is clicked, it will trigger a function in your javascript file. All you have to do now is to create the loadTheFile function and another function that loads the obj file. Here a simple snippet of it.
//The rest of your javascript file
function loadTheFile(){
//First we need to get the value of the text. We shall store the name of
//the file in a fileName variable
var fileName = document.getElementById("inputFile").value;
//Now what you should do is to run another function, let us call it loadingFile
//which has the argument fileName, the name of the file that we have received
loadingFile(fileName);
}
function loadingFile(fileName){
//Use your loader and instead of the static path, use fileName as your new path
//Please note that the path that you pass into the loader is a relative path
//to your main applciation. So for example you store your file in the model folder
//and the fileName variable is just a file name and not a relative directory, then
//you should add the path of the folder like as follows:
var completePath = "models/" + filename;
//the rest of your loader
// use OBJLoader
var loader = new THREE.OBJLoader();
loader.load(completePath + ".obj", function(object) {
scene.add(object);
});
}
Hope that helps!
To help you further, since we have declared and define a helper function, loadingFile, you can change the loader part in your init() function as follows:
before:
var loader = new THREE.OBJLoader();
loader.load(objFile.value + ".obj", function(object) {
scene.add(object);
});
after:
loadingFile(objFile.value);
Follow up question: Does your code run properly? As in you can see the object? This is because I see that your code is much cleaner and more concise than what I usually use; so if it does I can use your code to improve the readability of mine. Thank you very much and I really hope my answer helps you! ^u^