How to explode a 3D model group in ThreeJs? - three.js

I have a 3D model , containing many child meshes.
I want to "explode" that mesh ... meaning that every child mesh has to move away from a central point exactly like that thread :
Exploded view algorithm for CAD
To do that , I generate a bounding box of the ancestor mesh , with THREE.Box3 , and compute the center with .getCenter() .
The issue here comes from threeJS's scene graph ...
when setting the position of each child , the whole 3D model does explode when I set it to do so in all directions (XYZ) , but when I choose other combinations like X alone , the explode either bugs and spreads in all directions when it shouldn't, or doesn't explode at all .
Here is the code I am using:
/**
* obj : the current node on the scene graph
* box_ct_world : a vec3 center of the bounding box
*
*/
explode : function(obj , parent , box_ct_world , dif){
var scene = this.el.sceneEl.object3D ; //I am using Aframe , so this is how I retrieve the whole scene .
var object = this.el.object3D ; //same here , this for retrieving the whole 3D model .
var speed = 1 ;
if(obj instanceof THREE.Mesh){
var box_center = box_ct_world ;
var position = obj.position ;
/**
* so this is the beginning of my troubles : I am considering retrieving a world position instead of a local position...
* I have to translate the nodes without the parent matrices interfering .
* The current ligne works the same way as if I retrieved local transformations ... exploding on XYZ , but glitching on the other cases .
*/
position.setFromMatrixPosition(scene.matrixWorld) ;
var addx =0 ;
var addy =0 ;
var addz =0 ;
/**
* This is the vector from the center of the box to the node . we use that to translate every meshes away from the center
*/
if(this.data.X === true){
var addx =(position.x - box_center.x)*this.data.factor *speed ;
}
if(this.data.Y === true){
var addy =(position.y - box_center.y)*this.data.factor *speed;
}
if(this.data.Z === true){
var addz =(position.z - box_center.z)*this.data.factor *speed;
}
var explode_vectorx= addx;
var explode_vectory= addy;
var explode_vectorz= addz;
/**
* this is for making the nodes translate back to their original locations
*/
if(diff < 0 ){
if(explode_vectorx > 0)
explode_vectorx = -explode_vectorx ;
if(explode_vectory > 0)
explode_vectory = -explode_vectory;
if(explode_vectorz > 0)
explode_vectorz = -explode_vectorz;
}
if(diff > 0 ){
if(explode_vectorx < 0)
explode_vectorx = -explode_vectorx ;
if(explode_vectory < 0)
explode_vectory = -explode_vectory;
if(explode_vectorz < 0)
explode_vectorz = -explode_vectorz;
}
var vector = new THREE.Vector3(explode_vectorx , explode_vectory, explode_vectorz) ;
console.log(vector.x+" " + vector.y+ " " + vector.z );
/**
* and here is my biggest problem :
* this function seems to use ancestors matrices
* I need the nodes to move without calling the ancestors matrices , but still keep their original rotation and scale .
*/
obj.position.set(vector.x , vector.y , vector.z ) ;
if(obj.children.length != 0 ){
for(var i = 0 ; i < obj.children.length ; i++){
this.explode(obj.children[i] ,obj, box_ct_world , dif);
}
}
}
else{
if(obj.children.length != 0 )
{
for(var i = 0 ; i < obj.children.length ; i++){
this.explode(obj.children[i] ,obj, box_ct_world , dif);
}
}
}
},
so here is a screenshot using a XYZ explode :
It works pretty fine .
But when using a single axis , like X :
the mesh still goes on all axis as if it was random, when it is supposed to translate only on the X axis .
I think this could be fixed by translating the meshes directly in world space , but I don't know how that can be done in threeJS , especially in that case , because I cannot change the values in matrixWorld (A frame is updating the world matrix every frame I think for these objects , so even if I change any value in the matrixWorld , these will be overriden)
thank you all for your help.

Related

How to rotate my character while jumping (Gamemaker Studio 2)

