Jump to content

kion

Members
  • Content Count

    68
  • Joined

  • Last visited

  • Days Won

    12

kion last won the day on March 4

kion had the most liked content!

Community Reputation

66 Guardian

About kion

  • Rank
    2★

Console Information

  • Xbox 360
    No
  • Playstation 2
    No
  • PC/JP
    No

Recent Profile Visitors

322 profile views
  1. On a semi-positive note, I tried switching to a simpler model, one of the Yomei swords that has a spinning hilt. out-2020-02-04_00.34.48.mp4 Managed to get some of the spinning part. One thing that really confuses me is why the hilt seems to get closer to the sword, come out, spin, and then go back again. As far as I can tell the animation is simply a Z-rotation around the base of the sword, so there shouldn't be anything in there that changes the distance from the sword. A few other issues, bone id's versus bone indices are pretty confusing. The sword doesn't have any bone weight defined in the vertex groups, and has to be programmed into the draw calls. And in the Unm file, there is a lot of what looks like "junk" data that ends up not being used. Like multiple entries for the same frame for the same bone and things like that.
  2. Okay checked the values on the jaw and managed to get it to look like something reasonable. The angle values are pretty huge, so I might need to multiply the values against the inverse of the parent in case these are world angles. Or there could be some kind of scaling going on.
  3. I did some more testing for the root bone rotation and the values don't make any sense. I tried interpreting the values as floats, by reading two bytes, shifting them up and then reading that value as a float, and I got NaN values. So I don't think these are floats. My best guess is that there could be flags, masking, or maybe some kind of rotation scale setting that defines how the bones work. So I think I'll have to get into the game to test those values. Next I'll try looking at another bone like an arm or something to see if there's something special going on with the root bone, or if all of the bones are like this. And double check to see if I have the correct numbering or not.
  4. For animations, I went back to refactor my code. The first two entries in the animation table seem to be root position and root rotation. Before I had separated reading position and rotation. But I think it makes more sense to read the root bone values in one function, followed by the rest of the rotation tracks for the non-root bone after that. And doing it this way gave me the chance to look at specifically the root bone rotation values to see if they make sense for the animation. bandicam 2020-01-30 10-06-22-211.mp4 And the values don't make sense at all. The animation I'm using for testing is "die faceup(aomuki)". So The normal expectation for the animation would be for the model to rotate only in the X direction backward so that the model in facing up. What we end up seeing is somewhat random values in all three axis. And this is shown in the animation tracks. The first two bytes are the frame number. Meaning there are 6 bytes for each track. What that would normally mean is a 2 byte rotation for each angle where 0x0000 is 0 deg and 0xffff is 359.99 deg. If we were expecting the "die faceup" animation, then we would see the track number, 2 bytes for the x rotation, and then zero's for the following four bytes. But instead we see that all 6 bytes have values in them for different axis. So we have a couple of options: 1. Find a booma in game, zero out and start testing 2. Skip over root rotation and check values of other bones to see if the values make sense 3. Try interpreting the rotation values as 2 byte floats (shift up) to see if the angles are stored differently than expected
  5. Wasn't able to get anything that looked correct by messing around with the animations. So time to look more in-depth and start debugging. First up is the bone names, which are in the .una file. Pretty easy and similar to NJTL. We have the magic number and file length, followed by the pointer to the header 0xc0. At the header we have the pointer to the start of the bone names 0x10, and the number of bones 0x16. After that we have the bone number followed by the offset to the start of the string. So I should go ahead and add in the bone names so I can use them as a reference for the animation. And the code to parse it is simple enough. "use strict"; class UnaReader { constructor() { } parse(arraybuffer) { const view = new DataView(arraybuffer); const headerOfs = view.getUint32(0x08, true); console.log("Header offset: 0x%s", headerOfs.toString(16)); const boneCount = view.getUint32(headerOfs + 0x04, true); const names = new Array(boneCount); let ofs = view.getUint32(headerOfs + 0x08, true); for(let i = 0; i < boneCount; i++) { const id = view.getUint32(ofs + 0x00, true); let start = view.getUint32(ofs + 0x04, true); ofs += 8; let ch; let name = ""; while( (ch = view.getUint8(start++)) !== 0) { name += String.fromCharCode(ch); } console.log(name); names[id] = name; } return names; } }
  6. It's been about two weeks since I've had a chance to post. I managed to get my visa submitted and accepted yesterday. So now I can get back to looking into more file formats. The next step is animations. Well it's technically not the step, but we have a rigged model with bones. So it's generally the next logical step. bandicam 2020-01-28 16-01-05-215.mp4 After getting the correct bone layout, I went back and tried applying the walk animation, and then above video shows the result. The movement is a little bit more promising that what I had when the rig was wrong, but that doesn't mean the animation still isn't completely wrong. So what can we do to adjust this travesty of mangled polygons? We can try a few things. The first and easiest would probably be to try interpreting the Euler angles as world position instead of local position. I think the way we to this is take the Euler angles and apply them to a 4x4 transformation matrix. Then we take the world transformation matrix of the parent bone, and invert it. And then multiplying the Euler matrix against the inverted parent, should give us the local rotation for the bone. I'm not sure if that's exactly how it works, so we can try different options and try to record the result of each one. We have a couple of fallback options. I skipped over grabbing the bone names from the .una file. So grabbing that would probably make it a lot easier to tell what's going on for each individual bone. Specifically what we can write out a table of all of the bones in the file as a table. And then we can also write out a table of all of the bones, and their default rotations. And then we can try to see if the rotations make any sense when compared to their bone values. Specifically for a walk animation we would expect the thigh to swing forward and then swing back. So we can try and look at specific bones and see if the provided values make sense. And there's also the option of testing in game. In general I think this could be done with either AoI or any of the Phantasy Star Portable games, as the animation format for each of these should be the same. In general I find testing with emulators easier, as you can take a save state, edit values and test repeatedly from there. Though if anyone is familiar with cheat engine that's an option too. It might also be good idea to test against the idle animation, so that testing can be done in the lobby. Sometimes getting enemies to cooperate can be pretty difficult. But the general idea for testing would be to zero out the animation, change different values for potential flags, and see what happens. In general if you could test in-game and zero out the right attack animation until only the right shoulder moves, then that makes for a simpler use-case to work with. As you can trace down specifically for these values, and this one bone, this is the only interpretation that gives these values, and then be able to work from there. For right now I'll try interpreting the animations as world position, and then go from there.
  7. Okay, I think I'm finished with the visa application. I need to go tomorrow for my wife's application. And then I should be done with that. The next step is the .unm animation files, and then I asked JamRules for more hints on the other compression format used in .nbl. So I guess we can get started on those. https://github.com/imaya/zlib.js/
  8. I think .xnj might be pretty similar to .unj in terms of general structure. I think the main difference is that .unj seems to be a strip structure generated from the vertices directly and .xnj has a vertex list and faces are created by a list of indices. But that's only speculation from what little i've seen of the file type. For .xna, i think that's the bone names and it should look extremely similar to the NJTL texture list as I think they effectively use the same layout. And .xnm/.unm I think are actually the same file and layout. Though even after fixing the rig, it didn't make the animation look any better, so there is the possibility that the rotation angles need to be multiplied by the bind matrix or something like that. For now I'm really tempted to keep playing around .unm, but I have renew my visa. So I think I'll have to hold off on PSP models for a week or two to do paperwork so I don't get deported from my country of residence.
  9. I was pretty frustrated this afternoon of trying tons of different combinations and coming up short every time. Writing the recursive function approach actually helped a lot in getting me to turn a corner. Up until that point I was using the prs values and thinking "this is close, I just need to tweak this", and I ended up focusing on the prs values too much. After writing the recursive approach and getting the exact same result it made me realize that I shouldn't be trusting the prs values, and that I should take a look at the included matrix again. For the included matrix I wasn't exactly sure what it was, but knowing that the root bone isn't going to be multiplied by a parent, i went ahead and compared the prs values to the included matrix and found that I could get the prs matrix by transposing and inversing it. If I could calculate the matrix that way, it means that I could possibly get the local, or world and then local matrix for the other bones and worked my way through it. To be honest now that we have some working values, it's potentially viable to use the same approach as .nj using the prs values. Since we have a local matrix to check against, it's possible to look at the prs values, compare with the decomposed local matrix and then figure out which flags need to be set when and why. But as far as displaying the model, the inverse approach ended up being pretty simple, so I'm not in any particular hurry to look into it.
  10. After loosing my sanity it finally worked!!! const bindMat = new THREE.Matrix4(); bindMat.set( this.view.getFloat32(ofs + 0x30, true), this.view.getFloat32(ofs + 0x34, true), this.view.getFloat32(ofs + 0x38, true), this.view.getFloat32(ofs + 0x3c, true), this.view.getFloat32(ofs + 0x40, true), this.view.getFloat32(ofs + 0x44, true), this.view.getFloat32(ofs + 0x48, true), this.view.getFloat32(ofs + 0x4c, true), this.view.getFloat32(ofs + 0x50, true), this.view.getFloat32(ofs + 0x54, true), this.view.getFloat32(ofs + 0x58, true), this.view.getFloat32(ofs + 0x5c, true), this.view.getFloat32(ofs + 0x60, true), this.view.getFloat32(ofs + 0x64, true), this.view.getFloat32(ofs + 0x68, true), this.view.getFloat32(ofs + 0x6c, true) ); bindMat.transpose(); const childWorldMat = new THREE.Matrix4(); childWorldMat.getInverse(bindMat, true); ofs += 0x90; let num = i.toString(); while (num.length < 3) { num = "0" + num; } const bone = new THREE.Bone(); bone.name = "bone_" + num; let localMatrix = new THREE.Matrix4(); const parentBone = this.MEM.bones[unj_bone_t.parent_id]; if (parentBone) { localMatrix.getInverse(parentBone.matrixWorld, true); parentBone.add(bone); } localMatrix.multiply(childWorldMat); bone.applyMatrix(localMatrix); bone.updateMatrix(); bone.updateMatrixWorld(); this.MEM.bones.push(bone); So this is kind of nuts. The way it works is that the bone includes a 4x4 inverse bind matrix. To convert the bind matrix to the child's world matrix you transpose and then invert this matrix. Once we have the child's world matrix, we then need to derive the local matrix. In the case of the root bone, the world and local matrix are the same value. So we can get ahead and set as-is. In the case the child has a parent bone, then we need to use another inverse matrix. So the child's world matrix is going to be the parent's world matrix by child's local matrix. We have the child's world matrix, and we have the parent's world matrix. We inverse the parent's world matrix and multiply that by the child's world matrix and that gives us the local matrix, which means we have our bone!
  11. Recursive approach using similar style as nj: readBones() { let ofs = this.header.bone_ofs; const tmpBones = []; for (let i = 0; i < this.header.bone_count; i++) { const unj_bone_t = { flags: this.view.getUint32(ofs + 0x00, true); bone_id: this.view.getInt16(ofs + 0x04, true); parent_id: this.view.getInt16(ofs + 0x06, true); child_id: this.view.getInt16(ofs + 0x08, true); sibling_id: this.view.getInt16(ofs + 0x0a, true); pos: { x: this.view.getFloat32(ofs + 0x0c, true), y: this.view.getFloat32(ofs + 0x10, true), z: this.view.getFloat32(ofs + 0x14, true) }, rot: { x: this.view.getInt32(ofs + 0x18, true) * Math.PI / 0x8000, y: this.view.getInt32(ofs + 0x1c, true) * Math.PI / 0x8000, z: this.view.getInt32(ofs + 0x20, true) * Math.PI / 0x8000 }, scl: { x: this.view.getFloat32(ofs + 0x24, true), y: this.view.getFloat32(ofs + 0x28, true), z: this.view.getFloat32(ofs + 0x2c, true) } } const mat = new THREE.Matrix4(); mat.set( this.view.getFloat32(ofs + 0x30, true), this.view.getFloat32(ofs + 0x34, true), this.view.getFloat32(ofs + 0x38, true), this.view.getFloat32(ofs + 0x3c, true), this.view.getFloat32(ofs + 0x40, true), this.view.getFloat32(ofs + 0x44, true), this.view.getFloat32(ofs + 0x48, true), this.view.getFloat32(ofs + 0x4c, true), this.view.getFloat32(ofs + 0x50, true), this.view.getFloat32(ofs + 0x54, true), this.view.getFloat32(ofs + 0x58, true), this.view.getFloat32(ofs + 0x5c, true), this.view.getFloat32(ofs + 0x60, true), this.view.getFloat32(ofs + 0x64, true), this.view.getFloat32(ofs + 0x68, true), this.view.getFloat32(ofs + 0x6c, true) ); mat.transpose(); unj_bone_t.mat = mat; ofs += 0x90; tmpBones.push(unj_bone_t); } tmpBones.forEach(bone => { bone.child = tmpBones[bone.child_id]; bone.sibling = tmpBones[bone.sibling_id]; }); multiplyBones(tmpBones[0]); } multiplyBones(unj_bone_t, parentBone) { let num = this.MEM.bones.length.toString(); while (num.length < 3) { num = "0" + num; } let bone = new THREE.Bone(); bone.name = "bone_" + num; this.MEM.bones.push(bone); bone.scale.x = unj_bone_t.scl.x; bone.scale.y = unj_bone_t.scl.y; bone.scale.z = unj_bone_t.scl.z; let xRotMatrix = new THREE.Matrix4(); xRotMatrix.makeRotationX(unj_bone_t.rot.x); bone.applyMatrix(xRotMatrix); let yRotMatrix = new THREE.Matrix4(); yRotMatrix.makeRotationY(unj_bone_t.rot.y); bone.applyMatrix(yRotMatrix); let zRotMatrix = new THREE.Matrix4(); zRotMatrix.makeRotationZ(unj_bone_t.rot.z); bone.applyMatrix(zRotMatrix); bone.position.x = unj_bone_t.pos.x; bone.position.y = unj_bone_t.pos.y; bone.position.z = unj_bone_t.pos.z; bone.updateMatrix(); bone.updateMatrixWorld(); if (parentBone) { parentBone.add(bone); bone.updateMatrix(); bone.updateMatrixWorld(); } if (unj_bone_t.child) { multiplyBones(unj_bone_t.child, bone); } if (unj_bone_t.sibling) { multiplyBones(unj_bone_t.sibling, parentBone); } } Result: Looks like there's something else going on.
  12. Can't say I need pointers on .nj, I have a fully capable ninja tool with stages and characters with animations and exports. The whole issue that I've having is that the same approach and code I used for NJ DOESN'T WORK. And other alternatives that i've tried ARE NOT WORKING. Try writing some code for UNJ and see how that works. I would be very grateful to have some working code to compare against. My Phantasy Star Portable code is available here: https://gitlab.com/dashgl/psp2i for this current project being working on and the example files are included in the dat folder.
  13. With transpose and pre-multiplied by inverse parent world: const mat = new THREE.Matrix4(); mat.set( this.view.getFloat32(ofs + 0x30, true), this.view.getFloat32(ofs + 0x34, true), this.view.getFloat32(ofs + 0x38, true), this.view.getFloat32(ofs + 0x3c, true), this.view.getFloat32(ofs + 0x40, true), this.view.getFloat32(ofs + 0x44, true), this.view.getFloat32(ofs + 0x48, true), this.view.getFloat32(ofs + 0x4c, true), this.view.getFloat32(ofs + 0x50, true), this.view.getFloat32(ofs + 0x54, true), this.view.getFloat32(ofs + 0x58, true), this.view.getFloat32(ofs + 0x5c, true), this.view.getFloat32(ofs + 0x60, true), this.view.getFloat32(ofs + 0x64, true), this.view.getFloat32(ofs + 0x68, true), this.view.getFloat32(ofs + 0x6c, true) ); mat.transpose(); ofs += 0x90; const bone = new THREE.Bone(); bone.name = "bone_" + i.toString(); bone.userData = new THREE.Matrix4(); bone.userData.getInverse(mat, true); if (parent_id !== -1) { let parentBone = this.MEM.bones[parent_id]; mat.premultiply(parentBone.userData); parentBone.add(bone); } bone.applyMatrix(mat); this.MEM.bones.push(bone);
  14. Without transpose (skeleton doesn't even show up) const mat = new THREE.Matrix4(); mat.set( this.view.getFloat32(ofs + 0x30, true), this.view.getFloat32(ofs + 0x34, true), this.view.getFloat32(ofs + 0x38, true), this.view.getFloat32(ofs + 0x3c, true), this.view.getFloat32(ofs + 0x40, true), this.view.getFloat32(ofs + 0x44, true), this.view.getFloat32(ofs + 0x48, true), this.view.getFloat32(ofs + 0x4c, true), this.view.getFloat32(ofs + 0x50, true), this.view.getFloat32(ofs + 0x54, true), this.view.getFloat32(ofs + 0x58, true), this.view.getFloat32(ofs + 0x5c, true), this.view.getFloat32(ofs + 0x60, true), this.view.getFloat32(ofs + 0x64, true), this.view.getFloat32(ofs + 0x68, true), this.view.getFloat32(ofs + 0x6c, true) ); //mat.transpose(); ofs += 0x90; let num = i.toString(); while (num.length < 3) { num = "0" + num; } const bone = new THREE.Bone(); bone.name = "bone_" + num; bone.userData = new THREE.Matrix4(); bone.userData.getInverse(mat, true); if (parent_id !== -1) { let parentBone = this.MEM.bones[parent_id]; mat.multiply(parentBone.userData); parentBone.add(bone); } bone.applyMatrix(mat); bone.updateMatrix(); this.MEM.bones.push(bone);
  15. I guess I might as well keep track of the insanity that are these god-forsaken bones. If we can't get the prs values to play nice, then maybe we can take the included world matrix, and then multiply it by the inverse of the parent's world matrix to try and get the local transforms that way. Good approach maybe? Code: const mat = new THREE.Matrix4(); mat.set( this.view.getFloat32(ofs + 0x30, true), this.view.getFloat32(ofs + 0x34, true), this.view.getFloat32(ofs + 0x38, true), this.view.getFloat32(ofs + 0x3c, true), this.view.getFloat32(ofs + 0x40, true), this.view.getFloat32(ofs + 0x44, true), this.view.getFloat32(ofs + 0x48, true), this.view.getFloat32(ofs + 0x4c, true), this.view.getFloat32(ofs + 0x50, true), this.view.getFloat32(ofs + 0x54, true), this.view.getFloat32(ofs + 0x58, true), this.view.getFloat32(ofs + 0x5c, true), this.view.getFloat32(ofs + 0x60, true), this.view.getFloat32(ofs + 0x64, true), this.view.getFloat32(ofs + 0x68, true), this.view.getFloat32(ofs + 0x6c, true) ); mat.transpose(); ofs += 0x90; let num = i.toString(); while (num.length < 3) { num = "0" + num; } const bone = new THREE.Bone(); bone.name = "bone_" + num; bone.userData = new THREE.Matrix4(); bone.userData.getInverse(mat, true); if (parent_id !== -1) { let parentBone = this.MEM.bones[parent_id]; mat.multiply(parentBone.userData); parentBone.add(bone); } bone.applyMatrix(mat); bone.updateMatrix(); this.MEM.bones.push(bone); Result:
×
×
  • Create New...