Three.js Blender Animation Mic Frequency Trigger Issue - javascript

I am attempting to import a multiple layer animation from Blender exported using the three.js plugin into JavaScript and have the different animations triggered by microphone frequency input. I have been able to combine the playing of the animations via buttons and a .obj being rotated depending on the frequency from mic input. But I am struggling to assign the mic to the animation.
<div class="info">
<button onclick="fadeAction( 'idle' );">idle</button>
<button onclick="fadeAction( 'run' );">run</button>
<button onclick="fadeAction( 'wrap' );">wrap</button>
</div>
loader.load( 'tentacleanimation6.json', function( geometry, materials ) {
materials.forEach( function ( material ) {
material.skinning = true;
} );
mesh = new THREE.SkinnedMesh(
geometry,
new THREE.MeshFaceMaterial( materials )
);
mixer = new THREE.AnimationMixer( mesh );
action.idle = mixer.clipAction( geometry.animations[ 1 ] );
action.run = mixer.clipAction( geometry.animations[ 0 ] );
action.wrap = mixer.clipAction( geometry.animations[ 2 ] );
action.idle.setEffectiveWeight( 1 );
action.run.setEffectiveWeight( 1 );
action.wrap.setEffectiveWeight( 1 );
action.idle.play();
mesh.position.y = 0;
mesh.position.x = -3;
scene.add( mesh );
} );
fadeAction = function () {
var activeActionName = 'idle';
return function ( name ) {
var from = action[ activeActionName ].play();
var to = action[ name ].play();
from.enabled = true;
to.enabled = true;
from.crossFadeTo( to, .3 );
activeActionName = name;
}
}();
Then the mic input from getUserMedia is processed as followed triggering a .obj file to rotate:
script_processor_analysis_node.onaudioprocess = function() {
analyser_node.getByteFrequencyData(array_freq_domain);
if (microphone_stream.playbackState == microphone_stream.PLAYING_STATE) {
var render = function () {
for ( var i = 0; i < (array_freq_domain.length); i++ ){
var valueone = array_freq_domain[array_freq_domain.length / 2];
mouthone.rotation.x = deg2rad( valueone ) / 7;
var valuetwo = array_freq_domain[6];
mouthtwo.rotation.x = deg2rad( valuetwo ) / 7;
}
renderer.render(scene, camera);
}
requestAnimationFrame( render );
}
}
The issue becomes when the two are combined and I am not sure what needs to be called in the onaudioprocess section to trigger an animation to play? Also there seems to be an issue with rendering, as the function for onaudioprocess needs to be rendered as above and the animation needs to be rendered with the following and when these are combined they cancel each other out:
;( function update () {
requestAnimationFrame( update );
var delta = clock.getDelta();
var theta = clock.getElapsedTime();
if ( mixer ) { mixer.update( delta ); }
renderer.render( scene, camera );
} )();

Related

Loading 3D model with animation using CreateFromMorphTargetSequence Three.js

I'm trying to load this GBL file and play the animation. This code returns the following error:
TypeError: Cannot read property 'length' of undefined at Function.CreateFromMorphTargetSequence
function Animate() {
if(window.anim_flag)
{
// Hotspot_Ring_Anim();
requestAnimationFrame(Animate);
renderer.clear();
TWEEN.update();
orbit.update();
if (mixer.length > 0) {
var delta = clock.getDelta();
for (var i = 0; i < mixer.length; i++) {
mixer[i].update(delta);
}
}
renderer.render(scene, camera);
}
}
function Add_Hotspot_Rings(id,px,py,pz,rx,ry,rz,sx,sy,sz) {
const loader = new GLTFLoader();
// Optional: Provide a DRACOLoader instance to decode compressed mesh data
const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath( '../jsm/draco/' );
loader.setDRACOLoader( dracoLoader );
loader.load( '../Models/ABB_Clinic_AnimatedRings_Lowpoly_02.glb', function ( gltf ) {
const model = gltf.scene;
model.name = 'hotspot_rings';
model.position.set(px,py,pz);
model.rotation.set(0,ry,rz);
model.scale.set(0.90, 0.3, 0.90);
scene.add(model);
// MORPH
const mixerr = new THREE.AnimationMixer( model );
const clips = model.animations;
const morphClip = THREE.AnimationClip.CreateFromMorphTargetSequence( 'RingsRising', model.morphTargets );
mixerr.clipAction(morphClip).setDuration(1).play();
mixer.push(mixerr);
window.anim_flag= true;
Animate();
}, undefined, function ( error ) {
console.error( error );
} );
}
How can I resolve this error and load the model with the animation playing?
const morphClip = THREE.AnimationClip.CreateFromMorphTargetSequence( 'RingsRising', model.morphTargets );
Instances of Object3D do not have a morphTargets property.
Playing an animation from a glTF asset should look like so:
const animations = gltf.animations;
const mixer = new THREE.AnimationMixer( model );
mixer.clipAction( animations[ 0 ] ).setDuration( 1 ).play();
Live example: https://threejs.org/examples/webgl_morphtargets_horse

