var K3D = {};

K3D.load = function (path, resp) {
    var request = new XMLHttpRequest();
    request.open("GET", path, true);
    request.responseType = "arraybuffer";
    request.onload = function (e) {
        resp(e.target.response);
    };
    request.send();
}

K3D.save = function (buff, path) {
    var dataURI = "data:application/octet-stream;base64," + btoa(K3D.parse._buffToStr(buff));
    window.location.href = dataURI;
}

K3D.clone = function (o) {
    return JSON.parse(JSON.stringify(o));
}


K3D.bin = {};

K3D.bin.f = new Float32Array(1);
K3D.bin.fb = new Uint8Array(K3D.bin.f.buffer);

K3D.bin.rf = function (buff, off) {
    var f = K3D.bin.f, fb = K3D.bin.fb;
    for (var i = 0; i < 4; i++) fb[i] = buff[off + i];
    return f[0];
}
K3D.bin.rsl = function (buff, off) {
    return buff[off] | buff[off + 1] << 8;
}
K3D.bin.ril = function (buff, off) {
    return buff[off] | buff[off + 1] << 8 | buff[off + 2] << 16 | buff[off + 3] << 24;
}
K3D.bin.rASCII0 = function (buff, off) {
    var s = "";
    while (buff[off] != 0) s += String.fromCharCode(buff[off++]);
    return s;
}


K3D.bin.wf = function (buff, off, v) {
    var f = new Float32Array(buff.buffer, off, 1);
    f[0] = v;
}
K3D.bin.wsl = function (buff, off, v) {
    buff[off] = v;
    buff[off + 1] = v >> 8;
}
K3D.bin.wil = function (buff, off, v) {
    buff[off] = v;
    buff[off + 1] = v >> 8;
    buff[off + 2] = v >> 16;
    buff[off + 3] >> 24;
}
K3D.parse = {};

K3D.parse._buffToStr = function (buff) {
    var a = new Uint8Array(buff);
    var s = "";
    for (var i = 0; i < a.length; i++) s = s.concat(String.fromCharCode(a[i]));
    return s;
}

K3D.parse._strToBuff = function (str) {
    var buf = new ArrayBuffer(str.length);
    var bufView = new Uint8Array(buf);
    for (var i = 0; i < str.length; i++) bufView[i] = str.charCodeAt(i);
    return buf;
}

K3D.parse._readLine = function (a, off)    // Uint8Array, offset
{
    var s = "";
    while (a[off] != 10) s += String.fromCharCode(a[off++]);
    return s;
}
K3D.parse.fromJSON = function (buff) {
    var json = JSON.parse(K3D.parse._buffToStr(buff));
    return json;
}

K3D.parse.toJSON = function (object) {
    var str = JSON.stringify(object);
    return K3D.parse._strToBuff(str);
}

K3D.parse.fromOBJ = function (buff) {
    var res = {};
    res.groups = {};

    res.c_verts = [];
    res.c_uvt = [];
    res.c_norms = [];

    res.i_verts = [];
    res.i_uvt = [];
    res.i_norms = [];

    var cg = {from:0, to:0};	// current group
    var off = 0;
    var a = new Uint8Array(buff);

    while (off < a.length) {
        var line = K3D.parse._readLine(a, off);
        off += line.length + 1;
        line = line.replace(/ +(?= )/g, '');
        line = line.replace(/(^\s+|\s+$)/g, '');
        var cds = line.split(" ");
        if (cds[0] == "g") {
            cg.to = res.i_verts.length;
            if (res.groups[cds[1]] == null) res.groups[cds[1]] = {from:res.i_verts.length, to:0};
            cg = res.groups[cds[1]];
        }
        if (cds[0] == "v") {
            var x = parseFloat(cds[1]);
            var y = parseFloat(cds[2]);
            var z = parseFloat(cds[3]);
            res.c_verts.push(x, y, z);
        }
        if (cds[0] == "vt") {
            var x = parseFloat(cds[1]);
            var y = 1 - parseFloat(cds[2]);
            res.c_uvt.push(x, y);
        }
        if (cds[0] == "vn") {
            var x = parseFloat(cds[1]);
            var y = parseFloat(cds[2]);
            var z = parseFloat(cds[3]);
            res.c_norms.push(x, y, z);
        }
        if (cds[0] == "f") {
            var v0a = cds[1].split("/"), v1a = cds[2].split("/"), v2a = cds[3].split("/");
            var vi0 = parseInt(v0a[0]) - 1, vi1 = parseInt(v1a[0]) - 1, vi2 = parseInt(v2a[0]) - 1;
            var ui0 = parseInt(v0a[1]) - 1, ui1 = parseInt(v1a[1]) - 1, ui2 = parseInt(v2a[1]) - 1;
            var ni0 = parseInt(v0a[2]) - 1, ni1 = parseInt(v1a[2]) - 1, ni2 = parseInt(v2a[2]) - 1;

            var vlen = res.c_verts.length / 3, ulen = res.c_uvt.length / 2, nlen = res.c_norms.length / 3;
            if (vi0 < 0) vi0 = vlen + vi0 + 1;
            if (vi1 < 0) vi1 = vlen + vi1 + 1;
            if (vi2 < 0) vi2 = vlen + vi2 + 1;
            if (ui0 < 0) ui0 = ulen + ui0 + 1;
            if (ui1 < 0) ui1 = ulen + ui1 + 1;
            if (ui2 < 0) ui2 = ulen + ui2 + 1;
            if (ni0 < 0) ni0 = nlen + ni0 + 1;
            if (ni1 < 0) ni1 = nlen + ni1 + 1;
            if (ni2 < 0) ni2 = nlen + ni2 + 1;

            res.i_verts.push(vi0, vi1, vi2);  //cg.i_verts.push(vi0, vi1, vi2)
            res.i_uvt.push(ui0, ui1, ui2);  //cg.i_uvt  .push(ui0, ui1, ui2);
            res.i_norms.push(ni0, ni1, ni2);  //cg.i_norms.push(ni0, ni1, ni2);
            if (cds.length == 5) {
                var v3a = cds[4].split("/");
                var vi3 = parseInt(v3a[0]) - 1, ui3 = parseInt(v3a[1]) - 1, ni3 = parseInt(v3a[2]) - 1;
                if (vi3 < 0) vi3 = vlen + vi3 + 1;
                if (ui3 < 0) ui3 = ulen + ui3 + 1;
                if (ni3 < 0) ni3 = nlen + ni3 + 1;
                res.i_verts.push(vi0, vi2, vi3);  //cg.i_verts.push(vi0, vi2, vi3);
                res.i_uvt.push(ui0, ui2, ui3);  //cg.i_uvt  .push(ui0, ui2, ui3);
                res.i_norms.push(ni0, ni2, ni3);  //cg.i_norms.push(ni0, ni2, ni3);
            }
        }
    }
    cg.to = res.i_verts.length;

    return res;
}


