Javascript members not updated after function call - javascript

I am trying create a simple three.js app, but I'm getting stuck on some of the nuances of javascript. I don't really know how to explain the problem without an example:
var Model = function() {
THREE.Object3D.call(this);
this.loadedMesh = null;
this.meshIsLoaded = false;
};
Model.prototype = {
constructor: Model,
load: function(path, mtlName, objName) {
var onProgress = function(xhr) {...};
var onError = function(err) {...};
THREE.Loader.Handlers.add( /\.dds$/i, new THREE.DDSLoader());
mtlLoader.setPath(path);
mtlLoader.load(mtlName, function(materials) {
materials.preload();
objLoader.setMaterials(materials);
objLoader.setPath(path);
objLoader.load(objName, function(object) {
object.position.set(0, -5, 10);
object.scale.set(5, 5, 5);
object.rotation.y = Math.PI; // 180 degrees
scene.add(object);
this.loadedMesh = object;
this.meshIsLoaded = true;
});
}, onProgress, onError);
}
};
This wrapper should be a container for a three model. The loading works fine, but when I try and save the Model into a variable, none of the internal members are altered outside of the Model scope.
var spaceman = new Model();
spaceman.load("ACES/", "acesjustforroomshow.mtl", "acesjustforroomshow.obj");
...
function update() {
if (spaceman.meshIsLoaded)
spaceman.loadedMesh.rotation.x += 0.1;
}
It's weird because if I debug the app and wait until the model is loaded, I can hover over the internal members and they are populated. When I hover over the spaceman object, the values for loadedMesh and meshIsLoaded remain the default.
Is there some aspect of prototyping that I'm not understanding here?

My guess is that the issue is the this variable. Within the callback function you're passing to objLoader.load, it may no longer be set to your object. If I'm right, either of the below fixes should work:
Using bind:
objLoader.load(objName, function(object) {
object.position.set(0, -5, 10);
object.scale.set(5, 5, 5);
object.rotation.y = Math.PI; // 180 degrees
scene.add(object);
this.loadedMesh = object;
this.meshIsLoaded = true;
}.bind(this));
Capturing this in another variable:
var that = this;
objLoader.load(objName, function(object) {
object.position.set(0, -5, 10);
object.scale.set(5, 5, 5);
object.rotation.y = Math.PI; // 180 degrees
scene.add(object);
that.loadedMesh = object;
that.meshIsLoaded = true;
});

Related

Problems rendering a list of threeJs renderers

I'm attempting to display a list of renderer/scene/camera "groups" on the same html page. I ran into problems when trying to implement a render loop on all of them, using requestAnimationFrame. I am, however, unable to successfully render even a single frame by looping over the list.
calling renderer.render on each of the "groups" in the same function that I instantiate them in works fine. I refactored the code to allow calling render on each of the renderers outside of said function, and my blue spheres disappear;
I have some elements...
<div class="1"></div>
<div class="2"></div>
<div class="3"></div>
<div class="4"></div>
that I add some threeJs things to
$(document).ready(function(){
addResizeandCenterListener(window, $("#container"), 4/3);
addResizeListener($("#container"), $(".playerhalf"), 2/1);
for (let item of [".1", ".2", ".3", ".4", ".5", ".6", ".7", ".8"]) {
var threeDetails = addThreeRendererToElem($(item), item);
var scene = threeDetails["scene"];
addClickListenerToElem($(item), scene);
}
});
threeList = [];
//this adds a renderer/scene/camera to the element "elem"
function addThreeRendererToElem(elem, name){
var width = elem.innerWidth();
var height = elem.innerHeight();
var scene = newScene();
scene.name = name;
addDefaultLight(scene);
addBlueBall(scene);
var camera = addDefaultCamera(scene, width, height);
var renderer = newRenderer(width, height);
elem[0].appendChild(renderer.domElement);
//this works fine regardless of how many renderer/scene/camera groups I have placed in the DOM
//renderer.render(scene, camera);
var threeDict = {"renderer":renderer, "scene":scene, "camera":camera}
threeList.push(threeDict)
//this does not work, even if I only place a single renderer/scene camera group in the DOM
renderList();
}
function renderList() {
var tlist = threeList;
for (var i = 0; i< tlist;i++) {
var threeDict = tlist[i];
threeDict["renderer"].render(threeDict["scene"],
threeDict["camera"]);
}
}
function newScene(){
scene = new THREE.Scene();
return scene;
}
function newCamera(w, h){
camera = new THREE.PerspectiveCamera(45, w / h, 0.1, 100);
return camera;
}
function newRenderer(w, h){
renderer = new THREE.WebGLRenderer({alpha:true, antialias:true});
renderer.setSize(w, h);
return renderer;
}
function addObjectToScene(obj, scene){
scene.add(obj);
}
function ballObj(color){
var ballGeometry = new THREE.SphereGeometry(3, 16, 16);
var ballMaterial = new THREE.MeshPhongMaterial({color: 0x33aaff});
var ball = new THREE.Mesh(ballGeometry, ballMaterial);
return ball;
}
function spotLightObj(){
var spot = new THREE.SpotLight(0xffffff);
return spot;
}
function addDefaultCamera(scene, width, height){
var camera = newCamera(width, height);
camera.position.set(1,1,20);
camera.lookAt(scene.position);
return camera;
}
function addDefaultLight(scene){
var spot = spotLightObj();
spot.position.set(1, 10, 4);
addObjectToScene(spot, scene);
}
function addBlueBall(scene){
var hexcolor = "0x33aaff";
bball = ballObj(hexcolor);
addObjectToScene(bball, scene);
}
I expect to see some blue spheres, like when I am calling render from inside addThreeRendererToElem, but I don't. Instead, the canvases are all blank. The canvases are in the correct places on the web page, inspecting threeList in the console shows that all the objects are indeed there. There are no error messages.
Incorrect ending condition on your for-loop inside the renderList function.
You are missing .length. Should be:
function renderList() {
var tlist = threeList;
for ( var i = 0; i < tlist.length; i++ ) {
var threeDict = tlist[i];
threeDict["renderer"].render( threeDict["scene"], threeDict["camera"] );
}
}

