What format should I use for animated models?
- ArthCarvalho
- Active PSXDEV User
- Posts: 45
- Joined: Jan 29, 2016
- I am a: Artist, Programmer
- PlayStation Model: SCPH-103
What format should I use for animated models?
Hello everyone,
What is better? Should I create an animation format from the scratch or is using HMD a good option?
My objective is to have both hierarchical animation (each limb is made of separate meshes) and "skeletal" animation.
I do have an idea of how skeletal animation works and I could create my own format, but the most important thing is that it must be (relatively) fast so it doesn't take too much memory or CPU time. At least for my project, there should be about 3 to 4 (counting the player) complex animated objects on screen on a single frame at worst. (Since gameplay is divided by rooms and complex objects are not common on screen, for most part of other objects I probably can get away by simply rotating their models instead)
What is better? Should I create an animation format from the scratch or is using HMD a good option?
My objective is to have both hierarchical animation (each limb is made of separate meshes) and "skeletal" animation.
I do have an idea of how skeletal animation works and I could create my own format, but the most important thing is that it must be (relatively) fast so it doesn't take too much memory or CPU time. At least for my project, there should be about 3 to 4 (counting the player) complex animated objects on screen on a single frame at worst. (Since gameplay is divided by rooms and complex objects are not common on screen, for most part of other objects I probably can get away by simply rotating their models instead)
-
Administrator Verified
- Admin / PSXDEV
- Posts: 2691
- Joined: Dec 31, 2012
- I am a: Shadow
- PlayStation Model: H2000/5502
Best to start with the Sony HMD library to get an understanding of how it works, and if it's not good enough I'd then make your own 

Development Console: SCPH-5502 with 8MB RAM, MM3 Modchip, PAL 60 Colour Modification (for NTSC), PSIO Switch Board, DB-9 breakout headers for both RGB and Serial output and an Xplorer with CAETLA 0.34.
PlayStation Development PC: Windows 98 SE, Pentium 3 at 400MHz, 128MB SDRAM, DTL-H2000, DTL-H2010, DTL-H201A, DTL-S2020 (with 4GB SCSI-2 HDD), 21" Sony G420, CD-R burner, 3.25" and 5.25" Floppy Diskette Drives, ZIP 100 Diskette Drive and an IBM Model M keyboard.
PlayStation Development PC: Windows 98 SE, Pentium 3 at 400MHz, 128MB SDRAM, DTL-H2000, DTL-H2010, DTL-H201A, DTL-S2020 (with 4GB SCSI-2 HDD), 21" Sony G420, CD-R burner, 3.25" and 5.25" Floppy Diskette Drives, ZIP 100 Diskette Drive and an IBM Model M keyboard.
And if you think you need to limit CPU time spent, then tripple up that demand. The CPU is *slow* ;p
-
Administrator Verified
- Admin / PSXDEV
- Posts: 2691
- Joined: Dec 31, 2012
- I am a: Shadow
- PlayStation Model: H2000/5502
By today's standards, yes. Back in the early nineties, no

