Warren County Rocks Scavenger Hunt
Today was a bit of a fun day. After meeting the team at the PaveMint Smokin’ Taphouse, I participated in a scavenger hunt. Our team raised $125 for the Warren Coalition. We shot basketball hoops, sprayed water to knock over cups, put together puzzles, solved riddles, used a pool noodle as a javelin to toss through a hula hoop, miniature golf with unconventional equipment, and played with a giant tower of Jenga blocks. There was an assortment of goodies like cake, cookies, and sandwich wraps. I got a few rocks out of it and put them in the house of rock & roll under my Little Free Library for the neighborhood kids.


Little Free Library #149354
Image Conversion
I left off yesterday having problems converting a TGA image into PNG. In one case, an online converter resized the original 64×64 image to 178×178. The images need to be a size of base 2 (16, 32, 64, 128, 256, 512, or 1024) Another online converter retained the 64×64 image dimensions, but applied dithering – causing the embedded data to be corrupted. I’ve learned that you can’t trust an online editor to preserve the exact details of an image. The first thing that I need to do today is to convert the TGA image to PNG, preserving both the image size and original colors for each pixel.
It sort of irks me that the Author of the Rokuro/Tatara software isn’t offering an image format native to most operating systems (PNG or BMP). JPEG is lossy, so it’s pretty obvious why that isn’t offered. GIF can preserve the original color values, so long as you don’t have more than 256 unique vector positions. Still, the fallback should be either PNG, or BMP. I haven’t worked with TGA files with anything in the past except for Second Life and medical imaging.
I downloaded Gimp and did the conversion from TGA to PNG on my local computer. Gimp is similar to Adobe Photoshop and almost just as old. If you can’t afford Photoshop, then Gimp is often the tool to use. It’s got a different layout, so it’s a bit awkward trying to find the tools and functionality if you are familiar with Photoshop. Anyhow, here is the latest, expanded to reveal the lack of dithering:

Model Comparison
Did it work? Mostly. The edge of the bottom brim is fixed, and the inside walls are vertical. I’m still having odd behaviors in how the center of the object overlaps faces on the inside of the hat. The top of the hat still has its center on the edge of the hat rather than the center.








The question now is – it is the image itself, or am I reading the image incorrectly. Looking closely at the image, I see a dot that is separate from all the others, and it is not in the center of the image.

It’s time to check out the sculpted prim within Second Life to confirm the geometry. And rite off the bat, the viewer has another update (7.1.6.8745209917 – Maintenance-YZ). Maybe the developer is more active in keeping their software up to date than I had expected. Since I normally don’t see anyone else logged in, I often get the feeling like its abandonware that’s not maintained, our about to go offline any day. But surely enough, the developers are active.

This process of having to log out of Second Life and back in, just to see a Sculpted Prim that I just uploaded is getting on my nerves as well. I tried teleporting to another location and coming back, but the object was still invisible.
Looking at the object in Second Life’s Wireframe mode Develop > Rendering > Wireframe, I’m able to see that the center of the hat is at the center. Of course, it’s easier to see with the alignment texture.


Looking at the inside of the hat, I’m seeing the same behavior of faces fighting with each other overlapping the center set of faces.

Overlapping faces
This confirms that everything appears to be correct, except for the top pole. Lets take a look at our model in the web page with the point cloud.

Still confirmed. I’m not seeing any points in the center of the hat. It may be time to take a brute force approach to reading the X coordinate of the top pole. It’s currently reading 0x32 as the center pole.
It seems that brute force had no effect. I tried every pixel from 0 to 63 and did not see any change. I’m wondering if the model was saved in the opposite order. Maybe I’m considering the wrong value for the last row. Even changing the center of X for the last value appears as if nothing is changing. I may need to run down to the basement and see if tatara lets me view the data for individual points. If I can get the xyz values or image xy placement for the first/last pole, I can use that to verify that I’m using the correct information.
Last Y
On second thought, Looking at the alignment maps, it looks like it is reading the center of the image for the second row. It’s exactly at the center of the image as indicated by the thick red vertical line in the texture. The problem may actually be in getLastImageDataY. For a 64×64 image, it’s returning 62. Let’s override it to be 63.