I would like to make a simple animation of the character rotating itself when it jumps. I'm making an indie platformer so this should be simple to do, I think, but I'm too newbie for this.
Here's the movement code.
//------------------------- MOVEMENT INPUT
xMove = kRight - kLeft;
xSpd = xMove * mSpd;
ySpd += 0.65;
//------------------------- JUMP
onGround = place_meeting(x,y+1,oSolid);
if(onGround) airJump = 1;
if(kJump){
if(onGround or airJump > 0){
ySpd = -12;
airJump = 0;
}
}
//------------------------- FINAL MOVEMENT
if(place_meeting(x + xSpd, y, oSolid)){
while(!place_meeting(x + sign(xSpd), y, oSolid)) x += sign(xSpd);
xSpd = 0;
}
if(place_meeting(x + xSpd, y + ySpd, oSolid)){
while(!place_meeting(x + xSpd, y + sign(ySpd), oSolid)) y += sign(ySpd);
ySpd = 0;
}
x += xSpd;
y += ySpd;
if xSpd < 0 dir = -1;
if xSpd > 0 dir = 1;
The player is a simple square, so I would like to make it rotate 360 degrees while on the air.
You should be able to use image_angle for this, changing the value will change the angle of the sprite, and continiously increasing/decreasing that value will simulate a rotation.
However, keep in mind that if you rotate the sprite, the hitbox of the sprite will rotate as well. You can probably set the hitbox apart from the sprite so it won't interrupt with each other.
Example:
https://manual.yoyogames.com/GameMaker_Language/GML_Reference/Asset_Management/Sprites/Sprite_Instance_Variables/image_angle.htm
For player movement collision handling you want to avoid using image_angle variable by using your own variable for the image rotation with the draw_sprite_ext function. Also by change you end up wanting to use the image angle for anything its good to wrap it mostly later if your trying to use fov and what not.
For example
function Scr_Player_Create(){
image_offset = 0;
}
function Scr_Player_Step(){
image_offset += (keyboard_check(vk_right) - keyboard_check(vk_left)) * 10;
image_offset = wrap(image_offset, 0, 359);
}
function Scr_Player_Draw(){
draw_sprite_ext( sprite_index, image_index, x, y, image_xscale, image_yscale,
image_angle + image_offset, image_blend, image_alpha );
draw_text(10, 10, image_offset);
}
function wrap(wrap_value, wrap_minimum, wrap_maximum){
// Credit: Juju from GMLscripts forums!
var _mod = ( wrap_value - wrap_minimum ) mod ( wrap_maximum - wrap_minimum );
if ( _mod < 0 ) return _mod + wrap_maximum else return _mod + wrap_minimum;
}
Another approach you could do to avoid image_angle effecting your collision is this
var _angle = image_angle;
image_angle += image_offset;
draw_self();
image_angle = _angle;

How to morphTarget of an .obj file (BufferGeometry)

I'm trying to morph the vertices of a loaded .obj file like in this example: https://threejs.org/docs/#api/materials/MeshDepthMaterial - when 'wireframe' and 'morphTargets' are activated in THREE.MeshDepthMaterial.
But I can't reach the desired effect. From the above example the geometry can be morphed via geometry.morphTargets.push( { name: 'target1', vertices: vertices } ); however it seems that morphTargets is not available for my loaded 3D object as it is a BufferGeometry.
Instead I tried to change independently each vertices point from myMesh.child.child.geometry.attributes.position.array[i], it kind of works (the vertices of my mesh are moving) but not as good as the above example.
Here is a Codepen of what I could do.
How can I reach the desired effect on my loaded .obj file?
Adding morph targets to THREE.BufferGeometry is a bit different than THREE.Geometry. Example:
// after loading the mesh:
var morphAttributes = mesh.geometry.morphAttributes;
morphAttributes.position = [];
mesh.material.morphTargets = true;
var position = mesh.geometry.attributes.position.clone();
for ( var j = 0, jl = position.count; j < jl; j ++ ) {
position.setXYZ(
j,
position.getX( j ) * 2 * Math.random(),
position.getY( j ) * 2 * Math.random(),
position.getZ( j ) * 2 * Math.random()
);
}
morphAttributes.position.push(position); // I forgot this earlier.
mesh.updateMorphTargets();
mesh.morphTargetInfluences[ 0 ] = 0;
// later, in your render() loop:
mesh.morphTargetInfluences[ 0 ] += 0.001;
three.js r90

