I want to animate array of meshes but properties are not recognized
tl.staggerFrom(array,2,{"position.y":-100})
position.y doesnt change
when I use console.log(array[0].position.y) it gives the the inital value of position.y
how can I use stagger animation in threejs meshes
It looks like you're using GSAP (TweenMax), and that library requires you to use shallow objects as its animation parameter. This means you can't animate a variable that's 2+ levels deep; you can't ask it to animate array[0].position.y, but you can ask it to animate position.y. With this in mind, consider the following:
// Save reference to all positions into a new array
var allPositions = [];
for (var i = 0; i < array.length; i++) {
allPositions.push(array[i].position);
}
// Now use the shallower objects to animate the y attribute
tl.staggerFrom(allPositions,2,{y: -100});
I solved it with using proxy
I create a new proxy instance for every mesh and in its set property I just do what I want to do with it and add the proxy to an array and use that array in stagger
for ex:
for(let i=0;i<5;i++){
let mesh = new THREE.Mesh(geometry,material);
let proxy = new Proxy({positionY:null},{
set(target,key,value){
target[key] = value;
if(target[key] !== null){
mesh.position.y = target.positionY
}
return true;
},
get(target,key){
return target[key];
}
})
proxy.positionY = 0
aarray.push(proxy)
}
tl.staggerFrom(aarray,5,{positionY:-100})
Related
I am trying to make a series of cubes that can be clicked to highlight them. This will enable me to change their color or add a texture or manipulate them in some way. I have looked through the source code of all the interactive examples at https://threejs.org/examples/ and it appears that each example uses a slightly different way of creating and selecting objects in the scene. I am not used to using javascript though, so maybe I'm missing something simple.
I create an Object3D class named blocks to store all of the cubes
blocks = new THREE.Object3D()
I am using a for loop to create a 9 x 9 array of cubes starting at (0,0,0) coordinates with a slight gap between them, and add() them to blocks and add() blocks to the scene. example: (cube size 2,2,2)
function stack(mx,my,mz){
for (var i = 0; i < 9; i++){
line(mx,my,mz);
mz += 3;
}
}
function line(mx,my,mz){
for (var i = 0;i<9;i++){
var block = new THREE.Mesh( Geometry, Material);
block.position.x = mx;
block.position.y = my;
block.position.z = mz;
blocks.add(block);
mx+=3;
}
}
stack(mx,my,mz)
scene.add(blocks)
When I run this code, I can see them rendered. I use raycaster to .intersectObjects() which requires an array of objects. This is where I run into the problem of selecting just one object.
function onDocumentMouseDown(event) {
var vector = new THREE.Vector3(( event.clientX / window.innerWidth ) * 2 - 1, -( event.clientY / window.innerHeight ) * 2 + 1, 0.5);
projector.unprojectVector(vector, camera);
var raycaster = new THREE.Raycaster(camera.position, vector.sub(camera.position).normalize());
**var intersects = raycaster.intersectObjects(blocks.children, true);**
if (intersects.length > 0) {
intersects[0].object.material.transparent = true;
other code stuff blah blah blah
{
This will make all children clickable but they have the same .id as the first object created. so if I try to .getObjectById() in order to change something, it doesn't work.
I have tried to generate each element and add them to the scene iteratively instead of creating an object array to hold them and it still has a similar effect. I've tried storing them in a regular array and then using true parameter to recursively search the .intersectObject() array but it selects all of the objects when I click on it.
var intersects = raycaster.intersectObjects(blocks, true);
I have considered creating 81 unique variables to hold each element and statically typing an array of 81 variables (desperate option) but I can't find a secure way to dynamically create variable names in the for loop to hold the objects. This way was posted on stackoverflow as a solution to creating different named variables but it doesn't seem to create variables at all.
for (var i=0, i<9, i++){
var window["cube" + i] = new THREE.Mesh( Geometry, Material)
{
Main Question: How can I iteratively create multiple Mesh's (enough that statically typing each variable would be ill-advised) in a controllable way that I can select them and manipulate them individually and not as a group?
I think the reason why you met this problem is you reference same Material to build your Mesh, you did intersect a single object in blocks.children, but when you change some properties of the material others mesh who use the material would change too.
function line(mx,my,mz){
for (var i = 0;i<9;i++){
material = new THREE.MeshLambertMaterial({color: 0xffffff});
var block = new THREE.Mesh( Geometry, material);
block.position.x = mx;
block.position.y = my;
block.position.z = mz;
blocks.add(block);
mx+=3;
}
}
it works for me.
I exported a .json from the online 3D editor and I'm trying to load it and instantiate 20 versions of it, like this example. My code is flawed bc all 20 versions are actually acting like the same object. Not sure why they're not being added to the scene as separate objects in their given x,z coordinates.
var serverObject;
var allBrains = []
var xCenter;
var zCenter;
var spacing = .2;
var newServ;
var objectLoader = new THREE.ObjectLoader();
objectLoader.load("asset_src/model.json", function(obj) {
//give it a global name, so I can access it later?
serverObject = obj
//see what's inside of it
obj.traverse(function(child) {
if (child instanceof THREE.Mesh) {
console.log(child)
}
})
//was trying to add transparency this way, but ended up
//going through the online editor to apply it
// var cubeMaterial1 = new THREE.MeshBasicMaterial({
// color: 0xb7b7b7,
// refractionRatio: 0.98
// });
//Do i need to instantiate my mesh like this, if so, how do I make sure that it gets the materials from the json? The json has 6 children each with a different material
// serverObject = new THREE.Mesh( obj, cubeMaterial1 );
//make 20 versions of the file
for (var i = 0; i < 20; i++) {
xCenter = Math.cos(toRadians(i * spacing))
zCenter = Math.sin(toRadians(i * spacing))
serverObject.scale.set(.09, .09, .09)
//this amount of offset is correct for the scale of my world
//i was going to use my xCenter, zCenter but tried to simplify it till it works
serverObject.position.set((i * .1), controls.userHeight - 1, i * .1);
allBrains.push(serverObject)
//I've attempted a number of ways of adding the obj to the scene, this was just one
scene.add(allBrains[i]);
}
// see that there are 20 meshes
console.log(allBrains)
});
The return of my last console log looks like this:
At the moment, you have a single object (serverObject) which you manipulate and add multiple times, but each iteration of the loop just modifies the same object, overriding previous parameters.
You need to clone your mesh, using the... clone() method. You'll then be able to modify the settings of that object (the copy), and each of the meshes will remain independent.
Alternatively, you could run the objectLoader.load method inside the loop to create the object multiple times from the JSON file, but it's probably a waste of resources.
Thanks to #jcaor for the clone() idea, here is the working code:
var objectLoader = new THREE.ObjectLoader();
objectLoader.load("asset_src/model.json", function(obj) {
//give it a global name, so I can access it later
serverObject = obj
//see what's inside of it
obj.traverse(function(child) {
if (child instanceof THREE.Mesh) {
console.log(child)
}
})
for (var i = 0; i < 20; i++) {
var tempNew = serverObject.clone()
xCenter = Math.cos(toRadians(i * spacing))
zCenter = Math.sin(toRadians(i * spacing))
tempNew.scale.set(.05, .05, .05)
tempNew.position.set(xCenter, controls.userHeight - 1, zCenter);
allBrains.push(tempNew)
scene.add(allBrains[i]);
}
console.log(allBrains)
});
This looks like a pointer issue to me.
In JavaScript you can think of variables like pointers that is why you don't have to assign types to variables and how functions can be variables and still work in normal computer science.
In this case you are assigning the same pointer to each slot in the array.
This is a super simple version of the problem. obj2.foo was never changed but because we changed obj.foo obj2.foo changed because of the var simply pointing to the same object.
var obj = {
foo : 1
}
var obj2 = obj;
obj.foo = 2;
console.log(obj2.foo);
What I would do is create a new object and popluate it with the information of the master object in you case "serverObject"
allBrains.push(serverObject)
In HTML5 we usually use requestAnimationFrame to animate the shapes or objects. But is it a good practice to create these shapes and objects outside of the animate function?
Say I have a function
function animate() {
renderA();
renderB();
requestAnimateionFrame(animate);
}
and I have a pre-allocated array outside of animate() defined as:
let arr = new Array(99999);
let currentIndex = 4999;
Say the actual length of arr is 5000, the rest of the array is undefined.
Now I want to insert some new objects into the array, can I change the animate function to this?
function animate() {
renderA();
let length = 100;
for (let i = 0; i < length; i++) {
arr[currentIndex + 1 + i] = new SomeObject({...});
}
currentIndex += length;
renderB();
requestAnimateionFrame(animate);
}
If I remember correctly in this example the animate function will be called multiple times during canvas animation. So if I create a new object inside this animate() function and then render it afterwards the resources will eventually drain up? Is it OK to create new object inside the animation loop?
I am currently working on a private side/fun-project which is a small browser-game based on javascript and jQuery.
for this game i am saving data to an array of objects:
this.map.push(
{
"id" : id,
"x" : pos_x,
"y" : pos_y
}
);
now i need to find and/or update data in this array quite frequently (in "real time").
for example when i need to find the array-index at a position of my coords-system, i am using jQuery's $.each() function:
this.getIndexAtPos = function(x, y)
{
var index = false;
$.each(this.map, function(key, obj){
if(obj.x == x && obj.y == y)
{
index = key;
return false;
}
});
return index;
}
and another example: when i need to update data for an object with a specific id, i am using jQuery's $.map() function:
this.updateCoordsById = function(id, x, y)
{
this.map = $.map(this.map, function(obj, i)
{
if(obj.id == id)
{
//only update x, y!
obj.x = x;
obj.y = y;
return obj;
}
else
{
return obj;
}
});
}
it's all working fine for me so far, but i am on a rather fast computer and as the game is extending there will be more and more of these actions. like let's say a couple of hundred $.each and $.map calls per second.
that's why i am worried about performance issues, specially on slower computers.
MY QUESTIONS:
i know that $.each and $.map are (partly or fully) iterating my array on each call. that's why i used return false; inside my $.each function as soon as the entry is found to speed up things a little.
1. can i improve the performance by using other jQuery functions or changing the way i use them?
2. are there better ways (performance wise) to solve this by using native javascript?
3. should i use other datatypes/structures to boost performance?
note: the procedures i need to implement always are quite similar: find objects in the array by coords(x, y); find them by id; update objects by coords(x, y); update objects by id, and so on.
i would appreciate any help, opinions and suggestions!
OK as this is a grid it makes sense to store it as a grid in memory.
A 2-dimensional array will give you much faster access than a one-dimensional one, and also lets you access object directly by their coordinates instead of checking the coordinates of each object.
You can also have an container object which contains all your objects, with their ID as the property, which gives you fast lookup by ID.
You can put these together by storing the ID of the object in the grid, then looking up the object in the container by the ID.
I've created a fiddle at http://jsfiddle.net/d75zkvnb/1/ which shows a simple example of this. The structure of the Map object is below:
var Map = {
init: function(){
this.Width = 10;
this.Height = 10;
// grid stores the coordinates and the IDs of the object at that coordinate
// note this is stored by y then x.
this.grid = [];
// stored the actual objects, indexed by id
this.objects = {};
// set up the grid
for(var y = 0; y < this.Height; ++y)
{
this.grid.push(new Array(this.Width));
}
},
// gets the object at (x, y)
GetAtCoord: function(x, y)
{
// find the id
var id = this.grid[y][x];
// directly access it from the array
return this.objects[id];
},
// gets the object with the ID
GetById: function(objId)
{
// direct access to the object
return this.objects[objId];
},
// add an object at its stored coordinates
AddObject: function(obj){
this.grid[obj.y][obj.x] = obj.id;
this.objects[obj.id] = obj;
},
// Move an object in the grid
MoveObject: function(objId, newX, newY)
{
// get the objct to move
var theObj = this.objects[objId];
// reove it from the grid
this.grid[theObj.y][theObj.x] = null;
// updates its stored position
theObj.x = newX;
theObj.y = newY;
// add it back to the grid
this.grid[newY][newX] = objId;
}
};
I am making a game and I am trying to create a game end function. How would I go about resetting all images to their starting positions?
You can programaticly store all positions of a image class inside a Object inside a array.
And after you can loop the array restoring theirs positions back.
var positions = new Array(0); //positions array
function objPos(obj){
return({
'obj':obj,
'x':obj.style.left,
'y':obj.style.top
});
}
function savePositions(imgContainer){
var imgs = imgContainer.getElementsByTagName('IMG');
for(var i=0; i<imgs.length; i++){
positions[i]=objPos( imgs[i] );
}
}
function restoreStoredPositions(){
for(var i=0; i<positions.length; i++){
var img=positions[i];
img.style.top=positions[i].y;
img.style.left=positions[i].x;
}
}
Call the savePositions function on the initial state, and you can call restoreStoredPositions later. If you want persistent data, stringy the array to JSON and you can store it to a Cookie or HTML5 LocalStorage.
I know this is pure JS but this is just a concept that I wrote now.
You can adapt this to use the each() and the jquery selector on place of the getElementBy...
I used the style.top and left just for readability, computedStyles is the correct path.