Well would you look at that? I was off by one. Why is that?
function getLastImageDataY(options) {
return 63; // Override
const yMod = Math.pow(2, options.vDown + 1);
return options.height - yMod;
}
| Value | Formula | Eval | Notes |
|---|---|---|---|
| vDown | options.vDown | 0 | vertical downsampling |
| height | options.height | 64 | image height |
| Math.pow(2, vDown + 1) | Math.pow(2, 0+1) | 2 | 2 to the power of 1 = 2 |
| height – yMod | 64 – 2 | 62 | Last Y |
| 63 | Correction |
Now we know that the pole at the top of the hat is from the bottom row. Good to know. I need to re-evaluate my logic to determine the proper formula to find y for the last row for any given image height – mainly 16, 32, 64, 128, 256, 512, and 1024. This also affects the density maps translation/scaling of where to draw between X/Y coordinates for each vector.
I’m wondering if the answer is as simple as adding 1. I need other image sizes to confirm.
I headed on down to the basement again to create some sample images. Something very simple and basic. I created a sphere. Rokuro by default sets you up with a net of vertices that are 32×32. However, you can also change to 16×64 or 64×16 to get more detail along the profile of the image, or more sides as it spins your path around the center axis to make it appear more rounder. I created a simple sphere.

Grid: 64×16
Size: 256×64

Grid: 32×32
Size: 128×128

Grid: 16×64
Size: 64×256

Grid: 64×16
Size: 256×64

Grid: 32×32
Size: 128×128

Grid: 16×64
Size: 64×256
Let’s start evaluating the models and see what issues we are running into. The first thing that I see is that the top pole begins with cell number 2. I don’t see any object starting with A1 to AE1. I need to confirm if Rokuro doubled up on the top pole. I could always upload the files to Second Life and confirm.



The Second Life client displays the first row of cells in the alignment grid for all three sizes. This means it’s not the model – its me. I’m not reading the first full row of vertices correctly. Somehow I’m off by one row, or reading them all as the center pole.




It’s good to see the last cell on the bottom pole ends with 32. It is concerning that the numbers in each cell increment by 2. Here we see cells G28, G30, and G32 aligned vertically. The original model may have doubled the point, or the data may have been read from the wrong pixel.

This is why I made multiple images. The last row on the 128×128 sphere tends to meet at one spot on the equator. I believe this is a problem with the function to getLastImageDataY. It’s hard-coded to return 63. Now that the image size has doubled, it’s reading a pixel at the equator.





Checking the bottom of the 256×64 sphere in Second Life, the cells do not jump by 2 values. The evenly go from 28, 29, 30, 31, and 32.
It appears that I may be reading the same row values for every other row.
So let’s get a simplified list of whats going on.
- Bottom pole is hard-coded to 63
- Wide images are doubling up on row vertices
- Cells on top pole start at A2 to AE2 instead of A1 to AE1
Let’s work on that bottom pole, since that is what we were in the middle of addressing before finding the additional issues. It feels like the bottom pole should always be the last pixel (ie image height – 1). Why did I introduce exponents?
function getLastImageDataY(options) {
return options.height - 1;
}
That seems to have worked just fine for all three spheres. What I’m seeing now is that the last row of cells appears to be twice the height as all other cells on all three spheres, as of their is a missing row of vertices.

All points along the path were distributed evenly. This could be related to issue #3 where the cells at the top row start at A2 instead of A1. We have a missing row of vertices somewhere. Given the appearance of that top pole – the missing row is sitting at the pole itself. Looking at the density map of a sphere overlaid with the wireframe of the model, this is probably why our first circle from the center doesn’t match up. It immediately presents itself as a problem.

