47 tiling

This commit is contained in:
liquidex 2024-02-17 21:03:45 +01:00
parent d9b351ad64
commit 0e913e69a0
8 changed files with 246 additions and 38 deletions

View file

@ -161,6 +161,8 @@ styles = ["tairu.css"]
} }
``` ```
TODO this should be literate code
% template = true % template = true
id = "01HPMVT9BM9CS9375MX4H9WKW8" id = "01HPMVT9BM9CS9375MX4H9WKW8"
- and that gives us this result: - and that gives us this result:
@ -406,6 +408,98 @@ styles = ["tairu.css"]
4 4
``` ```
+ for my own (and your) convenience, here's a complete list of *all* the possible combinations in order.
- ```javascript ordinal-directions
function toString(bitset) {
if (bitset == 0) return "0";
let directions = [];
if (isSet(bitset, E)) directions.push("E");
if (isSet(bitset, SE)) directions.push("SE");
if (isSet(bitset, S)) directions.push("S");
if (isSet(bitset, SW)) directions.push("SW");
if (isSet(bitset, W)) directions.push("W");
if (isSet(bitset, NW)) directions.push("NW");
if (isSet(bitset, N)) directions.push("N");
if (isSet(bitset, NE)) directions.push("NE");
return directions.join(" | ");
}
for (let x in xToConnectionBitSet) {
console.log(`${x} => ${toString(xToConnectionBitSet[x])}`);
}
```
```output ordinal-directions
0 => 0
1 => E
2 => S
3 => E | S
4 => E | SE | S
5 => W
6 => E | W
7 => S | W
8 => E | S | W
9 => E | SE | S | W
10 => S | SW | W
11 => E | S | SW | W
12 => E | SE | S | SW | W
13 => N
14 => E | N
15 => S | N
16 => E | S | N
17 => E | SE | S | N
18 => W | N
19 => E | W | N
20 => S | W | N
21 => E | S | W | N
22 => E | SE | S | W | N
23 => S | SW | W | N
24 => E | S | SW | W | N
25 => E | SE | S | SW | W | N
26 => W | NW | N
27 => E | W | NW | N
28 => S | W | NW | N
29 => E | S | W | NW | N
30 => E | SE | S | W | NW | N
31 => S | SW | W | NW | N
32 => E | S | SW | W | NW | N
33 => E | SE | S | SW | W | NW | N
34 => E | N | NE
35 => E | S | N | NE
36 => E | SE | S | N | NE
37 => E | W | N | NE
38 => E | S | W | N | NE
39 => E | SE | S | W | N | NE
40 => E | S | SW | W | N | NE
41 => E | SE | S | SW | W | N | NE
42 => E | W | NW | N | NE
43 => E | S | W | NW | N | NE
44 => E | SE | S | W | NW | N | NE
45 => E | S | SW | W | NW | N | NE
46 => E | SE | S | SW | W | NW | N | NE
```
- with the lookup table generated, we are now able to prepare a tile strip like before - except now it's even more tedious work arranging the pieces together :ralsei_dead:
anyways I spent like 20 minutes doing that by hand, and now we have a neat tile strip just like before, except way longer:
![horizontal tile strip of 47 8x8 pixel metal tiles][pic:01HPW47SHMSVAH7C0JR9HWXWCM]
- now let's hook it up to our tileset renderer! TODO literate program.
% template = true
- with the capability to render with 47-tile tilesets, our examples suddenly look a whole lot better!
<canvas
is="tairu-editor"
data-tilemap-id="bitwiseAutotiling47"
data-tile-size="40"
>
Your browser does not support &lt;canvas&gt;.
<img class="resource" src="{% pic 01HPW47SHMSVAH7C0JR9HWXWCM %}" data-tairu-tileset-47="1">
</canvas>
% id = "01HPD4XQPWT9N8X9BD9GKWD78F" % id = "01HPD4XQPWT9N8X9BD9GKWD78F"
- bitwise autotiling is a really cool technique that I've used in plenty of games in the past. - bitwise autotiling is a really cool technique that I've used in plenty of games in the past.

View file