Three js particle system with mouse interaction

I try change behaviour particles with this example:
http://threejs.org/examples/#webgl_particles_random
to more like this:
http://minimal.be/lab/fluGL/
Now when I simply change this fragment of code:
for ( i = 0; i < scene.children.length; i ++ ) {
var object = scene.children[ i ];
if ( object instanceof THREE.ParticleSystem ) {
object.rotation.z = time * ( i < 4 ? i + 1 : - ( i + 1 ) );
}
}
Into that:
for ( i = 0; i < scene.children.length; i ++ ) {
var object = scene.children[ i ];
if ( object instanceof THREE.ParticleSystem ) {
object.position.x = mouseX;
object.position.y = -mouseY;
}
}
All particles move globally without change distance and velocity.
The change, which I present is only example. How to modify the code, to avoid global changes in move particles? Do I have to change something in shaders? Or Particle System in three.js is enough to create mouse attractor behaviour?

Walk a line between two points in a 3D voxel space visiting all cells

I have a line-of-sight problem I need to solve by visiting all possible cells in a 3D voxel space between two (non-grid-aligned) points.
I have considered using a 3D Bresenham algorithm, but it will skip out some cells.
A naive implementation might be to just check points along the line at a higher resolution than the voxel grid, but I was hoping for a more intelligent solution.
Anyone got any leads?
Came up with this, or see: http://jsfiddle.net/wivlaro/mkaWf/6/
function visitAll(gx0, gy0, gz0, gx1, gy1, gz1, visitor) {
var gx0idx = Math.floor(gx0);
var gy0idx = Math.floor(gy0);
var gz0idx = Math.floor(gz0);
var gx1idx = Math.floor(gx1);
var gy1idx = Math.floor(gy1);
var gz1idx = Math.floor(gz1);
var sx = gx1idx > gx0idx ? 1 : gx1idx < gx0idx ? -1 : 0;
var sy = gy1idx > gy0idx ? 1 : gy1idx < gy0idx ? -1 : 0;
var sz = gz1idx > gz0idx ? 1 : gz1idx < gz0idx ? -1 : 0;
var gx = gx0idx;
var gy = gy0idx;
var gz = gz0idx;
//Planes for each axis that we will next cross
var gxp = gx0idx + (gx1idx > gx0idx ? 1 : 0);
var gyp = gy0idx + (gy1idx > gy0idx ? 1 : 0);
var gzp = gz0idx + (gz1idx > gz0idx ? 1 : 0);
//Only used for multiplying up the error margins
var vx = gx1 === gx0 ? 1 : gx1 - gx0;
var vy = gy1 === gy0 ? 1 : gy1 - gy0;
var vz = gz1 === gz0 ? 1 : gz1 - gz0;
//Error is normalized to vx * vy * vz so we only have to multiply up
var vxvy = vx * vy;
var vxvz = vx * vz;
var vyvz = vy * vz;
//Error from the next plane accumulators, scaled up by vx*vy*vz
// gx0 + vx * rx === gxp
// vx * rx === gxp - gx0
// rx === (gxp - gx0) / vx
var errx = (gxp - gx0) * vyvz;
var erry = (gyp - gy0) * vxvz;
var errz = (gzp - gz0) * vxvy;
var derrx = sx * vyvz;
var derry = sy * vxvz;
var derrz = sz * vxvy;
do {
visitor(gx, gy, gz);
if (gx === gx1idx && gy === gy1idx && gz === gz1idx) break;
//Which plane do we cross first?
var xr = Math.abs(errx);
var yr = Math.abs(erry);
var zr = Math.abs(errz);
if (sx !== 0 && (sy === 0 || xr < yr) && (sz === 0 || xr < zr)) {
gx += sx;
errx += derrx;
}
else if (sy !== 0 && (sz === 0 || yr < zr)) {
gy += sy;
erry += derry;
}
else if (sz !== 0) {
gz += sz;
errz += derrz;
}
} while (true);
}
As far as I remember the original Bresenham algorithm assumes that movement along diagonals is allowed, in your case is makes sense to disallow it.
But the main idea is the same - for every voxel answer the question "what's next?"
Every voxel has 6 faces each leading to a different neighbour. Just check centre of which voxel is closer to the line than others. That's the next voxel.
Note: this assumes that voxel has the same size along every axis, if that's not the case, you should calculate modified distance (every component should be divided by voxel size along corresponding axis)
I think 3d Bresenham is the way to go, just tweaked a bit. As a first pass at the problem, proceed as Bresenham, but be suspicious when you're about to take a step, or you've just taken a step, as these are the places where the line could pass through extra cells.
For simplicity, let's assume that z is dominant, meaning that z increments every step. The 3d Bresenham question is: "when do we increment/decrement in x or y?" The answer is when accumulated error in x reaches .5, or when the error in y does, or both.
For your case, I think you need to have a secondary threshold that uses slopeY = deltaY/deltaZto decide if the line is about to cross into a neighboring cell. If stepZ is the change in z along the line for each pixel, then a test like error > .5 - slopeY/stepZ should tell you to get cells on both sides of the line in y. A similar test tells you if you have to get the extra cell in x. If you have to get the extra cell in both x AND y, then you have to get the cell diagonal to the Bresenham cell as well.
If you've detected that you added a cell in y before the increment, you won't add a cell after. If you haven't added a y cell before, you will have to after, unless you happened to pass through a cell corner. How you handle that depends on your use-case.
These are my thoughts on the issue, I haven't tested anything, but something like it should work.
Here is a public link to a recent port of my voxel ray from C++ into javascript:
https://github.com/jeremykentbgross/EmpathicCivGameEngine/blob/master/engine/public/scripts/Ray2D.js
Note: the port is currently in 2D on a quadtree (instead of 3D on an octtree), but only because one dimension is commented out for my 2D javascript engine. It works fine in my 3D C++ engine (where I ported it from) so if you uncomment the Z axis lines it will work. The file also has a lot of inline comments on how the math works.
You should also reference the RayTracer2D.js (in the same directory) which uses the ray to find all intersected objects and their intersection points in the order they are hit.
For reference the quad tree structure it is tracing through is also in the same folder: QuadTree.js
Note that you could also ray trace lower LOD's simply by limiting how deep you traverse into the tree during the trace.
Hope that helps.
https://code.activestate.com/recipes/578112-bresenhams-line-algorithm-in-n-dimensions/
Here is a numpy implementation for N-D bresenham line drawing in case someone came across this thread from googling 'bresenham 3d python'.

