Three.js : Box3 from an object not in scene - javascript

I have a problem adding a Bounding Box from an object in a different module. Right now, it is working fine as long as I write everything in my main function, but as soon as I create my function in a different file, and import in in the main file, it's not working anymore.
The error code I get :
Uncaught TypeError: Cannot read properties of undefined (reading 'updateWorldMatrix')
at Box3.expandByObject (three.module.js:4934:10)
at Box3.setFromObject (three.module.js:4852:15)
at camCollision (camColliders.js:68:37)
at HTMLDocument.<anonymous> (World.js:355:7)
camColliders.js being the file I'm trying to put the function in, and World.js my main file.
Here is the function :
function camCollision() {
const camBB = new Box3().setFromObject(camSphereDetector);
const boule1BB = new Box3().setFromObject(boule1Obj);
boule1BB.name = 'first';
const boule2BB = new Box3().setFromObject(boule2Obj);
boule2BB.name = 'second';
const boule3BB = new Box3().setFromObject(boule3Obj);
boule3BB.name = 'third';
const boulesBB = [boule1BB, boule2BB, boule3BB];
boulesBB.forEach((bbs) => {
if (bbs.intersectsBox(camBB)) {
console.log('got it');
}
});
}
document.addEventListener('mouseup', () => {
camCollision();
});
When I'm doing this in a separate file, i'm first importing the objects from another file and they are all Mesh.
I believe the problem is that I can't create the Bounding Boxes in a separate file, because it needs to be added to the scene first, and I'm only adding them in the scene in World.js. Yet, the error is leading me to line 68, the variable for 'boule1BB', which is weird because 'camBB' should have the problem first ?
Here is how I'm creating the Box3 (these are just copying some GLTF objects position and size, cause I can't manage to get a Box3 from it) :
const boule1Obj = new Mesh(
new SphereGeometry(2, 32, 16),
new MeshBasicMaterial({ color: 'red', transparent: true, opacity: 0 }),
);
boule1Obj.position.set(10, -3.5, 0);
Then, I would like to know, if I got the problem right : is there a way to create a Box3 in a different js file, from an object that is not added to the scene yet (even if it should when the function is being called) ? With a different way than 'setFromObject' maybe ? Or am I not understanding the real problem.
For a bit of context, the aim is to provide some informations about each model when the user clicks on it, and I'm planning on putting the informations as '.name' for each Mesh corresponding to a model. So I don't want to write all this stuff in the main file, but rather on a separate one.
I hope this is clear enough, and I've given enough content for a solution to be found. I couldn't find anyone else having this problem. If you need anything else, please tell me !
Thanks already for your help !

I believe the problem is that I can't create the Bounding Boxes in a separate file, because it needs to be added to the scene in World.js.
Not so. Since a constructed THREE.Mesh has a shape with extents (from its geometry) and a transform (by default, translated to the origin, with no scaling or rotation), Three.js can and will determine a bounding box from that information as though the mesh were in the scene. I've posted a demo of this on CodePen.
Nor should defining the object in one file and referencing it another make any difference, as long as the object is in scope and initialized at the time it's bound to.
Here, I suspect that you're assigning boule1Obj, boule2Obj, and boule3Obj in World.js. In that case, the imported function is being hoisted before the variables are assigned, and the function is seeingbinding to them as unassignedundefined.
Try changing camCollision() to accept the bouleXObjs as arguments.
function camCollision(...objs) {
const camBB = new Box3().setFromObject(camSphereDetector);
for(let i = 0; i < objs.length; i++) {
const objBB = new Box3().setFromGeometry(objs[i]);
objBB.name = `Bounding Box ${i + 1}`;
if(objBB.intersectsBox(camBB)) {
console.log("Got it!");
}
}
}
And then call it as
document.addEventListener("mouseup", () => {
camCollision(boule1Obj, boule2Obj, boule3Obj);
});

Related

three.js: How can I target object's position to another (grouped) object, while allowing rotation to follow AR camera?