@ -186,6 +186,7 @@ strong {
i, i,
em { em {
--recursive-slnt: -16.0; --recursive-slnt: -16.0;
font-style: normal;
} }
/* Lay out elements a bit more compactly */ /* Lay out elements a bit more compactly */
@ -240,18 +241,28 @@ th-literate-program {
transition: background-color var(--transition-duration); transition: background-color var(--transition-duration);
} }
th-literate-program { @media (prefers-color-scheme: light) {
white-space: pre;
}
.tree summary:hover { pre,
th-literate-program {
& pre, background-color: transparent;
& th-literate-program { border: 1px solid var(--border-1);
background-color: var(--shaded-against-background-twice);
} }
} }
@media (prefers-color-scheme: dark) {
.tree summary:hover {
& pre,
& th-literate-program {
background-color: var(--shaded-against-background-twice);
}
}
}
pre>code, pre>code,
th-literate-program>code { th-literate-program>code {
padding: 0; padding: 0;
@ -259,6 +270,10 @@ th-literate-program>code {
border-radius: 0px; border-radius: 0px;
} }
th-literate-program {
white-space: pre;
}
/* And don't let code examples fly off and overflow the window */ /* And don't let code examples fly off and overflow the window */
pre { pre {
@ -283,10 +298,12 @@ img.pic {
/* Image hints for tweaking rendering */ /* Image hints for tweaking rendering */
img { img {
&[src*='+pixel'] { &[src*='+pixel'] {
image-rendering: crisp-edges; image-rendering: pixelated;
border-radius: 0; border-radius: 0;
} }
/* TODO: These could be autogenerated! */
&[src*='+width160'] { &[src*='+width160'] {
width: 160px; width: 160px;
height: auto; height: auto;
@ -297,6 +314,11 @@ img {
height: auto; height: auto;
} }
&[src*='+width752'] {
width: 752px;
height: auto;
}
/* Resources for use in JavaScript. */ /* Resources for use in JavaScript. */
&.resource { &.resource {
display: none; display: none;
@ -532,11 +554,6 @@ th-literate-program[data-mode="output"] {
color: #e39393; color: #e39393;
} }
& code .return-value {
content: 'Return value: ';
opacity: 50%;
}
&::after { &::after {
content: 'Output'; content: 'Output';
@ -553,7 +570,15 @@ th-literate-program[data-mode="output"] {
/* Syntax highlighting */ /* Syntax highlighting */
:root { :root {
/* TODO: Light mode syntax highlighting */ --syntax-comment: #9b8580;
--syntax-identifier: var(--text-color);
--syntax-keyword1: #e15e2b;
--syntax-keyword2: #199aca;
--syntax-operator: #e3755b;
--syntax-function: #d57b07;
--syntax-literal: #a64fb3;
--syntax-string: #79ac3b;
--syntax-punct: #a28680;
} }
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
@ -607,4 +632,21 @@ th-literate-program[data-mode="output"] {
&.punct { &.punct {
color: var(--syntax-punct); color: var(--syntax-punct);
} }
}
.th-syntax-highlighting {
& .export {
text-decoration: underline dotted;
cursor: help;
text-decoration-color: transparent;
transition: text-decoration-color var(--transition-duration);
}
&:hover,
&:focus {
& .export {
text-decoration-color: var(--syntax-keyword1);
}
}
} }

View file

@ -46,7 +46,7 @@
margin: 8px; margin: 8px;
background-image: url('../pic/01HPHVDRV0F0251MD0A2EG66C4-tilemap-heavy-metal-16+pixel+width160.png'); background-image: url('../pic/01HPHVDRV0F0251MD0A2EG66C4-tilemap-heavy-metal-16+pixel+width160.png');
background-size: 400%; background-size: 400%;
image-rendering: crisp-edges; image-rendering: pixelated;
position: relative; position: relative;
} }
@ -155,7 +155,7 @@
background-image: url('../pic/01HPHVDRV0F0251MD0A2EG66C4-tilemap-heavy-metal-16+pixel+width160.png'); background-image: url('../pic/01HPHVDRV0F0251MD0A2EG66C4-tilemap-heavy-metal-16+pixel+width160.png');
background-size: 400%; background-size: 400%;
background-position: 100% 100%; background-position: 100% 100%;
image-rendering: crisp-edges; image-rendering: pixelated;
position: relative; position: relative;

View file

@ -121,7 +121,14 @@ class InputMode {
} }
static highlight(frame) { static highlight(frame) {
highlight(frame, InputMode.JAVASCRIPT); highlight(frame, InputMode.JAVASCRIPT, (token, span) => {
if (token.kind == "keyword1" && token.string == "export") {
// This is something a bit non-obvious about the treehouse's literate programs
// so let's document it.
span.classList.add("export");
span.title = "This item is exported and visible in code blocks that follow";
}
});
} }
} }
@ -145,7 +152,7 @@ class OutputMode {
if (this.worker != null) { if (this.worker != null) {
this.worker.terminate(); this.worker.terminate();
} }
this.worker = new Worker(`${TREEHOUSE_SITE}/static/js/components/literate-programming/worker.js`, { this.worker = new Worker(import.meta.resolve("./literate-programming/worker.js"), {
type: "module", type: "module",
name: `evaluate LiterateOutput ${this.frame.programName}` name: `evaluate LiterateOutput ${this.frame.programName}`
}); });
@ -187,13 +194,6 @@ class OutputMode {
}) })
.join(" "); .join(" ");
if (output.kind == "result") {
let returnValueText = document.createElement("span");
returnValueText.classList.add("return-value");
returnValueText.textContent = "Return value: ";
line.insertBefore(returnValueText, line.firstChild);
}
this.frame.appendChild(line); this.frame.appendChild(line);
} }

View file

@ -58,7 +58,7 @@ function tokenize(text, syntax) {
return tokens; return tokens;
} }
export function highlight(element, syntax) { export function highlight(element, syntax, customize = null) {
let tokens = tokenize(element.textContent, syntax); let tokens = tokenize(element.textContent, syntax);
element.textContent = ""; element.textContent = "";
@ -67,6 +67,9 @@ export function highlight(element, syntax) {
let span = document.createElement("span"); let span = document.createElement("span");
span.textContent = token.string; span.textContent = token.string;
span.classList.add(token.kind); span.classList.add(token.kind);
if (customize != null) {
customize(token, span);
}
element.appendChild(span); element.appendChild(span);
} }
} }

