/* * This file is part of the Haven & Hearth game client. * Copyright (C) 2009 Fredrik Tolf <fredrik@dolda2000.com>, and * Björn Johannessen <johannessen.bjorn@gmail.com> * * Redistribution and/or modification of this file is subject to the * terms of the GNU Lesser General Public License, version 3, as * published by the Free Software Foundation. * * This program 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. * * Other parts of this source tree adhere to other copying * rights. Please see the file `COPYING' in the root directory of the * source tree for details. * * A copy the GNU Lesser General Public License is distributed along * with the source tree of which this file is a part in the file * `doc/LPGL-3'. If it is missing for any reason, please see the Free * Software Foundation's website at <http://www.fsf.org/>, or write * to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, * Boston, MA 02111-1307 USA */ package haven; import java.util.*; import javax.media.opengl.*; public class Skeleton { public final Map<String, Bone> bones = new HashMap<String, Bone>(); public final Bone[] blist; /* Topologically sorted */ public final Pose bindpose; public Skeleton(Collection<Bone> bones) { Set<Bone> bset = new HashSet<Bone>(bones); blist = new Bone[bones.size()]; int idx = 0; for(Bone b : bones) this.bones.put(b.name, b); while(!bset.isEmpty()) { boolean f = false; for(Iterator<Bone> i = bset.iterator(); i.hasNext();) { Bone b = i.next(); boolean has; if(b.parent == null) { has = true; } else { has = false; for(Bone p : blist) { if(p == b.parent) { has = true; break; } } } if(has) { blist[b.idx = idx++] = b; i.remove(); f = true; } } if(!f) throw(new RuntimeException("Cyclical bone hierarchy")); } bindpose = mkbindpose(); } public static class Bone { public String name; public Coord3f ipos, irax; public float irang; public Bone parent; public int idx; public Bone(String name, Coord3f ipos, Coord3f irax, float irang) { this.name = name; this.ipos = ipos; this.irax = irax; this.irang = irang; } } private static float[] rotasq(float[] q, float[] axis, float angle) { float m = (float)Math.sin(angle / 2.0); q[0] = (float)Math.cos(angle / 2.0); q[1] = m * axis[0]; q[2] = m * axis[1]; q[3] = m * axis[2]; return(q); } private static float[] qqmul(float[] d, float[] a, float[] b) { float aw = a[0], ax = a[1], ay = a[2], az = a[3]; float bw = b[0], bx = b[1], by = b[2], bz = b[3]; d[0] = (aw * bw) - (ax * bx) - (ay * by) - (az * bz); d[1] = (aw * bx) + (ax * bw) + (ay * bz) - (az * by); d[2] = (aw * by) - (ax * bz) + (ay * bw) + (az * bx); d[3] = (aw * bz) + (ax * by) - (ay * bx) + (az * bw); return(d); } private static float[] vqrot(float[] d, float[] v, float[] q) { float vx = v[0], vy = v[1], vz = v[2]; float qw = q[0], qx = q[1], qy = q[2], qz = q[3]; /* I dearly wonder how the JIT's common-subexpression * eliminator does on these. */ d[0] = (qw * qw * vx) + (2 * qw * qy * vz) - (2 * qw * qz * vy) + (qx * qx * vx) + (2 * qx * qy * vy) + (2 * qx * qz * vz) - (qz * qz * vx) - (qy * qy * vx); d[1] = (2 * qx * qy * vx) + (qy * qy * vy) + (2 * qy * qz * vz) + (2 * qw * qz * vx) - (qz * qz * vy) + (qw * qw * vy) - (2 * qw * qx * vz) - (qx * qx * vy); d[2] = (2 * qx * qz * vx) + (2 * qy * qz * vy) + (qz * qz * vz) - (2 * qw * qy * vx) - (qy * qy * vz) + (2 * qw * qx * vy) - (qx * qx * vz) + (qw * qw * vz); return(d); } private static float[] vset(float[] d, float[] s) { d[0] = s[0]; d[1] = s[1]; d[2] = s[2]; return(d); } private static float[] qset(float[] d, float[] s) { d[0] = s[0]; d[1] = s[1]; d[2] = s[2]; d[3] = s[3]; return(d); } private static float[] vinv(float[] d, float[] s) { d[0] = -s[0]; d[1] = -s[1]; d[2] = -s[2]; return(d); } private static float[] qinv(float[] d, float[] s) { /* Assumes |s| = 1.0 */ d[0] = s[0]; d[1] = -s[1]; d[2] = -s[2]; d[3] = -s[3]; return(d); } private static float[] vvadd(float[] d, float[] a, float[] b) { float ax = a[0], ay = a[1], az = a[2]; float bx = b[0], by = b[1], bz = b[2]; d[0] = ax + bx; d[1] = ay + by; d[2] = az + bz; return(d); } private static float[] qqslerp(float[] d, float[] a, float[] b, float t) { float aw = a[0], ax = a[1], ay = a[2], az = a[3]; float bw = b[0], bx = b[1], by = b[2], bz = b[3]; if((aw == bw) && (ax == bx) && (ay == by) && (az == bz)) return(qset(d, a)); float cos = (aw * bw) + (ax * bx) + (ay * by) + (az * bz); if(cos < 0) { bw = -bw; bx = -bx; by = -by; bz = -bz; cos = -cos; } float d0, d1; if(cos > 0.9999f) { /* Reasonable threshold? Is this function even critical * for performance? */ d0 = 1.0f - t; d1 = t; } else { float da = (float)Math.acos(Utils.clip(cos, 0.0, 1.0)); float nf = 1.0f / (float)Math.sin(da); d0 = (float)Math.sin((1.0f - t) * da) * nf; d1 = (float)Math.sin(t * da) * nf; } d[0] = (d0 * aw) + (d1 * bw); d[1] = (d0 * ax) + (d1 * bx); d[2] = (d0 * ay) + (d1 * by); d[3] = (d0 * az) + (d1 * bz); return(d); } public Pose mkbindpose() { Pose p = new Pose(); for(int i = 0; i < blist.length; i++) { Bone b = blist[i]; p.lpos[i][0] = b.ipos.x; p.lpos[i][1] = b.ipos.y; p.lpos[i][2] = b.ipos.z; rotasq(p.lrot[i], b.irax.to3a(), b.irang); } p.gbuild(); return(p); } public class Pose { public float[][] lpos, gpos; public float[][] lrot, grot; private Pose from = null; public int seq = 0; private Pose() { int nb = blist.length; lpos = new float[nb][3]; gpos = new float[nb][3]; lrot = new float[nb][4]; grot = new float[nb][4]; } public Pose(Pose from) { this(); this.from = from; reset(); gbuild(); } public Skeleton skel() { return(Skeleton.this); } public void reset() { for(int i = 0; i < blist.length; i++) { vset(lpos[i], from.lpos[i]); qset(lrot[i], from.lrot[i]); } } public void gbuild() { int nb = blist.length; for(int i = 0; i < nb; i++) { Bone b = blist[i]; if(b.parent == null) { gpos[i][0] = lpos[i][0]; gpos[i][1] = lpos[i][1]; gpos[i][2] = lpos[i][2]; grot[i][0] = lrot[i][0]; grot[i][1] = lrot[i][1]; grot[i][2] = lrot[i][2]; grot[i][3] = lrot[i][3]; } else { int pi = b.parent.idx; qqmul(grot[i], grot[pi], lrot[i]); vqrot(gpos[i], lpos[i], grot[pi]); vvadd(gpos[i], gpos[i], gpos[pi]); } } seq++; } public void blend(Pose o, float d) { for(int i = 0; i < blist.length; i++) { qqslerp(lrot[i], lrot[i], o.lrot[i], d); lpos[i][0] = lpos[i][0] + ((o.lpos[i][0] - lpos[i][0]) * d); lpos[i][1] = lpos[i][1] + ((o.lpos[i][1] - lpos[i][1]) * d); lpos[i][2] = lpos[i][2] + ((o.lpos[i][2] - lpos[i][2]) * d); } } public Location bonetrans(final int bone) { return(new Location(Matrix4f.identity()) { private int cseq = -1; public Matrix4f fin(Matrix4f p) { if(cseq != seq) { Matrix4f xf = Transform.makexlate(new Matrix4f(), new Coord3f(gpos[bone][0], gpos[bone][1], gpos[bone][2])); if(grot[bone][0] < 0.9999) { float ang = (float)(Math.acos(grot[bone][0]) * 2.0); xf = xf.mul1(Transform.makerot(new Matrix4f(), new Coord3f(grot[bone][1], grot[bone][2], grot[bone][3]).norm(), ang)); } update(xf); cseq = seq; } return(super.fin(p)); } }); } public Location bonetrans2(final int bone) { return(new Location(Matrix4f.identity()) { private int cseq = -1; private float[] pos = new float[3], rot = new float[4]; public Matrix4f fin(Matrix4f p) { if(cseq != seq) { rot = qqmul(rot, grot[bone], qinv(rot, bindpose.grot[bone])); pos = vvadd(pos, gpos[bone], vqrot(pos, vinv(pos, bindpose.gpos[bone]), rot)); Matrix4f xf = Transform.makexlate(new Matrix4f(), new Coord3f(pos[0], pos[1], pos[2])); if(rot[0] < 0.999999) { float ang = (float)(Math.acos(rot[0]) * 2.0); xf = xf.mul1(Transform.makerot(new Matrix4f(), new Coord3f(rot[1], rot[2], rot[3]).norm(), ang)); } update(xf); cseq = seq; } return(super.fin(p)); } }); } public class BoneAlign extends Location { private final Coord3f ref; private final int orig, tgt; private int cseq = -1; public BoneAlign(Coord3f ref, Bone orig, Bone tgt) { super(Matrix4f.identity()); this.ref = ref; this.orig = orig.idx; this.tgt = tgt.idx; } public Matrix4f fin(Matrix4f p) { if(cseq != seq) { Coord3f cur = new Coord3f(gpos[tgt][0] - gpos[orig][0], gpos[tgt][1] - gpos[orig][1], gpos[tgt][2] - gpos[orig][2]).norm(); Coord3f axis = cur.cmul(ref).norm(); float ang = (float)Math.acos(cur.dmul(ref)); /* System.err.println(cur + ", " + ref + ", " + axis + ", " + ang); */ update(Transform.makexlate(new Matrix4f(), new Coord3f(gpos[orig][0], gpos[orig][1], gpos[orig][2])) .mul1(Transform.makerot(new Matrix4f(), axis, -ang))); cseq = seq; } return(super.fin(p)); } } public void boneoff(int bone, float[] offtrans) { /* It would be nice if these "new float"s get * stack-allocated. */ float[] rot = new float[4], xlate = new float[3]; rot = qqmul(rot, grot[bone], qinv(rot, bindpose.grot[bone])); xlate = vvadd(xlate, gpos[bone], vqrot(xlate, vinv(xlate, bindpose.gpos[bone]), rot)); offtrans[3] = 0; offtrans[7] = 0; offtrans[11] = 0; offtrans[15] = 1; offtrans[12] = xlate[0]; offtrans[13] = xlate[1]; offtrans[14] = xlate[2]; /* I must admit I don't /quite/ understand why the * rotation needs to be inverted... */ float w = -rot[0], x = rot[1], y = rot[2], z = rot[3]; float xw = x * w * 2, xx = x * x * 2, xy = x * y * 2, xz = x * z * 2; float yw = y * w * 2, yy = y * y * 2, yz = y * z * 2; float zw = z * w * 2, zz = z * z * 2; offtrans[ 0] = 1 - (yy + zz); offtrans[ 5] = 1 - (xx + zz); offtrans[10] = 1 - (xx + yy); offtrans[ 1] = xy - zw; offtrans[ 2] = xz + yw; offtrans[ 4] = xy + zw; offtrans[ 6] = yz - xw; offtrans[ 8] = xz - yw; offtrans[ 9] = yz + xw; } public final Rendered debug = new Rendered() { public void draw(GOut g) { GL2 gl = g.gl; g.st.put(Light.lighting, null); g.state(States.xray); g.apply(); gl.glBegin(GL2.GL_LINES); for(int i = 0; i < blist.length; i++) { if(blist[i].parent != null) { int pi = blist[i].parent.idx; gl.glColor3f(1.0f, 0.0f, 0.0f); gl.glVertex3f(gpos[pi][0], gpos[pi][1], gpos[pi][2]); gl.glColor3f(0.0f, 1.0f, 0.0f); gl.glVertex3f(gpos[i][0], gpos[i][1], gpos[i][2]); } } gl.glEnd(); } public boolean setup(RenderList rl) { rl.prepo(States.xray); return(true); } }; } public interface ModOwner { public double getv(); public Coord3f getc(); public Glob glob(); public static final ModOwner nil = new ModOwner() { public double getv() {return(0);} public Coord3f getc() {return(Coord3f.o);} public Glob glob() {throw(new NullPointerException());} }; } public abstract class PoseMod { public final ModOwner owner; public float[][] lpos, lrot; public PoseMod(ModOwner owner) { this.owner = owner; int nb = blist.length; lpos = new float[nb][3]; lrot = new float[nb][4]; for(int i = 0; i < nb; i++) lrot[i][0] = 1; } @Deprecated public PoseMod() { this(ModOwner.nil); } public Skeleton skel() {return(Skeleton.this);} public void reset() { for(int i = 0; i < blist.length; i++) { lpos[i][0] = 0; lpos[i][1] = 0; lpos[i][2] = 0; lrot[i][0] = 1; lrot[i][1] = 0; lrot[i][2] = 0; lrot[i][3] = 0; } } public void rot(int bone, float ang, float ax, float ay, float az) { float[] x = {ax, ay, az}; qqmul(lrot[bone], lrot[bone], rotasq(new float[4], x, ang)); } public void apply(Pose p) { for(int i = 0; i < blist.length; i++) { vvadd(p.lpos[i], p.lpos[i], lpos[i]); qqmul(p.lrot[i], p.lrot[i], lrot[i]); } } public boolean tick(float dt) { return(false); } public abstract boolean stat(); public abstract boolean done(); } public PoseMod nilmod() { return(new PoseMod(ModOwner.nil) { public boolean stat() {return(true);} public boolean done() {return(false);} }); } public static PoseMod combine(final PoseMod... mods) { PoseMod first = mods[0]; return(first.skel().new PoseMod(first.owner) { final boolean stat; { boolean s = true; for(PoseMod m : mods) s = s && m.stat(); stat = s; } public void apply(Pose p) { for(PoseMod m : mods) m.apply(p); } public boolean tick(float dt) { boolean ret = false; for(PoseMod m : mods) { if(m.tick(dt)) ret = true; } return(ret); } public boolean stat() { return(stat); } public boolean done() { for(PoseMod m : mods) { if(m.done()) return(true); } return(false); } }); } @Resource.PublishedCode(name = "pose") public interface ModFactory { public PoseMod create(Skeleton skel, ModOwner owner, Resource res, Message sdt); public static final ModFactory def = new ModFactory() { public PoseMod create(Skeleton skel, ModOwner owner, Resource res, Message sdt) { int mask = Sprite.decnum(sdt); Collection<PoseMod> poses = new LinkedList<PoseMod>(); for(ResPose p : res.layers(ResPose.class)) { if((p.id < 0) || ((mask & (1 << p.id)) != 0)) poses.add(p.forskel(owner, skel, p.defmode)); } if(poses.size() == 0) return(skel.nilmod()); else if(poses.size() == 1) return(Utils.el(poses)); else return(combine(poses.toArray(new PoseMod[0]))); } }; } public PoseMod mkposemod(ModOwner owner, Resource res, Message sdt) { ModFactory f = res.getcode(ModFactory.class, false); if(f == null) f = ModFactory.def; return(f.create(this, owner, res, sdt)); } @Resource.LayerName("skel") public static class Res extends Resource.Layer { public final Skeleton s; public Res(Resource res, byte[] buf) { res.super(); Map<String, Bone> bones = new HashMap<String, Bone>(); Map<Bone, String> pm = new HashMap<Bone, String>(); int[] off = {0}; while(off[0] < buf.length) { String bnm = Utils.strd(buf, off); Coord3f pos = new Coord3f((float)Utils.floatd(buf, off[0]), (float)Utils.floatd(buf, off[0] + 5), (float)Utils.floatd(buf, off[0] + 10)); off[0] += 15; Coord3f rax = new Coord3f((float)Utils.floatd(buf, off[0]), (float)Utils.floatd(buf, off[0] + 5), (float)Utils.floatd(buf, off[0] + 10)).norm(); off[0] += 15; float rang = (float)Utils.floatd(buf, off[0]); off[0] += 5; String bp = Utils.strd(buf, off); Bone b = new Bone(bnm, pos, rax, rang); if(bones.put(bnm, b) != null) throw(new RuntimeException("Duplicate bone name: " + b.name)); pm.put(b, bp); } for(Bone b : bones.values()) { String bp = pm.get(b); if(bp.length() == 0) { b.parent = null; } else { if((b.parent = bones.get(bp)) == null) throw(new Resource.LoadException("Parent bone " + bp + " not found for " + b.name, getres())); } } s = new Skeleton(bones.values()) { public String toString() { return("Skeleton(" + getres().name + ")"); } }; } public void init() {} } public class TrackMod extends PoseMod { public final Track[] tracks; public final FxTrack[] effects; public final float len; public final WrapMode mode; private final boolean stat; private boolean done; public float time = 0.0f; private boolean speedmod = false; private double nspeed = 0.0; private boolean back = false; public TrackMod(ModOwner owner, Track[] tracks, FxTrack[] effects, float len, WrapMode mode) { super(owner); this.tracks = tracks; this.effects = effects; this.len = len; this.mode = mode; for(Track t : tracks) { if((t != null) && (t.frames.length > 1)) { stat = false; aupdate(0.0f); return; } } stat = done = true; aupdate(0.0f); } @Deprecated public TrackMod(Track[] tracks, float len, WrapMode mode) { this(ModOwner.nil, tracks, new FxTrack[0], len, mode); } public void aupdate(float time) { if(time > len) time = len; reset(); for(int i = 0; i < tracks.length; i++) { Track t = tracks[i]; if((t == null) || (t.frames.length == 0)) continue; if(t.frames.length == 1) { qset(lrot[i], t.frames[0].rot); vset(lpos[i], t.frames[0].trans); } else { Track.Frame cf, nf; float ct, nt; int l = 0, r = t.frames.length; while(true) { /* c should never be able to be >= frames.length */ int c = l + ((r - l) >> 1); ct = t.frames[c].time; nt = (c < t.frames.length - 1)?(t.frames[c + 1].time):len; if(ct > time) { r = c; } else if(nt < time) { l = c + 1; } else { cf = t.frames[c]; nf = t.frames[(c + 1) % t.frames.length]; break; } } float d; if(nt == ct) d = 0; else d = (time - ct) / (nt - ct); qqslerp(lrot[i], cf.rot, nf.rot, d); lpos[i][0] = cf.trans[0] + ((nf.trans[0] - cf.trans[0]) * d); lpos[i][1] = cf.trans[1] + ((nf.trans[1] - cf.trans[1]) * d); lpos[i][2] = cf.trans[2] + ((nf.trans[2] - cf.trans[2]) * d); } } } private void playfx(float ot, float nt) { if(!(owner instanceof Gob)) return; Gob gob = (Gob)owner; if(ot > nt) { playfx(Math.min(ot, len), len); playfx(0, Math.max(0, nt)); } else { for(FxTrack t : effects) { for(FxTrack.Event ev : t.events) { if((ev.time >= ot) && (ev.time < nt)) { ev.trigger(gob); } } } } } public boolean tick(float dt) { if(speedmod) dt *= owner.getv() / nspeed; float nt = time + (back?-dt:dt); switch(mode) { case LOOP: nt %= len; break; case ONCE: if(nt > len) { nt = len; done = true; } break; case PONG: if(!back && (nt > len)) { nt = len; back = true; } else if(back && (nt < 0)) { nt = 0; done = true; } break; case PONGLOOP: if(!back && (nt > len)) { nt = len; back = true; } else if(back && (nt < 0)) { nt = 0; back = false; } break; } float ot = this.time; this.time = nt; if(!stat) { aupdate(this.time); if(!back) playfx(ot, nt); else playfx(nt, ot); return(true); } else { return(false); } } public boolean stat() { return(stat); } public boolean done() { return(done); } } public static class Track { public final String bone; public final Frame[] frames; public static class Frame { public final float time; public final float[] trans, rot; public Frame(float time, float[] trans, float[] rot) { this.time = time; this.trans = trans; this.rot = rot; } } public Track(String bone, Frame[] frames) { this.bone = bone; this.frames = frames; } } public static class FxTrack { public final Event[] events; public static abstract class Event { public final float time; public Event(float time) { this.time = time; } public abstract void trigger(Gob gob); } public FxTrack(Event[] events) { this.events = events; } public static class SpawnSprite extends Event { public final Indir<Resource> res; public final byte[] sdt; public final Location loc; public SpawnSprite(float time, Indir<Resource> res, byte[] sdt, Location loc) { super(time); this.res = res; this.sdt = (sdt == null)?new byte[0]:sdt; this.loc = loc; } public void trigger(Gob gob) { final Coord3f fc; try { fc = gob.getc(); } catch(Loading e) { return; } Gob n = gob.glob.oc.new Virtual(gob.rc, gob.a) { public Coord3f getc() { return(new Coord3f(fc)); } public boolean setup(RenderList rl) { if(SpawnSprite.this.loc != null) rl.prepc(SpawnSprite.this.loc); return(super.setup(rl)); } }; n.ols.add(new Gob.Overlay(-1, res, new Message(0, sdt))); } } } @Resource.LayerName("skan") public static class ResPose extends Resource.Layer implements Resource.IDLayer<Integer> { public final int id; public final float len; public final Track[] tracks; public final FxTrack[] effects; public final double nspeed; public final WrapMode defmode; private static Track.Frame[] parseframes(byte[] buf, int[] off) { Track.Frame[] frames = new Track.Frame[Utils.uint16d(buf, off[0])]; off[0] += 2; for(int i = 0; i < frames.length; i++) { float tm = (float)Utils.floatd(buf, off[0]); off[0] += 5; float[] trans = new float[3]; for(int o = 0; o < 3; o++) { trans[o] = (float)Utils.floatd(buf, off[0]); off[0] += 5; } float rang = (float)Utils.floatd(buf, off[0]); off[0] += 5; float[] rax = new float[3]; for(int o = 0; o < 3; o++) { rax[o] = (float)Utils.floatd(buf, off[0]); off[0] += 5; } frames[i] = new Track.Frame(tm, trans, rotasq(new float[4], rax, rang)); } return(frames); } private FxTrack parsefx(byte[] buf, int[] off) { FxTrack.Event[] events = new FxTrack.Event[Utils.uint16d(buf, off[0])]; off[0] += 2; for(int i = 0; i < events.length; i++) { float tm = (float)Utils.floatd(buf, off[0]); off[0] += 5; int t = Utils.ub(buf[off[0]++]); switch(t) { case 0: String resnm = Utils.strd(buf, off); int resver = Utils.uint16d(buf, off[0]); off[0] += 2; byte[] sdt = new byte[Utils.ub(buf[off[0]++])]; System.arraycopy(buf, off[0], sdt, 0, sdt.length); off[0] += sdt.length; Indir<Resource> res = Resource.load(resnm, resver).indir(); events[i] = new FxTrack.SpawnSprite(tm, res, sdt, null); break; default: throw(new Resource.LoadException("Illegal control event: " + t, getres())); } } return(new FxTrack(events)); } public ResPose(Resource res, byte[] buf) { res.super(); this.id = Utils.int16d(buf, 0); int fl = buf[2]; int mode = buf[3]; if(mode == 0) defmode = WrapMode.ONCE; else if(mode == 1) defmode = WrapMode.LOOP; else if(mode == 2) defmode = WrapMode.PONG; else if(mode == 3) defmode = WrapMode.PONGLOOP; else throw(new Resource.LoadException("Illegal animation mode: " + mode, getres())); this.len = (float)Utils.floatd(buf, 4); int[] off = {9}; if((fl & 1) != 0) { nspeed = Utils.floatd(buf, off[0]); off[0] += 5; } else { nspeed = -1; } Collection<Track> tracks = new LinkedList<Track>(); Collection<FxTrack> fx = new LinkedList<FxTrack>(); while(off[0] < buf.length) { String bnm = Utils.strd(buf, off); if(bnm.equals("{ctl}")) { fx.add(parsefx(buf, off)); } else { tracks.add(new Track(bnm, parseframes(buf, off))); } } this.tracks = tracks.toArray(new Track[0]); this.effects = fx.toArray(new FxTrack[0]); } public TrackMod forskel(ModOwner owner, Skeleton skel, WrapMode mode) { Track[] remap = new Track[skel.blist.length]; for(Track t : tracks) { Skeleton.Bone b = skel.bones.get(t.bone); if(b == null) throw(new RuntimeException("Bone \"" + t.bone + "\" in animation reference does not exist in skeleton " + skel)); remap[b.idx] = t; } TrackMod ret = skel.new TrackMod(owner, remap, effects, len, mode); if(nspeed > 0) { ret.speedmod = true; ret.nspeed = nspeed; } return(ret); } @Deprecated public TrackMod forskel(Skeleton skel, WrapMode mode) { return(forskel(ModOwner.nil, skel, mode)); } @Deprecated public TrackMod forgob(Skeleton skel, WrapMode mode, Gob gob) { return(forskel(gob, skel, mode)); } public Integer layerid() { return(id); } public void init() {} } @Resource.LayerName("boneoff") public static class BoneOffset extends Resource.Layer implements Resource.IDLayer<String> { public final String nm; public final Command[] prog; private static final HatingJava[] opcodes = new HatingJava[256]; static { opcodes[0] = new HatingJava() { public Command make(byte[] buf, int[] off) { final float x = (float)Utils.floatd(buf, off[0]); off[0] += 5; final float y = (float)Utils.floatd(buf, off[0]); off[0] += 5; final float z = (float)Utils.floatd(buf, off[0]); off[0] += 5; return(new Command() { public GLState make(Pose pose) { return(Location.xlate(new Coord3f(x, y, z))); } }); } }; opcodes[1] = new HatingJava() { public Command make(byte[] buf, int[] off) { final float ang = (float)Utils.floatd(buf, off[0]); off[0] += 5; final float ax = (float)Utils.floatd(buf, off[0]); off[0] += 5; final float ay = (float)Utils.floatd(buf, off[0]); off[0] += 5; final float az = (float)Utils.floatd(buf, off[0]); off[0] += 5; return(new Command() { public GLState make(Pose pose) { return(Location.rot(new Coord3f(ax, ay, az), ang)); } }); } }; opcodes[2] = new HatingJava() { public Command make(byte[] buf, int[] off) { final String bonenm = Utils.strd(buf, off); return(new Command() { public GLState make(Pose pose) { Bone bone = pose.skel().bones.get(bonenm); return(pose.bonetrans(bone.idx)); } }); } }; opcodes[3] = new HatingJava() { public Command make(byte[] buf, int[] off) { float rx1 = (float)Utils.floatd(buf, off[0]); off[0] += 5; float ry1 = (float)Utils.floatd(buf, off[0]); off[0] += 5; float rz1 = (float)Utils.floatd(buf, off[0]); off[0] += 5; float l = (float)Math.sqrt((rx1 * rx1) + (ry1 * ry1) + (rz1 * rz1)); final Coord3f ref = new Coord3f(rx1 / l, ry1 / l, rz1 / l); final String orignm = Utils.strd(buf, off); final String tgtnm = Utils.strd(buf, off); return(new Command() { public GLState make(Pose pose) { Bone orig = pose.skel().bones.get(orignm); Bone tgt = pose.skel().bones.get(tgtnm); return(pose.new BoneAlign(ref, orig, tgt)); } }); } }; } public interface Command { public GLState make(Pose pose); } public interface HatingJava { public Command make(byte[] buf, int[] off); } public BoneOffset(Resource res, byte[] buf) { res.super(); int[] off = {0}; this.nm = Utils.strd(buf, off); List<Command> cbuf = new LinkedList<Command>(); while(off[0] < buf.length) cbuf.add(opcodes[buf[off[0]++]].make(buf, off)); this.prog = cbuf.toArray(new Command[0]); } public String layerid() { return(nm); } public void init() { } public GLState forpose(Pose pose) { GLState[] ls = new GLState[prog.length]; for(int i = 0; i < prog.length; i++) ls[i] = prog[i].make(pose); return(GLState.compose(ls)); } } }