How to update shadow when updating vertices of custom geometry in a-frame / three.js

I have created a custom component in a-frame that creates a bespoke geometry. I am using the tick function to animate the update of the geometry's vertices. That is all working fine, however the shadow on the geometry does not update.
In my below example I toggle a flat shape between 2 states, folded and unfolded. When, for example it animates from folded to unfolded, the faces retain the shadow as though it were still folded.
Here is the HTML, please see <a-fold> in this example starting with the folded state (this can be changed to 'unfolded' by changing that attribute).
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script src="https://aframe.io/releases/0.7.0/aframe.min.js"></script>
</head>
<body>
<a-scene test>
<a-fold
state="folded"
color="blue"
position="-7.5 1 -20">
</a-fold>
<a-plane
position="0 0 -4"
rotation="-90 0 0"
width="20"
height="20"
color="#7BC8A4">
</a-plane>
<a-sky
color="#ECECEC">
</a-sky>
</a-scene>
</body>
</html>
and the javascript
//a trigger to test state change
AFRAME.registerComponent('test', {
init: function () {
setTimeout(function(){
var target = document.querySelector('a-fold');
target.setAttribute('changestate', true);
}, 2000);
}
});
//component
AFRAME.registerComponent('folder', {
schema: {
state: {type: 'string', default: 'unfolded'},
color: {type: 'color', default: '#000'},
changestate: {type: 'boolean', default: false},
changedur: {type: 'number', default: 400},
},
store: {
unfolded: [{"x":15,"y":0,"z":0},{"x":15,"y":15,"z":0},{"x":0,"y":15,"z":0},{"x":0,"y":0,"z":0},{"x":0,"y":15,"z":0},{"x":15,"y":0,"z":0},],
folded: [{"x":15,"y":0,"z":0},{"x":12,"y":12,"z":5},{"x":0,"y":15,"z":0},{"x":3,"y":3,"z":5},{"x":0,"y":15,"z":0},{"x":15,"y":0,"z":0},],
},
update: function () {
this.geometry = new THREE.Geometry();
var geometry = this.geometry
var verts = this.store[this.data.state]
$.each( verts, function( i, v3 ) {
geometry.vertices.push ( new THREE.Vector3(v3.x, v3.y, v3.z) );
});
geometry.faces.push(new THREE.Face3(0, 1, 2))
geometry.faces.push(new THREE.Face3(3, 4, 5))
geometry.computeBoundingBox();
geometry.computeFaceNormals();
geometry.computeVertexNormals();
this.material = new THREE.MeshStandardMaterial({color: this.data.color});
this.material.side = THREE.DoubleSide;
this.material.needsUpdate = true
this.mesh = new THREE.Mesh(this.geometry, this.material);
this.mesh.castShadow = true;
this.mesh.receiveShadow = true;
this.el.setObject3D('mesh', this.mesh);
},
tick: function (t, td) {
if (this.data.changestate === true){
var dur = this.data.changedur
var geom = this.el.getObject3D('mesh').geometry
var currVerts = geom.vertices
var toVerts = this.store[this.gotoState()]
var somethingChanged = false;
var allAxis = ["x", "y", "z"];
var thisParent = this
$.each( currVerts, function( i, vert) {
var curr = currVerts[i]
var to = toVerts[i]
$.each( allAxis, function( i, axis ) {
if (thisParent.approxEqual(curr[axis], to[axis])) {
somethingChanged = somethingChanged || false;
} else if (curr[axis] < to[axis]) {
var step = thisParent.stepCalc(curr[axis], to[axis], dur, td)
curr[axis] += step;
somethingChanged = true;
} else {
var step = thisParent.stepCalc(curr[axis], to[axis], dur, td)
curr[axis] -= step;
somethingChanged = true;
}
});
});
geom.verticesNeedUpdate = somethingChanged;
}
},
gotoState: function (){
var to = ""
var current = this.data.state
var states = Object.keys(this.store)
$.each( states, function( i, state) {
if ( state != current ){
to = state
}
});
return to;
},
approxEqual: function (x, y) {
return Math.abs(x - y) < 0.00001;
},
stepCalc: function (curr, to, dur, delta){
var distance = Math.abs(curr - to)
var speed = distance/dur
var step = speed*delta
return step;
},
remove: function () {
this.el.removeObject3D('mesh');
}
});
//primitive
AFRAME.registerPrimitive('a-fold', {
defaultComponents: {
folder: {}
},
mappings: {
state: 'folder.state',
color: 'folder.color',
changestate: 'folder.changestate',
changedur: 'folder.changedur'
}
});
Sorry, I know its a lot but I don't want to simplify it in case I lose the problem. As you can see in the component I have tried to add this.material.needsUpdate = true as well as adding this.mesh.castShadow = true and this.mesh.receiveShadow = true but it does not make a difference.
On a side note, if I do an animation of the whole entity (ie a rotation) I can see the material reflecting the light so the material does respond to lighting dynamically, just not when I change the shape by updating the vertices.
Here are 2 images that demonstrate what I mean.
You can see that in 2nd image, although the plane is flat, its shadows suggest it has a fold. And it does the same the other way around (ie if it starts unfolded, the shadow is correct and that persists when it folds).
Here is a js fiddle
Any more info needed, please ask. Any help is much appreciated as ever.
Lighting is calculated using normal vectors, in your case you're moving the mesh vertices but leaving the normal vectors, which is why the lighting doesn't change. You need to add geometry.computeVertexNormals(); in your tick function after you've altered the vertices.
In 3D shadows are different to shading or lighting, the problem you're having is related to lighting but not shadows. Which is why adjusting the mesh.castShadow properties didn't behave like you expected.

