I'm importing animations on a skinned mesh from a glTF created in Blender to THREE.js
I have two simultaneous animations that affect the translation of a bone. In Blender, these translations are summed, but in THREE.js they're averaged by weight.
For example, the animations are translate(2, 0, 0) and translate(4, 0, 0)
How do I get the AnimationMixer to result in (6, 0, 0) instead of (3, 0, 0)?
For my specific needs, I really only needed the values from the first and last frames (they're all 3 frame animations with the second being default values). So I pull the values from the keyframe tracks, and won't use the animation system at all.
//converts animations to values
fuction convertAnim( clips ){
var anims = [];
for ( i = 0; i < clips.length; i ++ ){
anims[i] = {name: "name", bone: []};
anims[i].name = clips[i].name;
for ( j = 0; j < clips[i].tracks.length; j++ ){
var val = clips[i].tracks[j].values
if ( clips[i].tracks[j].name.indexOf( 'position' ) > -1 ) {
anims[i].bone[j] = { name: "name", minTranslate: "min", maxTranslate: "max"};
anims[i].bone[j].name = clips[i].tracks[j].name;
anims[i].bone[j].minTranslate = new THREE.Vector3( val[0], val[1], val[3] );
anims[i].bone[j].maxTranslate = new THREE.Vector3( val[val.length - 3], val[val.length - 2], val[val.length - 1] );
}
else if ( clips[i].tracks[j].name.indexOf( 'scale' ) > -1 ) {
anims[i].bone[j] = { name: "name", minSize: "min", maxSize: "max"};
anims[i].bone[j].name = clips[i].tracks[j].name;
anims[i].bone[j].minSize = new THREE.Vector3( val[0], val[1], val[2] );
anims[i].bone[j].maxSize = new THREE.Vector3( val[val.length - 3], val[val.length - 2], val[val.length - 1] );
}
else if ( clips[i].tracks[j].name.indexOf( 'scale' ) > -1 ) {
anims[i].bone[j] = { name: "name", minRotation: "min", maxRotation: "max"};
anims[i].bone[j].name = clips[i].tracks[j].name;
anims[i].bone[j].minRotation = new THREE.Quaternion( val[0], val[1], val[2], val[3] );
anims[i].bone[j].maxRotation = new THREE.Quaternion( val[val.length -4], val[val.length - 3], val[val.length - 2], val[val.length - 1] );
}
}
}
return anims;
}
Related
i have initially breakable object with nice texture material loaded.
After breaking in fragments lost original material and load some default material.
Any suggest?
I use code from threejs example with ammo:
Source code:
import * as THREE from "three";
import {ConvexObjectBreaker} from "../jsm/misc/ConvexObjectBreaker";
import {updatePhysics} from "./updater";
export class MagicPhysics {
// Physics variables
gravityConstant = 7.8;
collisionConfiguration;
dispatcher;
broadphase;
solver;
physicsWorld;
margin = 0.05;
convexBreaker = new ConvexObjectBreaker();
// Rigid bodies include all movable objects
rigidBodies = [];
pos = new THREE.Vector3();
quat = new THREE.Quaternion();
transformAux1;
tempBtVec3_1;
objectsToRemove = [];
// Player
ammoTmpPos;
ammoTmpQuat;
tmpTrans;
numObjectsToRemove = 0;
impactPoint = new THREE.Vector3();
impactNormal = new THREE.Vector3();
// kinekt type of movement
kMoveDirection = {left: 0, right: 0, forward: 0, back: 0};
// velocity type of movement
moveDirection = {left: 0, right: 0, forward: 0, back: 0};
tmpPos = new THREE.Vector3();
tmpQuat = new THREE.Quaternion();
constructor(options) {
console.log("MagicPhysics =>", options)
this.updatePhysics = updatePhysics.bind(this);
this.config = options.config;
}
initPhysics() {
// Physics configuration
this.collisionConfiguration = new Ammo.btDefaultCollisionConfiguration();
this.dispatcher = new Ammo.btCollisionDispatcher(this.collisionConfiguration);
this.broadphase = new Ammo.btDbvtBroadphase();
const solver = new Ammo.btSequentialImpulseConstraintSolver();
this.physicsWorld = new Ammo.btDiscreteDynamicsWorld(
this.dispatcher,
this.broadphase,
solver,
this.collisionConfiguration
);
this.physicsWorld.setGravity(new Ammo.btVector3(0, -this.gravityConstant, 0));
this.transformAux1 = new Ammo.btTransform();
this.tempBtVec3_1 = new Ammo.btVector3(0, 0, 0);
}
createRigidBody(object, physicsShape, mass, pos, quat, vel, angVel) {
if(pos) {
object.position.copy(pos);
} else {
pos = object.position;
}
if(quat) {
object.quaternion.copy(quat);
} else {
quat = object.quaternion;
}
const transform = new Ammo.btTransform();
transform.setIdentity();
transform.setOrigin(new Ammo.btVector3(pos.x, pos.y, pos.z));
transform.setRotation(
new Ammo.btQuaternion(quat.x, quat.y, quat.z, quat.w)
);
const motionState = new Ammo.btDefaultMotionState(transform);
const localInertia = new Ammo.btVector3(0, 0, 0);
physicsShape.calculateLocalInertia(mass, localInertia);
const rbInfo = new Ammo.btRigidBodyConstructionInfo(
mass,
motionState,
physicsShape,
localInertia
);
const body = new Ammo.btRigidBody(rbInfo);
body.setFriction(0.5);
if(vel) {
body.setLinearVelocity(new Ammo.btVector3(vel.x, vel.y, vel.z));
}
if(angVel) {
body.setAngularVelocity(
new Ammo.btVector3(angVel.x, angVel.y, angVel.z)
);
}
object.userData.physicsBody = body;
object.userData.collided = false;
this.scene.add(object);
if(mass > 0) {
this.rigidBodies.push(object);
// Disable deactivation
body.setActivationState(4);
}
this.physicsWorld.addRigidBody(body);
return body;
}
createConvexHullPhysicsShape(coords) {
const shape = new Ammo.btConvexHullShape();
for(let i = 0, il = coords.length;i < il;i += 3) {
this.tempBtVec3_1.setValue(coords[i], coords[i + 1], coords[i + 2]);
const lastOne = i >= il - 3;
shape.addPoint(this.tempBtVec3_1, lastOne);
}
return shape;
}
createParalellepipedWithPhysics(sx, sy, sz, mass, pos, quat, material) {
const object = new THREE.Mesh(
new THREE.BoxGeometry(sx, sy, sz, 1, 1, 1),
material
);
const shape = new Ammo.btBoxShape(
new Ammo.btVector3(sx * 0.5, sy * 0.5, sz * 0.5)
);
shape.setMargin(this.margin);
this.createRigidBody(object, shape, mass, pos, quat);
return object;
}
createDebrisFromBreakableObject(object) {
object.castShadow = true;
object.receiveShadow = true;
const shape = this.createConvexHullPhysicsShape(
object.geometry.attributes.position.array
);
shape.setMargin(this.margin);
const body = this.createRigidBody(
object,
shape,
object.userData.mass,
null,
null,
object.userData.velocity,
object.userData.angularVelocity
);
// Set pointer back to the three object only in the debris objects
const btVecUserData = new Ammo.btVector3(0, 0, 0);
btVecUserData.threeObject = object;
body.setUserPointer(btVecUserData);
}
removeDebris(object) {
this.scene.remove(object);
this.physicsWorld.removeRigidBody(object.userData.physicsBody);
}
}
I'm trying to figure this out for a while now and I just can't find a solution.
I'm working with three JS and the Cannon Library.
I created a simple object/ mesh with a tween animation. With the Cannon Library I added a physical body to the mesh.
I put an animation an the mesh so it changes the rotation but the rotation of the physical body just doesn't change at all.
second position
First position
This is where I define the physics:
class Physics {
constructor() {
this.world = new CANNON.World();
this.stepSize = 0;
this.timeToGo = 0;
this.visualObjects = [];
this.physicalBodies = [];
}
addPair(visualObject, body) {
this.visualObjects.push(visualObject);
this.physicalBodies.push(body);
}
initialize(gravityX, gravityY, gravityZ, stepsize, addfloor) {
this.world.gravity.set(gravityX, gravityY, gravityZ);
this.world.broadphase = new CANNON.NaiveBroadphase();
his.stepSize = stepsize;
if (addfloor) {
var floor = new CANNON.Body({
shape: new CANNON.Plane(),
mass: 0
});
floor.quaternion.setFromAxisAngle(new CANNON.Vec3(1, -0, 0), -Math.PI / 2);
floor.position.y = -32;
this.world.addBody(floor);
}
}
update(delta) {
// Step physics world forward
this.timeToGo += delta;
while (this.timeToGo >= this.stepSize) {
this.world.step(this.stepSize);
this.timeToGo -= this.stepSize;
}
// Copy transformations
for (var i = 0; i < this.visualObjects.length; i++) {
this.visualObjects[i].position.copy(this.physicalBodies[i].position);
this.visualObjects[i].quaternion.copy(this.physicalBodies[i].quaternion);
}
}
getWorld() {
return this.world;
}
addStandPhysical(visualObject, mass){
var shapeAblage = new CANNON.Box(new CANNON.Vec3(0.5*2,0.5*8,0.5*40));
var shapeMittelstrebe = new CANNON.Box(new CANNON.Vec3(0.5*10,0.5*1,0.5*100));
var shapeAblage3 = new CANNON.Box(new CANNON.Vec3(0.5*2.5,0.5*1,0.5*15));
var shapeAblage4 = new CANNON.Box(new CANNON.Vec3(0.5*2.5,0.5*1,0.5*15));
var shapeFuss5 = new CANNON.Box(new CANNON.Vec3(0.5*2.5,0.5*1,0.5*15));
var shapeMittelding = new CANNON.Cylinder(2,2,25,32)
var body = new CANNON.Body({ mass: mass });
body.addShape(shapeAblage, new CANNON.Vec3(-1.25, 29, 41), new CANNON.Quaternion().setFromEuler( 0, 90*DEG_TO_RAD,90*DEG_TO_RAD, "XYZ")); //ablage1
body.addShape(shapeAblage, new CANNON.Vec3(-1.25, 29, -41), new CANNON.Quaternion().setFromEuler( 0, 90*DEG_TO_RAD,90*DEG_TO_RAD, "XYZ")); //ablage2
body.addShape(shapeAblage, new CANNON.Vec3(-1.25, -29, -41), new CANNON.Quaternion().setFromEuler( 0, 90*DEG_TO_RAD,90*DEG_TO_RAD, "XYZ")); //ablage3
body.addShape(shapeAblage, new CANNON.Vec3(-1.25, -29, 41), new CANNON.Quaternion().setFromEuler( 0, 90*DEG_TO_RAD,90*DEG_TO_RAD, "XYZ")); //ablage4
body.addShape(shapeMittelstrebe, new CANNON.Vec3( 1.75, 0, 0), new CANNON.Quaternion().setFromEuler(35* DEG_TO_RAD, 0, 0, "XYZ"));
body.addShape(shapeMittelstrebe, new CANNON.Vec3( -1.75, 0, 0), new CANNON.Quaternion().setFromEuler(-35* DEG_TO_RAD, 0, 0, "XYZ"));
body.position.copy(visualObject.position);
body.quaternion.copy(visualObject.quaternion);
this.world.addBody(body);
this.addPair(visualObject, body);
}
}
And this is basically my main:
function main() {
scene = new THREE.Scene();
physics = new Physics();
physics.initialize(0, -200, 0, 1 / 120, floor = false); //Hier mit der Stepsize vielleicht noch ein wenig experimentieren und herausfinden, was sie macht
physicsVisualDebugger = new THREE.CannonDebugRenderer(scene, physics.getWorld());
var stand = new Stand();
physics.addStandPhysical(stand,3);
scene.add(stand);
var clock = new THREE.Clock();
function mainLoop() {
stats.begin();
var delta = clock.getDelta();
physics.update(delta);
physicsVisualDebugger.update();
stand.animations.forEach(function (animation) {
animation.update(delta);
});
TWEEN.update();
renderer.render(scene, camera);
stats.end();
requestAnimationFrame(mainLoop);
}
}
So long question short: how do I get the physical Bodies to move with my animation of the meshes to the ground?
I consider but maybe I am wrong, that your two arrays
this.visualObjects, this.physicalBodies
have not the same length. So maybe the update loop do not copy right the positions and quaternions. Better use the loop this way:
world.bodies.forEach( function(body) { ... }
to get looped exact the bodies.
Following the example here:
http://learningthreejs.com/blog/2011/12/10/constructive-solid-geometry-with-csg-js/
And using Three.js with https://github.com/chandlerprall/ThreeCSG, I'm trying to do 3D boolean operations on nodes from the model. Like for example if I have a wall with a window, I want to do invert() on that to get just the window.
I have a function that returns all the vertices of the polygons of a node, here's an example of vertices of an object without holes https://pastebin.com/8dhYzPwE.
I'm using ThreeCSG like this:
const geometryThree = new THREE.Geometry();
geometryThree.vertices.push(
...vertices
);
const geometryCsg = new ThreeBSP(geometryThree);
But that's what I'm getting in geometryCsg:
"{
"matrix": {
"elements": {
"0": 1,
"1": 0,
"2": 0,
"3": 0,
"4": 0,
"5": 1,
"6": 0,
"7": 0,
"8": 0,
"9": 0,
"10": 1,
"11": 0,
"12": 0,
"13": 0,
"14": 0,
"15": 1
}
},
"tree": {
"polygons": []
}
}"
I think it's because the geometry.faces.length is 0.
How can I make the vertices array to be a proper Three.Geometry such that the faces won't be empty? Geometry.elementsNeedsUpdate doesn't work...
Is there an example that uses polygons of a shape as an array of Vector3s and transforms that to csg?
I just worked on a demo using THREE csg: the Viewer meshes have an indexed array of vertices so you cannot create a BSP directly out of it. Also my code is using a web worker to process the meshes in order to keep the UI responsive with large models, so I need first to send the mesh data to the worker and reconstruct a simple THREE.Mesh on the worker side, the code looks like below:
// Sends component geometry to the web worker
postComponent (dbId) {
const geometry = this.getComponentGeometry(dbId)
const msg = {
boundingBox: this.getComponentBoundingBox(dbId),
matrixWorld: geometry.matrixWorld,
nbMeshes: geometry.meshes.length,
msgId: 'MSG_ID_COMPONENT',
dbId
}
geometry.meshes.forEach((mesh, idx) => {
msg['positions' + idx] = mesh.positions
msg['indices' + idx] = mesh.indices
msg['stride' + idx] = mesh.stride
})
this.worker.postMessage(msg)
}
// get geometry for all fragments in a component
getComponentGeometry (dbId) {
const fragIds = Toolkit.getLeafFragIds(
this.viewer.model, dbId)
let matrixWorld = null
const meshes = fragIds.map((fragId) => {
const renderProxy = this.viewer.impl.getRenderProxy(
this.viewer.model,
fragId)
const geometry = renderProxy.geometry
const attributes = geometry.attributes
const positions = geometry.vb
? geometry.vb
: attributes.position.array
const indices = attributes.index.array || geometry.ib
const stride = geometry.vb ? geometry.vbstride : 3
const offsets = geometry.offsets
matrixWorld = matrixWorld ||
renderProxy.matrixWorld.elements
return {
positions,
indices,
offsets,
stride
}
})
return {
matrixWorld,
meshes
}
}
// On the worker side reconstruct THREE.Mesh
// from received data and create ThreeBSP
function buildComponentMesh (data) {
const vertexArray = []
for (let idx=0; idx < data.nbMeshes; ++idx) {
const meshData = {
positions: data['positions' + idx],
indices: data['indices' + idx],
stride: data['stride' + idx]
}
getMeshGeometry (meshData, vertexArray)
}
const geometry = new THREE.Geometry()
for (var i = 0; i < vertexArray.length; i += 3) {
geometry.vertices.push(vertexArray[i])
geometry.vertices.push(vertexArray[i + 1])
geometry.vertices.push(vertexArray[i + 2])
const face = new THREE.Face3(i, i + 1, i + 2)
geometry.faces.push(face)
}
const matrixWorld = new THREE.Matrix4()
matrixWorld.fromArray(data.matrixWorld)
const mesh = new THREE.Mesh(geometry)
mesh.applyMatrix(matrixWorld)
mesh.boundingBox = data.boundingBox
mesh.bsp = new ThreeBSP(mesh)
mesh.dbId = data.dbId
return mesh
}
function getMeshGeometry (data, vertexArray) {
const offsets = [{
count: data.indices.length,
index: 0,
start: 0}
]
for (var oi = 0, ol = offsets.length; oi < ol; ++oi) {
var start = offsets[oi].start
var count = offsets[oi].count
var index = offsets[oi].index
for (var i = start, il = start + count; i < il; i += 3) {
const a = index + data.indices[i]
const b = index + data.indices[i + 1]
const c = index + data.indices[i + 2]
const vA = new THREE.Vector3()
const vB = new THREE.Vector3()
const vC = new THREE.Vector3()
vA.fromArray(data.positions, a * data.stride)
vB.fromArray(data.positions, b * data.stride)
vC.fromArray(data.positions, c * data.stride)
vertexArray.push(vA)
vertexArray.push(vB)
vertexArray.push(vC)
}
}
}
The complete code of my sample is there: Wall Analyzer and the live demo there.
Lets say there is a svg with a closed filled rectangle in the middle and around that there's a white space of 2 points .
<path d="M2 2 H 3 V 3 H 2 Z" fill="transparent" stroke="black"/>
So I want to represent this a 2-d matrix where all the white space are represented as 0 and black spaces (covered area) is represented as 1. so for this example it should be-
[
[0, 0, 0, 0],
[0, 1, 1, 1],
[0, 1, 1, 1],
[0, 1, 1, 1]
]
It's a simple path , but I'm trying to find a way where it would work for complex paths including bezier curve. Actually I'm trying to convert an SVG world map to 0-1 matrix so that I can run some AI algorithms on it .
Implemented #Robert Longson suggestion. 1) Draw the svg in canvas 2) Get ImageData as CanvasContext Array 3) Iterate on that array and form your matrix. 4) Array returned by getImageData is a flat array and consecutive 4 array index correspond to one point of canvas and they are r, g, b and alpha (rgba) of the color of that point.
Here's a working react component .
import React, { Component } from 'react';
export default class IndexPage extends Component {
constructor(properties) {
super(properties);
this.canvasWidth = 1052;
this.canvasHeight = 580;
}
componentDidMount() {
const mapCanvas = this.refs.canvas;
const ctx = mapCanvas.getContext('2d');
const img = new Image();
img.onload = function() {
ctx.drawImage(img, 0, 0);
this.arrayFromSvg();
}.bind(this);
img.src = 'World.svg';
}
render() {
return ( < div >
< div styles={{
width: this.canvasWidth,
height: this.canvasHeight
}
} >
< canvas width = {
this.canvasWidth
}
height = {
this.canvasHeight
}
ref = "canvas" >
< /canvas> < /div >
< /div>
);
}
arrayFromSvg() {
const mapCanvas = this.refs.canvas;
const ctx = mapCanvas.getContext('2d');
const canvasWidth = mapCanvas.width;
const canvasHeight = mapCanvas.height;
const imageData = ctx.getImageData(0, 0, canvasWidth, canvasHeight).data;
const imageToMat = [];
for (let row = 0, count = -1; row < canvasWidth; row++) {
imageToMat[row] = [];
// imageToMat[row][col] = 'rgba(' + imageData[++count] + ', ' + imageData[++count] + ', ' + imageData[++count] + ', ' + imageData[++count] + ')';
for (let col = 0; col < canvasHeight; col++) {
if (imageData[++count] + imageData[++count] + imageData[++count] + imageData[++count] > 0) {
imageToMat[row][col] = 1;
} else {
imageToMat[row][col] = 0;
}
}
}
console.log(imageToMat);
}
}
I want to translate a merge function from the following JS code to us in RQL:
var d1 = {
del: {
1: {n: 1, v: 100, vFx:[100, 110]},
2: {n: 1, v: 100, vFx:[100, 110]}
}};
var d2 = {
del: {
2: {n: 1, v: 100, vFx:[100, 110]},
3: {n: 1, v: 100, vFx:[100, 110]}
}};
function merge(d1, d2) {
for(k in d2.del){
v = d2.del[k];
d1v = d1.del[k];
if(!d1v){
d1.del[k] = v;
} else{
d1v.n += v.n;
d1v.v += v.v;
for(var i = 0, _len = v.vFx.length; i < _len; i++)
d1v.vFx[i] += v.vFx[i];
}
}
};
// test
merge(d1, d2);
console.log(d1);
// GOAL
r.do(d1, d2, merge) // this of course doesn't work
My main problem is how to iterate through keys of a document? r.forEach requires me to make a save inside. My use case is that I want to use this merge function in update:
rqlexpr.update(merge(d1, r.row))
You can map over the keys of a document with keys, and construct an object with object. For example:
d1.merge(
r.object(
d2('del').keys().concatMap(function(key) {
return r.branch(
d1('del').hasField(key).not(),
[key, d2('del').getField(key)],
[key, d1('del').getField(key) + d2('del').getField(key)]
)
})
)
)