update issues

This commit is contained in:
りき萌 2024-07-22 20:34:42 +02:00
parent 04a346d851
commit 2a68cfbf63
44 changed files with 1201 additions and 318 deletions

View file

@ -29,14 +29,18 @@ styles = ["page/tairu.css"]
% id = "01HQ162WWAMCPC5M88QAXHX4BT"
- so to help us learn, I made a little tile editor so that we can experiment with rendering tiles! have a look:
<noscript>(…though you will need to enable JavaScript to try it out.
{% this could probably be written with some clever generator magic, but I'm too lazy for that %}
`<noscript>`{=html}
(…though you will need to enable JavaScript to try it out.
seriously, pinky promise I won't ever track you!
inspect the source code if you wanna.
if not, you will have to deal with static pictures.
but just keep in mind this was supposed to be an <strong><em>interactive</em></strong> exploration of autotiling techniques.
cheers!)</noscript>
but just keep in mind this was supposed to be an _*interactive*_ exploration of autotiling techniques.
cheers!)
`</noscript>`{=html}
```javascript tairu
{:program=tairu}
```javascript
import { Tilemap } from "tairu/tilemap.js";
import { TileEditor } from "tairu/editor.js";
@ -54,22 +58,26 @@ styles = ["page/tairu.css"]
});
```
```output tairu 01HQ47ZX7520PJNPJ75M793R5G
{:program=tairu :placeholder=01HQ47ZX7520PJNPJ75M793R5G}
```output
```
% id = "01HQ162WWAC3FN565QE3JAB87D"
- `Tilemap` is a class wrapping a flat [`Uint8Array`] with a `width` and a `height`, so that we can index it using (x, y) coordinates.
- `Tilemap` is a class wrapping a flat [`Uint8Array`][Uint8Array] with a `width` and a `height`, so that we can index it using (x, y) coordinates.
```javascript tairu
{:program=tairu}
```javascript
console.log(tilemapSquare.at(0, 0));
console.log(tilemapSquare.at(3, 1));
```
```output tairu
{:program=tairu}
```output
0
1
```
[`Uint8Array`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array
[Uint8Array]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array
% id = "01HQ162WWA090YW5BR1XW68XJN"
- `at` has a `setAt` counterpart which sets tiles instead of getting them.
@ -90,34 +98,35 @@ styles = ["page/tairu.css"]
- let's break this down into smaller steps. drawing a border around the rectangle will involve:
% id = "01HQ162WWATV30HXGBQVWERP2M"
- determining *on which tiles* to draw it,
- determining _on which tiles_ to draw it,
% id = "01HQ162WWAA0V0SS0D1Y38BDS1"
- determining *where in these tiles* to draw it,
- determining _where in these tiles_ to draw it,
% id = "01HQ162WWAGBCBDYF4VH26MX1B"
- and actually drawing it!
% id = "01HQ162WWA2PNGVV075HR3WMER"
- so let's zoom in a bit and look at the tiles one by one. in particular, let's focus on *these* two tiles:
- so let's zoom in a bit and look at the tiles one by one. in particular, let's focus on _these_ two tiles:
![the same red rectangle, now with a focus on the northern tile at its center][pic:01HPYWPJB1P0GK53BSJFJFRAGR]
% id = "01HQ162WWAYDS6CSD3T102NA9X"
- notice how the two highlighted tiles are *different.* therefore, we can infer we should probably connect together any tiles that are *the same*.
- notice how the two highlighted tiles are _different._ therefore, we can infer we should probably connect together any tiles that are _the same_.
% id = "01HQ162WWATDD86D4GY7RMT0BZ"
- knowing that, we can extract the logic to a function:
```javascript tairu
{:program=tairu}
```javascript
export function shouldConnect(a, b) {
return a == b;
}
```
% id = "01HQ162WWA9M6801Q0RNRSF09H"
+ now, also note that the border around this particular tile is only drawn on its *northern* edge -
therefore we can infer that borders should only be drawn on edges for whom `shouldConnect(thisTile, adjacentTile)` is **`false`** (not `true`!).
+ now, also note that the border around this particular tile is only drawn on its _northern_ edge -
therefore we can infer that borders should only be drawn on edges for whom `shouldConnect(thisTile, adjacentTile)` is *`false`* (not `true`!).
a tile generally has four edges - east, south, west, north - so we need to perform this check for all of them, and draw our border accordingly.
% id = "01HQ162WWAM5YYQCEXH791T0E9"
@ -135,7 +144,8 @@ styles = ["page/tairu.css"]
% id = "01HQ162WWA5W8NXSXVZY3BBQ0H"
- to do that, I'm gonna override the tile editor's `drawTilemap` function - as this is where the actual tilemap rendering happens!
```javascript tairu
{:program=tairu}
```javascript
import { TileEditor } from "tairu/editor.js";
export class TileEditorWithBorders extends TileEditor {
@ -160,7 +170,7 @@ styles = ["page/tairu.css"]
continue;
}
// Check which of this tile's neighbors should *not* connect to it.
// Check which of this tile's neighbors should _not_ connect to it.
let disjointWithEast = !shouldConnect(tile, this.tilemap.at(x + 1, y));
let disjointWithSouth = !shouldConnect(tile, this.tilemap.at(x, y + 1));
let disjointWithWest = !shouldConnect(tile, this.tilemap.at(x - 1, y));
@ -191,14 +201,17 @@ styles = ["page/tairu.css"]
and here's the result:
```javascript tairu
{:program=tairu}
```javascript
new TileEditorWithBorders({
tilemap: tilemapSquare,
tileSize: 40,
borderWidth: 4,
});
```
```output tairu 01HQ49TJZFMK719KSE16SG3F7B
{:program=tairu :placeholder=01HQ49TJZFMK719KSE16SG3F7B}
```output
```
% id = "01HQ162WWAAEKW1ECV5G3ZEY47"
@ -221,58 +234,58 @@ styles = ["page/tairu.css"]
id = "01HQ162WWAS502000K8QZWVBDW"
- we can split this tileset up into 16 individual tiles, each one 8 × 8 pixels; people choose various resolutions, I chose a fairly low one to hide my lack of artistic skill.
<div class="horizontal-tile-strip">
<span class="metal x-0 y-0"></span>
<span class="metal x-1 y-0"></span>
<span class="metal x-2 y-0"></span>
<span class="metal x-3 y-0"></span>
<span class="metal x-0 y-1"></span>
<span class="metal x-1 y-1"></span>
<span class="metal x-2 y-1"></span>
<span class="metal x-3 y-1"></span>
<span class="metal x-0 y-2"></span>
<span class="metal x-1 y-2"></span>
<span class="metal x-2 y-2"></span>
<span class="metal x-3 y-2"></span>
<span class="metal x-0 y-3"></span>
<span class="metal x-1 y-3"></span>
<span class="metal x-2 y-3"></span>
<span class="metal x-3 y-3"></span>
</div>
::: horizontal-tile-strip
[]{.metal .x-0 .y-0}
[]{.metal .x-1 .y-0}
[]{.metal .x-2 .y-0}
[]{.metal .x-3 .y-0}
[]{.metal .x-0 .y-1}
[]{.metal .x-1 .y-1}
[]{.metal .x-2 .y-1}
[]{.metal .x-3 .y-1}
[]{.metal .x-0 .y-2}
[]{.metal .x-1 .y-2}
[]{.metal .x-2 .y-2}
[]{.metal .x-3 .y-2}
[]{.metal .x-0 .y-3}
[]{.metal .x-1 .y-3}
[]{.metal .x-2 .y-3}
[]{.metal .x-3 .y-3}
:::
% classes.branch = "tileset-cardinal-directions-demo"
id = "01HQ162WWANBTYH1JJWCTZYYVN"
- the keen eyed among you have probably noticed that this is very similar to the case we had before with drawing procedural borders -
except that instead of determining which borders to draw based on a tile's neighbors, this time we'll determine which *whole tile* to draw based on its neighbors!
except that instead of determining which borders to draw based on a tile's neighbors, this time we'll determine which _whole tile_ to draw based on its neighbors!
<div class="horizontal-tile-strip">
<span class="metal x-0 y-0"><span class="east">E</span><span class="south">S</span></span>
<span class="metal x-1 y-0"><span class="east">E</span><span class="south">S</span><span class="west">W</span></span>
<span class="metal x-2 y-0"><span class="south">S</span><span class="west">W</span></span>
<span class="metal x-3 y-0"><span class="south">S</span></span>
<span class="metal x-0 y-1"><span class="east">E</span><span class="south">S</span><span class="north">N</span></span>
<span class="metal x-1 y-1"><span class="east">E</span><span class="south">S</span><span class="west">W</span><span class="north">N</span></span>
<span class="metal x-2 y-1"><span class="south">S</span><span class="west">W</span><span class="north">N</span></span>
<span class="metal x-3 y-1"><span class="south">S</span><span class="north">N</span></span>
<span class="metal x-0 y-2"><span class="east">E</span><span class="north">N</span></span>
<span class="metal x-1 y-2"><span class="east">E</span><span class="west">W</span><span class="north">N</span></span>
<span class="metal x-2 y-2"><span class="west">W</span><span class="north">N</span></span>
<span class="metal x-3 y-2"><span class="north">N</span></span>
<span class="metal x-0 y-3"><span class="east">E</span></span>
<span class="metal x-1 y-3"><span class="east">E</span><span class="west">W</span></span>
<span class="metal x-2 y-3"><span class="west">W</span></span>
<span class="metal x-3 y-3"></span>
</div>
::: horizontal-tile-strip
[[E]{.east} [S]{.south}]{.metal .x-0 .y-0}
[[E]{.east} [S]{.south} [W]{.west}]{.metal .x-1 .y-0}
[[S]{.south} [W]{.west}]{.metal .x-2 .y-0}
[[S]{.south}]{.metal .x-3 .y-0}
[[E]{.east} [S]{.south} [N]{.north}]{.metal .x-0 .y-1}
[[E]{.east} [S]{.south} [W]{.west} [N]{.north}]{.metal .x-1 .y-1}
[[S]{.south} [W]{.west} [N]{.north}]{.metal .x-2 .y-1}
[[S]{.south} [N]{.north}]{.metal .x-3 .y-1}
[[E]{.east} [N]{.north}]{.metal .x-0 .y-2}
[[E]{.east} [W]{.west} [N]{.north}]{.metal .x-1 .y-2}
[[W]{.west} [N]{.north}]{.metal .x-2 .y-2}
[[N]{.north}]{.metal .x-3 .y-2}
[[E]{.east}]{.metal .x-0 .y-3}
[[E]{.east} [W]{.west}]{.metal .x-1 .y-3}
[[W]{.west}]{.metal .x-2 .y-3}
[]{.metal .x-3 .y-3}
:::
% id = "01HQ162WWA4Z6KKWFV59BR4WD3"
- previously we represented which single border to draw with a single boolean.
now we will represent which single tile to draw with *four* booleans, because each tile can connect to four different directions.
now we will represent which single tile to draw with _four_ booleans, because each tile can connect to four different directions.
% id = "01HQ162WWAQ9GZ6JD8KESW4N53"
- four booleans like this can easily be packed into a single integer using some bitwise operations, hence we get ***bitwise autotiling*** - autotiling using bitwise operations!
- four booleans like this can easily be packed into a single integer using some bitwise operations, hence we get _*bitwise autotiling*_ - autotiling using bitwise operations!
% id = "01HQ162WWAMBM8RXKQTN3D0XR2"
- now the clever part of bitwise autotiling is that we can use this packed integer *as an array index* - therefore selecting which tile to draw can be determined using just a single lookup table! neat, huh?
- now the clever part of bitwise autotiling is that we can use this packed integer _as an array index_ - therefore selecting which tile to draw can be determined using just a single lookup table! neat, huh?
% id = "01HQ162WWA0ZGZ97JZZBFS41TF"
- but because I'm lazy, and CPU time is valuable, instead of using an array I'll just rearrange the tileset texture a bit to be able to slice it in place using this index.
@ -280,7 +293,8 @@ styles = ["page/tairu.css"]
% id = "01HQ162WWAQQ99TRBDY5DCSW3Z"
- say we arrange our bits like this:
```javascript tairu
{:program=tairu}
```javascript
export const E = 0b0001;
export const S = 0b0010;
export const W = 0b0100;
@ -291,32 +305,32 @@ styles = ["page/tairu.css"]
id = "01HQ162WWABANND0WGT933TBMV"
- that means we'll need to arrange our tiles like so, where the leftmost tile is at index 0 (`0b0000`) and the rightmost tile is at index 15 (`0b1111`):
<div class="horizontal-tile-strip">
<span class="metal x-3 y-3"></span>
<span class="metal x-0 y-3"><span class="east">E</span></span>
<span class="metal x-3 y-0"><span class="south">S</span></span>
<span class="metal x-0 y-0"><span class="east">E</span><span class="south">S</span></span>
<span class="metal x-2 y-3"><span class="west">W</span></span>
<span class="metal x-1 y-3"><span class="east">E</span><span class="west">W</span></span>
<span class="metal x-2 y-0"><span class="south">S</span><span class="west">W</span></span>
<span class="metal x-1 y-0"><span class="east">E</span><span class="south">S</span><span class="west">W</span></span>
<span class="metal x-3 y-2"><span class="north">N</span></span>
<span class="metal x-0 y-2"><span class="east">E</span><span class="north">N</span></span>
<span class="metal x-3 y-1"><span class="south">S</span><span class="north">N</span></span>
<span class="metal x-0 y-1"><span class="east">E</span><span class="south">S</span><span class="north">N</span></span>
<span class="metal x-2 y-2"><span class="west">W</span><span class="north">N</span></span>
<span class="metal x-1 y-2"><span class="east">E</span><span class="west">W</span><span class="north">N</span></span>
<span class="metal x-2 y-1"><span class="south">S</span><span class="west">W</span><span class="north">N</span></span>
<span class="metal x-1 y-1"><span class="east">E</span><span class="south">S</span><span class="west">W</span><span class="north">N</span></span>
</div>
::: horizontal-tile-strip
[]{.metal .x-3 .y-3}
[[E]{.east}]{.metal .x-0 .y-3}
[[S]{.south}]{.metal .x-3 .y-0}
[[E]{.east} [S]{.south}]{.metal .x-0 .y-0}
[[W]{.west}]{.metal .x-2 .y-3}
[[W]{.west} [E]{.east}]{.metal .x-1 .y-3}
[[W]{.west} [S]{.south}]{.metal .x-2 .y-0}
[[W]{.west} [E]{.east} [S]{.south}]{.metal .x-1 .y-0}
[[N]{.north}]{.metal .x-3 .y-2}
[[E]{.east} [N]{.north}]{.metal .x-0 .y-2}
[[S]{.south} [N]{.north}]{.metal .x-3 .y-1}
[[E]{.east} [S]{.south} [N]{.north}]{.metal .x-0 .y-1}
[[W]{.west} [N]{.north}]{.metal .x-2 .y-2}
[[E]{.east} [W]{.west} [N]{.north}]{.metal .x-1 .y-2}
[[S]{.south} [W]{.west} [N]{.north}]{.metal .x-2 .y-1}
[[E]{.east} [S]{.south} [W]{.west} [N]{.north}]{.metal .x-1 .y-1}
:::
% id = "01HQ162WWAJPW00XA25N0K6KS7"
- packing that into a single tileset, or rather this time, a *tile strip*, we get this image:
- packing that into a single tileset, or rather this time, a _tile strip_, we get this image:
![horizontal tile strip of 16 8x8 pixel metal tiles][pic:01HPMMR6DGKYTPZ9CK0WQWKNX5]
% id = "01HQ162WWAT2ZC7T2P9ATD6WG2"
- now it's time to actually implement it as code! I'll start by defining a *tile index* function as a general way of looking up tiles in a tileset.
- now it's time to actually implement it as code! I'll start by defining a _tile index_ function as a general way of looking up tiles in a tileset.
% id = "01HQ162WWA0NRHBB6HP2RERNBK"
- I want to make the tile renderer a bit more general, so being able to attach a different tile lookup function to each tileset sounds like a great feature.
@ -327,7 +341,8 @@ styles = ["page/tairu.css"]
% id = "01HQ162WWAYJ4JCG3Z24SJR8S9"
- …but anyways, here's the basic bitwise magic function:
```javascript tairu
{:program=tairu}
```javascript
export function tileIndexInBitwiseTileset(tilemap, x, y) {
let tile = tilemap.at(x, y);
@ -345,7 +360,8 @@ styles = ["page/tairu.css"]
id = "01HQ162WWAS813ANMBG1PWDZHC"
- we'll define our tilesets by their texture, tile size, and a tile indexing function. so let's create an object that will hold our tileset data:
```javascript tairu
{:program=tairu}
```javascript
// You'll probably want to host the assets on your own website rather than
// hotlinking to others. It helps longevity!
let tilesetImage = new Image();
@ -361,7 +377,8 @@ styles = ["page/tairu.css"]
% id = "01HQ162WWA0SC2GA7Y3KJE0W5F"
- with all that, we should now be able to write a tile renderer which can handle textures! so let's try it:
```javascript tairu
{:program=tairu}
```javascript
import { TileEditor } from "tairu/editor.js";
export class TilesetTileEditor extends TileEditor {
@ -409,20 +426,24 @@ styles = ["page/tairu.css"]
% id = "01HQ162WWAS2HYF41MZNJ18BXC"
- drum roll please…
```javascript tairu
{:program=tairu}
```javascript
new TilesetTileEditor({
tilemap: tilemapSquare,
tileSize: 40,
tilesets: [heavyMetalTileset],
});
```
```output tairu 01HQ49X8Z57FNMN3E79FYF8CMG
{:program=tairu :placeholder=01HQ49X8Z57FNMN3E79FYF8CMG}
```output
```
% id = "01HQ162WWA03JAGJYCT0DRZP24"
- it works! buuuut if you play around with it you'll quickly start noticing some problems:
```javascript tairu
{:program=tairu}
```javascript
import { Tilemap } from "tairu/tilemap.js";
export const tilemapEdgeCase = Tilemap.parse(" x", [
@ -438,7 +459,9 @@ styles = ["page/tairu.css"]
tilesets: [heavyMetalTileset],
});
```
```output tairu 01HQ49YDPQXYSAT5N6P241DG3C
{:program=tairu :placeholder=01HQ49YDPQXYSAT5N6P241DG3C}
```output
```
% id = "01HQ162WWAB0AYSPGB4AEVT03Z"
@ -448,9 +471,10 @@ styles = ["page/tairu.css"]
- ### thing is, it was never good in the first place
% id = "01HQ162WWARSVDRNHZE13ZF6W6"
- I'll be blunt: we don't have enough tiles to represent *corners*! like in this case:
- I'll be blunt: we don't have enough tiles to represent _corners_! like in this case:
```javacript tairu
{:program=tairu}
```javascript
import { Tilemap } from "tairu/tilemap.js";
new TilesetTileEditor({
@ -464,7 +488,9 @@ styles = ["page/tairu.css"]
tilesets: [heavyMetalTileset],
});
```
```output tairu 01HQ49Z8JWR75D85DGHCB34K8E
{:program=tairu :placeholder=01HQ49Z8JWR75D85DGHCB34K8E}
```output
```
% id = "01HQ1K39AS4VDW7DVTAGQ03WFM"
@ -476,50 +502,60 @@ styles = ["page/tairu.css"]
- it should kind of _"bend"_ to fit in with the tiles to the north and the south, but it doesn't :kamien:
% id = "01HQ1K39ASQQNF7B881SYJWRC7"
- so what if we made the tile look like *this* instead:
- so what if we made the tile look like _this_ instead:
![mockup showing that previous L-shape but with a real corner][pic:01HQ17GYEZSZCVRBFHP4HXAJV8]
% id = "01HQ1K39ASMKRMTXFV93FRHZTG"
- that sure as heck looks a lot nicer! but there's a problem: that tile, let's zoom in on it…
![that bent tile, and just *it* alone][pic:01HQ183RANGH4S7VZSG1ZGH0S5]
![that bent tile, and just _it_ alone][pic:01HQ183RANGH4S7VZSG1ZGH0S5]
% classes.branch = "tileset-four-to-eight-demo"
id = "01HQ1K39ASR81NWMW8Q0MF8QMP"
- enhance!
{% NOTE djot: I don't there's a way to achieve this in Djot alone %}
``` =html
<ul class="directions-square bend">
<li class="east">E</li>
<li class="south">S</li>
</ul>
```
% classes.branch = "tileset-four-to-eight-demo"
id = "01HQ1K39ASC5WTR2A2AJN85JK2"
- huh. interesting. it connects to the east and the south. so what about this tile -
``` =html
<ul class="directions-square e-s">
<li class="east">E</li>
<li class="south">S</li>
</ul>
```
% id = "01HQ1K39ASXYBH9QJH5Q0C45JZ"
- because it *also* connects to the east and the south :thinking:
- because it _also_ connects to the east and the south :thinking:
% id = "01HQ1K39ASW5PWS52NGA2X3M0P"
- seems like we'll need something to disambiguate the two cases - and what better thing to disambiguate with than *more bits*!
- seems like we'll need something to disambiguate the two cases - and what better thing to disambiguate with than _more bits_!
% classes.branch = "tileset-four-to-eight-demo"
id = "01HPQCCV4R5N97FJ1GS36HZJZ7"
- to represent the corners, we'll turn our four cardinal directions…
``` =html
<ul class="directions-square">
<li class="east">E</li>
<li class="south">S</li>
<li class="west">W</li>
<li class="north">N</li>
</ul>
```
into eight *ordinal* directions:
into eight _ordinal_ directions:
``` =html
<ul class="directions-square">
<li class="east">E</li>
<li class="south-east">SE</li>
@ -530,13 +566,14 @@ styles = ["page/tairu.css"]
<li class="north">N</li>
<li class="north-east"><a href="https://github.com/NoiseStudio/NoiseEngine/" title="NoiseEngine????">NE</a></li>
</ul>
```
% id = "01HQ1K39ASFN94YDY1RWQYS12K"
- at this point with the four extra corners we'll need 8 bits to represent our tiles, and that would make…
***256 tiles!?***
_*256 tiles!?*_
nobody in their right mind would actually draw 256 separate tiles, right? ***RIGHT???***
nobody in their right mind would actually draw 256 separate tiles, right? _*RIGHT???*_
% template = true
id = "01HQ1K39AS11M1M4GQQ60NXTY6"
@ -544,7 +581,8 @@ styles = ["page/tairu.css"]
if we arrange the tiles in a diagnonal cross like this, notice how the tile in the center would have the bits `SE | SW | NW | NE` set, which upon first glance would suggest us needing a different tile -
but it looks correct!
```javascript tairu
{:program=tairu}
```javascript
import { Tilemap } from "tairu/tilemap.js";
new TilesetTileEditor({
@ -559,29 +597,35 @@ styles = ["page/tairu.css"]
tilesets: [heavyMetalTileset],
});
```
```output tairu 01HQ4A01MPE6JT5ZZFEN9S635W
{:program=tairu :placeholder=01HQ4A01MPE6JT5ZZFEN9S635W}
```output
```
% id = "01HQ1K39AS7CRBZ67N1VVHCVME"
- therefore there must be *some* bit combinations that are redundant to others. let's find them!
- therefore there must be _some_ bit combinations that are redundant to others. let's find them!
% classes.branch = "tileset-four-to-eight-demo"
id = "01HQ1K39ASZPJ4E23EZ1XJ5J7K"
- let's pick one corner first, then generalize to all the other ones. I pick southeast!
``` =html
<ul class="directions-square e-s">
<li class="east">E</li>
<li class="south-east">SE</li>
<li class="south">S</li>
</ul>
```
% id = "01HQ1K39ASQTR054W0VWEAV2FS"
- in this case, if we remove the tile to the southeast, we get that bent tile from before:
``` =html
<ul class="directions-square bend">
<li class="east">E</li>
<li class="south">S</li>
</ul>
```
% id = "01HQ1K39AS6RGE6Z83T8MH1R0M"
- what we can learn from this is that for `E | S`, `ES` affects the result!
@ -589,6 +633,7 @@ styles = ["page/tairu.css"]
% id = "01HQ1K39ASVSAQ6F8ANEZE1WQ4"
- but if we add any other corner, nothing changes. heck, let's add all of them:
``` =html
<ul class="directions-square e-s">
<li class="east">E</li>
<li class="south-east">SE</li>
@ -597,6 +642,7 @@ styles = ["page/tairu.css"]
<li class="north-west">NW</li>
<li class="north-east">NE</li>
</ul>
```
% id = "01HQ1K39AST8RQTVSCDV7FSH62"
- this combination is definitely redundant!
@ -612,7 +658,8 @@ styles = ["page/tairu.css"]
+ we'll start off by redefining our bits to be ordinal directions instead. I still want to keep the [nice orderliness][branch:01HQ162WWAM5YYQCEXH791T0E9] that comes with
arranging the bits clockwise starting from east, so if we want that we can't just extend the indices with an extra four bits at the top.
```javascript tairu
{:program=tairu}
```javascript
export const E = 0b0000_0001;
export const SE = 0b0000_0010;
export const S = 0b0000_0100;
@ -626,7 +673,8 @@ styles = ["page/tairu.css"]
% id = "01HPSY4Y19HPNXC54VP6TFFHXN"
- I don't know about you, but I find the usual C-style way of checking whether a bit is set extremely hard to read, so let's take care of that:
```javascript tairu
{:program=tairu}
```javascript
export function isSet(integer, bit) {
return (integer & bit) == bit;
}
@ -636,7 +684,8 @@ styles = ["page/tairu.css"]
- now we can write a function that will remove the aforementioned redundancies.
the logic is quite simple - for southeast, we only allow it to be set if both south and east are also set, and so on and so forth.
```javascript tairu
{:program=tairu}
```javascript
// t is an existing tile index; variable name is short for brevity
export function removeRedundancies(t) {
if (isSet(t, SE) && (!isSet(t, S) || !isSet(t, E))) {
@ -658,7 +707,8 @@ styles = ["page/tairu.css"]
% id = "01HPSY4Y19HWQQ9XBW1DDGW68T"
- with that, we can find a set of all unique non-redundant combinations:
```javascript tairu
{:program=tairu}
```javascript
export function ordinalDirections() {
let unique = new Set();
for (let i = 0; i <= 0b1111_1111; ++i) {
@ -669,20 +719,22 @@ styles = ["page/tairu.css"]
```
% id = "01HPSY4Y19KG8DC4SYXR1DJJ5F"
- by the way, I find it quite funny how JavaScript's [`Array.prototype.sort`] defaults to ASCII ordering *for all types.*
- by the way, I find it quite funny how JavaScript's [`Array.prototype.sort`][sort] defaults to ASCII ordering _for all types._
even numbers! ain't that silly?
[`Array.prototype.sort`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort
[sort]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort
% id = "01HPSY4Y19V62YKTGK3TTKEB38"
- and with all the ingredients in the pot, we now _Let It Cook™_:
```javascript tairu
{:program=tairu}
```javascript
let dirs = ordinalDirections();
console.log(dirs.length);
```
```output tairu
{:program=tairu}
```output
47
```
@ -690,7 +742,7 @@ styles = ["page/tairu.css"]
- forty seven! that's how many unique tiles we actually need.
% id = "01HPSY4Y19C303Z595KNVXYYVS"
- you may find pixel art tutorials saying you need forty *eight* and not forty *seven*, but that is not quite correct -
- you may find pixel art tutorials saying you need forty _eight_ and not forty _seven_, but that is not quite correct -
the forty eighth tile is actually just the empty tile! saying it's part of the tileset is quite misleading IMO.
% id = "01HPSY4Y19TM2K2WN06HHEM3D0"
@ -707,13 +759,13 @@ styles = ["page/tairu.css"]
- so we only need to draw 47 tiles, but to actually display them in a game we still need to pack them into an image.
% id = "01HPWJB4Y0QX6YR6TQKZ7T1C2E"
- we *could* use a similar approach to the 16 tile version, but that would leave us with lots of wasted space!
- we _could_ use a similar approach to the 16 tile version, but that would leave us with lots of wasted space!
% id = "01HPWJB4Y0HKGSDABB56CNFP9H"
- think that with this redundancy elimination approach most of the tiles will never even be looked up by the renderer, because the bit combinations will be collapsed into a more canonical form before the lookup.
% id = "01HQ1K39ASM53P1E74HKRZ1T24"
- so instead of wasting space, we can compress the tiles into a compact strip, and use a lookup table from sparse tile indices to dense tile *positions* within the strip.
- so instead of wasting space, we can compress the tiles into a compact strip, and use a lookup table from sparse tile indices to dense tile _positions_ within the strip.
% id = "01HPWJB4Y0F9JGXQDAAVC3ERG1"
- I don't want to write the lookup table by hand, so let's generate it!
@ -721,7 +773,8 @@ styles = ["page/tairu.css"]
% id = "01HPWJB4Y0HTV32T4WMKCKWTVA"
- we'll start by obtaining our ordinal directions array again:
```javascript tairu
{:program=tairu}
```javascript
export let xToConnectionBitSet = ordinalDirections();
```
@ -730,7 +783,8 @@ styles = ["page/tairu.css"]
remember that our array has only 256 values, so it should be pretty cheap to represent using a [`Uint8Array`]:
```javascript tairu
{:program=tairu}
```javascript
export let connectionBitSetToX = new Uint8Array(256);
for (let i = 0; i < xToConnectionBitSet.length; ++i) {
connectionBitSetToX[xToConnectionBitSet[i]] = i;
@ -742,18 +796,22 @@ styles = ["page/tairu.css"]
% id = "01HPWJB4Y0CWQB9EZG6C91A0H0"
- and there we go! we now have a mapping from our bitset to positions within the tile strip. try to play around with the code example to see which bitsets correspond to which position!
```javascript tairu
{:program=tairu}
```javascript
console.log(connectionBitSetToX[E | SE | S]);
```
```output tairu
{:program=tairu}
```output
4
```
% id = "01HPWJB4Y09P9Q3NGN59XWX2X9"
+ for my own (and your) convenience, here's a complete list of *all* the possible combinations in order.
+ for my own (and your) convenience, here's a complete list of _all_ the possible combinations in order.
% id = "01HPWJB4Y01VJFMHYEC1WZ353W"
- ```javascript tairu
- {:program=tairu}
```javascript
function toString(bitset) {
if (bitset == 0) return "0";
@ -773,7 +831,9 @@ styles = ["page/tairu.css"]
console.log(`${x} => ${toString(xToConnectionBitSet[x])}`);
}
```
```output tairu
{:program=tairu}
```output
0 => 0
1 => E
2 => S
@ -836,7 +896,8 @@ styles = ["page/tairu.css"]
% id = "01HQ1M84GS09M7PMXFYHDPRTMT"
- since we already prepared the bulk of the framework before, it should be as simple as writing a new `tileIndex` function:
```javascript tairu
{:program=tairu}
```javascript
export function tileIndexInBitwiseTileset47(tilemap, x, y) {
let tile = tilemap.at(x, y);
@ -858,7 +919,8 @@ styles = ["page/tairu.css"]
id = "01HQ1M84GS4C99VQZC4150CMDS"
- now we can write a new tileset descriptor that uses this indexing function and the larger tile strip:
```javascript tairu
{:program=tairu}
```javascript
// Once again, use your own link here!
let tilesetImage = new Image();
tilesetImage.src = "{% pic 01HPW47SHMSVAH7C0JR9HWXWCM %}";
@ -873,7 +935,8 @@ styles = ["page/tairu.css"]
% id = "01HQ1M84GS9CC8VR1BVDC15W50"
- and Drum Roll 2: Return of the Snare please…
```javascript tairu
{:program=tairu}
```javascript
import { Tilemap } from "tairu/tilemap.js";
new TilesetTileEditor({
@ -888,14 +951,16 @@ styles = ["page/tairu.css"]
tilesets: [heavyMetalTileset47],
});
```
```output tairu 01HQ4A11RRXEQ850598GFBJN0B
{:program=tairu :placeholder=01HQ4A11RRXEQ850598GFBJN0B}
```output
```
% id = "01HQ1M84GSCXTPGVPXY840WCQ6"
- it works perfectly!
% id = "01HQ1M84GSVBG9T94ZN9XTXX58"
- but honestly, this is a bit *boring* if we're gonna build a game with procedural worlds.
- but honestly, this is a bit _boring_ if we're gonna build a game with procedural worlds.
% id = "01HQ1M84GSH0KTFFZET6GZZ4V2"
- heck, it's even boring for a level designer to have to lay out all the tiles manually -
@ -907,7 +972,8 @@ styles = ["page/tairu.css"]
% id = "01HQ1M84GS0KJ9NA6GPS62RC95"
- for now, have a big editor to play around with. it's a lot of fun arranging the tiles in various shapes!
```javascript tairu
{:program=tairu}
```javascript
import { Tilemap } from "tairu/tilemap.js";
new TilesetTileEditor({
@ -916,7 +982,9 @@ new TilesetTileEditor({
tilesets: [heavyMetalTileset47],
});
```
```output tairu 01HQ4A45WNAEJGCT2WDMQJHK14
{:program=tairu :placeholder=01HQ4A45WNAEJGCT2WDMQJHK14}
```output
```
:nap: <!--
@ -947,7 +1015,7 @@ You have been warned.
- after a while I switched to a fork - [Lite XL](https://github.com/lite-xl/lite-xl), which had better font rendering and more features
% id = "01HPD4XQPWB11TZSX5VAAJ6TCD"
- I stopped using it because VS Code was just more feature packed and usable; no need to reinvent the wheel, rust-analyzer *just works.*
- I stopped using it because VS Code was just more feature packed and usable; no need to reinvent the wheel, rust-analyzer _just works._
% id = "01HPD4XQPW3G7BXTBBTD05MB8V"
- the LSP plugin for Lite XL had some issues around autocompletions not filling in properly :pensive:
@ -956,11 +1024,11 @@ You have been warned.
while tinkering with your editor is something really cool, in my experience it's only cool up to a point.
% id = "01HPD4XQPWV1BAPA27SNDFR93B"
- the cool thing with Tilekit is that it's *more* than just your average bitwise autotiling - of course it *can* do basic autotiling, but it can also do so much more
- the cool thing with Tilekit is that it's _more_ than just your average bitwise autotiling - of course it _can_ do basic autotiling, but it can also do so much more
% id = "01HPD4XQPWM1JSAPXVT6NBHKYY"
classes.branch_children = "branch-quote"
- if I had to describe it, it's basically something of a *shader langauge for tilesets.* this makes it really powerful, as you can do little programs like
- if I had to describe it, it's basically something of a _shader langauge for tilesets._ this makes it really powerful, as you can do little programs like
% id = "01HPD4XQPWE7ZVR0SS67DHTGHQ"
- autotile using this base tileset