K3D.parse.fromMD2 = function (buff) {
    buff = new Uint8Array(buff);
    var res = {};
    var head = {};
    //res.head = head;
    head.ident = K3D.bin.ril(buff, 0);
    /* magic number: "IDP2" */
    head.version = K3D.bin.ril(buff, 4);
    /* version: must be 8 */

    head.skinwidth = K3D.bin.ril(buff, 8);
    /* texture width */
    head.skinheight = K3D.bin.ril(buff, 12);
    /* texture height */

    head.framesize = K3D.bin.ril(buff, 16);
    /* size in bytes of a frame */

    head.num_skins = K3D.bin.ril(buff, 20);
    /* number of skins */
    head.num_vertices = K3D.bin.ril(buff, 24);
    /* number of vertices per frame */
    head.num_st = K3D.bin.ril(buff, 28);
    /* number of texture coordinates */
    head.num_tris = K3D.bin.ril(buff, 32);
    /* number of triangles */
    head.num_glcmds = K3D.bin.ril(buff, 36);
    /* number of opengl commands */
    head.num_frames = K3D.bin.ril(buff, 40);
    /* number of frames */

    head.offset_skins = K3D.bin.ril(buff, 44);
    /* offset skin data */
    head.offset_st = K3D.bin.ril(buff, 48);
    /* offset texture coordinate data */
    head.offset_tris = K3D.bin.ril(buff, 52);
    /* offset triangle data */
    head.offset_frames = K3D.bin.ril(buff, 56);
    /* offset frame data */
    head.offset_glcmds = K3D.bin.ril(buff, 60);
    /* offset OpenGL command data */
    head.offset_end = K3D.bin.ril(buff, 64);
    /* offset end of file */

    var off = head.offset_st;
    res.c_uvt = [];
    for (var i = 0; i < head.num_st; i++) {
        var x = K3D.bin.rsl(buff, off) / head.skinwidth;
        var y = K3D.bin.rsl(buff, off + 2) / head.skinheight;
        res.c_uvt.push(x, y);
        off += 4;
    }

    var off = head.offset_tris;
    var vi = [], ti = [];
    res.i_verts = vi;
    res.i_uvt = ti;
    //res.tris = {i_verts : vi, i_uvt : ti};
    for (var i = 0; i < head.num_tris; i++) {
        vi.push(K3D.bin.rsl(buff, off), K3D.bin.rsl(buff, off + 2), K3D.bin.rsl(buff, off + 4));
        ti.push(K3D.bin.rsl(buff, off + 6), K3D.bin.rsl(buff, off + 8), K3D.bin.rsl(buff, off + 10));
        off += 12;
    }

    var off = head.offset_skins;
    res.skins = [];
    for (var i = 0; i < head.num_skins; i++) {
        res.skins.push(K3D.bin.rASCII0(buff, off));
        off += 64;
    }

    var off = head.offset_frames;
    res.frames = [];
    var nms = K3D.parse.fromMD2._normals;
    for (var i = 0; i < head.num_frames; i++) {
        var fr = {};
        var sx = K3D.bin.rf(buff, off), sy = K3D.bin.rf(buff, off + 4), sz = K3D.bin.rf(buff, off + 8);
        off += 12;
        var tx = K3D.bin.rf(buff, off), ty = K3D.bin.rf(buff, off + 4), tz = K3D.bin.rf(buff, off + 8);
        off += 12;
        fr.name = K3D.bin.rASCII0(buff, off);
        off += 16;
        fr.verts = [];
        fr.norms = [];

        for (var j = 0; j < head.num_vertices; j++) {
            fr.verts.push(buff[off] * sx + tx, buff[off + 1] * sy + ty, buff[off + 2] * sz + tz);
            fr.norms.push(nms[3 * buff[off + 3]], nms[3 * buff[off + 3] + 1], nms[3 * buff[off + 3] + 2]);
            off += 4;
        }
        res.frames.push(fr);
    }
    return res;
}