View file

@ -9,6 +9,52 @@ export function shouldConnect(a, b) {
return a == b; return a == b;
} }
const dirs47 = {
E: 0b0000_0001,
SE: 0b0000_0010,
S: 0b0000_0100,
SW: 0b0000_1000,
W: 0b0001_0000,
NW: 0b0010_0000,
N: 0b0100_0000,
NE: 0b1000_0000,
};
function isSet(integer, bit) {
return (integer & bit) == bit;
}
function removeRedundancies(t) {
if (isSet(t, dirs47.SE) && (!isSet(t, dirs47.S) || !isSet(t, dirs47.E))) {
t &= ~dirs47.SE;
}
if (isSet(t, dirs47.SW) && (!isSet(t, dirs47.S) || !isSet(t, dirs47.W))) {
t &= ~dirs47.SW;
}
if (isSet(t, dirs47.NW) && (!isSet(t, dirs47.N) || !isSet(t, dirs47.W))) {
t &= ~dirs47.NW;
}
if (isSet(t, dirs47.NE) && (!isSet(t, dirs47.N) || !isSet(t, dirs47.E))) {
t &= ~dirs47.NE;
}
return t;
}
function ordinalDirections() {
let unique = new Set();
for (let i = 0; i <= 0b1111_1111; ++i) {
unique.add(removeRedundancies(i));
}
return Array.from(unique).sort((a, b) => a - b);
}
let xToConnectionBitSet = ordinalDirections();
let connectionBitSetToX = new Uint8Array(256);
for (let i = 0; i < xToConnectionBitSet.length; ++i) {
connectionBitSetToX[xToConnectionBitSet[i]] = i;
}
console.log(connectionBitSetToX);
export class TileEditor extends Frame { export class TileEditor extends Frame {
constructor() { constructor() {
super(); super();
@ -44,12 +90,16 @@ export class TileEditor extends Frame {
// 0st element is explicitly null because it represents the empty tile. // 0st element is explicitly null because it represents the empty tile.
this.tilesets = [null]; this.tilesets = [null];
this.tilesets47 = [null];
let attachedImages = this.getElementsByTagName("img"); let attachedImages = this.getElementsByTagName("img");
for (let image of attachedImages) { for (let image of attachedImages) {
if (image.hasAttribute("data-tairu-tileset")) { if (image.hasAttribute("data-tairu-tileset")) {
let tilesetIndex = parseInt(image.getAttribute("data-tairu-tileset")); let tilesetIndex = parseInt(image.getAttribute("data-tairu-tileset"));
this.tilesets[tilesetIndex] = image; this.tilesets[tilesetIndex] = image;
} else if (image.hasAttribute("data-tairu-tileset-47")) {
let tilesetIndex = parseInt(image.getAttribute("data-tairu-tileset-47"));
this.tilesets47[tilesetIndex] = image;
} }
} }
@ -105,7 +155,7 @@ export class TileEditor extends Frame {
get hasTilesets() { get hasTilesets() {
// Remember that tile 0 represents emptiness. // Remember that tile 0 represents emptiness.
return this.tilesets.length > 1; return this.tilesets.length > 1 || this.tilesets47.length > 1;
} }
drawTiles() { drawTiles() {
@ -135,22 +185,34 @@ export class TileEditor extends Frame {
for (let x = 0; x < this.tilemap.width; ++x) { for (let x = 0; x < this.tilemap.width; ++x) {
let tile = this.tilemap.at(x, y); let tile = this.tilemap.at(x, y);
if (tile != 0) { if (tile != 0) {
let tileset = this.tilesets[tile]; let tileset16 = this.tilesets[tile];
let tileset47 = this.tilesets47[tile];
let tileset = tileset47 != null ? tileset47 : tileset16;
let connectedWithEast = shouldConnect(tile, this.tilemap.at(x + 1, y)) ? 0b0001 : 0; let tileIndex = 0;
let connectedWithSouth = shouldConnect(tile, this.tilemap.at(x, y + 1)) ? 0b0010 : 0; if (tileset47 != null) {
let connectedWithWest = shouldConnect(tile, this.tilemap.at(x - 1, y)) ? 0b0100 : 0; let rawTileIndex = 0;
let connectedWithNorth = shouldConnect(tile, this.tilemap.at(x, y - 1)) ? 0b1000 : 0; rawTileIndex |= shouldConnect(tile, this.tilemap.at(x + 1, y)) ? dirs47.E : 0;
let tileIndex = connectedWithNorth rawTileIndex |= shouldConnect(tile, this.tilemap.at(x + 1, y + 1)) ? dirs47.SE : 0;
| connectedWithWest rawTileIndex |= shouldConnect(tile, this.tilemap.at(x, y + 1)) ? dirs47.S : 0;
| connectedWithSouth rawTileIndex |= shouldConnect(tile, this.tilemap.at(x - 1, y + 1)) ? dirs47.SW : 0;
| connectedWithEast; rawTileIndex |= shouldConnect(tile, this.tilemap.at(x - 1, y)) ? dirs47.W : 0;
rawTileIndex |= shouldConnect(tile, this.tilemap.at(x - 1, y - 1)) ? dirs47.NW : 0;
rawTileIndex |= shouldConnect(tile, this.tilemap.at(x, y - 1)) ? dirs47.N : 0;
rawTileIndex |= shouldConnect(tile, this.tilemap.at(x + 1, y - 1)) ? dirs47.NE : 0;
tileIndex = connectionBitSetToX[removeRedundancies(rawTileIndex)];
} else {
tileIndex |= shouldConnect(tile, this.tilemap.at(x + 1, y)) ? 0b0001 : 0;
tileIndex |= shouldConnect(tile, this.tilemap.at(x, y + 1)) ? 0b0010 : 0;
tileIndex |= shouldConnect(tile, this.tilemap.at(x - 1, y)) ? 0b0100 : 0;
tileIndex |= shouldConnect(tile, this.tilemap.at(x, y - 1)) ? 0b1000 : 0;
}
let tilesetTileSize = tileset.height; let tilesetTileSize = tileset.height;
let tilesetX = tileIndex * tilesetTileSize; let tilesetX = tileIndex * tilesetTileSize;
let tilesetY = 0; let tilesetY = 0;
this.ctx.drawImage( this.ctx.drawImage(
this.tilesets[tile], tileset,
tilesetX, tilesetY, tilesetTileSize, tilesetTileSize, tilesetX, tilesetY, tilesetTileSize, tilesetTileSize,
x * this.tileSize, y * this.tileSize, this.tileSize, this.tileSize, x * this.tileSize, y * this.tileSize, this.tileSize, this.tileSize,
); );

View file

@ -36,5 +36,12 @@ export default {
" x x ", " x x ",
" ", " ",
]), ]),
bitwiseAutotiling47: parseTilemap([
" x ",
" x ",
" xx xx ",
" xxxx ",
" x ",
]),
}; };

Binary file not shown.

After

Width:  |  Height:  |  Size: 455 B