how to smear six squares in to one sphere.
#Method A “two at a time spinning trick, plus KISS corner trick”:
.
- write a left-right smearer, we’ll call it LRS( L:SquareImage, R:SquareImage )
it joins the right edge of L with the left edge of R going in say one inch
-
write a turns 90 degrees CW, we’ll call it TCW( X:SquareImage )
-
we do this …
// smear using EZ spinning trick
LRS( front, right )
LRS( right, rear )
LRS( rear, left )
LRS( left, front )
LRS( top, right )
TCW(top)
LRS( top, front )
TCW(top)
LRS( top, left )
TCW(top)
LRS( top, rear )
TCW(top)
LRS( bottom, right )
TCW(bottom)
LRS( bottom, rear )
TCW(bottom)
LRS( bottom, left )
TCW(bottom)
LRS( bottom, front )
TCW(bottom)
Production note at this stage for unit testing, do just the first four above, and see results on screen. the heightmaps should mate around the girdle of the sphere for the four sides. this checks that the LRS is working fine. of course the top and bottom will meet the four sides in a mess.
the “spin them” trick is easier than writing a smearer that annoyingly has to be able to do any two edges
now they will all meet, other than the last inch of the ones that were done early in the cycle because those would have been disturbed buy the later ones pointing the other way around that corner.
So. “but first”…
Point (-2) write a routine CornerAdjustor(SquareImage) which looks at the top right 1" square of the squareimage…
We want the extreme top right point (TRP) to be 0.625. let’s say the interesting difference (ID) of the TRP from 0.625 is “+0.137”, so ID is +0.137 in the example
for each pixel in the small dotted 1" square. find the mysterious distance (MD) to TRP. if MD is over 1" forget it. otherwise, at that point subtract ( (1.0-MD) * ID )
[footnote] acute readers will notice that ID is the only place we utilise two dimensional distance, so in fact that innocent line of code is the platonic heart of the “two ways how???!” smearing problem.
Point (-1) so do this for each of the six sides
CornerAdjustor( s )
TCW(s)
CornerAdjustor( s )
TCW(s)
CornerAdjustor( s )
TCW(s)
CornerAdjustor( s )
TCW(s)
Point (0) because each “last run of pixels” now all end at 0.625 on both ends… so imagine we are about to smear a NS edge using LRS(). the top and bottom of that NS line (first and last pixels) are indeed 0.625. say you smear inwards two inches on each side (ie, when you run horizontally, we penetrate two inches on either side, let’s say). so, when you do the first or final line, in fact, it will, indeed, already meet (indeed at 0.625) so actually nothing, whatsoever, will happen with the first and last (top and bottom) two-inch runs. (the next runs in will be slightly adjusted, and ever more so as you move more inwards from the top and bottom.) So now, one can go ahead and run points (+) 1, 2, 3, 4 above. they will sort of “magically” mesh perfectly at the corners, due to points -2, -1 here.
Now that’s all very well, I’ve realised there’s a more elegant way (but not KISS) to do it. I believe the above is the KISS solution.
Note that, of course, instead of using “0.625” simply find the average of all twenty-four corner pixels of the six square images, and use that instead of 0.625.
here’s a more elegant but not KISS (“MEB-NOKISS”) way to do it:
#Method B “MEB-NOKISS”:
.
(Point -3) write a routine Half Edge Lines Two Step LinearAdder HELTSLA
HELTSLA( SquareImage, newT:float, newA:float, newB:float )
notice the square in the top diagram. notice the last half-line of pixels from a to T and the last half-line of pixels from b to T
aT now starts at grey value a and ends at grey value T. add a line to it so that it runs from a to newT
same, add a line to bT so it runs from b to NewT
now in the other direction add a line to newTa so that it runs from newT to NewA
same, for newTb, add a line so it runs from newT to newB.
do the to-newT direction first and then do the other direction afterwards
by “add a line” I mean literally add a mathematical line slope x distance, so add “0” to the first pixel increasing linearly to the max delta so that the end becomes the new desired value.
(Point -2) note that HELTSLA operates only on the top-right corner. write a simple wrapper (use TCW from the other reality above) which allows HELTSA to operate on either: top right, top left, or bottom right corner. To achieve this, use the spinning trick explained in the above unrelated method. unit test. don’t forget to spin it back after the operation!
(Point -1) write a similar trivial helper routine that does this: look at the diagram aTb. the helper routine very simply returns the gray values at a, at T, and at b. again, you have to be able to tell it to “think” using either the top right, top left, or bottom right corner. again use the spinning trick. unit test. don’t forget to spin it back after the operation!
So we’ll call that HELValueGrabber()
(Point 0) create a routine that takes three square images and processes them. We’ll call it, um PROTHREE
PROTHREE( A, B, C: SquareImage )
Notice the three squares A B C. Notice the six half-edges 1 2 3 4 5 6
(NOTE - the term “half edge” is a term of art when dealing with 3D mesh. there is utterly no connection here. I just mean it happens to be “half of” the last line of pixels in a square image!!!)
notice all six halfEdgeLines have a Beginning and an End.
all six Ends are simply the one point in the middle. Using HELValueGrabber three times, simply get the average of that point. Let’s say it is 0.24715.
Note that your three calls will look something like this:
HELValueGrabber( A, bottom-right );
HELValueGrabber( B, top-left );
HELValueGrabber( C, top-right );
So the new END value for all six halfEdgeLines is 0.24715
For the new BEGIN value for both 1 and 2, simply get the average between the old BEGIN values for 1 and 2. identically for 3,4 and then for 5,6
To repeat 1 and 2 have identical newBegin values, 3,4 have identical newBegin values, and 5,6 have identical new newBegin values. All six simply have the same newEnd value
Now, actually run HELTSLA using those values. Your code should look like this
HELTSLA( A, bottom-right, newBegin1,newEnd,newBegin6 );
HELTSLA( B, top-left, newBegin3,newEnd,newBegin2);
HELTSLA( C, top-right, newBegin5,newEnd,newBegin4);
(to repeat, newBegin1 and newBegin2 are the same number)
(Personally, I would program that to look like this: “Do Heltsa Calculating Values From These Two Wings On My Left And Right”, so that PROTHREE would contain only three lines of code, but that’s irrelevant.)
(Point 1) so to recap. in the diagram A B C, in fact the halfEdgeLines 1 2 3 4 5 6 are now DONE, they have the final grey values they will ever have.
So now, look at C. Notice the quarter at the top right. We now have to modify the grey values in that top right quarter. So we’ll make a routine MODQUARTER( X:SquareImage )
Of course, you must have ALREADY run PROTHREE entirely (on all three) before you run MODQUARTER on any one of them or it is meaningless. (Many good algorithms are temporal.)
So what the hell does MODQUARTER do? Look at the next small diagram of the Quarter in question. Take any point P inside the Quarter. You don’t have to do the edges (5 and 4) as they are done.
Get the distance DDDDD of P from the home corner at the top right. If DDDDD is bigger than the length of 5/4, forget about P and do nothing to that pixel.
Get the angle Theta. say 22 degrees. convert to a fractional angle FFFFF of closeness from the line 5. so that would be ( 1.0 - 22/90 ) == 0.8712 FFFFF
Get the pixel, on line 5, distance DDDDD away from the home point. get GrayDelt, the delta between the grey there and at P.
For the pixel at P, add to its gray value: ( GrayDelt * FFFFF )
(To be clear - if P is just about touching 5, it will more or less totally force P to have a value almost the same as that value on 5. If P is further away, it will strongly push P towards that value. if P is quite far away, it will nudge P a little but towards that value.)
Now do the same thing … going the other way towards 4.
{Aside - you might wonder, what the hell happens to the omitted slice? isn’t there the possibility of an abrupt change? NO - because we already smeared them in a certain way earlier, precluding any abrupt changes now.}
(Point 2) As usual, make MODQUARTER understand that it can do either the topright, bottomright or topleft quarter of a SquareImage. And then do this
MODQUARTER( A, bottom-right );
MODQUARTER( B, top-left );
MODQUARTER( C, top-right );
(Point 3) Looking at the diagram A B C. Imagine a cube where indeed A is the top face, C is the front face and B is the right face. Construct a Grand Routine…
DoThisPointySection( A, B, C: SquareImages )
where indeed A B C are oriented as in the sketch above and as in the discussion above. Now get very very sober to do this:
DoThisPointySection( top, right,front)
TCW(top)
DoThisPointySection( top, rear,right)
TCW(top)
DoThisPointySection( top, left,rear)
TCW(top)
DoThisPointySection( top, front,left)
TCW(top) // (returns it to normal!)
Now unit test and you will see the top half of the sphere mate smoothly and beautifully.
Note that those four lines of code do all the “top quarters” of the four side faces. (That’s why each side face gets called twice in those four lines of code.)
Now do this TCW twice for all the side faces, then do the same four lines of code using the bottom face instead of the top face, and that will do the bottom. (in other words, turn the cube profoundly upside down, and do the same thing again.)
Again … the alternative to all this spinning, is, you would have to write an absurdly complicated DoThisPointySection routine that has arguments along the lines of … DoThisPointySection( A, which quarter, B, which quarter, C, which quarter ) rather than our elegant DoThisPointySection( A B C ).