/*
 static MD2 normals
 */

K3D.parse.fromMD2._normals =
    [
        -0.525731, 0.000000, 0.850651,
        -0.442863, 0.238856, 0.864188,
        -0.295242, 0.000000, 0.955423,
        -0.309017, 0.500000, 0.809017,
        -0.162460, 0.262866, 0.951056,
        0.000000, 0.000000, 1.000000,
        0.000000, 0.850651, 0.525731,
        -0.147621, 0.716567, 0.681718,
        0.147621, 0.716567, 0.681718,
        0.000000, 0.525731, 0.850651,
        0.309017, 0.500000, 0.809017,
        0.525731, 0.000000, 0.850651,
        0.295242, 0.000000, 0.955423,
        0.442863, 0.238856, 0.864188,
        0.162460, 0.262866, 0.951056,
        -0.681718, 0.147621, 0.716567,
        -0.809017, 0.309017, 0.500000,
        -0.587785, 0.425325, 0.688191,
        -0.850651, 0.525731, 0.000000,
        -0.864188, 0.442863, 0.238856,
        -0.716567, 0.681718, 0.147621,
        -0.688191, 0.587785, 0.425325,
        -0.500000, 0.809017, 0.309017,
        -0.238856, 0.864188, 0.442863,
        -0.425325, 0.688191, 0.587785,
        -0.716567, 0.681718, -0.147621,
        -0.500000, 0.809017, -0.309017,
        -0.525731, 0.850651, 0.000000,
        0.000000, 0.850651, -0.525731,
        -0.238856, 0.864188, -0.442863,
        0.000000, 0.955423, -0.295242,
        -0.262866, 0.951056, -0.162460,
        0.000000, 1.000000, 0.000000,
        0.000000, 0.955423, 0.295242,
        -0.262866, 0.951056, 0.162460,
        0.238856, 0.864188, 0.442863,
        0.262866, 0.951056, 0.162460,
        0.500000, 0.809017, 0.309017,
        0.238856, 0.864188, -0.442863,
        0.262866, 0.951056, -0.162460,
        0.500000, 0.809017, -0.309017,
        0.850651, 0.525731, 0.000000,
        0.716567, 0.681718, 0.147621,
        0.716567, 0.681718, -0.147621,
        0.525731, 0.850651, 0.000000,
        0.425325, 0.688191, 0.587785,
        0.864188, 0.442863, 0.238856,
        0.688191, 0.587785, 0.425325,
        0.809017, 0.309017, 0.500000,
        0.681718, 0.147621, 0.716567,
        0.587785, 0.425325, 0.688191,
        0.955423, 0.295242, 0.000000,
        1.000000, 0.000000, 0.000000,
        0.951056, 0.162460, 0.262866,
        0.850651, -0.525731, 0.000000,
        0.955423, -0.295242, 0.000000,
        0.864188, -0.442863, 0.238856,
        0.951056, -0.162460, 0.262866,
        0.809017, -0.309017, 0.500000,
        0.681718, -0.147621, 0.716567,
        0.850651, 0.000000, 0.525731,
        0.864188, 0.442863, -0.238856,
        0.809017, 0.309017, -0.500000,
        0.951056, 0.162460, -0.262866,
        0.525731, 0.000000, -0.850651,
        0.681718, 0.147621, -0.716567,
        0.681718, -0.147621, -0.716567,
        0.850651, 0.000000, -0.525731,
        0.809017, -0.309017, -0.500000,
        0.864188, -0.442863, -0.238856,
        0.951056, -0.162460, -0.262866,
        0.147621, 0.716567, -0.681718,
        0.309017, 0.500000, -0.809017,
        0.425325, 0.688191, -0.587785,
        0.442863, 0.238856, -0.864188,
        0.587785, 0.425325, -0.688191,
        0.688191, 0.587785, -0.425325,
        -0.147621, 0.716567, -0.681718,
        -0.309017, 0.500000, -0.809017,
        0.000000, 0.525731, -0.850651,
        -0.525731, 0.000000, -0.850651,
        -0.442863, 0.238856, -0.864188,
        -0.295242, 0.000000, -0.955423,
        -0.162460, 0.262866, -0.951056,
        0.000000, 0.000000, -1.000000,
        0.295242, 0.000000, -0.955423,
        0.162460, 0.262866, -0.951056,
        -0.442863, -0.238856, -0.864188,
        -0.309017, -0.500000, -0.809017,
        -0.162460, -0.262866, -0.951056,
        0.000000, -0.850651, -0.525731,
        -0.147621, -0.716567, -0.681718,
        0.147621, -0.716567, -0.681718,
        0.000000, -0.525731, -0.850651,
        0.309017, -0.500000, -0.809017,
        0.442863, -0.238856, -0.864188,
        0.162460, -0.262866, -0.951056,
        0.238856, -0.864188, -0.442863,
        0.500000, -0.809017, -0.309017,
        0.425325, -0.688191, -0.587785,
        0.716567, -0.681718, -0.147621,
        0.688191, -0.587785, -0.425325,
        0.587785, -0.425325, -0.688191,
        0.000000, -0.955423, -0.295242,
        0.000000, -1.000000, 0.000000,
        0.262866, -0.951056, -0.162460,
        0.000000, -0.850651, 0.525731,
        0.000000, -0.955423, 0.295242,
        0.238856, -0.864188, 0.442863,
        0.262866, -0.951056, 0.162460,
        0.500000, -0.809017, 0.309017,
        0.716567, -0.681718, 0.147621,
        0.525731, -0.850651, 0.000000,
        -0.238856, -0.864188, -0.442863,
        -0.500000, -0.809017, -0.309017,
        -0.262866, -0.951056, -0.162460,
        -0.850651, -0.525731, 0.000000,
        -0.716567, -0.681718, -0.147621,
        -0.716567, -0.681718, 0.147621,
        -0.525731, -0.850651, 0.000000,
        -0.500000, -0.809017, 0.309017,
        -0.238856, -0.864188, 0.442863,
        -0.262866, -0.951056, 0.162460,
        -0.864188, -0.442863, 0.238856,
        -0.809017, -0.309017, 0.500000,
        -0.688191, -0.587785, 0.425325,
        -0.681718, -0.147621, 0.716567,
        -0.442863, -0.238856, 0.864188,
        -0.587785, -0.425325, 0.688191,
        -0.309017, -0.500000, 0.809017,
        -0.147621, -0.716567, 0.681718,
        -0.425325, -0.688191, 0.587785,
        -0.162460, -0.262866, 0.951056,
        0.442863, -0.238856, 0.864188,
        0.162460, -0.262866, 0.951056,
        0.309017, -0.500000, 0.809017,
        0.147621, -0.716567, 0.681718,
        0.000000, -0.525731, 0.850651,
        0.425325, -0.688191, 0.587785,
        0.587785, -0.425325, 0.688191,
        0.688191, -0.587785, 0.425325,
        -0.955423, 0.295242, 0.000000,
        -0.951056, 0.162460, 0.262866,
        -1.000000, 0.000000, 0.000000,
        -0.850651, 0.000000, 0.525731,
        -0.955423, -0.295242, 0.000000,
        -0.951056, -0.162460, 0.262866,
        -0.864188, 0.442863, -0.238856,
        -0.951056, 0.162460, -0.262866,
        -0.809017, 0.309017, -0.500000,
        -0.864188, -0.442863, -0.238856,
        -0.951056, -0.162460, -0.262866,
        -0.809017, -0.309017, -0.500000,
        -0.681718, 0.147621, -0.716567,
        -0.681718, -0.147621, -0.716567,
        -0.850651, 0.000000, -0.525731,
        -0.688191, 0.587785, -0.425325,
        -0.587785, 0.425325, -0.688191,
        -0.425325, 0.688191, -0.587785,
        -0.425325, -0.688191, -0.587785,
        -0.587785, -0.425325, -0.688191,
        -0.688191, -0.587785, -0.425325
    ];

K3D.parse.fromCollada = function (buff) {
    var str = K3D.parse._buffToStr(buff);
    var xml = new DOMParser().parseFromString(str, "text/xml");
    xml = xml.childNodes[0];
    var resp = {};

    //console.log(xml);

    var ass = xml.getElementsByTagName("asset")[0];
    var geo = xml.getElementsByTagName("library_geometries")[0];
    var ima = xml.getElementsByTagName("library_images")[0];
    var mat = xml.getElementsByTagName("library_materials")[0];
    var eff = xml.getElementsByTagName("library_effects")[0];

    //console.log(xml);
    if (ass) resp.asset = K3D.parse.fromCollada._asset(ass);
    if (geo) resp.geometries = K3D.parse.fromCollada._libGeometries(geo);
    if (ima) resp.images = K3D.parse.fromCollada._libImages(ima);
    if (mat) resp.materials = K3D.parse.fromCollada._libMaterials(mat);
    if (eff) resp.effects = K3D.parse.fromCollada._libEffects(eff);
    return resp;
}

K3D.parse.fromCollada._asset = function (xml) {
    //console.log(xml);
    return {
        created:xml.getElementsByTagName("created")[0].textContent,
        modified:xml.getElementsByTagName("modified")[0].textContent,
        up_axis:xml.getElementsByTagName("up_axis")[0].textContent
    };
}

K3D.parse.fromCollada._libGeometries = function (xml) {
    xml = xml.getElementsByTagName("geometry");
    var res = [];
    for (var i = 0; i < xml.length; i++) {
        var g = xml[i];
        var o = K3D.parse.fromCollada._getMesh(g.getElementsByTagName("mesh")[0]);
        res.push(o);
    }
    return res;
}

K3D.parse.fromCollada._getMesh = function (mesh) {
    //console.log(mesh);
    var res = {};
    var ss = mesh.getElementsByTagName("source");
    var sources = res.sources = {};
    for (var i = 0; i < ss.length; i++) {
        var farr = ss[i].getElementsByTagName("float_array")[0].textContent.split(" ");
        var fl = farr.length - (farr[farr.length - 1] == "" ? 1 : 0);
        var arr = new Array(fl);
        for (var j = 0; j < fl; j++) arr[j] = parseFloat(farr[j]);
        sources[ss[i].getAttribute("id")] = arr;
    }

    res.triangles = [];
    var tgs = mesh.getElementsByTagName("triangles");
    if (tgs == null) return res;
    for (var i = 0; i < tgs.length; i++) {
        var t = {};
        var tnode = tgs[i];
        t.material = tnode.getAttribute("material");
        var inputs = tnode.getElementsByTagName("input");
        var inds = [];
        for (var j = 0; j < inputs.length; j++) {
            var inp = inputs[j], arr = [];
            inds[parseInt(inp.getAttribute("offset"))] = arr;
            var par = inp.getAttribute("semantic");
            t["s_" + par] = (par == "VERTEX") ?
                mesh.getElementsByTagName("vertices")[0].getElementsByTagName("input")[0].getAttribute("source").substring(1)
                : inp.getAttribute("source").substring(1);

            t["i_" + par] = arr;
            var psrc = sources[t["s_" + par]];
        }
        var indices = tnode.getElementsByTagName("p")[0].textContent.split(" ");
        var inum = 3 * Math.floor(indices.length / 3);
        for (var j = 0; j < inum; j++) inds[j % inputs.length].push(parseInt(indices[j]));

        /*
         if(t.s_VERTEX  ) t.u_VERTEX   = K3D.edit.unwrap(t.i_VERTEX  , sources[t.s_VERTEX  ], 3);
         if(t.s_TEXCOORD) t.u_TEXCOORD = K3D.edit.unwrap(t.i_TEXCOORD, sources[t.s_TEXCOORD], 2);
         if(t.s_NORMAL  ) t.u_NORMAL   = K3D.edit.unwrap(t.i_NORMAL  , sources[t.s_NORMAL  ], 3);
         //*/
        //if(t.s_TEXCOORD) for(var j=1; j<t.u_TEXCOORD.length; j+=2) t.u_TEXCOORD[j] = 1 - t.u_TEXCOORD[j];

        /*
         t.u_INDEX = new Array(t.i_VERTEX.length);
         for(var j=0; j<t.i_VERTEX.length; j++) t.u_INDEX[j] = j;
         */

        res.triangles.push(t);
    }
    return res;
}

K3D.parse.fromCollada._libImages = function (xml) {
    xml = xml.getElementsByTagName("image");
    var res = {};
    for (var i = 0; i < xml.length; i++) {
        res[xml[i].getAttribute("id")] = xml[i].getElementsByTagName("init_from")[0].textContent;
    }
    return res;
}

K3D.parse.fromCollada._libMaterials = function (xml) {
    xml = xml.getElementsByTagName("material");
    var res = {};
    for (var i = 0; i < xml.length; i++) {
        res[xml[i].getAttribute("name")] = xml[i].getElementsByTagName("instance_effect")[0].getAttribute("url").substring(1);
    }
    return res;
}

K3D.parse.fromCollada._libEffects = function (xml) {
    xml = xml.getElementsByTagName("effect");
    var res = {};
    for (var i = 0; i < xml.length; i++) {
        var eff = {};
        var params = xml[i].getElementsByTagName("newparam");
        for (var j = 0; j < params.length; j++) {
            var srf = params[j].getElementsByTagName("surface")[0];
            if (srf) eff.surface = srf.getElementsByTagName("init_from")[0].textContent;
        }
        res[xml[i].getAttribute("id")] = eff;
    }
    return res;
}


K3D.parse.from3DS = function (buff) {
    buff = new Uint8Array(buff);
    var res = {};
    if (K3D.bin.rsl(buff, 0) != 0x4d4d) return null;
    var lim = K3D.bin.ril(buff, 2);

    var off = 6;
    while (off < lim) {
        var cid = K3D.bin.rsl(buff, off);
        var lng = K3D.bin.ril(buff, off + 2);
        //console.log(cid.toString(16), lng);

        if (cid == 0x3d3d) res.edit = K3D.parse.from3DS._edit3ds(buff, off, lng);
        if (cid == 0xb000) res.keyf = K3D.parse.from3DS._keyf3ds(buff, off, lng);

        off += lng;
    }
    return res;
}

K3D.parse.from3DS._edit3ds = function (buff, coff, clng)    // buffer, chunk offset, length
{
    var res = {};
    var off = coff + 6;
    while (off < coff + clng) {
        var cid = K3D.bin.rsl(buff, off);
        var lng = K3D.bin.ril(buff, off + 2);
        //console.log("\t", cid.toString(16), lng);

        if (cid == 0x4000) {
            if (res.objects == null) res.objects = [];
            res.objects.push(K3D.parse.from3DS._edit_object(buff, off, lng));
        }
        //if(cid == 0xb000) res.KEYF3DS = K3D.parse.from3DS._keyf3ds(buff, off, lng);

        off += lng;
    }
    return res;
}

K3D.parse.from3DS._keyf3ds = function (buff, coff, clng) {
    var res = {};
    var off = coff + 6;
    while (off < coff + clng) {
        var cid = K3D.bin.rsl(buff, off);
        var lng = K3D.bin.ril(buff, off + 2);
        //console.log("\t\t", cid.toString(16), lng);

        //if(cid == 0x4000) { res.objects.push(K3D.parse.from3DS._edit_object(buff, off, lng)); }
        if (cid == 0xb002) {
            if (res.desc == null) res.desc = [];
            res.desc.push(K3D.parse.from3DS._keyf_objdes(buff, off, lng));
        }

        off += lng;
    }
    return res;
}