Development Console: SCPH-5502 with 8MB RAM, MM3 Modchip, PAL 60 Colour Modification (for NTSC), PSIO Switch Board, DB-9 breakout headers for both RGB and Serial output and an Xplorer with CAETLA 0.34.
PlayStation Development PC: Windows 98 SE, Pentium 3 at 400MHz, 128MB SDRAM, DTL-H2000, DTL-H2010, DTL-H201A, DTL-S2020 (with 4GB SCSI-2 HDD), 21" Sony G420, CD-R burner, 3.25" and 5.25" Floppy Diskette Drives, ZIP 100 Diskette Drive and an IBM Model M keyboard.
PlayStation Development PC: Windows 98 SE, Pentium 3 at 400MHz, 128MB SDRAM, DTL-H2000, DTL-H2010, DTL-H201A, DTL-S2020 (with 4GB SCSI-2 HDD), 21" Sony G420, CD-R burner, 3.25" and 5.25" Floppy Diskette Drives, ZIP 100 Diskette Drive and an IBM Model M keyboard.
No no, the GTE is fast and the GPU can deliver. But that CPU core for your basic game loop, that is a dog :p
- ArthCarvalho
- Active PSXDEV User
- Posts: 45
- Joined: Jan 29, 2016
- I am a: Artist, Programmer
- PlayStation Model: SCPH-103
I'm just being cautious here, since I've never coded for low end machines, specially ones that are over 20 years old.
So I have no idea of how much CPU power I am using or how much it has available for me, so I'm threading on the cautious side. If I try to imagine the PlayStation CPU as any less powerful it'll be equivalent to the processor of a pocket calculator.
After reading the documentation I noticed that libhmd is dependent on libgs, since I'm not using libgs this pretty much rules out using HMD.
I made a dancing cube thing ouf of a box model and created a hierarchy transformation so it would wiggle using a sine function so I could test the performance of this on a real console later.
[BBvideo=560,315]https://www.youtube.com/watch?v=SrgE7Jahjvk[/BBvideo]
The code for it is like this:
So I have no idea of how much CPU power I am using or how much it has available for me, so I'm threading on the cautious side. If I try to imagine the PlayStation CPU as any less powerful it'll be equivalent to the processor of a pocket calculator.
After reading the documentation I noticed that libhmd is dependent on libgs, since I'm not using libgs this pretty much rules out using HMD.
I made a dancing cube thing ouf of a box model and created a hierarchy transformation so it would wiggle using a sine function so I could test the performance of this on a real console later.
[BBvideo=560,315]https://www.youtube.com/watch?v=SrgE7Jahjvk[/BBvideo]
The code for it is like this:
Code: Select all
// Wiggling Cube
test_cube_obj.matrix = m_identity;
temp_vec.vx = icos(temp_counter%4096)>>5;
temp_vec.vy = 0;
temp_vec.vz = 0;
RotMatrix_gte(&temp_vec,&test_cube_obj.matrix);
CompMatrixLV(&camera.view_matrix, &test_cube_obj.matrix, &test_cube_obj.matrix);
gte_SetTransMatrix(&test_cube_obj.matrix);
gte_SetRotMatrix(&test_cube_obj.matrix);
packet_b_ptr = SGM_UpdateModel(&test_cube_model, packet_b_ptr, (u_long*)G.pOt);
// Wiggling Cube 2
temp_mat = test_cube_obj.matrix;
temp_vec.vx = isin(temp_counter%4096)>>5;
temp_vec.vy = 0;
temp_vec.vz = 0;
temp_vec_p.vx = 0;
temp_vec_p.vy = 154;
temp_vec_p.vz = 0;
RotMatrix_gte(&temp_vec,&test_cube_obj.matrix);
TransMatrix(&test_cube_obj.matrix,&temp_vec_p);
CompMatrix(&temp_mat, &test_cube_obj.matrix, &test_cube_obj.matrix);
temp_mat2 = test_cube_obj.matrix;
gte_SetTransMatrix(&test_cube_obj.matrix);
gte_SetRotMatrix(&test_cube_obj.matrix);
packet_b_ptr = SGM_UpdateModel(&test_cube_model, packet_b_ptr, (u_long*)G.pOt);
// Wiggling Cube the 3rd
temp_vec.vx = icos(temp_counter%4096)>>4;
temp_vec.vy = 0;
temp_vec.vz = 0;
temp_vec_p.vx = 0;
temp_vec_p.vy = 154;
temp_vec_p.vz = 154;
RotMatrix_gte(&temp_vec,&test_cube_obj.matrix);
TransMatrix(&test_cube_obj.matrix,&temp_vec_p);
CompMatrix(&temp_mat, &test_cube_obj.matrix, &test_cube_obj.matrix);
gte_SetTransMatrix(&test_cube_obj.matrix);
gte_SetRotMatrix(&test_cube_obj.matrix);
packet_b_ptr = SGM_UpdateModel(&test_cube_model, packet_b_ptr, (u_long*)G.pOt);
// Wiggling Cube HD Remix ULTRA
temp_vec.vx = icos(temp_counter%4096)>>3;
temp_vec.vy = 0;
temp_vec.vz = 0;
temp_vec_p.vx = 0;
temp_vec_p.vy = 154;
temp_vec_p.vz = -154;
RotMatrix_gte(&temp_vec,&test_cube_obj.matrix);
TransMatrix(&test_cube_obj.matrix,&temp_vec_p);
CompMatrix(&temp_mat, &test_cube_obj.matrix, &test_cube_obj.matrix);
gte_SetTransMatrix(&test_cube_obj.matrix);
gte_SetRotMatrix(&test_cube_obj.matrix);
packet_b_ptr = SGM_UpdateModel(&test_cube_model, packet_b_ptr, (u_long*)G.pOt);
// Wiggling Cube Origins
temp_vec.vx = icos(temp_counter%4096)>>3;
temp_vec.vy = 0;
temp_vec.vz = 0;
temp_vec_p.vx = 0;
temp_vec_p.vy = 154;
temp_vec_p.vz = 0;
RotMatrix_gte(&temp_vec,&test_cube_obj.matrix);
TransMatrix(&test_cube_obj.matrix,&temp_vec_p);
CompMatrix(&temp_mat2, &test_cube_obj.matrix, &test_cube_obj.matrix);
gte_SetTransMatrix(&test_cube_obj.matrix);
gte_SetRotMatrix(&test_cube_obj.matrix);
packet_b_ptr = SGM_UpdateModel(&test_cube_model, packet_b_ptr, (u_long*)G.pOt);
// Wiggling Cube Returns
temp_mat2 = test_cube_obj.matrix;
temp_vec.vx = icos(temp_counter%4096)>>2;
temp_vec.vy = 0;
temp_vec.vz = 0;
temp_vec_p.vx = 0;
temp_vec_p.vy = 154;
temp_vec_p.vz = 0;
RotMatrix_gte(&temp_vec,&test_cube_obj.matrix);
TransMatrix(&test_cube_obj.matrix,&temp_vec_p);
CompMatrix(&temp_mat2, &test_cube_obj.matrix, &test_cube_obj.matrix);
gte_SetTransMatrix(&test_cube_obj.matrix);
gte_SetRotMatrix(&test_cube_obj.matrix);
packet_b_ptr = SGM_UpdateModel(&test_cube_model, packet_b_ptr, (u_long*)G.pOt);
-
NITROYUASH Verified
- Extreme PSXDEV User
- Posts: 127
- Joined: Jan 07, 2018
- I am a: Game Designer
- PlayStation Model: SCPH-7502
- Contact:
Just one question.
Can we see what inside SGM_UpdateModel() function ? :)
Can we see what inside SGM_UpdateModel() function ? :)
- ArthCarvalho
- Active PSXDEV User
- Posts: 45
- Joined: Jan 29, 2016
- I am a: Artist, Programmer
- PlayStation Model: SCPH-103
It's a function that transforms vertices from a specific model and adds them to the ordering table.
Not optimized yet and messy due to constant format changes and idea changes and it's format specific.
I don't think it is worth looking at separately but here it is anyway:
It initializes new polygons on the fly it can be used for instancing, like the wiggling boxes I made, they are the same model.
Not optimized yet and messy due to constant format changes and idea changes and it's format specific.
I don't think it is worth looking at separately but here it is anyway:
Code: Select all
u_char * SGM_UpdateModel(SGM_MDL * model, u_char * packet_ptr, u_long * ot) {
int i;
SGM_FILE * data = model->file;
SGM_DATAHEADER * dh = data->dh;
u_short tpageid;
u_short clutid;
// Transformation Data
long p, otz, opz, flg;
int temp, temp2, temp3;
int isfront;
DIVPOLYGON4 divprop;
POLY_GT4_VERTEX * vtx_src_gt4;
POLY_GT4_TEX * tex_src_gt4;
POLY_GT4_COLOR * col_src_gt4;
POLY_GT3_VERTEX * vtx_src_gt3;
POLY_GT3_TEX * tex_src_gt3;
POLY_GT3_COLOR * col_src_gt3;
POLY_GT4 * vtx_dest_gt4;
POLY_GT3 * vtx_dest_gt3;
POLY_G4 * vtx_dest_g4;
POLY_G3 * vtx_dest_g3;
u_char * packet = packet_ptr;
divprop.pih = SCREEN_W;
divprop.piv = SCREEN_H;
divprop.ndiv = 1;
vtx_dest_gt4 = (POLY_GT4*)packet;
tpageid = getTPage(1, 0, 0,0);
clutid = GetClut(0,256);
vtx_src_gt4 = data->pgt4_vtx;
for(i = 0; i < model->file->dh->poly_gt4_count; i++, vtx_src_gt4++) {
// Initialize destination poly
setPolyGT4(vtx_dest_gt4);
// Transform destination poly
gte_RotAverageNclip3(
(SVECTOR*)&vtx_src_gt4->v0,
(SVECTOR*)&vtx_src_gt4->v1,
(SVECTOR*)&vtx_src_gt4->v2,
(long *)&vtx_dest_gt4->x0, (long *)&vtx_dest_gt4->x1,
(long *)&vtx_dest_gt4->x2, &p, &otz, &flg, &isfront);
otz = otz >> 1;
// Check side
if(isfront <= 0) continue;
// Only transform 4th vector if facing towards the view
gte_RotTransPers((SVECTOR*)&vtx_src_gt4->v3,(long *)&vtx_dest_gt4->x3, &temp2, &temp3, &temp);
// Clip quads that are offscreen
if(quad_clip(&G.screen_rect,
(DVECTOR*)&vtx_dest_gt4->x0,
(DVECTOR*)&vtx_dest_gt4->x1,
(DVECTOR*)&vtx_dest_gt4->x2,
(DVECTOR*)&vtx_dest_gt4->x3)) continue;
// If on screen, now process the rest of the primitive.
tex_src_gt4 = &data->pgt4_uv[i];
col_src_gt4 = &data->pgt4_col[i];
// Set Vertex Colors
memcpy(&vtx_dest_gt4->r0, &col_src_gt4->r0, sizeof(u_char) * 3);
memcpy(&vtx_dest_gt4->r1, &col_src_gt4->r1, sizeof(u_char) * 3);
memcpy(&vtx_dest_gt4->r2, &col_src_gt4->r2, sizeof(u_char) * 3);
memcpy(&vtx_dest_gt4->r3, &col_src_gt4->r3, sizeof(u_char) * 3);
// Set Texture Coordinates
memcpy(&vtx_dest_gt4->u0, &tex_src_gt4->u0, sizeof(u_char) * 2);
memcpy(&vtx_dest_gt4->u1, &tex_src_gt4->u1, sizeof(u_char) * 2);
memcpy(&vtx_dest_gt4->u2, &tex_src_gt4->u2, sizeof(u_char) * 2);
memcpy(&vtx_dest_gt4->u3, &tex_src_gt4->u3, sizeof(u_char) * 2);
// Set CLUT
vtx_dest_gt4->clut = clutid;
// Set Texture Page
vtx_dest_gt4->tpage = tpageid;
if (otz > 0 && otz < OTSIZE)
{
// Check for subdivision
if( otz < 600 ) {
if(otz > 300) {
divprop.ndiv = 1;
debug_face_count += 8;
} else {
divprop.ndiv = 1;
debug_face_count += 8;
}
debug_subdiv_count += 1;
// Subdivide poly
vtx_dest_gt4 = DivideGT4(
(SVECTOR*)&vtx_src_gt4->v0, (SVECTOR*)&vtx_src_gt4->v1, (SVECTOR*)&vtx_src_gt4->v2, (SVECTOR*)&vtx_src_gt4->v3,
(u_long*)&vtx_dest_gt4->u0, (u_long*)&vtx_dest_gt4->u1, (u_long*)&vtx_dest_gt4->u2, (u_long*)&vtx_dest_gt4->u3,
(CVECTOR*)&vtx_dest_gt4->r0, (CVECTOR*)&vtx_dest_gt4->r1, (CVECTOR*)&vtx_dest_gt4->r2, (CVECTOR*)&vtx_dest_gt4->r3,
vtx_dest_gt4, (u_long *)(ot+otz), &divprop);
} else {
// Add primitive to Ordering Table and advance pointer
// to point to the next primitive to be processed.
AddPrim(ot+otz, vtx_dest_gt4++);
debug_face_count += 2;
}
}
}
vtx_dest_gt3 = (POLY_GT3*)vtx_dest_gt4;
vtx_src_gt3 = data->pgt3_vtx;
for(i = 0; i < model->file->dh->poly_gt3_count; i++, vtx_src_gt3++) {
// Initialize destination poly
setPolyGT3(vtx_dest_gt3);
// Transform destination poly
gte_RotAverageNclip3(
(SVECTOR*)&vtx_src_gt3->v0,
(SVECTOR*)&vtx_src_gt3->v1,
(SVECTOR*)&vtx_src_gt3->v2,
(long *)&vtx_dest_gt3->x0, (long *)&vtx_dest_gt3->x1,
(long *)&vtx_dest_gt3->x2, &p, &otz, &flg, &isfront);
otz = otz >> 1;
// Check side
if(isfront <= 0) continue;
// Clip quads that are offscreen
if(tri_clip(&G.screen_rect,
(DVECTOR*)&vtx_dest_gt3->x0,
(DVECTOR*)&vtx_dest_gt3->x1,
(DVECTOR*)&vtx_dest_gt3->x2)) continue;
// If on screen, now process the rest of the primitive.
tex_src_gt3 = &data->pgt3_uv[i];
col_src_gt3 = &data->pgt3_col[i];
// Set Vertex Colors
memcpy(&vtx_dest_gt3->r0, &col_src_gt3->r0, sizeof(u_char) * 3);
memcpy(&vtx_dest_gt3->r1, &col_src_gt3->r1, sizeof(u_char) * 3);
memcpy(&vtx_dest_gt3->r2, &col_src_gt3->r2, sizeof(u_char) * 3);
// Set Texture Coordinates
memcpy(&vtx_dest_gt3->u0, &tex_src_gt3->u0, sizeof(u_char) * 2);
memcpy(&vtx_dest_gt3->u1, &tex_src_gt3->u1, sizeof(u_char) * 2);
memcpy(&vtx_dest_gt3->u2, &tex_src_gt3->u2, sizeof(u_char) * 2);
// Set CLUT
vtx_dest_gt3->clut = clutid;
// Set Texture Page
vtx_dest_gt3->tpage = tpageid;
if (otz > 0 && otz < OTSIZE)
{
// Add primitive to Ordering Table and advance pointer
// to point to the next primitive to be processed.
AddPrim(ot+otz, vtx_dest_gt3++);
debug_face_count += 1;
}
}
vtx_dest_g4 = (POLY_G4 *) vtx_dest_gt3;
vtx_src_gt4 = data->pg4_vtx;
for(i = 0; i < model->file->dh->poly_g4_count; i++, vtx_src_gt4++) {
// Initialize destination poly
setPolyG4(vtx_dest_g4);
// Transform destination poly
gte_RotAverageNclip3(
(SVECTOR*)&vtx_src_gt4->v0,
(SVECTOR*)&vtx_src_gt4->v1,
(SVECTOR*)&vtx_src_gt4->v2,
(long *)&vtx_dest_g4->x0, (long *)&vtx_dest_g4->x1,
(long *)&vtx_dest_g4->x2, &p, &otz, &flg, &isfront);
otz = otz >> 1;
// Check side
if(isfront <= 0) continue;
// Only transform 4th vector if facing towards the view
gte_RotTransPers((SVECTOR*)&vtx_src_gt4->v3,(long *)&vtx_dest_g4->x3, &temp2, &temp3, &temp);
// Clip quads that are offscreen
if(quad_clip(&G.screen_rect,
(DVECTOR*)&vtx_dest_g4->x0,
(DVECTOR*)&vtx_dest_g4->x1,
(DVECTOR*)&vtx_dest_g4->x2,
(DVECTOR*)&vtx_dest_g4->x3)) continue;
// If on screen, now process the rest of the primitive.
col_src_gt4 = &data->pg4_col[i];
// Set Vertex Colors
memcpy(&vtx_dest_g4->r0, &col_src_gt4->r0, sizeof(u_char) * 3);
memcpy(&vtx_dest_g4->r1, &col_src_gt4->r1, sizeof(u_char) * 3);
memcpy(&vtx_dest_g4->r2, &col_src_gt4->r2, sizeof(u_char) * 3);
memcpy(&vtx_dest_g4->r3, &col_src_gt4->r3, sizeof(u_char) * 3);
if (otz > 0 && otz < OTSIZE)
{
// Add primitive to Ordering Table and advance pointer
// to point to the next primitive to be processed.
AddPrim(ot+otz, vtx_dest_g4++);
debug_face_count += 2;
}
}
vtx_dest_g3 = (POLY_G3*)vtx_dest_g4;
vtx_src_gt3 = data->pg3_vtx;
for(i = 0; i < model->file->dh->poly_g3_count; i++, vtx_src_gt3++) {
// Initialize destination poly
setPolyG3(vtx_dest_g3);
// Transform destination poly
gte_RotAverageNclip3(
(SVECTOR*)&vtx_src_gt3->v0,
(SVECTOR*)&vtx_src_gt3->v1,
(SVECTOR*)&vtx_src_gt3->v2,
(long *)&vtx_dest_g3->x0, (long *)&vtx_dest_g3->x1,
(long *)&vtx_dest_g3->x2, &p, &otz, &flg, &isfront);
otz = otz >> 1;
// Check side
if(isfront <= 0) continue;
// Clip quads that are offscreen
if(tri_clip(&G.screen_rect,
(DVECTOR*)&vtx_dest_g3->x0,
(DVECTOR*)&vtx_dest_g3->x1,
(DVECTOR*)&vtx_dest_g3->x2)) continue;
// If on screen, now process the rest of the primitive.
col_src_gt3 = &data->pg3_col[i];
// Set Vertex Colors
memcpy(&vtx_dest_g3->r0, &col_src_gt3->r0, sizeof(u_char) * 3);
memcpy(&vtx_dest_g3->r1, &col_src_gt3->r1, sizeof(u_char) * 3);
memcpy(&vtx_dest_g3->r2, &col_src_gt3->r2, sizeof(u_char) * 3);
if (otz > 0 && otz < OTSIZE)
{
// Add primitive to Ordering Table and advance pointer
// to point to the next primitive to be processed.
AddPrim(ot+otz, vtx_dest_g3++);
debug_face_count += 1;
}
}
packet = (u_char *) vtx_dest_g3;
// return the updated packet pointer
return packet;
}
-
NITROYUASH Verified
- Extreme PSXDEV User
- Posts: 127
- Joined: Jan 07, 2018
- I am a: Game Designer
- PlayStation Model: SCPH-7502
- Contact:
Thanks for that source. It will help me when i will do some experiments with animation. c:
-
Administrator Verified
- Admin / PSXDEV
- Posts: 2691
- Joined: Dec 31, 2012
- I am a: Shadow
- PlayStation Model: H2000/5502
That cube animation system is pretty neat. Just your 3D engine alone is better then 90% of all Net Yaroze games. lol.
Development Console: SCPH-5502 with 8MB RAM, MM3 Modchip, PAL 60 Colour Modification (for NTSC), PSIO Switch Board, DB-9 breakout headers for both RGB and Serial output and an Xplorer with CAETLA 0.34.
PlayStation Development PC: Windows 98 SE, Pentium 3 at 400MHz, 128MB SDRAM, DTL-H2000, DTL-H2010, DTL-H201A, DTL-S2020 (with 4GB SCSI-2 HDD), 21" Sony G420, CD-R burner, 3.25" and 5.25" Floppy Diskette Drives, ZIP 100 Diskette Drive and an IBM Model M keyboard.
PlayStation Development PC: Windows 98 SE, Pentium 3 at 400MHz, 128MB SDRAM, DTL-H2000, DTL-H2010, DTL-H201A, DTL-S2020 (with 4GB SCSI-2 HDD), 21" Sony G420, CD-R burner, 3.25" and 5.25" Floppy Diskette Drives, ZIP 100 Diskette Drive and an IBM Model M keyboard.
- ArthCarvalho
- Active PSXDEV User
- Posts: 45
- Joined: Jan 29, 2016
- I am a: Artist, Programmer
- PlayStation Model: SCPH-103
Thanks! I hope I can get it optimized so it'll be truly usable.
I have an idea on how to implement "skeletal animation".
In modern games you'll use vertex skinning that is controlled by weights, this is definitely not feasible on hardware like the PS1, so the best option is a derivative of hierarchical animation (the one where models are made of individual parts) where you generate faces to connect the objects, for instance, suppose you have two cubes that make an "arm":