Rotation Automation

I've run into a few problems with this expression in Maya, basically anytime the radius is less than 1, the calculation is thrown off way too much.
float $radius = `getAttr prefix66_calculations_shape.rad`;
float $prevZval = `getAttr -time (frame -1) prefix66_driver.translateZ`;
float $prevXval = `getAttr -time (frame -1) prefix66_driver.translateX`;
float $Zval = prefix66_driver.translateZ - $prevZval;
float $Xval = prefix66_driver.translateX - $prevXval;
float $distance = ($Zval * $Zval) + ($Xval * $Xval);
float $direction;
$distance = sqrt($distance);
if ($prevZval > prefix66_driver.translateZ) {
$direction = 360;
}
else {
$direction = 360;
}
float $rotation = ($distance / (2 * 3.142 * $radius)) * $direction;
print $rotation;
pCube1.rotateX = pCube1.rotateX + $rotation;
Maybe my order of operations is wrong?
The rotation part of your code looks ok. However, you have an if/else block that returns the same thing in both cases, and as mentioned by #joojaa, you can avoid getAttr -time if you cache the translation values. In fact you should avoid getAttr and setAttr completely in expressions.
Instead, refer to the attributes you want directly and Maya will create connections for you. This is much faster and less prone to errors when you rename nodes and so on.
To cache the translation values, and calculate change in position you can add attributes to the node and use them in the expression.
Let's say you have a cylinder called wheel that rotates around its local X and is parented to a group node called control:
Add a vector attribute: control.lastTranslate
Add a vector attribute: control.deltaTranslate
Add a float attribute: control.distance
Here's an expression that will store the change in translation, then rotate the wheel based on the distance travelled.
// When deltaTranslate is calculated, lastTranslate still has its previous value.
control.deltaTranslateX = control.translateX - control.lastTranslateX;
control.deltaTranslateY = control.translateY - control.lastTranslateY;
control.deltaTranslateZ = control.translateZ - control.lastTranslateZ;
control.lastTranslateX = control.translateX;
control.lastTranslateY = control.translateY;
control.lastTranslateZ = control.translateZ;
control.distance = mag(<<control.deltaTranslateX,control.deltaTranslateY,control.deltaTranslateZ>>);
// Get radius from history node (or somewhere) and move the wheel's hub off the floor.
wheel.translateY = polyCylinder1.radius;
// add rotation to the wheel
float $tau = 6.283185307179586;
wheel.rotateX = wheel.rotateX + ( control.distance* -360.0) / (polyCylinder1.radius * $tau );
It's best to test this kind of thing by animating rather than dragging nodes around in the view.
If you wanted to make the wheel aim to the direction of travel, you could add a locator at translate + deltaTranslate and hook up an aim constraint.
e.g.
aimLocator.translateX = (control.deltaTranslateX / control.distance) + control.translateX;
aimLocator.translateY = (control.deltaTranslateY / control.distance) + control.translateY;
aimLocator.translateZ = (control.deltaTranslateZ / control.distance) + control.translateZ;
Dividing by distance will normalize the offset. You should probably check that distance is not zero.
I believe I have figured it out :)
Queering the old traslation average, with the new translation average will give me a true or false answer, which is what I needed to change direction.
Also added an if statement that if the ball is static and rotating, that the wheel doesn't turn automatically.
float $oldRotateAverage;
float $oldTransAverage;
float $direction;
nurbsCircle1.DeltaTranslateX = nurbsCircle1.translateX - nurbsCircle1.LastTranslateX;
nurbsCircle1.DeltaTranslateY = nurbsCircle1.translateY - nurbsCircle1.LastTranslateY;
nurbsCircle1.DeltaTranslateZ = nurbsCircle1.translateZ - nurbsCircle1.LastTranslateZ;
nurbsCircle1.LastTranslateX = nurbsCircle1.translateX;
nurbsCircle1.LastTranslateY = nurbsCircle1.translateY;
nurbsCircle1.LastTranslateZ = nurbsCircle1.translateZ;
nurbsCircle1.Distance = mag(<<nurbsCircle1.DeltaTranslateX,nurbsCircle1.DeltaTranslateY,nurbsCircle1.DeltaTranslateZ>>);
if ($oldTransAverage >= (nurbsCircle1.LastTranslateX + nurbsCircle1.LastTranslateY + nurbsCircle1.LastTranslateZ)){
$direction = -360.00;
} else {
$direction = 360.00;
};
if (Sh54_anim.auto == 1 )
{
Sh54_point_grp.rotateZ -= nurbsCircle1.Distance * $direction / 2 / 3.14 / 2;
};
if ((nurbsCircle1.rotateX + nurbsCircle1.rotateY + nurbsCircle1.rotateZ) != $oldRotateAverage && nurbsCircle1.Distance == $oldTransAverage){
Sh54_anim.auto = 0;
} else {
Sh54_anim.auto = 1;
};
Sh54_point_grp.back_up = Sh54_point_grp.translateX;
$oldRotateAverage = nurbsCircle1.rotateX + nurbsCircle1.rotateY + nurbsCircle1.rotateZ;
$oldTransAverage = nurbsCircle1.translateX + nurbsCircle1.translateY + nurbsCircle1.translateZ;

Resources