K3D.parse.from3DS._keyf_objdes = function (buff, coff, clng) {
    var res = {};
    var off = coff + 6;
    while (off < coff + clng) {
        var cid = K3D.bin.rsl(buff, off);
        var lng = K3D.bin.ril(buff, off + 2);
        //console.log("\t\t\t", cid.toString(16), lng);

        if (cid == 0xb010) res.hierarchy = K3D.parse.from3DS._keyf_objhierarch(buff, off, lng);
        if (cid == 0xb011) res.dummy_name = K3D.bin.rASCII0(buff, off + 6);
        off += lng;
    }
    return res;
}

K3D.parse.from3DS._keyf_objhierarch = function (buff, coff, clng) {
    var res = {};
    var off = coff + 6;
    res.name = K3D.bin.rASCII0(buff, off);
    off += res.name.length + 1;
    res.hierarchy = K3D.bin.rsl(buff, off + 4);
    return res;
}

K3D.parse.from3DS._edit_object = function (buff, coff, clng)    // buffer, chunk offset, length
{
    var res = {};
    var off = coff + 6;
    res.name = K3D.bin.rASCII0(buff, off);
    off += res.name.length + 1;
    //console.log(res.name);
    while (off < coff + clng) {
        var cid = K3D.bin.rsl(buff, off);
        var lng = K3D.bin.ril(buff, off + 2);
        //console.log("\t\t", cid.toString(16), lng);

        if (cid == 0x4100) res.mesh = K3D.parse.from3DS._obj_trimesh(buff, off, lng);
        //if(cid == 0xb000) res.KEYF3DS = K3D.parse.from3DS._keyf3ds(buff, off, lng);

        off += lng;
    }
    return res;
}

K3D.parse.from3DS._obj_trimesh = function (buff, coff, clng)    // buffer, chunk offset, length
{
    var res = {};
    var off = coff + 6;

    while (off < coff + clng) {
        var cid = K3D.bin.rsl(buff, off);
        var lng = K3D.bin.ril(buff, off + 2);
        //console.log("\t\t\t", cid.toString(16), lng);

        if (cid == 0x4110) res.vertices = K3D.parse.from3DS._tri_vertexl(buff, off, lng);
        if (cid == 0x4120) res.indices = K3D.parse.from3DS._tri_facel1(buff, off, lng);
        if (cid == 0x4140) res.uvt = K3D.parse.from3DS._tri_mappingcoors(buff, off, lng);
        if (cid == 0x4160) res.local = K3D.parse.from3DS._tri_local(buff, off, lng);
        off += lng;
    }
    return res;
}

K3D.parse.from3DS._tri_vertexl = function (buff, coff, clng)    // buffer, chunk offset, length
{
    var res = [];
    var off = coff + 6;
    var n = K3D.bin.rsl(buff, off);
    off += 2;
    for (var i = 0; i < n; i++) {
        res.push(K3D.bin.rf(buff, off));
        res.push(K3D.bin.rf(buff, off + 4));
        res.push(K3D.bin.rf(buff, off + 8));
        off += 12;
    }
    return res;
}

K3D.parse.from3DS._tri_facel1 = function (buff, coff, clng)    // buffer, chunk offset, length
{
    var res = [];
    var off = coff + 6;
    var n = K3D.bin.rsl(buff, off);
    off += 2;
    for (var i = 0; i < n; i++) {
        res.push(K3D.bin.rsl(buff, off));
        res.push(K3D.bin.rsl(buff, off + 2));
        res.push(K3D.bin.rsl(buff, off + 4));
        off += 8;
    }
    return res;
}

K3D.parse.from3DS._tri_mappingcoors = function (buff, coff, clng)    // buffer, chunk offset, length
{
    var res = [];
    var off = coff + 6;
    var n = K3D.bin.rsl(buff, off);
    off += 2;
    for (var i = 0; i < n; i++) {
        res.push(K3D.bin.rf(buff, off));
        res.push(1 - K3D.bin.rf(buff, off + 4));
        off += 8;
    }
    return res;
}

K3D.parse.from3DS._tri_local = function (buff, coff, clng)    // buffer, chunk offset, length
{
    var res = {};
    var off = coff + 6;
    res.X = [K3D.bin.rf(buff, off), K3D.bin.rf(buff, off + 4), K3D.bin.rf(buff, off + 8)];
    off += 12;
    res.Y = [K3D.bin.rf(buff, off), K3D.bin.rf(buff, off + 4), K3D.bin.rf(buff, off + 8)];
    off += 12;
    res.Z = [K3D.bin.rf(buff, off), K3D.bin.rf(buff, off + 4), K3D.bin.rf(buff, off + 8)];
    off += 12;
    res.C = [K3D.bin.rf(buff, off), K3D.bin.rf(buff, off + 4), K3D.bin.rf(buff, off + 8)];
    off += 12;
    return res;
}

K3D.parse.fromBIV = function (buff) {
    buff = new Uint8Array(buff);
    var res = {};

    var head = {};

    head.id = K3D.bin.ril(buff, 0);

    head.verS = K3D.bin.ril(buff, 4);
    head.texS = K3D.bin.ril(buff, 8);
    head.indS = K3D.bin.ril(buff, 12);

    head.verO = K3D.bin.ril(buff, 16);
    head.verL = K3D.bin.ril(buff, 20);
    head.texO = K3D.bin.ril(buff, 24);
    head.texL = K3D.bin.ril(buff, 28);
    head.indO = K3D.bin.ril(buff, 32);
    head.indL = K3D.bin.ril(buff, 36);

    if (head.verO != 0) res.vertices = K3D.parse.fromBIV._readFloats(buff, head.verO, head.verL);
    if (head.texO != 0) res.uvt = K3D.parse.fromBIV._readFloats(buff, head.texO, head.texL);
    if (head.indO != 0) res.indices = K3D.parse.fromBIV._readInts(buff, head.indO, head.indL, head.indS);

    return res;
}

