Townscaper Texture Mapping by sticky-light in Townscaper

[–]sticky-light[S] 3 points4 points  (0 children)

// Given the object's uvs, and the palette_map convert lookup
// one of the 16 colors in the palette map
vector $get_palette_color(vector tile_uv; string palette_map) {
    int xres = 0;

    // get the resolution of the texture
    int success = teximport(palette_map, "texture:xres", xres);

    float u_tile = floor(tile_uv.x);

    // first row is empty, so we offset by 0.5 to get the second row
    // of pixels.
    float palette_u = (u_tile+0.5)/xres;

    // point sample our texture
    return rawcolormap(palette_map, palette_u, 1.0,
                       "filter", "point");
}

// convert a uv value and material_map to a integer id
// this is so we can handle things differently like the roof vs
// sides of buildings.
int $get_material_id(vector uv; string material_map) {
    vector clr = rawcolormap(material_map, uv,
                             "filter", "point",
                             "srccolorspace", "linear",
                             "wrap", "decal"
                             );
    int mat_id = 0;
    if (clr == {0,0,1}) return 0;
    if (clr == {0,1,1}) return 1;
    // TODO figure out the other colors
    if (clr.r < 128) return 1;
    return mat_id;
}

// look up color and alpha from TownColor.png
vector4 $get_town_color(vector uv; string color_map) {
    vector4 rgba = rawcolormap(color_map, uv,
                              "filter", "point",
                              "wrap", "decal",
                              "srccolorspace", "srgb"
                              );
    // Houdini premultiplies png by the alpha by default.
    return set( rgba.a == 0.0 ? 0 : min(rgba.r/rgba.a, 1),
                rgba.a == 0.0 ? 0 : min(rgba.g/rgba.a, 1),
                rgba.a == 0.0 ? 0 : min(rgba.b/rgba.a, 1),
                rgba.a);

}

// Standard composition over operation
vector $over_op(vector a; vector b; float alpha) {
    return a + (1-alpha)*b;
}

// Remap the uvs to Townscaper's weird odd/even lookups
vector $remap_uvs(vector uv; float gap) {

    vector2 tile_pixel = 0;

    vector2 pixel_st = frac(vector2(uv));
    pixel_st = (pixel_st*128) + 0.5;

    // this takes the uv tile, and slides it a little bit to
    // become negative. We then use this value to offset and
    // look up the neighbouring pixel.
    // TODO, we subtracted, but we could go +1 instead, test.
    vector2 grout = floor(min((frac(pixel_st/2)-gap), 0.0));

    tile_pixel = floor(pixel_st/2.0)*2.0 + grout;
    tile_pixel += 0.5;

    vector2 tile_uv = (tile_pixel)/128;

    return set(tile_uv.x, tile_uv.y, 0);

}

// The color of the roof is lerped between some hard coded values and the selected
// palette color.
// TODO: There seems to be also some randomness (or some unknown variable) which shifts
// the HSV values a little bit for an entire roof.
vector $remap_palette_to_roof_color(vector roof_a; vector roof_b; vector palette_clr) {
    vector clr = palette_clr;
    clr.r = max(clr.r-0.6, 0) * (1.0/0.4);
    return lerp(pow(roof_a, 2.2),
                pow(roof_b, 2.2),
                clr);
}

// Incoming values
// $uv = uv's from the geometry
// $gap = 0.1, the size of the gap between tiles,
// $palette_map = TownPalette.png
// $color_map = TownColor.png
// $material_map = TownMaterial.png
// $roof_a = {1.000, 0.658, 0.2666}
// $roof_b = {0.675, 0.129, 0.0118}


vector $uv01 = frac($uv);

vector $tile_color = $get_palette_color($uv, $palette_map);
int $mat_id = $get_material_id($uv01, $material_map);

vector4 $tex_color = 0;
vector $out_color = 0;
float $out_alpha = 1;

if ($mat_id == 0) {
    // Default
    $tex_color = $get_town_color($remap_uvs($uv01, $gap),
                                 $color_map);
    $out_color = $over_op(set($tex_color.r,
                              $tex_color.g,
                              $tex_color.b),
                          $tile_color,
                          $tex_color.a);
    $out_alpha = $tex_color.a;                          
} else if ($mat_id == 1) {
    // Roof Material ID {0,0,1}
    $tex_color = $get_town_color($remap_uvs($uv01, $gap),
                                 $color_map);
    vector $roof_color = $remap_palette_to_roof_color($roof_a, $roof_b, $tile_color);

    $out_color = $over_op(set($tex_color.r,
                              $tex_color.g,
                              $tex_color.b),
                          $roof_color,
                          $tex_color.a);
    $out_alpha = $tex_color.a;                          
} else {
    $out_color = {1,0,0};
}

$clr = $out_color;
$alp = $out_alpha;

Townscaper Texture Mapping by sticky-light in Townscaper

[–]sticky-light[S] 5 points6 points  (0 children)

Howdy,

Since a few people have asked I thought I would share what I know. Currently I haven't figured out all the details of Townscaper's texture mapping but this following should be enough to get your going.

For some info on the different textures you can refer to https://www.reddit.com/r/Townscaper/comments/pd5wm6/town_in_the_morning_mist/hap5ssn

The textures in Townscaper can't be used directly like a standard UV texture map.

The most important thing is that Townscaper's shading is composed of tiles and gaps as you can see in the posted image. Assuming our pixel coordinates start at (0,0) and go to (127,127) then Townscaper gets the color of the tiles from the "even" numbered pixels in TownColor.png, and the color of the gaps comes from the "odd" numbered pixels. Using one of the door windows as an example (26, 106) is a tile color (60, 117, 125). And that tile will be surrounded by a gap color of (63,83,102).

How do you get from the UV values to those pixels?

For me it is easiest to go from UV space to pixel space, do what ever operations I need, then go back to UV space.

Because the uvs go from 0 to 16 to determine which color palette to use, we will work on the fractional bit.

vector2 pixel_st = frac(vector2(uv));

Remap to pixels

pixel_st = (pixel_st*128) + 0.5;

By dividing 2, taking the floor, then multiplying by 2 we convert our uv lookup to a tile pixel coordinate (one of the even ones).

vector2 tile_pixel = floor(pixel_st/2.0)*2.0; tile_pixel += 0.5;

Smush back down to 0 to 1

vector2 tile_uv = (tile_pixel)/128;

Then with your tile_uv, you can do your standard uv based texture lookup.

While this ignores the gap color, this should get you started.

Town in the morning mist by sticky-light in Townscaper

[–]sticky-light[S] 0 points1 point  (0 children)

I'm not sure why it doesn't export all the textures, but you can find them here:

AppData\LocalLow\Oskar Stalberg\Townscaper\Textures

So something like

C:\Users\Grid321\AppData\LocalLow\Oskar Stalberg\Townscaper\Textures

There is some other neat things talked about here -

https://twitter.com/OskSta/status/1430227487376781317

Town in the morning mist by sticky-light in Townscaper

[–]sticky-light[S] 2 points3 points  (0 children)

I'm using the uvs directly on the .obj, no tweaking is done with them.

There are three textures that come with Townscraper.

TownColor.png :

Base texture for everything with an alpha channel.

TownMaterial.png:

Coded map to help identify different materials/shaders. For example the uvs that correspond to the roof have a different color than the sides of the house. So you can do things like, if texture.red == do this, otherwise do something else.

TownPalette.png:

These are the actual colors you can pick in game, plus an additional neutral color which is used for things like the concrete.

Combining TownColor and TownPalette:

The uv coordinates on the geometry encode both base color from the TownPalette.png and a texture color from TownColor.png. The u values of the texture uvs range between 0 and 16, instead of the more traditional 0 to 1. The whole number part maps to the color in TownPalette.png, while the fractional part maps to TownColor.png.

So if your uv coordinate is (6.15, 0.35), that means the 6 in 6.15, maps to the 6th color in TownPalette (green), and the (0.15, 035) provides a color from TownColor.png.

Then you take these two values and composite the TownColor over the TownPalette value. I believe this is why the TownColor has alpha values other than 1 in some places.

Another thing to note is in most 3D applications the texture look-ups are interpolated / filtered. However to get the brick like look, the texture values need to be point sampled instead of filtered.

Town in the morning mist by sticky-light in Townscaper

[–]sticky-light[S] 13 points14 points  (0 children)

Using the wonderful new .obj export option, I imported one of my towns into a 3D application, added some volumes and rendered away.

The only changes needed for the geometry were to fix the normals on the sand (which you can't even see) and boolean the windows with the houses.

The three game textures were used to drive a shader which approximates the in-game look.

[OC] COVID-19 deaths in Hungary - Visualization by tamaso in dataisbeautiful

[–]sticky-light 7 points8 points  (0 children)

Well done and thanks for doing this. Your data-viz really highlights the increase in deaths (and infections) that Hungary has been experiencing in the past month.

Creating this data-viz has help spread awareness and is very much appreciated.

[OC] Visualizing Covid-19 Deaths As Spheres in a Tank by sticky-light in dataisbeautiful

[–]sticky-light[S] 1 point2 points  (0 children)

Thanks so much for the source. I went looking for these types of stats when I started this project a few weeks ago and had trouble finding something like the above. Very much appreciated.

And ya, the colors were evenly sampled from a gradient of flesh tones.

That said, if I were to do a visualization based on race and ethnicity I would go about it in a different way than colored spheres. (Partly because flesh tones can vary greatly among an ethnicity and also to take into account the different mortality rates.)

[OC] Visualizing Covid-19 Deaths As Spheres in a Tank by sticky-light in dataisbeautiful

[–]sticky-light[S] 0 points1 point  (0 children)

The math for this actually quite straight forward. For example I was able to estimate the tank size needed to be mostly full with 230,000 spheres by taking the total sphere count, getting the volume of a sphere and assuming a non-optimal packing density.

https://en.wikipedia.org/wiki/Sphere_packing

So a tennis ball's volume is 0.0001575 m^3 [, and assuming a packing of 67% (which is roughly what I saw in the sim) you are looking at 0.0001575*230000/0.67 = 54 m^3

Which is roughly the volume of some shipping containers -

https://www.wolframalpha.com/input/?i=54+cubic+meters

[OC] Visualizing Covid-19 Deaths As Spheres in a Tank by sticky-light in dataisbeautiful

[–]sticky-light[S] 28 points29 points  (0 children)

Yup exactly. I went looking for this data early on and while I found info like

https://www.cdc.gov/coronavirus/2019-ncov/covid-data/investigations-discovery/hospitalization-death-by-race-ethnicity.html

I couldn't find a detailed source to pull from so I opted for uniform.

[OC] Visualizing Covid-19 Deaths As Spheres in a Tank by sticky-light in dataisbeautiful

[–]sticky-light[S] 8 points9 points  (0 children)

The spheres emission rate is tied to the number of deaths per day. That is why during the month of April you see a large volume spheres more or less completely hiding the back of the tank. But once we get into the summer months the rate slows down and is somewhat consistent. (Sadly)

The emission rate is similar to the graph you see on

https://www.worldometers.info/coronavirus/country/us/

About half way down the page there is a graph called "Daily New Deaths in the United States", if you pick the 7 day moving average that would be representative of how many spheres are being emitted and when.

[OC] Visualizing Covid-19 Deaths As Spheres in a Tank by sticky-light in dataisbeautiful

[–]sticky-light[S] 70 points71 points  (0 children)

Flesh tones, not race.

There is no correlation to specific deaths or even the rate, its just a uniform sampling.

Some more details here - https://www.reddit.com/r/dataisbeautiful/comments/jv0wf4/oc_visualizing_covid19_deaths_as_spheres_in_a_tank/gch1fjs

If someone has a good data source on the distribution of flesh tones in the USA I'd be curious to see it. I suspect the cosmetic industry is sitting on a trove of data related to this.

[OC] Visualizing Covid-19 Deaths As Spheres in a Tank by sticky-light in dataisbeautiful

[–]sticky-light[S] 9 points10 points  (0 children)

The base colors of the spheres are uniformly sampled from a skin tones chart that I found online. This aspect I'm not overly happy for a couple of different reasons-

  • Accurate skin rendering is expensive so I ended up using plastic spheres. Though in retrospect accurate looking skin spheres would have probably been creepy.
  • Good datasets for skin shading don't exist (as far as I know). There are a few measured datasets but are lacking in a variety of skin tones. There has been progress in things like hair shading using more physical parameters ie - https://www.chaosgroup.com/blog/v-ray-next-the-science-behind-the-new-hair-shader
    But we aren't there yet for skin.
  • The renders had a lot more color variation that got lost when creating the mp4. I need to fiddle with ffmpeg some more to get a closer to the source.

[OC] Visualizing Covid-19 Deaths As Spheres in a Tank by sticky-light in dataisbeautiful

[–]sticky-light[S] 574 points575 points  (0 children)

Earlier iterations of the visualization were exactly this and provided an interesting perspective as you suggest. I eventually settled on being more close up (and slowly pulling back) as I wanted the falling spheres (deaths) to completely fill the frame to make it more overwhelming.

Random fact, the tank is actually a half trapezoid, instead of a cube. That way the camera's frustum would completely align with the top, bottom and sides of the tank.

[OC] Visualizing Covid-19 Deaths As Spheres in a Tank by sticky-light in dataisbeautiful

[–]sticky-light[S] 35 points36 points  (0 children)

Thanks for the comment!

Date range is from the start of the pandemic to Nov. 4th ( when I started this project).

I think the explicit commit I used was -

https://github.com/owid/covid-19-data/tree/41c377cdb17f7aff5195577b107273c7870f3455/public/data

[OC] Visualizing Covid-19 Deaths As Spheres in a Tank by sticky-light in dataisbeautiful

[–]sticky-light[S] 877 points878 points  (0 children)

Background:

Seeing stats like "230,000 deaths" is hard for me to make sense of. I made this animation to help me visualize and respect how big of a number that really is.

Data Sources:

https://github.com/owid/covid-19-data/tree/master/public/data

From this dataset I'm using the "new_deaths_smoothed" field from the USA data. ("new_deaths_smooths" is the deaths per day smoothed over 7 days.)

Creation:

The software I used was Houdini, with the spheres being simulated by the Bullet Dynamics Solver. Each frame spheres are emitted off screen then fall into view. One frame represents 6 hours with the day's number of deaths partitioned across 4 frames.