Use " vector.applyQuaternion " or similar in ammo.js

I want to make a browser VR shooting game, using Three.js and Ammo.js for the physics and rigidbodies. The vr head and controllers are set up the models are loaded, but the bullets won't shoot from the gun as I want to. When I tried to do the same using only Three.js with no rigidbodies in the scene, I used the "vector.applyQuaternion" from the Three.js documentation, and it worked, the bullets were fired from the top of the gun. The problem is I didn't find anything similar for using ammo.js
Code without using ammo.js
... function handleController( controller ) {
if ( controller1.userData.isSelecting ) {
bullet1.position.set( controller1.position.x , controller1.position.y + 0.018 , controller1.position.z -0.01);
bullet1.userData.velocity.x = 0;
bullet1.userData.velocity.y = 10;
bullet1.userData.velocity.z = 10;
bullet1.userData.velocity.applyQuaternion( controller1.quaternion );
scene.add(bullet1);
}
if ( controller2.userData.isSelecting ) {
bullet2.position.set( controller2.position.x , controller2.position.y + 0.018 , controller2.position.z -0.01 );
bullet2.userData.velocity.x = 0;
bullet2.userData.velocity.y = 10;
bullet2.userData.velocity.z = 10;
bullet2.userData.velocity.applyQuaternion( controller2.quaternion );
scene.add(bullet2);
}
} ...
function render() {
handleController( controller1 );
handleController( controller2 );
var delta = clock.getDelta()
bullet1.position.x -= bullet1.userData.velocity.x * delta;
bullet1.position.y -= bullet1.userData.velocity.y * delta;
bullet1.position.z -= bullet1.userData.velocity.z * delta;
bullet2.position.x -= bullet2.userData.velocity.x * delta;
bullet2.position.y -= bullet2.userData.velocity.y * delta;
bullet2.position.z -= bullet2.userData.velocity.z * delta;
renderer.render( scene, camera );
}
code with ammo.js
function createBullet1RigidBody( threeObject, physicsShape, mass ) {
//threeObject.position.copy( pos );
//threeObject.quaternion.copy( quat1 );
quat1.set( controller1.position.x, controller1.position.y , controller1.position.z , Math.PI/2 );
pos1 = new THREE.Vector3 ( controller1.position.x, controller1.position.y , controller1.position.z );
var transform = new Ammo.btTransform();
transform.setIdentity();
transform.setOrigin( new Ammo.btVector3( controller1.position.x, controller1.position.y, controller1.position.z -0.5) );
transform.setRotation( new Ammo.btQuaternion( quat1.x, quat1.y, quat1.z, quat1.w ) );
var motionState = new Ammo.btDefaultMotionState( transform );
var localInertia = new Ammo.btVector3( 0, 0, 0 );
physicsShape.calculateLocalInertia( mass, localInertia );
var rbInfo = new Ammo.btRigidBodyConstructionInfo( mass, motionState, physicsShape, localInertia );
var body1 = new Ammo.btRigidBody( rbInfo );
threeObject.userData.physicsBody = body1;
scene.add( threeObject );
if ( mass > 0 ) {
bullet1Bodies.push( threeObject );
// Disable deactivation
body1.setActivationState( 4 );
}
physicsWorld.addRigidBody( body1 );
return body1;
}
function createBullet2RigidBody( threeObject, physicsShape, mass ) {
//threeObject.position.copy( pos );
//threeObject.quaternion.copy( quat2 );
var transform = new Ammo.btTransform();
transform.setIdentity();
transform.setOrigin( new Ammo.btVector3( controller2.position.x, controller2.position.y, controller2.position.z -0.5 ) );
transform.setRotation( new Ammo.btQuaternion( quat.x, quat.y, quat.z, quat.w ) );
var motionState = new Ammo.btDefaultMotionState( transform );
var localInertia = new Ammo.btVector3( 0, 0, 0 );
physicsShape.calculateLocalInertia( mass, localInertia );
var rbInfo = new Ammo.btRigidBodyConstructionInfo( mass, motionState, physicsShape, localInertia );
var body2 = new Ammo.btRigidBody( rbInfo );
threeObject.userData.physicsBody = body2;
scene.add( threeObject );
if ( mass > 0 ) {
bullet2Bodies.push( threeObject );
// Disable deactivation
body2.setActivationState( 4 );
}
physicsWorld.addRigidBody( body2 );
return body2;
}
...
function handleController( controller ) {
if ( controller1.userData.isSelecting ) {
var bullet1Mass = 0.1;
var bullet1Radius = 0.6;
var bullet1 = new THREE.Mesh( geometry_bullet, new THREE.MeshLambertMaterial( { color: 0x2661b9 } ) );
var bullet1Shape = new Ammo.btSphereShape ( bullet1Radius );
bullet1Shape.setMargin ( margin );
var bullet1_body = createBullet1RigidBody( bullet1, bullet1Shape, bullet1Mass, bullet1Radius );
//bullet1.position.set( pos1.x, pos1.y, pos1.z );
bullet1.userData.physicsBody.setFriction( 0 );
bullet1_body.setLinearVelocity( new Ammo.btVector3( 100, 100, 100 , controller1.quaternion) );
}
if ( controller2.userData.isSelecting ) {
var bullet2Mass = 0.1;
var bullet2Radius = 0.6;
var bullet2 = new THREE.Mesh( geometry_bullet, new THREE.MeshLambertMaterial( { color: 0xee2f2f } ) );
var bullet2Shape = new Ammo.btSphereShape ( bullet2Radius );
var pos2 = new THREE.Vector3 ( controller2.position.x, controller2.position.y + 10, controller2.position.z +10 );
bullet2Shape.setMargin ( margin );
//quat2.set( 0,0,0,1 );
var bullet2_body = createBullet2RigidBody( bullet2, bullet2Shape, bullet2Mass, bullet2Radius, pos2, quat2 );
//var pos2 = bullet2.position.set( controller2.position.x, controller2.position.y, controller2.position.z -0.1 );
bullet2.userData.physicsBody.setFriction( 0 );
bullet2_body.setLinearVelocity( new Ammo.btVector3( 0, 0, controller2.position.z *100 ) );
//bullet2.userData.physicsBody.applyQuaternion(controller2.quaternion);
}
}
The result is that the bullets are created but they're moving only on the z axis. The velocity of the bullet2 changes if I move the controller on the x axis, and the bullets are moving only on z axis. Bullets of the bullet1 follows the movement on the x,y,z axis but not the rotation of the controller. The "bullet2.userData.physicsBody.applyQuaternion(controller2.quaternion);" line is a comment, if I remove the bars I get this error "TypeError: bullet2.userData.physicsBody.applyQuaternion is not a function". I was expecting to do the same as I did in the previous example without ammo (userData.velocity.applyQuaternion) but there's no LinearVelocity in the ammo.js, I can only use getLinearVelocity and setLinearVelocity.
Based on ammo versions I've seen, your call to setLinearVelocity() is not correct. The btVector3 constructor must take x,y,z only. Calculate it using ThreeJS math similarly to where you've done elsewhere (choose magnitude and rotate the vector3 by the controller's quaternion). That probably solves your issue.
Note: I question why your bullets need to be physically simulated. Most shooting games do not do this. Easier and faster is to visually update its progress while checking for when it intercepts a player/wall. If you desire a gravity effect then apply small vertical drop every frame. If you want ricochet then use the normal of the intercepted surface to alter the velocity. Ammo engine is good for things like a grenade bouncing (complex, unpredictable interactions).

Access parts of a GLTF import in the main rendering loop (sorry if noobish)

I’m a Unity developer, trying to learn Three.js.
One the many problems I encounter may sound simple, but it’s a pain in the a** for me.
All I wanna do is to import and animate a 3D logo in my Three.js app.
This logo is made out of 4 different meshes (elem1 to elem4) which don’t overlap.
It was exported as a FBX, then converted to GLTF using an online converter.
No problem when importing it, resizing it and even changing its material.
My problem is : how to refer to the whole object, and also to its 4 elements, in order to animate them in my « animate » function (I mean in my main rendering loop) ?
The only thing I could do was to create a second « animate » function within the loader callback, which seems a bit weird to me.
I can’t find a way to refer to them in the main scope of my app.
Dumping my GLTF import gives this hierarchy (these are called « nodes », am I right ?) :
AuxScene [Scene]
*no-name* [Object3D]
elem1 [Mesh]
elem2 [Mesh]
elem3 [Mesh]
elem4 [Mesh]
Here’s my code, cleared from unnecessary stuff :
'use strict';
// CANVAS AND RENDERER
const canvas = document.querySelector('#myCanvas');
const renderer = new THREE.WebGLRenderer({ canvas, antialias: true });
renderer.setSize( window.innerWidth, window.innerHeight );
// SCENE AND BACKGROUND
var scene = new THREE.Scene();
const loader = new THREE.TextureLoader();
scene.background = loader.load( 'images/background.jpg');
// CAMERA
var camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );
camera.position.z = 100;
// MATERIALS
var material = new THREE.MeshNormalMaterial ();
// ---------------------------------- LOGO IMPORTATION
var gltfLoader = new THREE.GLTFLoader();
var root;
var elem1, elem2, elem3, elem4;
gltfLoader.load('gltf/logo.gltf', function(gltf) {
root = gltf.scene;
root.rotation.x = Math.PI / 2;
scene.add(root);
root.traverse( function( child ) {
if ( child instanceof THREE.Mesh ) { child.material = material; }
} );
elem1 = root.getObjectByName('element1');
elem2 = root.getObjectByName('element2');
elem3 = root.getObjectByName('element3');
elem4 = root.getObjectByName('element4');
console.log(dumpObject(root).join('\n'));
// logo animations
var speed = 0.0005;
var turnsBeforeStop = 4;
requestAnimationFrame( animate2 );
function animate2( time ) {
root.rotation.z = Math.sin (time * 0.0005) * 0.5;
root.rotation.x = Math.PI/3 + Math.sin(time * 0.0003) * 0.5;
if(elem1.rotation.y < Math.PI * turnsBeforeStop){
elem1.rotation.y = time * speed*2;
elem2.rotation.z = time * speed*2;
elem3.rotation.y = time * -speed;
elem4.rotation.z = time * -speed*2;
}
requestAnimationFrame( animate2 );
}
});
// ------------------------------------------------------------ END LOGO
renderer.render( scene, camera );
requestAnimationFrame( animate );
// ANIMATION MAIN LOOP
function animate( time ) {
/*
This is where I would like to access my logo (as a whole, and also its separate parts).
But root.rotation or elem1.rotation won't work here and give me this error :
TypeError: undefined is not an object (evaluating 'elem1.rotation')
*/
renderer.render( scene, camera );
requestAnimationFrame( animate );
}
// OBJECT DUMPING
function dumpObject(obj, lines = [], isLast = true, prefix = ' ') {
const localPrefix = isLast ? '└─' : '├─';
lines.push(`${prefix}${prefix ? localPrefix : ''}${obj.name || '*no-name*'} [${obj.type}]`);
const newPrefix = prefix + (isLast ? ' ' : '│ ');
const lastNdx = obj.children.length - 1;
obj.children.forEach((child, ndx) => {
const isLast = ndx === lastNdx;
dumpObject(child, lines, isLast, newPrefix);
});
return lines;
}
Thanx for any help.
The problem is that you try to access root before a value (the glTF model) is assigned to it. Notice that GLTFLoader.load() is asynchronous. So the onLoad() callback which sets root is not immediately called but with some amount of delay.
There are several approaches to solve this issue. You can check in your animation loop if root is not undefined. It would look like so:
function animate( time ) {
requestAnimationFrame( animate );
if ( root ) {
// do something with root
}
renderer.render( scene, camera );
}
Or you start animating after onLoad() has finished. In this case, the code would look like so:
'use strict';
// CANVAS AND RENDERER
const canvas = document.querySelector('#myCanvas');
const renderer = new THREE.WebGLRenderer({ canvas, antialias: true });
renderer.setSize( window.innerWidth, window.innerHeight );
// SCENE AND BACKGROUND
var scene = new THREE.Scene();
const loader = new THREE.TextureLoader();
scene.background = loader.load( 'images/background.jpg');
// CAMERA
var camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );
camera.position.z = 100;
// MATERIALS
var material = new THREE.MeshNormalMaterial ();
// ---------------------------------- LOGO IMPORTATION
var gltfLoader = new THREE.GLTFLoader();
var root;
var elem1, elem2, elem3, elem4;
gltfLoader.load('gltf/logo.gltf', function(gltf) {
root = gltf.scene;
root.rotation.x = Math.PI / 2;
scene.add(root);
root.traverse( function( child ) {
if ( child instanceof THREE.Mesh ) { child.material = material; }
} );
animate(); // start animating
});
// ------------------------------------------------------------ END LOGO
// ANIMATION MAIN LOOP
function animate() {
requestAnimationFrame( animate );
// do something with root
renderer.render( scene, camera );
}
// OBJECT DUMPING
function dumpObject(obj, lines = [], isLast = true, prefix = ' ') {
const localPrefix = isLast ? '└─' : '├─';
lines.push(`${prefix}${prefix ? localPrefix : ''}${obj.name || '*no-name*'} [${obj.type}]`);
const newPrefix = prefix + (isLast ? ' ' : '│ ');
const lastNdx = obj.children.length - 1;
obj.children.forEach((child, ndx) => {
const isLast = ndx === lastNdx;
dumpObject(child, lines, isLast, newPrefix);
});
return lines;
}