Actually, I think I’m wrong on that. The density map is based on … hmm. Too much maths and geometry going on in my head trying to evaluate the spacial relationship of the poles. There is something pretty wild going on with the top & bottom poles of the spheres UV maps, but we’ll get to that after I tackle the problems with building the models geometry.
Back to this hidden mismatched row. From what I’ve seen, the rokuro models are built in the opposite vertical direction. So the pixels of the vectors for the top row of the models geometry are actually located at the bottom of the image. But… no. That getLastDataImageY was affecting vectors at the bottom pole – not the top. Is this the vertical offset people popping its head up again?
It looks like we are currently getting some mismatch errors on the console translating between index values to rows/columns, and back again.
mismatch 0x125 to 993 (row: 32, col: 16) - got 64x127 index 994 out of range. Max 993 ... index 1025 out of range. Max 993 expected 994 pieces of data - got 1026
The first error is soft of a pseudo error. It’s not really looking at an index. Instead its looking at options.mapping.dataCount and assuming the value is out of range. The question is – is the value dataCount wrong? Let’s evaluate.
const MAPPING_TYPE = {
spherical: {
// horizontal pole - 32
// horizontal - 0, 2 ... 60, 62, 0
// vertical - 0, 2, ... 60, 62, 63
x: 32,
y: 33,
yHasPoles: true,
xStitched: true,
yStitched: false,
dataCount: (32 * 31) + 2,
vectorCount: 33 * 33
}
}
| Image Width | 128 |
| Image Height | 128 |
| Vectors per standard row | 32 |
| Vectors per pole | 1 |
| Row Count (non poles) | 31 |
| Pole Count | 2 |
| Formula | (row count * vectors per row) + (pole count * vectors per pole) |
| Numbers | (31 * 32) + (2 * 1) |
| Multiplied | 992 + 2 |
| Total Vectors | 994 |
The data is correct. Here’s something – the function to evaluate isXyVertex returned more values than expected.

Let’s take a look at that function.
function imageXyIsImageData(x, y, options) {
if(y === 0) return x === Math.floor(options.width / 2);
const yMod = Math.pow(2, options.vDown + 1);
const xMod = Math.pow(2, options.hDown + 1);
const lastY = getLastImageDataY(options);
if(y === lastY) {
return x === Math.floor(options.width / 2);
} else if(y > lastY) {
return false;
}
if(y % yMod !== 1) return false;
if(x % xMod !== 0) return false;
return true;
}
And here are the pixels it choose as being valid.
| X | Y |
|---|---|
| 64 | 0 |
| 0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, 60, 64, 68, 72, 76, 80, 84, 88, 92, 96, 100, 104, 108, 112, 116, 120, 124 | 1, 5, 9, 13, 17, 21, 25, 29, 33, 37, 41, 45, 49, 53, 57, 61, 65, 69, 73, 77, 81, 85, 89, 93, 97, 101, 105, 109, 113, 117, 121, 125 |
| 64 | 127 |
We have 32 rows of Y that are not poles. So here is the question. Which Y row is wrong? 1? 125? The Technical Explanation of Sculpted Prims. Only gives an example of a 64×64 image and doesn’t give details on increased dimensions. Now – along those lines, the technical explanation says that for a 64×64 image, Row 1 starts on pixel 2. Why am I starting on pixel 1 for a 128×128 image?
Well, it appears that I’m starting on pixel 1 of a 64×64 image as well. Uploading a cube where unused pixels are blacked out, the viewer recognizes the image just fine – Except for one of the coordinates in the first row…



Uploading spheres with the Second Life client will not reveal anything. If a sculpted prim image doesn’t have valid data points – I would still see a sphere if all points map to <0, 0, 0>. I need to create some cubs in the larger image size to confirm in the viewer that the correct Y coordinates are being masked for the larger images. However, that wont tell me which one is incorrect – unless its all of them. Wait – I can save the cube as a larger image size now.

Uncaught TypeError: Cannot read properties of undefined (reading ‘dataCount’)
That’s unfortunate. Let’s see where I went wrong. Ah, I see. I’ve been making changes to how options are constructed, I had never updated the exportImage function accordingly. Here is the original code with a fix
const options = getModelReadOptions({width,height})
/*
const options = {
width,
height,
hDown,
vDown
};
*/