How to load an image on only front part of a json model object using Three Js

Hi i am trying to render an image on the model object which is using a json file. I have been able to render the model but the image won't render on the JSON at all.
var loader1 = new THREE.AssimpJSONLoader();
loader1.load(modelUrl, function(assimpjson){
// console.log(assimpjson.geometry);
assimpjson.traverse(function(child){
if(child instanceof THREE.Mesh) {
// newMesh = new THREE.Mesh(assimpjson, mat);
object_json = assimpjson;
assimpjson.traverse(function(child) {
if(child instanceof THREE.Mesh){
// I am able to set the color of the child
// but how can i set the image on the model?
// I tried loading the image like this
// var image = new THREE.TextureLoader().load('assets/images/image1.jpg');
// Is there a way than i can directly set the image to the mesh child in here
// and give a custom height and width to the image.
// child.material.color.setHex(0xFFFFFF);
}
});
assimpjson.scale.x = 30;
assimpjson.scale.y = 30;
assimpjson.scale.z = 30;
assimpjson.position.x = 120;
assimpjson.position.y = -200;
assimpjson.position.z = 0;
assimpjson.updateMatrix();
if (previous) scene.remove(previous);
scene.add(assimpjson);
previous = assimpjson;
}
});
});
Thanks a tonnn for any help!!!
How about this? -
function load_model(modelUrl, texture) {
var loader1 = new THREE.AssimpJSONLoader();
loader1.load(modelUrl, function (assimpjson) {
object_json = assimpjson;
if (texture) {
assimpjson.traverse(function (child) {
if (child instanceof THREE.Mesh && child.material) {
child.material.map = texture;
child.material.needsUpdate = true;
}
});
}
assimpjson.scale.x = 30;
assimpjson.scale.y = 30;
assimpjson.scale.z = 30;
assimpjson.position.x = 120;
assimpjson.position.y = -200;
assimpjson.position.z = 0;
assimpjson.updateMatrix();
if (previous)
scene.remove(previous);
scene.add(assimpjson);
previous = assimpjson;
});
}
// instantiate a loader
let loader_t = new THREE.TextureLoader().load("path", function (texture) {
load_model(modelUrl, texture);
},
// Function called when download progresses
function (xhr) {
console.log((xhr.loaded / xhr.total * 100) + '% loaded');
},
// Function called when download errors
function (xhr) {
console.log('An error happened');
load_model(modelUrl);
});
If you want to have a texture on certain triangles of your model you can assign a THREE.MultiMaterial (Array of Materials) MultiMaterial (does not exist any more)
Depending if your loader generates Geometry or BufferGeometry you need to assign the wanted material ID to each triangle of your model.
For BufferGeometry you can assign BufferGeometry.groups with { start: Integer, count: Integer, materialIndex: Integer }
For Geometry you need to assign foreach Geometry.faces the Face3.materialIndex with an Integer
Hope this helps.

