Skip to content

Instantly share code, notes, and snippets.

@ruby0x1
Last active August 29, 2015 14:06
Show Gist options
  • Save ruby0x1/a13f224d4b8398a9d76c to your computer and use it in GitHub Desktop.
Save ruby0x1/a13f224d4b8398a9d76c to your computer and use it in GitHub Desktop.
package enhaxe.resource.impl;
import enhaxe.math.Aabb;
import enhaxe.math.DualQuat;
import enhaxe.math.Mat44;
import enhaxe.math.Quat;
import enhaxe.math.Vec3;
import enhaxe.rendering.IGeometry;
import enhaxe.resource.Resource.IResourceData;
import enhaxe.resource.Resource.ResourceContext;
import enhaxe.resource.Resource.IGPUResourceData;
import enhaxe.resource.Resource.GPUResourceContext;
import foo3d.RenderDevice;
import haxe.io.Bytes;
import haxe.io.BytesData;
import haxe.io.BytesInput;
import haxe.io.BytesOutput;
typedef IqmHeader = {
var filesize:Int;
var flags:Int;
var num_text:Int; var ofs_text:Int;
var num_meshes:Int; var ofs_meshes:Int;
var num_vertexarrays:Int; var num_vertices:Int; var ofs_vertexarrays:Int;
var num_triangles:Int; var ofs_triangles:Int; var ofs_adjacency:Int;
var num_joints:Int; var ofs_joints:Int;
var num_poses:Int; var ofs_poses:Int;
var num_anims:Int; var ofs_anims:Int;
var num_frames:Int; var num_framechannels:Int; var ofs_frames:Int; var ofs_bounds:Int;
var num_comment:Int; var ofs_comment:Int;
var num_extensions:Int; var ofs_extensions:Int;
}
typedef IqmVertexArray = {
var type:Int;
var flags:Int;
var format:Int;
var size:Int;
var offset:Int;
}
typedef IqmMesh = {
var name:String;
var material:String;
var vstart:Int; var bcount:Int;
var aabb:Aabb;
}
class IqmJoint {
public var name:String;
public var parent:Int;
public var pos:Vec3;
public var scale:Vec3;
public var rot:Quat;
public var mBase:Mat44;
public var mInvBase:Mat44;
public var dqBase:DualQuat;
public var dqInvBase:DualQuat;
public function new() {
name = "";
parent = -1;
pos = new Vec3(0,0,0);
scale = new Vec3(1,1,1);
rot = new Quat(0,0,0,1);
mBase = new Mat44();
mInvBase = new Mat44();
dqBase = new DualQuat();
dqInvBase = new DualQuat();
}
}
typedef IqmAnim = {
var name:String;
var first_frame:Int; var num_frames:Int;
var framerate:Float;
var flags:Int;
}
typedef IqmPose = {
var parent:Int;
var mask:Int;
var channeloffset:Array<Float>;
var channelscale:Array<Float>;
}
typedef IqmData = {
layout:Int,
vBufs:Array<VBufferInfo>,
iBuf:IBufferInfo
}
class Iqm
implements IResourceData
implements IGPUResourceData
implements IGeometry
{
public static var NULL:Iqm = null;
inline public static var IQM_POSITION:Int = 0; // float, 3
inline public static var IQM_TEXCOORD:Int = 1; // float, 2
inline public static var IQM_NORMAL:Int = 2; // float, 3
inline public static var IQM_TANGENT:Int = 3; // float, 4
inline public static var IQM_BLENDINDEXES:Int = 4; // ubyte, 4
inline public static var IQM_BLENDWEIGHTS:Int = 5; // ubyte, 4
inline public static var IQM_COLOR:Int = 6; // ubyte, 4
inline public static var IQM_BYTE:Int = 0;
inline public static var IQM_UBYTE:Int = 1;
inline public static var IQM_SHORT:Int = 2;
inline public static var IQM_USHORT:Int = 3;
inline public static var IQM_INT:Int = 4;
inline public static var IQM_UINT:Int = 5;
inline public static var IQM_HALF:Int = 6;
inline public static var IQM_FLOAT:Int = 7;
inline public static var IQM_DOUBLE:Int = 8;
inline public static var IQM_LOOP:Int = (1<<0);
public var src:String;
public var ctx:ResourceContext;
public var gpuCtx:GPUResourceContext;
public var data:GeometryData;
public var meshes:Array<IqmMesh>;
public var joints:Array<IqmJoint>;
public var anims:Array<IqmAnim>;
public var poses:Array<IqmPose>;
public var frames:Array<Mat44>;
public var dqFrames:Array<DualQuat>;
static var defaultVertexLayout:Int = -1;
static var skinnedVertexLayout:Int = -1;
public static function create(_src:String):Iqm {
return new Iqm(_src);
}
public function new(_src:String) {
src = _src;
ctx = new ResourceContext();
gpuCtx = new GPUResourceContext();
}
public static function registerVertexLayout(_rd:RenderDevice):Void {
defaultVertexLayout = _rd.registerVertexLayout([
new RDIVertexLayoutAttrib("vPos", 0, 3, 0),
new RDIVertexLayoutAttrib("vUv", 1, 2, 0),
new RDIVertexLayoutAttrib("vNormal", 2, 3, 0),
new RDIVertexLayoutAttrib("vTangent", 3, 4, 0)
]);
skinnedVertexLayout = _rd.registerVertexLayout([
new RDIVertexLayoutAttrib("vPos", 0, 3, 0),
new RDIVertexLayoutAttrib("vUv", 1, 2, 0),
new RDIVertexLayoutAttrib("vNormal", 2, 3, 0),
new RDIVertexLayoutAttrib("vTangent", 3, 3, 0),
new RDIVertexLayoutAttrib("vJointIndices", 4, 4, 0, RDIDataType.UNSIGNED_BYTE),
new RDIVertexLayoutAttrib("vWeights", 5, 4, 0, RDIDataType.UNSIGNED_BYTE)
]);
}
public function load(_data:Dynamic):Int {
var time:Int = Std.int(haxe.Timer.stamp()*1000);
var assetData = Core.app.assets.bytes(src);
if (assetData == null) {
enhaxe.Core.log("[Iqm::load]: Unknown file: " + src, 0);
return 0;
}
try {
var fin:BytesInput = new BytesInput(assetData.bytes);
fin.bigEndian = false;
var id = fin.readString(16);
var version = fin.readInt32();
if (!StringTools.startsWith(id, "INTERQUAKEMODEL") || version != 2) {
enhaxe.Core.log("ERROR [Iqm::load]: Invalid geometry file! " + src, 0);
return 0;
}
_data.geometry = _parse(fin);
enhaxe.Core.log("Iqm parsing: " + (Std.int(haxe.Timer.stamp()*1000) - time) + "ms of " + src);
} catch (e:Dynamic) {
enhaxe.Core.log("[Iqm::load]: " + e, 0);
return 0;
}
return 1;
}
public function onLoadComplete(_data:Dynamic):Void {
this.data = new GeometryData(
_data.geometry.layout,
cast _data.geometry.vBufs,
cast _data.geometry.iBuf,
null
);
//this.meshes = _data.geometry.meshes;
//this.anims = _data.geometry.anims;
}
public function onUnloadComplete():Void {
this.dqFrames = null;
this.frames = null;
this.joints = null;
this.meshes = null;
this.anims = null;
this.data = null;
}
public function prepare(_rd:Dynamic):Void {
var time:Int = Std.int(haxe.Timer.stamp()*1000);
for (i in 0...this.data.vBufs.length) {
var vb = this.data.vBufs[i];
vb.handle = _rd.createVertexBuffer(vb.raw.length, vb.raw, RDIBufferUsage.STATIC, vb.stride);
}
var ib = this.data.iBuf;
ib.handle = _rd.createIndexBuffer(ib.raw.length, ib.raw, RDIBufferUsage.STATIC);
enhaxe.Core.log("Iqm upload: " + (Std.int(haxe.Timer.stamp()*1000) - time) + "ms of " + src);
}
public function unprepare(_rd:Dynamic):Void {
for (i in 0...this.data.vBufs.length) {
_rd.destroyBuffer(this.data.vBufs[i].handle);
this.data.vBufs[i].handle = -1;
}
_rd.destroyBuffer(this.data.iBuf.handle);
this.data.iBuf.handle = -1;
this.data.jMats = null;
}
inline public function getNull():IResourceData {
if (Iqm.NULL == null) {
registerVertexLayout(Core.renderDevice);
NULL = create("null.iqm");
var data = { geometry: {
layout: defaultVertexLayout,
vBufs: [],
iBuf: new IBufferInfo(-1, RDIDataType.UNSIGNED_INT, haxe.io.Bytes.alloc(0)),
meshes: []
}};
//NULL.load(data);
NULL.onLoadComplete(data);
NULL.prepare(Core.renderDevice);
NULL.ctx.state = ResourceContext.LOADED;
NULL.gpuCtx.state = GPUResourceContext.PREPARED;
}
return NULL;
}
inline function _parse(_fin:BytesInput):IqmData {
// parse header
var hdr:IqmHeader = {
filesize: _fin.readInt32(),
flags: _fin.readInt32(),
num_text: _fin.readInt32(), ofs_text: _fin.readInt32(),
num_meshes: _fin.readInt32(), ofs_meshes: _fin.readInt32(),
num_vertexarrays: _fin.readInt32(), num_vertices: _fin.readInt32(), ofs_vertexarrays: _fin.readInt32(),
num_triangles: _fin.readInt32(), ofs_triangles: _fin.readInt32(), ofs_adjacency: _fin.readInt32(),
num_joints: _fin.readInt32(), ofs_joints: _fin.readInt32(),
num_poses: _fin.readInt32(), ofs_poses: _fin.readInt32(),
num_anims: _fin.readInt32(), ofs_anims: _fin.readInt32(),
num_frames: _fin.readInt32(), num_framechannels: _fin.readInt32(), ofs_frames: _fin.readInt32(), ofs_bounds: _fin.readInt32(),
num_comment: _fin.readInt32(), ofs_comment: _fin.readInt32(),
num_extensions: _fin.readInt32(), ofs_extensions: _fin.readInt32(),
};
trace(hdr);
// vertexarrays are non-interleaved component buffers
_fin.position = hdr.ofs_vertexarrays;
var vBufs:Array<VBufferInfo> = [];
if (hdr.num_vertexarrays > 0) {
for (i in 0...hdr.num_vertexarrays) {
var va:IqmVertexArray = {
type: _fin.readInt32(),
flags: _fin.readInt32(),
format: _fin.readInt32(),
size: _fin.readInt32(),
offset: _fin.readInt32()
};
var valid:Bool = false;
var elmSize:Int = 0;
switch(va.type) {
case IQM_POSITION: va.format != IQM_FLOAT || va.size != 3 ?
Core.log("[Iqm::_parse]: IQM_POSITION is invalid!", 0) : valid = true; elmSize = 4;
case IQM_NORMAL: va.format != IQM_FLOAT || va.size != 3 ?
Core.log("[Iqm::_parse]: IQM_NORMAL is invalid!", 0) : valid = true; elmSize = 4;
case IQM_TANGENT: va.format != IQM_FLOAT || va.size != 4 ?
Core.log("[Iqm::_parse]: IQM_TANGENT is invalid!", 0) : valid = true; elmSize = 4;
case IQM_TEXCOORD: va.format != IQM_FLOAT || va.size != 2 ?
Core.log("[Iqm::_parse]: IQM_TEXCOORD is invalid!", 0) : valid = true; elmSize = 4;
case IQM_BLENDINDEXES: va.format != IQM_UBYTE || va.size != 4 ?
Core.log("[Iqm::_parse]: IQM_BLENDINDEXES are invalid!", 0) : valid = true; elmSize = 1;
case IQM_BLENDWEIGHTS: va.format != IQM_UBYTE || va.size != 4 ?
Core.log("[Iqm::_parse]: IQM_BLENDWEIGHTS are invalid!", 0) : valid = true; elmSize = 1;
case IQM_COLOR: va.format != IQM_UBYTE || va.size != 4 ?
Core.log("[Iqm::_parse]: IQM_COLOR is invalid!", 0) : valid = true; elmSize = 1;
default:
Core.log("[Iqm::_parse]: Unknown vertexarray type! " + va.type, 1);
}
if (!valid)
continue;
var oldPos = _fin.position;
_fin.position = va.offset;
var v = new BytesOutput();
for (i in 0...hdr.num_vertices*va.size*elmSize) {
v.writeByte(_fin.readByte()); // just move the bytes
}
_fin.position = oldPos;
vBufs.push(new VBufferInfo(-1, 0, va.size*elmSize, v.getBytes()));
}
}
// triangles are indexbuffers
_fin.position = hdr.ofs_triangles;
var inds = new BytesOutput();
for (i in 0...hdr.num_triangles*3*4) {
inds.writeByte(_fin.readByte()); // just move the bytes
}
var iBuf = new IBufferInfo(-1, RDIDataType.UNSIGNED_INT, inds.getBytes());
// meshes
this.meshes = [];
for (i in 0...hdr.num_meshes) {
_fin.position = hdr.ofs_meshes + (i * 24);
var namePos = _fin.readInt32() + hdr.ofs_text;
var materialPos = _fin.readInt32() + hdr.ofs_text;
_fin.position += 8; // skip vertices
var m:IqmMesh = {
name: "",
material: "",
vstart: _fin.readInt32()*3*4,
bcount: _fin.readInt32()*3,
aabb: new Aabb()
}
// read strings
m.name = _readString(_fin, namePos);
m.material = _readString(_fin, materialPos);
// calc the aabb for this mesh
m.aabb.beginExtend();
var x:Float = 0;
var y:Float = 0;
var z:Float = 0;
var vert:Int = 0;
var vBuf = new snow.utils.Float32Array(snow.utils.ByteArray.fromBytes(vBufs[0].raw)); // index 0 is position
var iBuf = new snow.utils.UInt32Array(snow.utils.ByteArray.fromBytes(iBuf.raw));
var start = Std.int(m.vstart/4); // factor out byte-count
var end = start + Std.int(m.bcount);
for (i in start...end) {
vert = (iBuf[i]*3);
x = vBuf[vert];
y = vBuf[vert+1];
z = vBuf[vert+2];
m.aabb.extendByFloats(x, y, z);
}
this.meshes.push(m);
}
// joints
this.joints = [];
for (i in 0...hdr.num_joints) {
_fin.position = hdr.ofs_joints + (i * 48);
var namePos = _fin.readInt32() + hdr.ofs_text;
var j:IqmJoint = new IqmJoint();
j.parent = _fin.readInt32();
j.pos.set(_fin.readFloat(), _fin.readFloat(), _fin.readFloat());
j.rot.set(_fin.readFloat(), _fin.readFloat(), _fin.readFloat(), _fin.readFloat());
j.scale.set(_fin.readFloat(), _fin.readFloat(), _fin.readFloat());
j.rot.invert(); // TODO: move to exporter! should be done when converting coord-system!
j.rot.normalize();
j.mBase.recompose(j.rot, j.scale, j.pos);
j.mInvBase.recompose(j.rot, j.scale, j.pos);
j.mInvBase.invert();
j.dqBase.pack(j.rot, j.pos);
j.dqInvBase = j.dqBase.inverted();
if (j.parent >= 0) {
j.mBase = this.joints[j.parent].mBase * j.mBase;
j.mInvBase *= this.joints[j.parent].mInvBase;
j.dqBase = this.joints[j.parent].dqBase * j.dqBase;
j.dqInvBase *= this.joints[j.parent].dqInvBase;
}
j.name = _readString(_fin, namePos);
this.joints.push(j);
}
// animations
this.anims = [];
for (i in 0...hdr.num_anims) {
_fin.position = hdr.ofs_anims + (i * 20);
var namePos = _fin.readInt32() + hdr.ofs_text;
var a:IqmAnim = {
name: "",
first_frame: _fin.readInt32(),
num_frames: _fin.readInt32(),
framerate: _fin.readFloat(),
flags: _fin.readInt32()
};
a.name = _readString(_fin, namePos);
this.anims.push(a);
}
// read poses, these are the animation-frames
this.poses = [];
_fin.position = hdr.ofs_poses;
for (i in 0...hdr.num_poses) {
var p:IqmPose = {
parent: _fin.readInt32(),
mask: _fin.readInt32(),
channeloffset: [],
channelscale: [],
}
for (j in 0...10)
p.channeloffset.push(_fin.readFloat());
for (j in 0...10)
p.channelscale.push(_fin.readFloat());
this.poses.push(p);
}
this.frames = [];
this.dqFrames = [];
_fin.position = hdr.ofs_frames;
for (i in 0...hdr.num_frames) {
for (j in 0...hdr.num_poses) {
var p = poses[j];
var pos = new Vec3(p.channeloffset[0], p.channeloffset[1], p.channeloffset[2]);
var rot = new Quat(p.channeloffset[3], p.channeloffset[4], p.channeloffset[5], p.channeloffset[6]);
var scale = new Vec3(p.channeloffset[7], p.channeloffset[8], p.channeloffset[9]);
if (p.mask&0x01!=0) pos.x += _fin.readUInt16() * p.channelscale[0];
if (p.mask&0x02!=0) pos.y += _fin.readUInt16() * p.channelscale[1];
if (p.mask&0x04!=0) pos.z += _fin.readUInt16() * p.channelscale[2];
if (p.mask&0x08!=0) rot.x += _fin.readUInt16() * p.channelscale[3];
if (p.mask&0x10!=0) rot.y += _fin.readUInt16() * p.channelscale[4];
if (p.mask&0x20!=0) rot.z += _fin.readUInt16() * p.channelscale[5];
if (p.mask&0x40!=0) rot.w += _fin.readUInt16() * p.channelscale[6];
if (p.mask&0x80!=0) scale.x += _fin.readUInt16() * p.channelscale[7];
if (p.mask&0x100!=0) scale.y += _fin.readUInt16() * p.channelscale[8];
if (p.mask&0x200!=0) scale.z += _fin.readUInt16() * p.channelscale[9];
rot.invert(); // TODO: move to exporter! should be done when converting coord-system!
rot.normalize();
// TODO: building the matrix is redundant. we are using dqs directly now.
var m = new Mat44();
m.recompose(rot, scale, pos);
var dq = DualQuat.create(rot, pos);
if (p.parent >= 0) {
m = this.frames[(i*hdr.num_poses) + p.parent] * m;
dq = this.dqFrames[(i*hdr.num_poses) + p.parent] * dq;
}
this.frames.push(m);
this.dqFrames.push(dq);
}
}
return {
layout: vBufs.length > 4 ? skinnedVertexLayout : defaultVertexLayout,
vBufs: vBufs,
iBuf: iBuf
}
}
inline function _readString(_fin:BytesInput, _pos:Int):String {
_fin.position = _pos;
var buf = new StringBuf();
while(true) {
var c = _fin.readByte();
if (c == 0x00)
break;
buf.addChar(c);
}
return buf.toString();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment