Sculpted Prim Data Coordinates

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.

Team Fabulocity
“The Dog House”
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:

64×64 no dithering Top Hat Sculpted Prim

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.

Brim Edge Diamonds
Brim Edge Consistent
Inside Wall Twisted
Vertical Walls
Overlapping Faces
Overlapping Faces
Center of edge
Center of edge

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.

Lone dot

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.

Wireframe in Second Life
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.

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.

Center Alignment
lastY = 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;
}
ValueFormulaEvalNotes
vDownoptions.vDown0vertical downsampling
heightoptions.height64image height
Math.pow(2, vDown + 1)Math.pow(2, 0+1)22 to the power of 1 = 2
height – yMod64 – 262Last Y
63Correction

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.

128×128
256×64
64×256

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.

128×128
256×64
64×256
256×64 sphere bottom 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.

128×128 sphere bottom pole

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.

128×128 left
128×128 front
64×256 left
64×256 front
256×64 Bottom

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.

  1. Bottom pole is hard-coded to 63
  2. Wide images are doubling up on row vertices
  3. 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.

Bottom cells around pole are tall and narrow

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.

Density map of a sphere

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 Width128
Image Height128
Vectors per standard row32
Vectors per pole1
Row Count (non poles)31
Pole Count2
Formula(row count * vectors per row) + (pole count * vectors per pole)
Numbers(31 * 32) + (2 * 1)
Multiplied992 + 2
Total Vectors994

The data is correct. Here’s something – the function to evaluate isXyVertex returned more values than expected.

isXyVertex mismatch

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.

XY
640
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, 1241, 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
64127

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…

Cube
Mismatch
Sculpted Data Points Only

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.

Save to new size

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
};
*/
128×128 black cube
Cube Preview

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.

Working Dots

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.

Same Data, Same Size

The good news is that we have a working list of coordinates for 128×128 images that the Second Life client recognizes.

XY
640
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, 1243, 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
64127
Sculpted Prim data coordinates for 128×128 images

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!

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:

XY
320
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, 621, 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
3263
Sculpted Prim data coordinates for 64×64 images

I tried my luck with a lossy 256×256 sculpted prim. He we are with the micro prims again…

Micro Prim

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.

XY
1280
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, 2487, 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
128255
Sculpted Prim data coordinates for 256×256 images

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!

Disappearing LOD

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.

Tiny spec off set from the center
Sculpted Prim Preview

Here are the coordinates that I’m reading for the 512×512 image:

XY
2560
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, 49615, 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
256511
Sculpted Prim data coordinates for 512×512 images

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.

XY
320
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, 621, 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
3263
Sculpted Prim data coordinates for 64×64 images – updated

Here are the coordinates for the 1024×1024 sculpted prims.

XY
5120
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, 99231, 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
5121023
Sculpted Prim data coordinates for 1024×1024 images

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.

Uniform consistency
Circles lining up with wireframe

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.

XY
80
0, 2, 4, 6, 8, 10, 12, 141, 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
8255
Sculpted Prim data coordinates for 16×256 images

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.

Discover more from Lewis Moten

Subscribe now to keep reading and get access to the full archive.

Continue reading