As expected, the preview of the black cube in the Second Life viewer displayed a sphere since it was only reading the black pixels as <0, 0, 0>. My x/y coordinates are off. Hopefully it’s just the Y coordinate.
There are two functions that I need to work with: indexOfImageDataToImageXy and imageXyIsImageData. These two functions are responsible for reading and writing to the correct pixels.
function indexOfImageDataToImageXy(i, options) {
const { row, column } = indexOfImageDataToRowAndColumn(i, options);
const powX = Math.pow(2, options.hDown + 1);
const powY = Math.pow(2, options.vDown + 1);
let x = column * powX;
let y = row * powY;
if(row === options.rows) {
y = getLastImageDataY(options);
} else if(row !== 0) {
y -= powY - 1;
}
return { x, y };
}
function imageXyIsImageData(x, y, options) {
if(y === 0) return x === Math.floor(options.width / 2);
const yMod = Math.pow(2, options.vDown + 1);
const xMod = Math.pow(2, options.hDown + 1);
const lastY = getLastImageDataY(options);
if(y === lastY) {
return x === Math.floor(options.width / 2);
} else if(y > lastY) {
return false;
}
if(y % yMod !== 1) return false;
if(x % xMod !== 0) return false;
return true;
}
Usually when something is off – it’s the Y coordinate. And… yes. It was the Y coordinate. I finally got a dotted texture to upload into the Second Life client. Oddly enough, the cube was tiny. I had to inflate its height it to four times the size get it to be close to the standard 0.5 meter cube. Its width and length were increased to 2.82 meters.


Something is definitely off with the size. Is my image the problem, or is it a problem with the asset server converting the image to JPEG2000. We can download the image from the asset server as a PNG file and see what our web page displays.
It’s the same exact data… same sized cube.

The good news is that we have a working list of coordinates for 128×128 images that the Second Life client recognizes.
| X | Y |
|---|---|
| 64 | 0 |
| 0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, 60, 64, 68, 72, 76, 80, 84, 88, 92, 96, 100, 104, 108, 112, 116, 120, 124 | 3, 7, 11, 15, 19, 23, 27, 31, 35, 39, 43, 47, 51, 55, 59, 63, 67, 71, 75, 79, 83, 87, 91, 95, 99, 103, 107, 111, 115, 119, 123 |
| 64 | 127 |
Curiosity is starting to get the better of me. Even though we start facing lossy compression above 128, I wonder if I can get the cube to render with a 256, 512, or 1024 square image?
More weird behavior. I went to verify a 64×64 cube still worked with different offsets. Logging back onto the grid, I found that my original “micro cube” was now a giant cube!

I resized it down to a half meter in all dimensions and it returned to a normal size. The more that I work with sculpted prims, the more unstable they feel in the viewer. I don’t like the idea that I have to log out, and back in again just to see the sculpted prim render. Now that I can’t trust the size when I do log back in just adds another wrench in the whole process of verifying my models render in the same way as the viewer.
For the 64×64 coordinates, this is what the viewer is compatible with:
| X | Y |
|---|---|
| 32 | 0 |
| 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62 | 1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31, 33, 35, 37, 39, 41, 43, 45, 47, 49, 51, 53, 55, 57, 59, 61 |
| 32 | 63 |
I tried my luck with a lossy 256×256 sculpted prim. He we are with the micro prims again…

You can see the effect of the lossy compression as the top of the prim does not have a strait horizontal edge. For 256×256 images, I used the following coordinates.
| X | Y |
|---|---|
| 128 | 0 |
| 0, 8, 16, 24, 32, 40, 48, 56, 64, 72, 80, 88, 96, 104, 112, 120, 128, 136, 144, 152, 160, 168, 176, 184, 192, 200, 208, 216, 224, 232, 240, 248 | 7, 15, 23, 31, 39, 47, 55, 63, 71, 79, 87, 95, 103, 111, 119, 127, 135, 143, 151, 159, 167, 175, 183, 191, 199, 207, 215, 223, 231, 239, 247 |
| 128 | 255 |
I went for 512×512. It looked fine uploading. When I logged out and back in, I only saw a ball. My last 256×256 sculpted prim is still on a micro scale. What’s even worse is that as I zoom in on the 512×512, the level of detail (LOD) should increase, but the prim disappears!

Maybe it’s a viewer issue. Let’s try Phoenix Firestorm… Nope! The 256×256 micro prim is there and the 512×512 prim isn’t visible from any distance. I am able to select it, but its center appears to be way off compared what renders. It appears that maybe all of the points rendered as <0, 0, 0> with just the black background. The object looked fine in the preview window before uploading.