K3D.parse.toBIV = function (obj) {
    var maxi = 0;
    for (var i = 0; i < obj.indices.length; i++)    maxi = Math.max(maxi, obj.indices[i]);

    var indS = 32;
    if (maxi <= 0xffff) indS = 16;

    var len = 40;
    if (obj.vertices) len += obj.vertices.length * 4;
    if (obj.uvt) len += obj.uvt.length * 4;
    if (obj.indices) len += obj.indices.length * indS / 8;


    var buff = new Uint8Array(len);

    K3D.bin.wil(buff, 0, 0x6976616e);

    K3D.bin.wil(buff, 4, 32);
    K3D.bin.wil(buff, 8, 32);
    K3D.bin.wil(buff, 12, indS);

    var off = 40;
    if (obj.vertices) {
        K3D.bin.wil(buff, 16, off);
        K3D.bin.wil(buff, 20, 4 * obj.vertices.length);
        K3D.parse.fromBIV._writeFloats(buff, off, obj.vertices);
        off += 4 * obj.vertices.length;
    }
    if (obj.uvt) {
        K3D.bin.wil(buff, 24, off);
        K3D.bin.wil(buff, 28, 4 * obj.uvt.length);
        K3D.parse.fromBIV._writeFloats(buff, off, obj.uvt);
        off += 4 * obj.uvt.length;
    }
    if (obj.indices) {
        K3D.bin.wil(buff, 32, off);
        K3D.bin.wil(buff, 36, 4 * obj.indices.length);
        K3D.parse.fromBIV._writeInts(buff, off, obj.indices, indS);
    }
    return buff.buffer;
}

K3D.parse.fromBIV._readFloats = function (buff, off, len) {
    var arr = [];
    for (var i = 0; i < len / 4; i++) arr.push(K3D.bin.rf(buff, off + 4 * i));
    return arr;
}

K3D.parse.fromBIV._writeFloats = function (buff, off, arr) {
    for (var i = 0; i < arr.length; i++) K3D.bin.wf(buff, off + 4 * i, arr[i]);
}

K3D.parse.fromBIV._readInts = function (buff, off, len, cs) {
    var arr = [];
    for (var i = 0; i < len / 4; i++) {
        if (cs == 16) arr.push(K3D.bin.rsl(buff, off + 2 * i));
        if (cs == 32) arr.push(K3D.bin.ril(buff, off + 4 * i));
    }
    return arr;
}

K3D.parse.fromBIV._writeInts = function (buff, off, arr, cs) {
    for (var i = 0; i < arr.length; i++) {
        if (cs == 16) K3D.bin.wsl(buff, off + 2 * i, arr[i]);
        if (cs == 32) K3D.bin.wil(buff, off + 4 * i, arr[i]);
    }
}
K3D.gen = {};

K3D.gen.Plane = function (sw, sh, tsw, tsh) {
    if (!tsw) tsw = 1;
    if (!tsh) tsh = 1;
    var r = {verts:[], inds:[], uvt:[]};
    var ssw = sw + 1, ssh = sh + 1
    for (var i = 0; i < ssh; i++) {
        for (var j = 0; j < ssw; j++) {
            var x = -1 + j * (2 / sw);
            var y = -1 + i * (2 / sh);
            r.verts.push(x, y, 0);
            r.uvt.push(tsw * j / sw, tsh * i / sh);
            if (i < sh && j < sw)
                r.inds.push(i * ssw + j, i * ssw + j + 1, (i + 1) * ssw + j, i * ssw + j + 1, (i + 1) * ssw + j, (i + 1) * ssw + j + 1);
        }
    }
    return r;
}
K3D.gen.Cube = function () {
    var r = {
        verts:[    -1, 1, -1, 1, 1, -1, -1, -1, -1, 1, -1, -1, // front
            -1, 1, 1, 1, 1, 1, -1, -1, 1, 1, -1, 1, // back

            -1, 1, 1, -1, 1, -1, -1, -1, 1, -1, -1, -1, // left
            1, 1, 1, 1, 1, -1, 1, -1, 1, 1, -1, -1, // right

            -1, 1, -1, 1, 1, -1, -1, 1, 1, 1, 1, 1, // top
            -1, -1, -1, 1, -1, -1, -1, -1, 1, 1, -1, 1  // bottom
        ],
        inds:[    0, 1, 2, 1, 2, 3, 4, 5, 6, 5, 6, 7,
            8, 9, 10, 9, 10, 11, 12, 13, 14, 13, 14, 15,
            16, 17, 18, 17, 18, 19, 20, 21, 22, 21, 22, 23
        ],
        uvt:[
            1 / 4, 1 / 4, 2 / 4, 1 / 4, 1 / 4, 2 / 4, 2 / 4, 2 / 4, // front
            4 / 4, 1 / 4, 3 / 4, 1 / 4, 4 / 4, 2 / 4, 3 / 4, 2 / 4, // back

            0 / 4, 1 / 4, 1 / 4, 1 / 4, 0 / 4, 2 / 4, 1 / 4, 2 / 4, // left
            3 / 4, 1 / 4, 2 / 4, 1 / 4, 3 / 4, 2 / 4, 2 / 4, 2 / 4, // right

            1 / 4, 1 / 4, 2 / 4, 1 / 4, 1 / 4, 0 / 4, 2 / 4, 0 / 4, // top
            1 / 4, 2 / 4, 2 / 4, 2 / 4, 1 / 4, 3 / 4, 2 / 4, 3 / 4, // bottom
        ]
    };
    return r;
}
K3D.gen.Sphere = function (sx, sy) {
    var r = {verts:[], inds:[], uvt:[]};

    var dx = 2 * Math.PI / sx;
    var dy = Math.PI / sy;
    var nx = sx + 1, ny = sy + 1;
    for (var i = 0; i < ny; i++)    // rows
    {
        for (var j = 0; j < nx; j++) // cols
        {
            var lat = -Math.PI / 2 + i * Math.PI / sy;
            var lon = j * 2 * Math.PI / sx;
            var x = Math.cos(lat) * Math.cos(lon);
            var y = Math.sin(lat);
            var z = Math.cos(lat) * Math.sin(lon);

            r.verts.push(x, y, z);
            r.uvt.push(j / sx, i / sy);
            if (i < sy && j < sx)          // 6 indices for 2 triangles
                r.inds.push(nx * i + j, nx * i + j + 1, nx * (i + 1) + j, nx * i + j + 1, nx * (i + 1) + j, nx * (i + 1) + j + 1);
        }
    }
    return r;
}
K3D.mat = {};