Textgeometry facing user when camera turns

I am creating this 3d graph that represents a network of people. The nodes in the network are the names of people and between them lines are drawn whi9ch represents a connection. The purpose of this network is that people can turn it around and look through it to see what their network looks like, however, when I turn the camera around the network, the 3d text that I creat3ed with textgeometry is stuck to the same position and unformtunately therefore unreadable for the user when they want to look at the network from a different side. I know that this question has been asked several times before and I have read most of the answers on here, but I still cannot get it to work.
The problem that I encounter mostly is that when I try to do something like this:
nodeRenderer.lookAt(camera.position);
I get a problem that the camera is not defined. The camera is added to the scene, however this happens in a different .js document. This is where the scene gets created and the camera gets added (/ngraph.three/index.js):
var THREE = require('./lib/three');
module.exports = function (graph, settings) {
var merge = require('ngraph.merge');
settings = merge(settings, {
interactive: true
});
var beforeFrameRender;
var isStable = false;
var disposed = false;
var layout = createLayout(settings);
var renderer = createRenderer(settings);
var camera = createCamera(settings);
var scene = settings.scene || new THREE.Scene();
(...)
function renderNode(nodeId) {
nodeRenderer(nodeUI[nodeId]);
}
(...)
function initNode(node) {
var ui = nodeUIBuilder(node);
if (!ui) return;
// augment it with position data:
ui.pos = layout.getNodePosition(node.id);
// and store for subsequent use:
nodeUI[node.id] = ui;
scene.add(ui);
}
(...)
function createCamera(settings) {
if (settings.camera) {
return settings.camera;
}
var container = renderer.domElement;
var camera = new THREE.PerspectiveCamera(75, container.clientWidth/container.clientHeight, 0.1, 3000);
camera.position.z = 400;
return camera;
}
And this is where the nodes get created (ngraph.three/lib/defaults.js):
var THREE = require('./three');
module.exports.createNodeUI = createNodeUI;
module.exports.createLinkUI = createLinkUI;
module.exports.nodeRenderer = nodeRenderer;
module.exports.linkRenderer = linkRenderer;
function createNodeUI(node) {
var nodeMaterial = new THREE.MeshFaceMaterial( [
new THREE.MeshPhongMaterial( { color: 0x00cccc, shading: THREE.FlatShading } ), // front
new THREE.MeshPhongMaterial( { color: 0xffffff, shading: THREE.SmoothShading } ) // side
] );
var nodeGeometry = new THREE.TextGeometry( node.data, { size: 5, height: 2, curveSegments: 6, font: "helvetiker", weight: "normal", style: "normal" });
var nodeDirection = new THREE.Quaternion (THREE.Camera.Quaternion);
return new THREE.Mesh(nodeGeometry, nodeMaterial);
}
function createLinkUI(link) {
var linkGeometry = new THREE.Geometry();
linkGeometry.vertices.push(new THREE.Vector3(0, 0, 0));
linkGeometry.vertices.push(new THREE.Vector3(0, 0, 0));
var linkMaterial = new THREE.LineBasicMaterial({ color: 0x00cccc });
return new THREE.Line(linkGeometry, linkMaterial);
}
(...)
function nodeRenderer(node) {
node.position.x = node.pos.x;
node.position.y = node.pos.y;
node.position.z = node.pos.z;
}
(...)
I tried ui.quaternion = camera.quaternion; in /ngraph.three/index.js, but this didn't do anything, and I've tried nodeUI.lookAt(camera.position); but this gave an error: TypeError: nodeUI.lookAt is not a function.
PS: I'm using Three.js Rev. 68, Ngraph and node.js.
I can only guess since a lot of code is missing but your createNodeUI returns a THREE.Mesh which is a subclass of Object3D which has the function .lookAt(vector) to align its local z-axis to the given vector.
And you said you receive this Error : TypeError: nodeUI.lookAt is not a function that looks like you re trying to call .lookAt of the array and not of the node itself.
so try nodeUI[node.id].lookAt(camera.position)

Flame is not showing in THREE.js World

I am making a flame using the THREE.js and spark.js, but when I render the world I can't see the flame and the world is empty. I saw the console for the error but there is no error regarding this. I tried a lot but can't find out the actual error. Here is the code.
threexSparks = new THREEx.Sparks({
maxParticles : 400,
counter : new SPARKS.SteadyCounter(300)
});
//threexSparks.position.x = 1000;
// setup the emitter
//var emitter = threexSparks.emitter();
var counter = new SPARKS.SteadyCounter(500);
var emitter = new SPARKS.Emitter(counter);
var initColorSize = function() {
this.initialize = function(emitter, particle) {
particle.target.color().setHSV(0.3, 0.9, 0.4);
particle.target.size(150);
};
};
emitter.addInitializer(new initColorSize());
emitter.addInitializer(new SPARKS.Position(new SPARKS.PointZone(new THREE.Vector3(1000, 0, 0))));
emitter.addInitializer(new SPARKS.Lifetime(0, 0.8));
emitter.addInitializer(new SPARKS.Velocity(new SPARKS.PointZone(new THREE.Vector3(0, 250, 00))));
emitter.addAction(new SPARKS.Age());
emitter.addAction(new SPARKS.Move());
emitter.addAction(new SPARKS.RandomDrift(1000, 0, 1000));
emitter.addAction(new SPARKS.Accelerate(0, -200, 0));
Thanks
Tere is strange problems with particles and WebGL render. It will be good if you're using CanvasRender. But for WebGL not.
Also in your code you forgot about creating threejs objects for particles. Sparks.js allows only interface for particles. But you need to create particles itself.
You can look at my jsfiddle example. there I use modified version of sparks.js library. Changes just to be able to override VectorPool behaviour.
http://jsfiddle.net/YeJ4X/35/
Main part there is:
var particleCount = 1800,
particles = new THREE.Geometry(), //store particle vertices
pMaterial = new THREE.ParticleBasicMaterial({
size: 10,
map: txture, //in jsfiddle i create texture from canvas
transparent: true
});
var particleSystem = new THREE.ParticleSystem(particles, pMaterial); //threejs particle system
//initialize our particles (and set that are dirty). sparkjs initialize it later for us
for(var p = 0; p < particleCount; p++) {
v = new THREE.Vector3(numMax,numMax,numMax);
v.isDirty=true;
particles.vertices.push(v);
}
SPARKS.VectorPool.__pools = particles.vertices; //initialize vectors pool
And there is my new vector pool for sparksjs
SPARKS.VectorPool = {
__pools: [],
get: function() {
var ret = _.find(this.__pools, function(v){return v.isDirty});
ret.isDirty=false;
return ret;
},
release: function(v) {
v.isDirty=true;
v.set(numMax,numMax,numMax);
}
};
Of course you must care about count of partices that are used in sparks.js and precreated by hands.
My sparkjs fork is here: https://github.com/elephanter/sparks.js there I fix problem with lastest tweenjs and add other little changes I described before.

Categories

Resources