/*
* Copyright 2016 Nathan Howard
*
* This file is part of OpenGrave
*
* OpenGrave is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* OpenGrave is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with OpenGrave. If not, see <http://www.gnu.org/licenses/>.
*/
package com.opengrave.og.engine;
import java.util.ArrayList;
import java.util.Collections;
import com.opengrave.common.world.CommonObject;
import com.opengrave.common.world.MaterialList;
import com.opengrave.og.base.RenderableBoneAnimatedStatic;
import com.opengrave.og.engine.gait.BipedWalk;
import com.opengrave.og.engine.gait.Bone;
import com.opengrave.og.engine.gait.Skeleton;
import com.opengrave.og.engine.gait.Walk;
import com.opengrave.og.light.Shadow;
import com.opengrave.og.models.DAEAnimClip;
import com.opengrave.og.models.DAEAnimCollection;
import com.opengrave.og.resources.Resources;
import com.opengrave.og.util.Matrix4f;
import com.opengrave.og.util.Quaternion;
import com.opengrave.og.util.Vector4f;
public class AnimatedObject extends BaseObject {
private ArrayList<AnimatedObjectAnimation> animations = new ArrayList<AnimatedObjectAnimation>();
// float speed = 0.0005f;
private ArrayList<Matrix4f> skinningMatrixList;
int boneCount = 128;
public Skeleton skele;
public Walk walk;
// public AnimatedObject(RenderableBoneAnimatedStatic coll) {
// this.renderable = coll;
// boneCount = renderable.getSkeleton().getBoneCount();
// }
public Matrix4f getMatrix() {
return new Matrix4f();
}
public AnimatedObject(CommonObject cobj) {
super(cobj);
}
public void doUpdate(float delta) {
// TODO Apply walking and gaits somewhere more sensible
if (walk == null && this.cobj.getModelLabel().startsWith("mod/craig")) {
setWalk(new BipedWalk(this, getSkeleton().getBone("UpperLeg.left"), getSkeleton().getBone("UpperLeg.right")));
}
moveBasedOnPath(delta);
lookAt();
ArrayList<AnimatedObjectAnimation> delete = new ArrayList<AnimatedObjectAnimation>();
for (AnimatedObjectAnimation aoa : animations) {
if (aoa.delete) {
System.out.println("Animation ended naturally : " + aoa.name);
delete.add(aoa);
} else {
aoa.update(delta);
}
}
for (AnimatedObjectAnimation aoa : delete) {
animations.remove(aoa);
}
// animations.addTime(delta * 0.05f);
if (skele != null) {
skele.location = location;
}
setupSkeleton(delta);
renderable.update(delta);
}
public void setMaterialList(MaterialList matList) {
this.matList = matList;
}
private void setupSkeleton(float delta) {
skinningMatrixList = new ArrayList<Matrix4f>();
for (int i = 0; i < boneCount; i++) {
skinningMatrixList.add(new Matrix4f());
}
renderable.getBoundingBox().clear();
// Pass one - position relative to ident matrix
Skeleton skel = ((RenderableBoneAnimatedStatic) renderable).getSkeleton();
if (skel == null || skel.root == null) {
return;
}
positionBone(skel.root, new Matrix4f());
if (walk != null) {
walk.update(delta);
setSkinningMatrixForBone(skele.root);
}
}
private void positionBone(Bone bone, Matrix4f parentWorld) {
if (bone == null) {
return;
}
Matrix4f local = null;
Collections.sort(animations);
// First pass - find the highest priority anim that cares about this
// bone.
ArrayList<AnimatedObjectAnimation> mergedAnims = new ArrayList<AnimatedObjectAnimation>();
for (AnimatedObjectAnimation aoa : animations) {
// if (aoa.name.equalsIgnoreCase("walk")
// || aoa.name.equalsIgnoreCase("run")) {
// mergedAnims.add(aoa);
// continue;
// }
local = aoa.getMatrixForBone(bone);
if (aoa.isImportant(bone)) {
break;
}
}
// Ignore. Can't seem to get this block to perform
if (false == true && local == null && mergedAnims.size() > 0) {
float tW = 0f;
for (AnimatedObjectAnimation aoa : mergedAnims) {
tW += aoa.blendPerc;
}
Vector4f trans = new Vector4f();
Quaternion quat = null; // makeQuat(mat);
for (AnimatedObjectAnimation aoa : mergedAnims) {
Quaternion quat2 = new Quaternion();
Matrix4f matBone = aoa.getMatrixForBone(bone);
quat2.setFromMatrix(matBone);
if (trans == null) {
trans = new Vector4f(matBone.get(0, 3), matBone.get(1, 3), matBone.get(2, 3), matBone.get(3, 3));
}
// aoa.getMatrixForBone(bone).
// Vector4f quat2 = makeQuat(aoa.getMatrixForBone(bone));
// quat2.x = quat2.x * (aoa.blendPerc/tW);
// quat2.y = quat2.y * (aoa.blendPerc/tW);
// quat2.z = quat2.z * (aoa.blendPerc/tW);
// quat2.w = quat2.w * (aoa.blendPerc/tW);
if (quat == null) {
quat = quat2;
} else {
nlerp(quat, quat2, tW);
}
// quat = Vector4f.add(quat, quat2, null);
}
// Quaternion.
local = quatToMatrix(quat);
local.set(0, 3, trans.x);
local.set(1, 3, trans.y);
local.set(2, 3, trans.z);
local.set(3, 3, trans.w);
}
// Second pass - find out if an anim poses this in the same direction
// all the way through
// TODO : Test if the exact same transform is used in every anim - a
// surefire way to correctly pose the model
if (local == null) {
if (animations.size() > 0) {
local = animations.get(animations.size() - 1).getMatrixForBone(bone);
}
}
// Final fallback - bind pose of bone. Most likely wrong since the
// exporter assumed y+ as up.
if (local == null) {
local = bone.local;
}
Matrix4f world = parentWorld.mult(local, null);
bone.setWorldMatrix(local, world);
Vector4f v = world.mult4(new Vector4f(0f, 0f, 0f, 0f), null);
renderable.getBoundingBox().addVector4f(v);
skinningMatrixList.set(bone.index, bone.skinningMatrix);
for (Bone child : bone.getChildren()) {
positionBone(child, world);
}
}
public void setSkinningMatrixForBone(Bone bone) {
skinningMatrixList.set(bone.index, bone.skinningMatrix);
for (Bone child : bone.getChildren()) {
setSkinningMatrixForBone(child);
}
}
private void nlerp(Quaternion quat, Quaternion quat2, float blend) {
float dot = quat.dot(quat2);
float blendI = 1.0f - blend;
if (dot < 0.0f) {
quat.x = blendI * quat.x - blend * quat2.x;
quat.y = blendI * quat.y - blend * quat2.y;
quat.z = blendI * quat.z - blend * quat2.z;
quat.w = blendI * quat.w - blend * quat2.w;
} else {
quat.x = blendI * quat.x + blend * quat2.x;
quat.y = blendI * quat.y + blend * quat2.y;
quat.z = blendI * quat.z + blend * quat2.z;
quat.w = blendI * quat.w + blend * quat2.w;
}
quat = quat.normalise(null);
}
public final Matrix4f quatToMatrix(Quaternion q) {
Matrix4f m = new Matrix4f();
float sqw = q.w * q.w;
float sqx = q.x * q.x;
float sqy = q.y * q.y;
float sqz = q.z * q.z;
// invs (inverse square length) is only required if quaternion is not
// already normalised
float invs = 1 / (sqx + sqy + sqz + sqw);
m.set(0, 0, (sqx - sqy - sqz + sqw) * invs); // since sqw + sqx + sqy + sqz
// =1/invs*invs
m.set(1, 1, (-sqx + sqy - sqz + sqw) * invs);
m.set(2, 2, (-sqx - sqy + sqz + sqw) * invs);
float tmp1 = q.x * q.y;
float tmp2 = q.z * q.w;
m.set(1, 0, 2f * (tmp1 + tmp2) * invs);
m.set(0, 1, 2f * (tmp1 - tmp2) * invs);
tmp1 = q.x * q.z;
tmp2 = q.y * q.w;
m.set(2, 0, 2f * (tmp1 - tmp2) * invs);
m.set(0, 2, 2f * (tmp1 + tmp2) * invs);
tmp1 = q.y * q.z;
tmp2 = q.x * q.w;
m.set(2, 1, 2f * (tmp1 + tmp2) * invs);
m.set(1, 2, 2f * (tmp1 - tmp2) * invs);
return m;
}
public AnimatedObjectAnimation getAnimation(String label) {
for (AnimatedObjectAnimation aoa : animations) {
if (aoa.name.equalsIgnoreCase(label)) {
return aoa;
}
}
return null;
}
public void startAnimation(String label, float speed, boolean once) {
boolean blend = false;
if (label.equalsIgnoreCase("walk") || label.equalsIgnoreCase("run")) {
return;
// These should technically be mutually exclusive. We'll attempt to
// blend between them
// blend = true;
}
System.out.println("Start Anim request : " + label);
if (getAnimation(label) != null) {
getAnimation(label).setSpeed(speed);
getAnimation(label).setStopNextRound(once);
return;
}
System.out.println("Not already running : " + label);
DAEAnimClip animClip = getAnimClip(label);
if (animClip == null) {
return;
}
System.out.println("Starting... : " + label);
if (blend) {
AnimatedObjectAnimation anim = getAnimation("walk");
if (anim != null) {
anim.blendOut(3f);
}
anim = getAnimation("idle");
if (anim != null) {
anim.blendOut(3f);
}
anim = getAnimation("run");
if (anim != null) {
anim.blendOut(3f);
}
}
AnimatedObjectAnimation aoa = new AnimatedObjectAnimation(animClip, speed, ((RenderableBoneAnimatedStatic) renderable).getSkeleton());
if (blend) {
aoa.blendIn(3f);
}
aoa.once = once;
animations.add(aoa);
System.out.println("Animation started : " + label);
}
public void stopAnimation(String label) {
AnimatedObjectAnimation anim = getAnimation(label);
if (anim != null) {
animations.remove(anim);
System.out.println("Animation stopped : " + label);
}
}
public DAEAnimClip getAnimClip(String label) {
for (DAEAnimClip clip : ((RenderableBoneAnimatedStatic) renderable).getSkeleton().root.animation) {
int loc = clip.id.lastIndexOf("-");
String bit = clip.id;
if (loc > 0) {
bit = clip.id.substring(0, loc);
}
if (bit.equalsIgnoreCase(label)) {
return clip;
}
}
return null;
}
@Override
public void doRender(Matrix4f parent) {
if (!visible) {
return;
}
renderable.setMaterialList(matList);
((RenderableBoneAnimatedStatic) renderable).setSkinningMatrix(skinningMatrixList);
renderable.setContext(context);
renderable.render(parent, style);
}
@Override
public void doRenderShadows(Matrix4f parent, Shadow shadow) {
((RenderableBoneAnimatedStatic) renderable).setSkinningMatrix(skinningMatrixList);
renderable.setContext(context);
renderable.renderShadows(parent, shadow);
}
@Override
public void doRenderForPicking(Matrix4f parent) {
((RenderableBoneAnimatedStatic) renderable).setSkinningMatrix(skinningMatrixList);
renderable.setContext(context);
renderable.renderForPicking(parent, this);
}
@Override
public void renderableLabelChanged(String s) {
DAEAnimCollection f = Resources.getAnimatedModel(s);
this.renderable = f.getRenderable();
if (f.getRenderable().getSkeleton() == null) {
System.out.println("No skeleton for anim model");
} else {
this.skele = f.getRenderable().getSkeleton();
}
}
@Override
public String getType() {
return "animated";
}
@Override
public void doRenderSemiTransparent(Matrix4f matrix) {
}
@Override
public RenderView getContext() {
return context;
}
@Override
public BoundingBox getBoundingBox() {
return renderable.getBoundingBox();
}
public RenderableBoneAnimatedStatic getRenderable() {
return (RenderableBoneAnimatedStatic) renderable;
}
public Skeleton getSkeleton() {
return skele;
}
public void setWalk(Walk walk) {
this.walk = walk;
}
public Walk getWalk() {
return walk;
}
public Surface getSurface() {
return s;
}
}