Three.js animation mixer not playing?

Ok I have an fbx successfully loaded into Three.js exported from blender with all animation boxes checked. Ive tried all export settings from blender so that's not it.
This is how I bring the model in, and I am able to accurately determine whether the fbx has animations or not:
var geoFromScene = new THREE.Geometry();
var FBXLoader = require('three-fbx-loader');
var loader = new FBXLoader();
loader.load(string, function ( object ) {
object.traverse( function ( child ) {
if ( child.isMesh ) {
child.castShadow = true;
child.receiveShadow = true;
geoFromScene = (new THREE.Geometry()).fromBufferGeometry(child.geometry);
}
} );
var theModel = new THREE.Mesh();
theModel.geometry = geoFromScene;
theModel.material = material;
theModel.position.set(5,5,-8);
//theModel.rotation.set(new THREE.Vector3( 0, MATH.pi/2, 0));
theModel.scale.set(0.1, 0.1, 0.1);
localThis.scene.add(theModel);
localThis.mixer = new THREE.AnimationMixer(theModel);
if(theModel.animations[0])
{
var action = localThis.mixer.clipAction(theModel.animations[0]);
action.play();
} else {
console.log("No animations");
}
} );
In update (which does work, because I can print the animation.time):
this.mixer.update(this.clock.getDelta());
And yet the model is just static. Whats wrong here?
UPDATE:
code from example copy pasted-
var geoFromScene = new THREE.Geometry();
var FBXLoader = require('three-fbx-loader');
var loader = new FBXLoader();
loader.load( string, function ( object ) {
localThis.mixer = new THREE.AnimationMixer( object );
var action = localThis.mixer.clipAction( object.animations[ 0 ] );
action.play();
object.traverse( function ( child ) {
if ( child.isMesh ) {
child.castShadow = true;
child.receiveShadow = true;
}
} );
object.position.set(5,5,-8)
object.scale.set(0.1, 0.1, 0.1);
localThis.scene.add( object );
} );
in animate-
this.mixer.update(this.clock.getDelta());
All I get is the model armature it seems -
New approach:
var FBXLoader = require('wge-three-fbx-loader'); //https://www.npmjs.com/package/wge-three-fbx-loader
var loader = new FBXLoader();
loader.load( string, function ( object ) {

three.js - get object name with mouse click

I had loaded 3 external model with the name into my scene using json loader and now i want to get the name of the model/object by clicking it.
Below is the that i had used to load the model
var object_material = new THREE.MeshBasicMaterial({
color: 0xd6d6d6,
side: THREE.DoubleSide
});
var loader = new THREE.JSONLoader();
loader.load("models/"+file,
function(geometry, object_material)
{
var object = new THREE.Mesh(geometry, new THREE.MeshFaceMaterial(object_material));
model = new THREE.Object3D();
model.id=file;
model.name='sample'+file;
model.userData.id='sampledata'+file;
model.add(object);
model.position.set(obj_x,obj_y,obj_z);
model.mirroredLoop = true;
model.castShadow = true;
model.receiveShadow = true;
scene.add(model);
}
);
Below is my mouse down function
function onMouseDown(event) {
event.preventDefault();
mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
var vector = new THREE.Vector3( mouse.x, mouse.y, 1 );
projector.unprojectVector( vector, camera );
var pLocal = new THREE.Vector3(0, 0, -1);
var pWorld = pLocal.applyMatrix4(camera.matrixWorld);
var ray = new THREE.Raycaster(pWorld, vector.sub(pWorld).normalize());
// Get meshes from all objects
var getMeshes = function(children) {
var meshes = [];
for (var i = 0; i < children.length; i++)
{
if (children[i].children.length > 0) {
meshes = meshes.concat(getMeshes(children[i].children));
} else if (children[i] instanceof THREE.Mesh) {
meshes.push(children[i]);
}
}
return meshes;
};
function attributeValues(o)
{
var out = [];
for (var key in o) {
if (!o.hasOwnProperty(key))
continue;
out.push(o[key]);
}
return out;
}
var objects = attributeValues(this.o3dByEntityId);
var meshes = getMeshes(objects);
var intersects = ray.intersectObjects(meshes);
raycaster.set( camera.position, vector.sub( camera.position ).normalize() );
var intersects = raycaster.intersectObjects( scene.children );
console.log(scene);
// this console displays all the objects under children - THREE.Object3D - name as name: "sample513.js"
if ( intersects.length > 0 )
{
// but for the clickedObject - the length is > 0 and name is empty
var clickedObject = intersects[0].object;
console.log(clickedObject.parent.userData.id); // return as undefined
if ( INTERSECTED != intersects[ 0 ].object )
{
INTERSECTED= intersects[ 0 ].object;
name = INTERSECTED.name;
}
} else {
console.log('intersects.length is 0');
}
}
Even-though i had provided the model name in the userData , i am not able to retrieve it . can any one guide me how to retrieve the name of the object when it is clicked
Try to make through this example. Look at messages in the console.
<script src="js/controls/EventsControls.js"></script>
EventsControls = new EventsControls( camera, renderer.domElement );
EventsControls.attachEvent( 'onclick', function() {
console.log( 'this.focused.name: ' + this.focused.name );
});
// if use drag and drop
EventsControls.attachEvent( 'dragAndDrop', function () {
this.container.style.cursor = 'move';
this.focused.position.y = this.previous.y;
});
EventsControls.attachEvent( 'mouseOut', function () {
this.container.style.cursor = 'auto';
});
var jsonLoader = new THREE.JSONLoader();
jsonLoader.load( "models/Tux.js", addModelToScene );
function addModelToScene( geometry, materials ) {
var material = new THREE.MeshFaceMaterial( materials );
model = new THREE.Mesh( geometry, material );
model.scale.set( 10, 10, 10 ); model.name = 'Tux';
model.rotation.x = -Math.PI/2;
model.position.set( 175, 45, 125 );
scene.add( model );
EventsControls.attach( model );
}
The parent of the clickedObject is probably undefined. Perhaps you can console log the clickedObject and see which path you need to access the id.

Categories