Here are the coordinates that I’m reading for the 512×512 image:
| X | Y |
|---|---|
| 256 | 0 |
| 0, 16, 32, 48, 64, 80, 96, 112, 128, 144, 160, 176, 192, 208, 224, 240, 256, 272, 288, 304, 320, 336, 352, 368, 384, 400, 416, 432, 448, 464, 480, 496 | 15, 31, 47, 63, 79, 95, 111, 127, 143, 159, 175, 191, 207, 223, 239, 255, 271, 287, 303, 319, 335, 351, 367, 383, 399, 415, 431, 447, 463, 479, 495 |
| 256 | 511 |
The last image to try out is 1024×1024, as that is the largest that the asset server supports. Even though the viewer is still having trouble rending both the 256 and 512 images, let’s go ahead and get the coordinates, confirm the preview works, and take a chance on rezzing it in-world on the grid.
Before we move on, I just noticed that my 64×64 cube looks fine for the alignment grids top row starting at 1 and the bottom rows ending at 32. The Y data coordinates have changed with the modifications that I have been making.
| X | Y |
|---|---|
| 32 | 0 |
| 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62 | 1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31, 33, 35, 37, 39, 41, 43, 45, 47, 49, 51, 53, 55, 57, 59, 61 |
| 32 | 63 |
Here are the coordinates for the 1024×1024 sculpted prims.
| X | Y |
|---|---|
| 512 | 0 |
| 0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 480, 512, 544, 576, 608, 640, 672, 704, 736, 768, 800, 832, 864, 896, 928, 960, 992 | 31, 63, 95, 127, 159, 191, 223, 255, 287, 319, 351, 383, 415, 447, 479, 511, 543, 575, 607, 639, 671, 703, 735, 767, 799, 831, 863, 895, 927, 959, 991 |
| 512 | 1023 |
Hat Tricks
With all of that… is our hat rendering correctly now? YES! The center of the top of the hat is at the center. The faces look like they end on 32. Looking at the reference spheres… Yes! We start from A1 on the top row all the way down to A32. Even our density sphere looks uniform at both the top and bottom of the sphere.


I think at this point, I can safely say that I’ve identified the exact data pixels used for 64×64 to 1024×1024 sculpted prims. Odd aspect ratios use the same numbers. I still have the prim-oven images that go down to 16×256. From what I see, they still look fine on the web page and the image previewer before uploading.
| X | Y |
|---|---|
| 8 | 0 |
| 0, 2, 4, 6, 8, 10, 12, 14 | 1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31, 33, 35, 37, 39, 41, 43, 45, 47, 49, 51, 53, 55, 57, 59, 61, 63, 65, 67, 69, 71, 73, 75, 77, 79, 81, 83, 85, 87, 89, 91, 93, 95, 97, 99, 101, 103, 105, 107, 109, 111, 113, 115, 117, 119, 121, 123, 125, 127, 129, 131, 133, 135, 137, 139, 141, 143, 145, 147, 149, 151, 153, 155, 157, 159, 161, 163, 165, 167, 169, 171, 173, 175, 177, 179, 181, 183, 185, 187, 189, 191, 193, 195, 197, 199, 201, 203, 205, 207, 209, 211, 213, 215, 217, 219, 221, 223, 225, 227, 229, 231, 233, 235, 237, 239, 241, 243, 245, 247, 249, 251, 253 |
| 8 | 255 |
The final changes to my code involved offsetting Y. I was very close. Now I’ve got it!
function indexOfImageDataToImageXy(i, options) {
const { row, column } = indexOfImageDataToRowAndColumn(i, options);
const powX = Math.pow(2, options.hDown + 1);
const powY = Math.pow(2, options.vDown + 1);
let x = column * powX;
let y = row * powY;
if(row === options.rows) {
y = getLastImageDataY(options);
} else if(row !== 0) {
y -= 1;
}
return { x, y };
}
function imageXyIsImageData(x, y, options) {
if(y === 0) return x === Math.floor(options.width / 2);
const yMod = Math.pow(2, options.vDown + 1);
const xMod = Math.pow(2, options.hDown + 1);
const lastY = getLastImageDataY(options);
if(y === lastY) {
return x === Math.floor(options.width / 2);
} else if(y > lastY) {
return false;
}
y+=2;
if(y % yMod !== 1) return false;
if(x % xMod !== 0) return false;
return true;
}
No daily wrap up video for today as there aren’t any new visible features to demonstrate.