I'm using an augmented reality library that does some fancy image tracking stuff. After learning a whole lot about this project, I'm now beyond my current ability and could use some help. For our purposes, the library creates an (empty) anchor point at the center of an IRL image target in-camera. Then moves the virtual world around the IRL camera.
My goal is to drive plane.rotation to always face the camera, while keeping plane.position locked to the anchor point. Additionally, plane.rotation values will be referenced later in development.
const THREE = window.MINDAR.IMAGE.THREE;
document.addEventListener('DOMContentLoaded', () => {
const start = async() => {
// initialize MindAR
const mindarThree = new window.MINDAR.IMAGE.MindARThree({
container: document.body,
imageTargetSrc: '../../assets/targets/testQR.mind',
});
const {renderer, scene, camera} = mindarThree;
// create AR object
const geometry = new THREE.PlaneGeometry(1, 1.25);
const material = new THREE.MeshBasicMaterial({color: 0x00ffff, transparent: true, opacity: 0.5});
const plane = new THREE.Mesh(geometry, material);
// create anchor
const anchor = mindarThree.addAnchor(0);
anchor.group.add(plane);
// start AR
await mindarThree.start();
renderer.setAnimationLoop(() => {
renderer.render(scene, camera);
});
}
start();
});
Everything I've tried so far went into the solutions already massaged into the (functioning draft) code. I have, however, done some research and found a couple avenues that might or might not work. Just tossing them out to see what might stick or inspire another solution. Skill-wise, I'm still in the beginner category, so any help figuring this out is much appreciated.
identify plane object by its group index number;
drive (override lib?) object rotation (x, y, z) to face camera;
possible solutions from dev:
"You can get those values through the anchor object, e.g. anchor.group.position. Meaning that you can use the current three.js API and get those values but without using it for rendering i.e. don't append the renderer.domElement to document."
"You can hack into the source code of mindar (it's open source)."
"Another way might be easier for you to try is to just create another camera yourself. I believe you can have multiple cameras, and just render another layer on top using your new camera."
I think it may be as simple as calling lookAt in the animation loop function:
// start AR
await mindarThree.start();
renderer.setAnimationLoop(() => {
plane.lookAt(new THREE.Vector3());
renderer.render(scene, camera);
});
This assumes the camera is always located at (0,0,0) (i.e., new THREE.Vector3()). This seems to be true from my limited testing. I found it helpful to debug by copy-pasting the MindAR three.js example into this codepen and printing some relevant values to the console.
Also note that, internally, MindAR's three.js module seems to directly modify the world matrix of the anchor.group object without modifying the position/rotation/scale parameters.

Instanciating an object via DOM element inside setup() on p5

I'm facing a problem where I'm trying to instantiate an object inside the setup function of p5, using a callback to do so:
This is the part of sketch.js relevant to the problem:
var g; //for graph instance
var dropzone; //for the file dropping area
function setup() {
createCanvas(640, 480);
dropzone = select('#dropzone'); //selects the <p> element I'm using as dropzone
dropzone.drop(process_file, unhighlight); //unhighlight is just a function to make the drop area not-highlighted
}
function draw() {
background(200);
if (g) {
g.show();
}else{
msg = "Please, load a file by dropping it in the box above";
text(msg, width/2, height/2);
}
}
// callback being used to process the text file content
function process_file(file){
data = file.data; //gets file data(content)
lines = data.split('\n'); // split the lines
digraph = lines[0] == 1 ? true : false; // is it a digraph?
v = int(lines[1]);
g = Graph(digraph, v); //the class I"m using to instantiate the graph
console.log(g); // <-- says undefined
g.init_graph(); // initialize all vertices
for (var i = 2; i < lines.length; i++) {
edge = lines[i].split(' ');
g.add_edge(edge[0], edge[1], edge[2]);
}
}
I already checked using console.log() and the content of the file is being correctly loaded, and the values are correct in what I was expecting. the Graph() class is in another file, but it is being imported as well as the sketch.js. I also tried to put the script importing at the end of the page, but got the same result, the g is still saying undefined.
What I didn't try is to put the whole code of my Graph class into the sketch.js file, but I will need to put more 15 algorithms on the class later, so the sketch file will grow in a unneeded size. I thought the by declaring g as a global variable I would have no problems with it.
I'm fairly inexperienced with JavaScript, so this is probably a rookie mistake about some kind of loading order, so please, if you answer this question, show me the why it is not working as it is. If there's need of any other piece of code, please let me know.
Thanks in advance.
Looking at this line:
g = Graph(digraph, v);
I think you meant to do this:
g = new Graph(digraph, v);
This creates a new instance and stores a reference to that instance in the g variable.

Colors are different in literally-drawing a real time collaborative board