Each box in hierarchical animation has its ends closed off so the mesh has no holes in it. It gives that classic early playstation title look.
But generating a closed of, linked mesh is basically free (except for drawing and memory costs) because you already have transformed the vertices in both objects, you just need to draw one that will connect between them:




Now the question is to get Blender to cooperate when exporting like this.
When exporting every part of the model that is part of a "bone" has to be transformed to the origin of the model, resulting in a mess of parts that starts from XYZ 0,0,0 and point in the direction of it's origin. But that's more about Blender than it is about the PS1.
I have an idea on how to implement "skeletal animation".
In modern games you'll use vertex skinning that is controlled by weights, this is definitely not feasible on hardware like the PS1, so the best option is a derivative of hierarchical animation (the one where models are made of individual parts) where you generate faces to connect the objects, for instance, suppose you have two cubes that make an "arm":

Each box in hierarchical animation has its ends closed off so the mesh has no holes in it. It gives that classic early playstation title look.
But generating a closed of, linked mesh is basically free (except for drawing and memory costs) because you already have transformed the vertices in both objects, you just need to draw one that will connect between them:




Now the question is to get Blender to cooperate when exporting like this.
When exporting every part of the model that is part of a "bone" has to be transformed to the origin of the model, resulting in a mess of parts that starts from XYZ 0,0,0 and point in the direction of it's origin. But that's more about Blender than it is about the PS1.
That is a genius idea! I hope you find a way to export them correctly.ArthCarvalho wrote: ↑November 29th, 2019, 1:51 pm ...the best option is a derivative of hierarchical animation (the one where models are made of individual parts) where you generate faces to connect the objects, for instance, suppose you have two cubes that make an "arm":
...
But generating a closed of, linked mesh is basically free (except for drawing and memory costs) because you already have transformed the vertices in both objects, you just need to draw one that will connect between them:
- MrQuetch
- Active PSXDEV User
- Posts: 42
- Joined: Apr 01, 2018
- I am a: Programmer and artist.
- Motto: You can accomplish anything.
- Location: United States
I saw one of your videos earlier. I really love what you're doing with the PS1. I hope to get animated models at some point, too. This already gives a very firm foundation to getting animated models working.
Who is online
Users browsing this forum: No registered users and 4 guests