← Basics3D assetfogon/bote-a-remo
Bote a remo
Drag to turn it// «Bote a remo» · gpt-6-sol (medium)
// Rowboat («Bote a remo»).
// Open wooden rowboat, stern toward the viewer and raised bow at the back.
const dark = "#663716";
const hull = ["#79431f", "#854b23", "#92552a", "#a26330"];
const light = ["#ad7139", "#bf8447", "#ca9252"];
const inside = ["#744522", "#80502a", "#8d592e"];
function widthAt(y) {
if (y <= 9) return 7.2 + (y - 5) * 0.5;
if (y <= 22) return 9.2 + (y - 9) * 0.15;
if (y <= 32) return 11.15 - (y - 22) * 0.2;
return Math.max(1.5, 9.15 - (y - 32) * 0.69);
}
function pick(colors) {
return colors[Math.floor(rand() * colors.length)];
}
// Solid, curved lower hull. Its sides climb above an open interior.
for (let y = 5; y <= 43; y++) {
const w = widthAt(y);
const bow = y >= 36 ? Math.floor((y - 36) / 3) : 0;
for (let z = 0; z <= 12 + bow; z++) {
const taper = z === 0 ? 0.39 : z === 1 ? 0.55 :
z === 2 ? 0.7 : z === 3 ? 0.82 :
z === 4 ? 0.92 : 1;
const edge = w * taper;
for (let x = Math.ceil(24 - edge); x <= Math.floor(24 + edge); x++) {
const side = Math.abs(x - 24);
const end = y <= 7 || y >= 41;
const floor = z <= 5;
const wall = side >= w - 2.3;
if (!floor && !wall && !end) continue;
let color;
if (z <= 2) color = rand() < 0.35 ? dark : hull[0];
else if (z <= 5) color = pick(hull.slice(0, 3));
else if (wall || end) {
color = z < 9 ? pick(hull.slice(0, 3)) : pick(hull.slice(1));
if (z === 8 || z === 11) color = rand() < 0.65 ? hull[0] : hull[1];
} else color = pick(inside);
set(x, y, z, color);
}
}
}
// Interior floorboards, with fine lengthwise gaps.
for (let y = 8; y <= 39; y++) {
const w = widthAt(y) - 2.7;
for (let x = Math.ceil(24 - w); x <= Math.floor(24 + w); x++) {
const seam = (x - 24 + 48) % 4 === 0;
set(x, y, 6, seam ? "#643b20" : pick(inside));
if (rand() < 0.07 && !seam) set(x, y, 6, "#a16a38");
}
}
// Thick, light-catching gunwales around the opening.
for (let y = 5; y <= 43; y++) {
const w = widthAt(y);
const raised = y >= 36 ? Math.floor((y - 36) / 3) : 0;
const z = 12 + raised;
for (const side of [-1, 1]) {
const edge = Math.round(24 + side * (w - 0.7));
for (let dx = -1; dx <= 1; dx++) {
set(edge + dx, y, z, dx === 0 ? pick(light) : "#a36a35");
}
if (y % 5 === 0) set(edge, y, z + 1, "#d39a57");
}
}
for (const y of [5, 6, 42, 43]) {
const w = widthAt(y);
const z = y > 40 ? 15 : 12;
for (let x = Math.ceil(24 - w); x <= Math.floor(24 + w); x++) {
set(x, y, z, pick(light));
}
}
// Dark plank joints along the visible outside of the hull.
for (let y = 9; y <= 38; y += 5) {
const w = widthAt(y);
for (const side of [-1, 1]) {
const x = Math.round(24 + side * w);
for (let z = 5; z <= 10; z++) set(x, y, z, "#6d3c1c");
}
}
// Two broad seats across the open boat.
for (const y of [16, 29]) {
const w = widthAt(y) - 2.1;
for (let x = Math.ceil(24 - w); x <= Math.floor(24 + w); x++) {
for (let yy = y; yy <= y + 2; yy++) {
set(x, yy, 9, yy === y + 2 ? "#a96d37" : "#c18a4b");
if (yy === y) set(x, yy, 10, pick(light));
}
}
for (const side of [-1, 1]) {
const x = Math.round(24 + side * (w - 1));
box(x, y + 1, 6, x, y + 1, 8, "#75431f");
}
}
// Short raised platforms at the stern and pointed bow.
for (let y = 7; y <= 10; y++) {
const w = widthAt(y) - 2.5;
for (let x = Math.ceil(24 - w); x <= Math.floor(24 + w); x++)
set(x, y, 8, y === 7 ? "#bd8243" : pick(inside));
}
for (let y = 38; y <= 41; y++) {
const w = widthAt(y) - 2;
for (let x = Math.ceil(24 - w); x <= Math.floor(24 + w); x++)
set(x, y, 9 + Math.floor((y - 38) / 2), pick(light));
}
// Oarlocks, one on each gunwale.
box(12, 23, 12, 14, 25, 14, "#654021");
box(34, 20, 12, 36, 22, 14, "#654021");
set(13, 24, 15, "#d59c59");
set(35, 21, 15, "#d59c59");
// Long shafts sweep outward and down from the boat.
line(20, 24, 13, 5, 24, 8, 0, "#a96932");
line(28, 21, 13, 43, 21, 7, 0, "#b97839");
line(14, 24, 14, 6, 24, 9, 0, "#d09750");
line(34, 21, 14, 42, 21, 9, 0, "#d09750");
// Flat, chunky paddle blades at the outer ends.
for (let x = 2; x <= 8; x++) {
const half = x <= 4 ? 1 : 2;
for (let y = 24 - half; y <= 24 + half; y++)
set(x, y, 7, y === 24 - half ? "#d19853" : "#9d602d");
}
for (let x = 40; x <= 46; x++) {
const half = x >= 43 ? 2 : 1;
for (let y = 21 - half; y <= 21 + half; y++) {
set(x, y, 6, y === 21 - half ? "#d59a52" : "#a56630");
if (x >= 43 && x <= 45 && y === 21) set(x, y, 7, "#bb7b3b");
}
}import { draw } from "./assets/voxel";
import { boteARemo } from "./assets/bote-a-remo";
// Every frame: standing on (x, y), the centre of its base.
draw(ctx, boteARemo, x, y, { size: 64, angle: 0, t: performance.now() / 1000 });
// size: its longest side, in px
// angle: turn on the floor, in radians
// t: seconds; if it moves, that animates it# In a terminal, in the game's folder
fogon assets add bote-a-remo
# With Fogón's tools (connector)
add_assets({ slug: "<your-game>", ids: ["bote-a-remo"] })
# It lands in client/assets/bote-a-remo.ts
# https://fogon.page/object/bote-a-remo