K3D.mat.scale = function (x, y, z) {
    return [
        x, 0, 0, 0,
        0, y, 0, 0,
        0, 0, z, 0,
        0, 0, 0, 1
    ];
}

K3D.mat.translate = function (x, y, z) {
    return [
        1, 0, 0, 0,
        0, 1, 0, 0,
        0, 0, 1, 0,
        x, y, z, 1
    ];
}

K3D.mat.rotateDeg = function (x, y, z) {
    var r = Math.PI / 180;
    return K3D.mat.rotate(x * r, y * r, z * r);
}

K3D.mat.rotate = function (x, y, z) {
    var m = [
        1, 0, 0, 0,
        0, 1, 0, 0,
        0, 0, 1, 0,
        0, 0, 0, 1
    ];
    var a = x;	// alpha
    var b = y;	// beta
    var g = z;	// gama

    var ca = Math.cos(a), cb = Math.cos(b), cg = Math.cos(g);
    var sa = Math.sin(a), sb = Math.sin(b), sg = Math.sin(g);

    m[0] = cb * cg;
    m[1] = -cb * sg;
    m[2 ] = sb;
    m[4] = (ca * sg + sa * sb * cg);
    m[5] = (ca * cg - sa * sb * sg);
    m[6 ] = -sa * cb;
    m[8] = (sa * sg - ca * sb * cg);
    m[9] = (sa * cg + ca * sb * sg);
    m[10] = ca * cb;

    return m;
}


K3D.edit = {};

K3D.edit.interpolate = function (a, b, d, t) {
    for (var i = 0; i < a.length; i++) d[i] = a[i] + t * (b[i] - a[i]);
}


K3D.edit.transform = function (a, m) {
    for (var i = 0; i < a.length; i += 3) {
        var x = a[i], y = a[i + 1], z = a[i + 2];
        a[i + 0] = m[0] * x + m[4] * y + m[8 ] * z + m[12];
        a[i + 1] = m[1] * x + m[5] * y + m[9 ] * z + m[13];
        a[i + 2] = m[2] * x + m[6] * y + m[10] * z + m[14];
    }
}

// starting indices, starting coordinates, coordinates per index

K3D.edit.unwrap = function (ind, crd, cpi) {
    var ncrd = new Array(Math.floor(ind.length / 3) * cpi);
    for (var i = 0; i < ind.length; i++) {
        for (var j = 0; j < cpi; j++) {
            ncrd[i * cpi + j] = crd[ind[i] * cpi + j];
        }
    }
    return ncrd;
}

// current indices, new indices, current array, coordinates per vertex

K3D.edit.remap = function (ind, nind, arr, cpi) {
    var ncrd = new Array(arr.length);
    for (var i = 0; i < ind.length; i++) {
        for (var j = 0; j < cpi; j++) {
            ncrd[nind[i] * cpi + j] = arr[ind[i] * cpi + j];
        }
    }
    return ncrd;
}


K3D.utils = {};

K3D.utils.getAABB = function (vts) {
    var minx, miny, minz, maxx, maxy, maxz;
    minx = miny = minz = 999999999;
    maxx = maxy = maxz = -minx;

    for (var i = 0; i < vts.length; i += 3) {
        var vx = vts[i + 0];
        var vy = vts[i + 1];
        var vz = vts[i + 2];
        if (vx < minx) minx = vx;
        if (vx > maxx) maxx = vx;
        if (vy < miny) miny = vy;
        if (vy > maxy) maxy = vy;
        if (vz < minz) minz = vz;
        if (vy > maxz) maxz = vz;
    }
    return {min:{x:minx, y:miny, z:minz}, max:{x:maxx, y:maxy, z:maxz}};
}