I'm using Literally-Drawing which is real time collaborative drawing board.
Here is the Github and Demo link:
Github Literally-Drawing
Demo Link
This Repository is using two different libraries, one is Fabricjs and the second one is togetherjs, Initially there is only one default "PencilBrush" tool I wanted to add more tools into this but before that I was testing if I can dynamically change the color to "#ccc" or anything and brush type to "CircleBrush" They have connected together both the above mentioned libraries in the "Fabric-Whiteboard.js" file. There are two major functions WB.Core and WB.Collabrative. Core is creating the canvas and collabtive is pushing the data to Togetherjs server so that other browsers who are in the session can listen.
I have made some changes into Core.prototype._createCanvas function within WB.Core function originally it was only creating the canvas so I changed it to add "CircleBrush"
Originally
Core.prototype._createCanvas = function(id)
{
return new fabric.Canvas(id);
};
Changed to
Core.prototype._createCanvas = function(id)
{
var canvasCreate = new fabric.Canvas(id);
canvasCreate.freeDrawingBrush = new fabric['CircleBrush'](canvasCreate);
canvasCreate.freeDrawingBrush.color = "#CCCCCC";
return canvasCreate;
};
Now this part is working fine i guess because it is giving any error also i have made some change in the "drawStart" within WB.Collaborate function.
Originally
this.TJS.hub.on('drawStart', function(data)
{
var _base, _name;
if ((_base = _this.client)[_name = data.clientId] == null)
{
_base[_name] = new fabric['PencilBrush'](canvas); // origianlly
}
return _this.client[data.clientId].onMouseDown(data.point);
});
Problem:
The problem is I want the same drawing to be broadcasted to other sessions and if I add following two lines it gives me an error of "Cannot set property 'color' of undefined" where as I can override the default value within WB.core successfully.
Tried to Change it to:
this.TJS.hub.on('drawStart', function(data)
{
var _base, _name;
if ((_base = _this.client)[_name = data.clientId] == null)
{
var testCanvas = new fabric['CircleBrush'](canvas); // origianlly
testCanvas.freeDrawingBrush.color = "#ccc";
_base[_name] = testCanvas;
}
return _this.client[data.clientId].onMouseDown(data.point);
});
JSFiddle:
Here is the JsFiddle I tried to make but for some reason real time collabration is not working on both my version and original one
MyJsFiddle: https://jsfiddle.net/mwgi2005/p7cwu9ao/7/
This is what it looks like, I have set the color to grey and tool type to "CircleBrush". When I draw in parent Window to the left, it is drawing in grey color where as the child window is drawing in the black which is the default color defined in the fabric library, and vice versa.
Thanks

Toggle between multiple THREE.EffectComposer scenes using the same renderer in three.js

