spaztron64 Posted December 4, 2024 Posted December 4, 2024 Early last year I worked with mariokart64n with the goal of converting Soul Calibur 1's Arcade models into a usable format in other software. I spent many months reverse engineering the arcade game and documenting my observations, which MK used to write his script in only a few days. We've been largely successful in getting geometry, UVs, and bone transforms to import into Max for both characters and weapons: For a long time I thought that the Dreamcast model format, seeing how much higher quality the port's modeling work is, is completely different, and ditto for the custom LZSS compression scheme. Turns out I was completely wrong. The compression scheme is completely unchanged from Arcade, and much of the overall *structure* of the model format has been carried over from Arcade. Of course, some obvious changes like the switch from unsigned shorts to floats for vertices and normals were made, but also the face/triangle format. The latter requires some further explanation. See, Namco elected to embed PSX GPU GP0 commands directly into the command list, not too dissimilar to Sony's standard TMD format, with the major difference being that references to vertices in the transformation buffer are done through bitpacked indices: Three 10-bit indices for triangles (largely used by characters), four 8-bit indices for triangle pairs (largely used by weapons), both of which fit into a single 32-bit word, with the caveat of each being limited to 1024 and 256 vertices respectively. These limits are breached in the DC port, and naturally GP0 commands are not compatible with the PVR, so this part of the format has received the most drastic changes. VincentNL has also confirmed that the game uses the low-level Kamui API for interfacing with the PVR. Unfortunately, I am not familiar with the intricacies of Dreamcast rendering whatsoever, so I'd appreciate any help in refactoring the script to be DC compatible. Right now bone transforms will import, although it seems they're a bit screwed in the data itself: Attached below are the script, and samples of Xianghua's 1P model from Arcade and Dreamcast respectively. sc1_mxs.zip 1
spaztron64 Posted December 5, 2024 Author Posted December 5, 2024 Some initial observations I made while examining the command packets during a MAME debugging session: Red - Vertex counter (how many double words follow) Orange - Vtx1 Color Blue - Vtx1 Index Unmarked word - Unknown, potentially UVs?? Violet - Vtx2 Color Scarlet - Vtx2 Index and the cycle repeats until the next Vertex Counter is hit There doesn't appear to be any more bitpacking of vertex indices.
spaztron64 Posted December 9, 2024 Author Posted December 9, 2024 Well, even after referencing the original Arcade documentation and combining it with the new DC discoveries, I'm still at a dead end: If I'm doing something wrong, I can't tell what that is. Here's the docs: Main header: char[23] filename; // 24 character filename that doesn't really contain anything useful uint32_t triangle_strip_table_pointer; // points to the first triangle_strip_t; uint8_t unk1; uint8_t unk2; uint8_t unk3; uint8_t submesh_count; // How many model_submesh_t instances exist; model_submesh_t[n] model_submesh_table; // One or more submesh headers. Usually no more than 17 typedef struct{ uint8_t bone_id0; // NULL on weapons? uint8_t bone_id1; uint32_t vertex_list_pointer; // points to a vertex_list_t uint32_t unknown; // Always 0x00000000? uint16_t vertex_count; uint16_t normal_count; int16_t bone_rotation_x; int16_t bone_rotation_y; int16_t bone_rotation_z; int16_t unknown; int16_t bone_position_x; int16_t bone_position_y; int16_t bone_position_z; int16_t parent_bone_id; } model_submesh_t; typedef struct{ float vertex_x; float vertex_y; float vertex_z; uint16_t vertex_index; uint16_t flags: // It's not clear what these do } vertex_list_t; typedef struct{ float normal_x; float normal_y; float normal_z; uint16_t normal_index; uint16_t flags: // It's not clear what these do } normal_list_t; typedef struct{ uint16_t vertex_index; // Reference to a vertex index fetched from a vertex_list_t; uint16_t vertex_color; // Influences shading uint32_t uv_coordinates; // UVs } triangle_t; typedef struct{ uint16_t strip_length; uint8_t uv_coordinate; // Why are there three UV coordinates? uint8_t unknown; triangle_t[strip_length-1] triangles; // Array of triangle_t elements; } triangle_strip_t; The current import script is attached below. soul_calibur_1_importer.py 1
spaztron64 Posted February 9 Author Posted February 9 Consider this a bump. Every time I've been trying to get back to this, I couldn't make any progress. I can get a handful of bodyparts to form some kind of shape, like the shoes, but the actual strips are still totally screwed, as you can see in the attached screenies: Additionally I'll attach the most up-to-date "workbench" of data and scripts: sc1_mxs.zip It has 010 and ImHex templates for highlighting, three DC character models, and the current import script, as well as reference data and scripts used for Arcade for comparison's sake. Treat the highlight templates as the documentation, the one I put previously is not quite correct. Additionally, there isn't really a strip counter, the strips are continuously iterated until a pair of 32-bit 00000000 words is hit. I had to put one in to tell ImHex to at least stop somewhere.
Engineer shak-otay Posted February 9 Engineer Posted February 9 (edited) Are you sure that this point cloud is correct? If so it's not surprising that you got what you get building faces all over these points. Right screen portion of points in meshlab: Edited February 9 by shak-otay
spaztron64 Posted February 9 Author Posted February 9 (edited) 7 hours ago, shak-otay said: Are you sure that this point cloud is correct? The vertices themselves are good, that I can tell you with complete certainty, but the positioning of each submesh is not. Do note that there is no A/T-pose mesh at play here (at least not in the mesh itself), every body part is positioned at world origin, and each bone that it is bound to also sets that body part's default position. Take a look at an example from the Arcade version: Without the bone transforms, we get this. | But with the skeleton applied, we get this. A similar situation is happening on Dreamcast as well, with the major difference being that vertices and normals now use floats instead of integers. If you look at the header of the Arcade and Dreamcast model binaries, you'll see that they have a very similar structure. Even the bones are still stored as shorts, but rescaled by the game into floats at runtime. I think this is the part where I'm getting it wrong, hence why the positioning of each bodypart is bad in my current script. Edited February 9 by spaztron64 1
Engineer shak-otay Posted February 9 Engineer Posted February 9 I see. In your script for _ in range(submesh["vertex_count"]): assembles small sub meshes with vertex counts from 3 to 14, 18 or 19. But I don't see how to combine them to a shoe or a head sub mesh. btw, "for _ in" looks weird to me - you shouldn't use _ as an index identifier.
spaztron64 Posted February 9 Author Posted February 9 Yeah I was LLM-ing the crap out of this because I barely know anything about Blender's Py API (or 3D graphics programming in general), most of my work was analysis of the data at runtime with a debugger. UVs are also a bit weird, I've been messing around with them a few months back: 1
spaztron64 Posted February 11 Author Posted February 11 Been deleting some of the larger geometry to see if I could find something recognizable underneath. Lo and behold, I got something, specifically fingers for the left and right hand criss-crossing one another. That alone confirms what I already knew before: that my positioning of objects based on the bone transforms is completely wrong.. But, now I can clearly see that my strip formation is somewhat functional. The geometry of each hand's fingers are being connected into a mesh just fine, but some of their vertices are connecting to other parts of the model they should have no contact with. The only thing I can conclude from this is that my termination of strip formations is fundamentally flawed. Problem is.... I can't really tell how.
Engineer shak-otay Posted February 11 Engineer Posted February 11 There's almost always struggeling with extra faces with PS2 models, too. I have no idea whether this will help you in some way. Just in case.
spaztron64 Posted February 11 Author Posted February 11 Yeah, I see what you mean. I might be having a similar issue, but I won't know till I find a way to iterate over the strip table without all of the vertices being loaded. If I try to limit the number of submesh vertex lists being processed in my script to, I dunno, 5 or less, the strip formation loop will complain about out of range indices being referenced, obviously because they're not loaded, and not return any kind of mesh.
spaztron64 Posted February 12 Author Posted February 12 Slight progress improvement! It didn't make any sense to me that the tips of the foot would form triangles with other parts of the body, let alone sides of the fingers forming faces with one another, so when I actually looked at what my loop was doing, I spotted a major mistake: I was accumulating strips until a terminator was hit, and forming faces in batches. What I should've done from the getgo was draw each strip the moment it was finished, 0x0 terminated afterwards or not. Which means... I can finally get some geometry out cleanly! Submesh positioning still remains an issue, but honestly this is the biggest breakthrough yet. 1
spaztron64 Posted February 13 Author Posted February 13 This is what I currently have: soul_calibur_1_importer_v6.py For the life of me I just can't get a good skeleton to form. I tried to implement some of the hardcoded corrections that MK did for Arcade, as well as the float scales as per the DC version's (well actually the Android port of DC SC1) code, but to no avail. 1
spaztron64 Posted Monday at 12:11 AM Author Posted Monday at 12:11 AM Well, I finally... sort of got an armature going! The legs still need 180 degree flips just like on Arcade. The weights seem a bit... shifted though. Like they're not assigned to the correct vertex blocks. soul_calibur_1_importer_v7b.py
spaztron64 Posted Monday at 06:59 PM Author Posted Monday at 06:59 PM Mariokart64n hopped in to help out with the armature a bit. By every metric, we finally have it sorted out. Unfortunately, the weighting still seems super off. I theorize that what's happening is that every submesh is paired with one bone too high in the hierarchy, but that's just a wild guess right now. soul_calibur_1_importer_v8.py 1
spaztron64 Posted Tuesday at 08:25 AM Author Posted Tuesday at 08:25 AM (edited) EDIT: Yeah nevermind, that wasn't it. Sure, I have all the faces, but now I have waaay too many verts and transforms that are still broken. Back to the drawing board... Seems strip processing isn't quite so done yet. Somebody was kind enough to do a scene rip, which is largely useless except for the number of faces: 3694, although I might've missed removing a few sword faces. Now, my importer only reports back 3198. Pretty big gap. Blender will throw an exception if it tries to create duplicate faces. I edited the handler to count the number of such duplicated faces. Add them together with properly processed faces aaaand: Drawn faces: 3198 Skipped faces: 494 Total faces: 3692 Bingo, there's our problem. Now the tricky part is forcing blender to accept duplicate faces. Blender gurus, I'm all ears. Edited Tuesday at 08:39 AM by spaztron64
Recommended Posts
Create an account or sign in to comment
You need to be a member in order to leave a comment
Create an account
Sign up for a new account in our community. It's easy!
Register a new accountSign in
Already have an account? Sign in here.
Sign In Now