Colourfully Symmetrical (of Sorts): 3rd Parade of Hopefuls

April 30th, 2020

This week's work focused on the required additions regarding both skeleton generation and hopeful presentation: introducing symmetry (of sorts, given the collapsing of points otherwise) along one plane or along two perpendicular planes and then making the resulting hopefuls pose for the camera in 3 positions as well as trying on various skins. The result is that I had to leave it running during the night to generate all the screenshots for the first 50 skeleton seeds and the resulting pile of files is so huge that I'll just link it for download so as to not force everyone loading the page to load them all. After all, it's 50 skeleton seeds * 2 symmetry options (1 plane or both planes) * 3 poses * 11 textures, making 3300 screenshots and more than 1G total size to go through, just for this!

For future reference and for clarity otherwise, here's what I implemented/changed this week to the whole orchestra:

  1. Mirroring and collapsing of points: in the skeleton generation script, after obtaining the pseudo-random points both in the unit sphere and on its surface, I calculate the longest segment between 2 points and then the plane that contains that line as well as the origin (i.e. the centre of the unit sphere). While the original statement was a plane aligned parallel to the longest segment, some choice had to be made as to *which* plane exactly and the one going through the origin seems the most logical choice to me in this situation. At any rate, if it should be some *other* plane parallel to this, the equations are in the code and can be changed accordingly, not a huge deal. After this is done, all points are mirrored in this plane. If the 2nd plane is to be used as well, this 2nd plane is calculated to be perpendicular on the first and still containing that original longest segment. There was again a decision I had to make here as to *which* perpendicular plane - I considered that the idea was to use that original longest line as a sort of axis of symmetry, hence chose it as intersection of the two perpendicular planes. Once this 2nd plane is obtained, the *original* points (so NOT the ones already mirrored) are mirrored into this plane too1. Then the average distance between any 2 points is calculated. Working always with closest 2 points, the algorithm will collapse them - replacing the 2 points with only one point that is exactly in the middle between the 2 (on all 3 directions). This collapsing is repeated until either 2/3 of the points (as counted immediately after the mirrorings) were collapsed this way or no pair of points is found to have the distance below the original average. Note that the average is not updated after collapsing points (as per previous requirements) but the collapsing is taking into account when finding the next pair of points for collapsing (i.e. with smallest distance between them).
  2. Centering the whole model in the originThe previous version of the skeleton generation script translated at the end the whole skeleton so that the resulting creature "stands on the ground" - meaning that the lowest y coordinate was guaranteed to be exactly 0. This helped when loading the model in Eulora's client, since that's roughly the client's expectation2 but the client being what it is, it's of course entirely NOT worth it to play by its rules AT ALL and AT ANY TIME. (I'm shouting here at my future self, maybe I finally remember this upfront so as to not even try anymore at all.) As I needed to rotate the models around x, y and z axes, I discovered that there is no way to do that around the *model's own axes* so as to have it really just rotated but staying in place. The transformations that supposedly work to actually adjust the model's own coordinates are called "hard transforms" in cs parlance but they turn out to be ...not available for Cal3d models! And otherwise getting the transformation "from world coordinates to model coordinates" and piling there translation, rotation and back translation to achieve the same thing is an exercise in maximum frustration because there's no reliable way to get the *exact* centre point of a loaded model (so as to translate it to make sure that it's a rotation around that point basically as opposed to some ever-so-slightly off-centre point that will throw the rotated model all over the place). Having tried ~everything to make do with what the client offers on this, I got not only all the experience I never wanted with all the different ways to attempt this at the intersection between CS, Cal3d and PS but also additional insight into client's idiotic behaviour. Basically the PS approach to "we required models on ground for convenience and this fucks rotations around anything other than y axis because it will go all over the place" is to "oh, it's fine, we can therefore fuck also rotations - we won't do any other than around y axis". Moreover, I found out that when "looking at" something, the client as it is now ignores the y coordinate because otherwise it's not made well enough to tilt the creature's *head* as opposed to the whole body - so if the thing you are trying to look at is taller/higher up than your character, you end up looking at their feet or something, not at all at the point you thought you'd look at; if, on the other hand, you force the proper "look at", your character gets tilted and basically can't walk properly anymore. Those wonderful findings aside - and under a pile of assorted cussings - the sane solution is therefore to simply centre the model in its own origin *outside* of the bloody client and then let the client work with that, as it can. For the task at hand, this went then (finally!) as it should have gone from the start - quick and easy, rotations work as they should, the thing stays in place and given that its maximum size is anyway known to be 2 at most (remember that unit sphere), it's enough to render the whole thing a bit higher off the ground and force that cally to stare exactly at it, tilted if she can't in any other way. Obviously, this also meant bypassing all the PS cruft and using directly CS methods for ~everything involved.
  3. Pose parameter for client - the smallest change to the client's code that I could see so that I can show properly the hopeful in the desired poses was to add a command line parameter indicating the pose (by number). Based on that, the hopeful will then be rotated (or not) and shown at the same place. Basically this change was minimal and works perfectly fine for the purpose.
  4. Updated overall script: since every hopeful is to be shown in several poses and wearing several textures, I updated the controlling script that runs the whole orchestra so that it has 2 additional loops to go through after generating each and every hopeful - the first loop iterates through the poses (so only 1 to 3 for now, but easily extended otherwise); the second loop goes through all available textures (assuming they are conveniently called tex_1.png and so on), copies the current one to where the client expects the "default texture" and then fires up the client to take the screenshot. Obviously, the names used for the screenshots now carry all this information too, to keep them reasonably ordered otherwise.

Other than the above changes, the rest of the time went more into struggling with the client's various idiocies so it gets counted at "experience earned" rather than anything else. One thing that starts getting annoying too is that the client "saves resources" by refusing to render if its window looses focus - while this might be ok when playing the game, it starts getting on my nerves when just rendering hopefuls since it effectively means that the machine can't be used for something else at the same time. At any rate, if it gets truly annoying, I'll probably just hunt for that bit of "saving" and comment it out for now, but so far I didn't spend the time on this. There are still the ideas and things I wanted to implement from last week but didn't get around to - as I focused on adding the symmetry and then fighting with the uncooperative client.

As to the results themselves, here are first of all the 11 textures that each hopeful in this round tries on:
tex_1.png
tex_2.png
tex_3.png
tex_4.png
tex_5.png
tex_6.png
tex_7.png
tex_8.png
tex_9.png
tex_10.png
tex_11.png

The full set of screenshots is available directly here with filenames including the seed used for the skeleton, the number of symmetry planes (1 or 2), the texture number (see above the set of textures, if you want to find some specific one) and the number of the pose (pose 1 is without any rotation so as generated; pose 2 is a rotation of 90 degress along x, y and z axes; pose 3 is a rotation of 135 degrees around x,y and z axes). All the hopefuls this time start with points in the sphere between 3 and 5, points on the surface between 5 and 13, so you can compare them with the previous run with those same parameters before introducing symmetry. Finally, here are a few shots shown here too, for those that simply want to load this page and not go otherwise through all the 3k+ pictures:
skel_1_sym_1_tex10_pose1_640.png

skel_1_sym_1_tex11_pose1_640.png

skel_1_sym_1_tex1_pose1_640.png

skel_1_sym_1_tex2_pose1_640.png

skel_1_sym_1_tex3_pose1_640.png

skel_1_sym_1_tex4_pose1_640.png

skel_1_sym_1_tex5_pose1_640.png

skel_1_sym_1_tex6_pose1_640.png

skel_1_sym_1_tex7_pose1_640.png

skel_1_sym_1_tex8_pose1_640.png

skel_1_sym_1_tex9_pose1_640.png

skel_5_sym_1_tex10_pose1_640.png

skel_5_sym_1_tex11_pose1_640.png

skel_5_sym_1_tex1_pose1_640.png

skel_5_sym_1_tex2_pose1_640.png

skel_5_sym_1_tex3_pose1_640.png

skel_5_sym_1_tex4_pose1_640.png

skel_5_sym_1_tex5_pose1_640.png

skel_5_sym_1_tex6_pose1_640.png

skel_5_sym_1_tex7_pose1_640.png

skel_5_sym_1_tex8_pose1_640.png

skel_5_sym_1_tex9_pose1_640.png

skel_5_sym_2_tex10_pose1_640.png

skel_5_sym_2_tex11_pose1_640.png

skel_5_sym_2_tex1_pose1_640.png

skel_5_sym_2_tex2_pose1_640.png

skel_5_sym_2_tex3_pose1_640.png

skel_5_sym_2_tex4_pose1_640.png

skel_5_sym_2_tex5_pose1_640.png

skel_5_sym_2_tex6_pose1_640.png

skel_5_sym_2_tex7_pose1_640.png

skel_5_sym_2_tex8_pose1_640.png

skel_5_sym_2_tex9_pose1_640.png

skel_37_sym_2_tex10_pose1_640.png

skel_37_sym_2_tex11_pose1_640.png

skel_37_sym_2_tex1_pose1_640.png

skel_37_sym_2_tex2_pose1_640.png

skel_37_sym_2_tex3_pose1_640.png

skel_37_sym_2_tex4_pose1_640.png

skel_37_sym_2_tex5_pose1_640.png

skel_37_sym_2_tex6_pose1_640.png

skel_37_sym_2_tex7_pose1_640.png

skel_37_sym_2_tex8_pose1_640.png

skel_37_sym_2_tex9_pose1_640.png

skel_47_sym_2_tex10_pose1_640.png

skel_47_sym_2_tex11_pose1_640.png

skel_47_sym_2_tex1_pose1_640.png

skel_47_sym_2_tex2_pose1_640.png

skel_47_sym_2_tex3_pose1_640.png

skel_47_sym_2_tex4_pose1_640.png

skel_47_sym_2_tex5_pose1_640.png

skel_47_sym_2_tex6_pose1_640.png

skel_47_sym_2_tex7_pose1_640.png

skel_47_sym_2_tex8_pose1_640.png

skel_47_sym_2_tex9_pose1_640.png


  1. I interpreted it this way because of the collapsing of "between 1/3 and 2/3" of the points - basically the 2 mirrorings applied to the original points only triple the total so collapsing that many at the end makes some sense. Again, if it was something else specifically wanted here, it's quite easy to adjust - if anything, it's the rendering and screenshooting that takes significantly longer than any number fiddling. 

  2. Roughly, because otherwise and in the infuriatingly idiotic style known by now as PS signature approach, there is further a "leg length" defined for each "race" so as to adjust afterwards this on ground ever so here-and-there-just-to-make-something-to-do.