I am in the process of creating complex scenes with Composer in three.js.
I am wanting to know if it is possible to switch between two scenes that have different composer effects attributed to them. To gain some sort of perspective I have created an example which allows you to toggle between two normally rendered scenes.
Two scene example
From my understanding of how composer works you create an instance of it and then apply a render pass like so:
this.composer = new THREE.EffectComposer(this.renderer.default.init);
this.renderPass = new THREE.RenderPass(this.stage, this.camera);
this.renderPass.renderToScreen = true;
this.composer.addPass(this.renderPass);
and then apply a composer render like so:
this.composer.render();
So my question is if I have a second scene which a composer instance how can I then:
Use the same renderer (if possible)
Toggle between scene 1 and scene 2 like in a similar fashion to my example.
You can just switch from one effectComposer to another one the same way as you switch from one scene to the other. So that would be something like this:
const scenes = [
new THREE.Scene(),
new THREE.Scene()
];
const composers = scenes.map(function(scene) {
const composer = new THREE.EffectComposer(renderer);
// configure render-passes
const renderpass = new THREE.RenderPass(scene, camera);
renderpass.renderToScreen = true;
composer.addPass(renderpass);
scene.composer = composer;
return composer;
});
// then use the composer for the scene
let activeScene = scenes[0];
activeScene.composer.render();
You should even be able to reuse certain render-passes if you want to.
So before I begin I just want to give a shout out to Martin Schuhfuß who provided the insight to this solution.
Disclaimer
I am not an expert or professional Programmer in javascript and I by no means suggest that this is the only way of doing this, my presentation should be taken in an abstract fashion. My thoughts only explain the theoretical premises for which the solution is based on and implementation of this will depend upon your own architecture.
Architecture
I am using an OOP method in this example, you should be able to manipulate the solution to whatever method you are using.
Method
So based on Martin’s current example you can see that we able to add the composer property/object to our scene object this means that scenes could inherit the same composer effects which is brilliant, however In my case I have many scenes with different composer effects so I needed to rethink the problem.
1. Create a Composer object.
So I created an object to put composer objects in and keep to the nice ‘composer’ name convention when reference my effects.
This.composer = {};
2. Create a function to initialise RenderPasses with the concerning scenes.
So it is important to remember that we need to define and initailse the RenderPasses first before will call our composer. RenderPasses allows us to attribute the effects (know has shaders) that we want. In my example I have two scenes therefore I needed to create:
Two RenderPasses.
One RenderPass copied the scene.
The other RenderPass applied a sepia effect to its scene.
Example of Code:
this.init = function() {
stackoverflow.webgl.pass.base = new THREE.RenderPass(stackoverflow.webgl.scene.default,
stackoverflow.webgl.camera);
stackoverflow.webgl.pass.base2 = new THREE.RenderPass(stackoverflow.webgl.scene.test,
stackoverflow.webgl.camera);
stackoverflow.webgl.pass.sepia = new
THREE.ShaderPass(THREE.SepiaShader);
stackoverflow.webgl.pass.sepia.renderToScreen = true;
stackoverflow.webgl.pass.copy = new THREE.ShaderPass(THREE.CopyShader);
stackoverflow.webgl.pass.copy.renderToScreen = true;
}
Note: some file names had to be renamed due to legal reason, but the point being is that this function is called into a larger object.
3. Create a start function and lets assign composers to scenes
The purpose of this function is just to demonstrate how we combine it all together. So the code that we have is something like this.
// so we create an object to use for reference for EffectComposer objects
this.composer = {};
// this property is used to set the scene that we want
// this.scene.default = new THREE.Scene();
this.activeScene = this.scene.default;
// this function is just a wrapper for our code
this.start = function () {
// so call our initialise our RenderPasses
stackoverflow.webgl.pass.init();
// my own method of seting up the WebGLRenderer scene (You do it your Way!)
stackoverflow.webgl.renderer.default.setup;
// Create a new composer for scene 1
this.composer.ui = new THREE.EffectComposer(stackoverflow.webgl.renderer.default.init);
// Create a new composer for scene 1
this.composer.ui2 = new THREE.EffectComposer(stackoverflow.webgl.renderer.default.init);
// Now here is the cool stuff you can assign a composer to each scene
this.scene.default.composer = stackoverflow.webgl.composer.ui;
this.scene.test.composer = stackoverflow.webgl.composer.ui2;
// and i always like to check that things happen and they do ;)
console.log(this.scene.default);
console.log(this.scene.test);
console.log(this.composer);
// so you will need to add the passes some place, I created a function call render, (you do according to your code structure)
stackoverflow.webgl.render();
}
4. Define where you add the passes in your architecture
So now we will need to add the pass declarations (addpass functions from composer) for our effects to take place.
this.render = function () {
stackoverflow.webgl.composer.ui.addPass(stackoverflow.webgl.pass.base);
stackoverflow.webgl.composer.ui.addPass(stackoverflow.webgl.pass.copy);
stackoverflow.webgl.composer.ui2.addPass(stackoverflow.webgl.pass.base2);
stackoverflow.webgl.composer.ui2.addPass(stackoverflow.webgl.pass.sepia);
};
5. Add the EffectComposer render method
So this line of code will need to be placed wherever you do the composer processing (that depends on your setup).
stackoverflow.webgl.activeScene.composer.render();
Please take note that the ‘activecScene’ part makes reference to the actual scene used at the time. It is this that allows us to change the scene.
6. Create a button and toggle between the scenes
So I created two buttons in a dat.gui instance that allows me to toggle between the two scenes.
Again you can create a button, function whatever you like.
// again just change the vale of active scene to the scene you wish to change to
// button 1 value will be something like this:
stackoverflow.webgl.activeScene = stackoverflow.webgl.scene.test;
// button 2 value will takes us back to scene 1
stackoverflow.webgl.activeScene = stackoverflow.webgl.scene.default;
Conclusion
This is my method of achieving this effect, but if there are better or alternative ways then please add to the discussion and share.

Java script preload contents three.js

Simply i would like to pre-load contents with a bar or what ever really in my three.js project or JavaScript either way should be fine, im familiar with action-script to do this task but cant seem to manage this in javascript:
Heres the code:
var loader = new THREE.OBJMTLLoader();
loader.load( 'myobject.obj', 'myobject.mtl', function ( object ) {
for(k in object.children){
object.children[k].position.z =-5;
console.log("position Changed");
}
scene.add( object );
console.log("the item is loaded");
I have looked at the loader examples to do with three.js sample files but i really dont follow it to well, is there way to check the file size vs file bytes received and implement it with the above, i have tried but returns under-fine. Jquery is also welcomed
I have similar problem. I have created an Array to preload and store all the objects in there. Passed and returned the currentId, to be able to store them after that. In Objects3DListJson I put the info for the Paths
for (var curobj = 0; curobj < Objects3DListJson.length; curobj++) {
loaderMTL.load(curobj, ObjPath, mtlPath, function (object, currentID) {
var obj3D = new Obj3D(Objects3DListJson[currentID].ObjectName, object)
objects3D.push(obj3D);
}
}
Of course I changed OBJMTLLoader to return the Current ID. I hope this help you on some way.

Categories

Resources