← NaturalezaObjeto 3Dfogon/tuna
Tuna
Arrastrá para girarlo// «Tuna» · gpt-6-sol (medium)
// A prickly pear cactus: several flat oval green pads growing out of each other, with tiny spines and a few red-purple fruits on the edges.
// Flat, overlapping prickly pear pads, facing the viewer (-y).
const pads = [
// This lower pad extends to z = 0, so the cactus rests on the floor.
{ x: 24, y: 25, z: 9, rx: 7, ry: 3.3, rz: 10, angle: 0.03 },
{ x: 13, y: 25, z: 20, rx: 7.5, ry: 2.8, rz: 10, angle: -0.17 },
{ x: 35, y: 26, z: 20, rx: 8, ry: 2.9, rz: 10, angle: 0.15 },
{ x: 24, y: 24, z: 24, rx: 9, ry: 3.2, rz: 12, angle: -0.04 },
{ x: 15, y: 24, z: 31, rx: 6, ry: 2.6, rz: 8, angle: -0.15 },
{ x: 33, y: 25, z: 32, rx: 6, ry: 2.6, rz: 7, angle: 0.16 }
];
function padCoordinates(p, x, z) {
const dx = x - p.x, dz = z - p.z;
const c = Math.cos(p.angle), s = Math.sin(p.angle);
return {
u: (dx * c + dz * s) / p.rx,
v: (-dx * s + dz * c) / p.rz
};
}
for (const p of pads) {
for (let x = Math.floor(p.x - p.rx - 3); x <= Math.ceil(p.x + p.rx + 3); x++) {
for (let z = Math.max(0, Math.floor(p.z - p.rz - 3)); z <= Math.ceil(p.z + p.rz + 3); z++) {
const { u, v } = padCoordinates(p, x, z);
const oval = u * u + v * v;
if (oval > 1) continue;
const halfDepth = p.ry * Math.sqrt(1 - oval);
for (let y = Math.ceil(p.y - halfDepth); y <= Math.floor(p.y + halfDepth); y++) {
const depth = Math.abs(y - p.y) / p.ry;
const rim = oval > 0.80 || depth > 0.82;
let color;
if (rim) color = z > p.z ? "#36aa50" : "#258d43";
else if (z > p.z + 2) color = rand() < 0.18 ? "#86e583" : "#67d773";
else color = rand() < 0.16 ? "#68d878" : "#46c961";
if (y > p.y + 1 && rand() < 0.45) color = "#35ae51";
set(x, y, z, color);
}
}
}
}
// Tiny pale areoles and prickles on the exposed front of each pad.
for (const p of pads) {
for (let x = Math.floor(p.x - p.rx + 2); x <= p.x + p.rx - 2; x += 4) {
for (let z = Math.floor(p.z - p.rz + 2); z <= p.z + p.rz - 2; z += 4) {
const px = Math.round(x + ((Math.floor(z / 4) % 2) ? 1 : 0));
const pz = Math.round(z);
const { u, v } = padCoordinates(p, px, pz);
if (u * u + v * v > 0.75) continue;
let front;
for (let y = 17; y <= 29; y++) {
if (get(px, y, pz)) { front = y; break; }
}
if (front === undefined) continue;
set(px, front - 1, pz, "#e9dc9b");
if (rand() < 0.78) set(px, front - 2, pz, "#fff0bd");
if (rand() < 0.42) {
set(px + (rand() < 0.5 ? -1 : 1), front - 1, pz + 1, "#f5e9ae");
}
}
}
}
// Plump red-purple fruits perched along the pad edges.
function fruit(x, y, z) {
ellipsoid(x, y, z, 2.2, 2.1, 3.3, "#ac3158");
ellipsoid(x, y - 0.5, z + 0.3, 1.7, 1.5, 2.7, "#e94b70");
box(x - 1, y - 2, z + 1, x, y - 2, z + 2, "#fa8195");
set(x, y - 2, z - 1, "#f77e92");
set(x + 1, y - 2, z, "#b42e59");
box(x - 1, y - 1, z + 3, x + 1, y + 1, z + 3, "#8d3153");
set(x, y, z + 4, "#78304b");
}
fruit(9, 24, 34);
fruit(16, 24, 40);
fruit(34, 25, 40);
fruit(40, 26, 31);import { draw } from "./assets/voxel";
import { tuna } from "./assets/tuna";
// En cada cuadro: parado sobre (x, y), el centro de su base.
draw(ctx, tuna, x, y, { size: 64, angle: 0, t: performance.now() / 1000 });
// size: lo que mide en px su lado más largo
// angle: giro sobre el piso, en radianes
// t: segundos; si se mueve, se anima solo con eso# En una terminal, en la carpeta del juego
fogon assets add tuna
# Con las herramientas de Fogón (conector)
add_assets({ slug: "<tu-juego>", ids: ["tuna"] })
# Queda en client/assets/tuna.ts
# https://fogon.page/object/tuna