Entropie git · main
Entropie festival game; first steps
C 88.5% Markdown 9.1% GLSL 2.3%git clone https://git.christianimmanuel.de/games/Entropie.gitwget https://git.christianimmanuel.de/games/Entropie/archive/Entropie.tar.gzentropie/src/render/shapes.c raw
/*
* The big pieces of the world as meshes: buildings, trees, roads, gravel
* and stones, and the building blocks everything is made of.
*/
#include "render/shapes_internal.h"
static const Vec3 PALETTE_FACES[][FACE_COUNT] = {
[PALETTE_STANDARD] = { CREAM, CREAM, CREAM, CREAM, CREAM, CREAM },
[PALETTE_GROUND] = { EARTH, EARTH, EARTH, EARTH,
{ 0.35f, 0.24f, 0.15f }, /* bottom */
{ 0.47f, 0.62f, 0.28f } }, /* grass */
[PALETTE_MODERN] = { { 0.92f, 0.45f, 0.36f }, /* coral */
{ 0.22f, 0.62f, 0.62f }, /* teal */
{ 0.28f, 0.38f, 0.78f }, /* cobalt */
{ 0.92f, 0.74f, 0.28f }, /* mustard */
{ 0.62f, 0.34f, 0.62f }, /* plum */
{ 0.52f, 0.80f, 0.66f } }, /* mint */
[PALETTE_WARM] = { TERRACOTTA, TERRACOTTA, TERRACOTTA,
TERRACOTTA, TERRACOTTA, TERRACOTTA },
};
static const Vec3 COLOR_BRICK = { 0.78f, 0.42f, 0.30f };
static const Vec3 COLOR_ROOF = { 0.42f, 0.36f, 0.52f };
static const Vec3 COLOR_WINDOW = { 0.55f, 0.75f, 0.88f };
static const Vec3 COLOR_DOOR = { 0.45f, 0.26f, 0.16f };
const Vec3 COLOR_BARK = { 0.48f, 0.32f, 0.22f };
const Vec3 COLOR_LEAVES = { 0.36f, 0.62f, 0.34f };
const Vec3 COLOR_STEEL = { 0.55f, 0.58f, 0.62f };
static Vec3 paletteColor(Palette p) { return PALETTE_FACES[p][FACE_BACK]; }
/* ---------- Building blocks ---------- */
static const Vec3 FACE_NORMALS[FACE_COUNT] = {
{ 0, 0, -1 }, { 0, 0, 1 }, { -1, 0, 0 }, { 1, 0, 0 }, { 0, -1, 0 }, { 0, 1, 0 },
};
/* Corner signs per face, in order around the face edge. */
static const Vec3 FACE_CORNERS[FACE_COUNT][4] = {
{ { -1, -1, -1 }, { 1, -1, -1 }, { 1, 1, -1 }, { -1, 1, -1 } },
{ { -1, -1, 1 }, { 1, -1, 1 }, { 1, 1, 1 }, { -1, 1, 1 } },
{ { -1, -1, -1 }, { -1, 1, -1 }, { -1, 1, 1 }, { -1, -1, 1 } },
{ { 1, -1, -1 }, { 1, 1, -1 }, { 1, 1, 1 }, { 1, -1, 1 } },
{ { -1, -1, -1 }, { 1, -1, -1 }, { 1, -1, 1 }, { -1, -1, 1 } },
{ { -1, 1, -1 }, { 1, 1, -1 }, { 1, 1, 1 }, { -1, 1, 1 } },
};
void shapes_addBox(MeshBuilder *b, Vec3 center, Vec3 size, const Vec3 colors[FACE_COUNT]) {
Vec3 h = vec3_scale(size, 0.5f);
for (int f = 0; f < FACE_COUNT; f++) {
Vec3 p[4];
for (int c = 0; c < 4; c++) {
Vec3 s = FACE_CORNERS[f][c];
p[c] = vec3(center.x + s.x * h.x, center.y + s.y * h.y, center.z + s.z * h.z);
}
MeshBuilder_gridQuad(b, p[0], p[1], p[2], p[3], FACE_NORMALS[f], colors[f], BOX_CELL);
}
}
/* Upright tube from base_y to base_y + height, radius tapering from
r_bottom to r_top, optionally with flat caps. */
void shapes_addTube(MeshBuilder *b, float base_y, float r_bottom, float r_top, float height,
int segments, Vec3 color, bool caps) {
const int rings = MeshBuilder_segments(height, CELL_SIZE);
const GLuint side = (GLuint)b->vertex_count;
const float slope = (r_bottom - r_top) / height; /* normal tilts up on a narrowing tube */
for (int r = 0; r <= rings; r++) {
float t = (float)r / (float)rings;
float y = base_y + height * t;
float radius = r_bottom + (r_top - r_bottom) * t;
for (int i = 0; i < segments; i++) {
float a = 2.0f * PI_F * (float)i / (float)segments;
Vec3 n = vec3_normalize(vec3(cosf(a), slope, sinf(a)));
MeshBuilder_vertex(b, vec3(cosf(a) * radius, y, sinf(a) * radius), n, color);
}
}
for (int r = 0; r < rings; r++) {
for (int i = 0; i < segments; i++) {
GLuint a = side + (GLuint)(r * segments + i);
GLuint c = side + (GLuint)(r * segments + (i + 1) % segments);
MeshBuilder_quad(b, a, c, c + (GLuint)segments, a + (GLuint)segments);
}
}
if (!caps) return;
for (int top = 0; top <= 1; top++) {
Vec3 n = vec3(0.0f, top ? 1.0f : -1.0f, 0.0f);
float y = base_y + (top ? height : 0.0f);
float radius = top ? r_top : r_bottom;
GLuint center = MeshBuilder_vertex(b, vec3(0.0f, y, 0.0f), n, color);
GLuint ring = (GLuint)b->vertex_count;
for (int i = 0; i < segments; i++) {
float a = 2.0f * PI_F * (float)i / (float)segments;
MeshBuilder_vertex(b, vec3(cosf(a) * radius, y, sinf(a) * radius), n, color);
}
for (int i = 0; i < segments; i++)
MeshBuilder_triangle(b, center, ring + (GLuint)i, ring + (GLuint)((i + 1) % segments));
}
}
/* Box walls plus a gable roof whose ridge runs along X. */
static const Vec3 COLOR_HALL_WALL = { 0.84f, 0.82f, 0.76f };
static const Vec3 COLOR_PLASTER = { 0.88f, 0.82f, 0.68f };
static const Vec3 COLOR_LIGHT_ROOF = { 0.66f, 0.64f, 0.62f };
static const Vec3 COLOR_DARK_ROOF = { 0.38f, 0.38f, 0.43f };
static const Vec3 COLOR_HALL_ROOF = { 0.58f, 0.58f, 0.60f };
static const Vec3 COLOR_RED_ROOF = { 0.66f, 0.24f, 0.20f };
static const Vec3 COLOR_SOLAR = { 0.16f, 0.22f, 0.42f };
#define PANEL_LENGTH 2.0f /* solar panels along the ridge */
#define PANEL_DEPTH 1.1f /* ... and down the slope */
#define PANEL_GAP 0.12f
#define PANEL_MARGIN 0.5f
/* Rows of solar panels on one roof slope (eave to ridge), a little above it. */
static void addSolarPanels(MeshBuilder *b, float length, Vec3 eave_l, Vec3 ridge_l, Vec3 normal) {
Vec3 up_slope = vec3_sub(ridge_l, eave_l);
float slope = vec3_length(up_slope);
up_slope = vec3_scale(up_slope, 1.0f / slope);
const Vec3 along = vec3(1.0f, 0.0f, 0.0f), lift = vec3_scale(normal, 0.04f);
int cols = (int)((length - 2.0f * PANEL_MARGIN) / PANEL_LENGTH);
int rows = (int)((slope - 2.0f * PANEL_MARGIN) / PANEL_DEPTH);
float x0 = (length - (float)cols * PANEL_LENGTH) * 0.5f, v0 = (slope - (float)rows * PANEL_DEPTH) * 0.5f;
for (int r = 0; r < rows; r++) {
for (int c = 0; c < cols; c++) {
float u = x0 + (float)c * PANEL_LENGTH, v = v0 + (float)r * PANEL_DEPTH;
Vec3 p = vec3_add(vec3_add(eave_l, lift), vec3_add(vec3_scale(along, u), vec3_scale(up_slope, v)));
Vec3 du = vec3_scale(along, PANEL_LENGTH - PANEL_GAP), dv = vec3_scale(up_slope, PANEL_DEPTH - PANEL_GAP);
GLuint a = MeshBuilder_vertex(b, p, normal, COLOR_SOLAR);
GLuint q = MeshBuilder_vertex(b, vec3_add(p, du), normal, COLOR_SOLAR);
GLuint w = MeshBuilder_vertex(b, vec3_add(vec3_add(p, du), dv), normal, COLOR_SOLAR);
GLuint e = MeshBuilder_vertex(b, vec3_add(p, dv), normal, COLOR_SOLAR);
MeshBuilder_quad(b, a, q, w, e);
}
}
}
#define EAVES 0.45f /* the roof reaches this far past the long walls ... */
#define VERGE 0.35f /* ... and past the gables */
#define TILE_ROW 0.30f /* rows of roof tiles up the slope */
#define TILE_LIP 0.035f /* each row's lower edge stands out a little */
static const Vec3 COLOR_FASCIA = { 0.42f, 0.30f, 0.22f };
/* A tiled roof slope from the eaves (below the wall top, past the wall) up to the ridge:
rows of tiles, each starting a little proud of the one below, an underside, and the board at the eaves. */
static void addTiledSlope(MeshBuilder *b, float lx, float wz, float wy, float roof_height, float side, Vec3 roof) {
const float pitch = roof_height / wz;
const float z0 = side * (wz + EAVES), y0 = wy - EAVES * pitch; /* eaves */
const float z1 = 0.0f, y1 = wy + roof_height; /* ridge */
const float slope = sqrtf((z1 - z0) * (z1 - z0) + (y1 - y0) * (y1 - y0));
const int rows = (int)ceilf(slope / TILE_ROW);
Vec3 up = vec3(0.0f, (y1 - y0) / slope, (z1 - z0) / slope); /* up the slope */
Vec3 n = vec3_normalize(vec3(0.0f, wz, side * roof_height)); /* out of the roof */
const float x0 = -lx - VERGE, x1 = lx + VERGE;
for (int r = 0; r < rows; r++) {
float t0 = (float)r * TILE_ROW, t1 = fminf(t0 + TILE_ROW + 0.04f, slope);
Vec3 a = vec3(0.0f, y0 + up.y * t0, z0 + up.z * t0), c = vec3(0.0f, y0 + up.y * t1, z0 + up.z * t1);
Vec3 lip = vec3_scale(n, TILE_LIP);
Vec3 shade = vec3_scale(roof, 0.92f + 0.12f * (float)(r % 2));
/* the row: from its proud lower edge back down onto the roof at its top */
Vec3 p0 = vec3_add(a, lip);
MeshBuilder_gridQuad(b, vec3(x0, p0.y, p0.z), vec3(x1, p0.y, p0.z), vec3(x1, c.y, c.z), vec3(x0, c.y, c.z),
n, shade, CELL_SIZE * 2.0f);
/* the lower edge of the row: painted darker, but lit like the roof, so the
ink pass doesn't trace every row (far off they boiled into dark blotches) */
MeshBuilder_gridQuad(b, vec3(x0, a.y, a.z), vec3(x1, a.y, a.z), vec3(x1, p0.y, p0.z), vec3(x0, p0.y, p0.z),
n, vec3_scale(roof, 0.6f), CELL_SIZE * 2.0f);
}
/* Underside, all of it (from inside under a roof without a ceiling, or below the
overhang, you don't look through it), and the board along the eaves */
Vec3 under = vec3_scale(n, -1.0f), down = vec3_scale(n, -0.02f);
MeshBuilder_gridQuad(b, vec3_add(vec3(x1, y0, z0), down), vec3_add(vec3(x0, y0, z0), down), vec3_add(vec3(x0, y1, z1), down),
vec3_add(vec3(x1, y1, z1), down), under, vec3_scale(roof, 0.55f), CELL_SIZE * 2.0f);
addBlock(b, vec3(0.0f, y0 - 0.06f, z0), vec3(x1 - x0, 0.16f, 0.04f), COLOR_FASCIA);
/* ... and under the roof where it reaches past the gables (else seen from below you look through it) */
for (int e = -1; e <= 1; e += 2) {
float xa = (float)e * lx, xb = (float)e * (lx + VERGE);
MeshBuilder_gridQuad(b, vec3(xa, wy, side * wz), vec3(xb, wy, side * wz), vec3(xb, y1, z1), vec3(xa, y1, z1),
under, vec3_scale(roof, 0.55f), CELL_SIZE * 2.0f);
}
/* Boards along the gable ends */
for (int e = -1; e <= 1; e += 2) {
Vec3 mid = vec3((float)e * (lx + VERGE), (y0 + y1) * 0.5f - 0.05f, (z0 + z1) * 0.5f);
size_t first = b->vertex_count;
addBlock(b, vec3_zero(), vec3(0.04f, 0.16f, slope), COLOR_FASCIA);
Mat4 m = mat4_mul(mat4_translate(mid), mat4_rotate_x(-side * atan2f(y1 - y0, fabsf(z1 - z0)))); /* (clockwise, see linalg.h) */
transformSince(b, first, m);
}
}
static Vec3 wallColor(BuildingLook look) {
const bool plaster = look == BUILDING_PLASTER || look == BUILDING_PLASTER_DARK;
return look == BUILDING_BRICK ? COLOR_BRICK : plaster ? COLOR_PLASTER : COLOR_HALL_WALL;
}
/* The roof on walls of that size (ridge along X): tiled or a plain hall roof, and the gables. */
static void addRoof(MeshBuilder *b, float length, float width, float wall_height, float roof_height, BuildingLook look) {
const bool plaster = look == BUILDING_PLASTER || look == BUILDING_PLASTER_DARK;
const bool tiled = plaster || look == BUILDING_BRICK;
const Vec3 wall = wallColor(look);
const Vec3 roof = look == BUILDING_BRICK ? COLOR_ROOF : look == BUILDING_PLASTER ? COLOR_LIGHT_ROOF
: look == BUILDING_PLASTER_DARK ? COLOR_DARK_ROOF
: look == BUILDING_HALL_RED ? COLOR_RED_ROOF : COLOR_HALL_ROOF;
const float lx = length * 0.5f, wz = width * 0.5f;
const float wy = wall_height, ry = wall_height + roof_height;
if (tiled) { /* tiles, overhanging eaves and verges, a ridge cap */
addTiledSlope(b, lx, wz, wy, roof_height, -1.0f, roof);
addTiledSlope(b, lx, wz, wy, roof_height, 1.0f, roof);
size_t first = b->vertex_count;
shapes_addTube(b, -lx - VERGE, 0.11f, 0.11f, length + 2.0f * VERGE, 8, vec3_scale(roof, 0.75f), true);
transformSince(b, first, mat4_mul(mat4_translate(vec3(0.0f, ry + 0.03f, 0.0f)), mat4_rotate_z(-PI_F / 2.0f)));
} else {
/* Farm halls: plain roof slopes with real slope normals */
Vec3 back_n = vec3_normalize(vec3(0.0f, wz, -roof_height));
Vec3 front_n = vec3_normalize(vec3(0.0f, wz, roof_height));
MeshBuilder_gridQuad(b, vec3(-lx, wy, -wz), vec3(lx, wy, -wz), vec3(lx, ry, 0), vec3(-lx, ry, 0),
back_n, roof, CELL_SIZE);
MeshBuilder_gridQuad(b, vec3(-lx, wy, wz), vec3(lx, wy, wz), vec3(lx, ry, 0), vec3(-lx, ry, 0),
front_n, roof, CELL_SIZE);
if (look == BUILDING_HALL_SOLAR) {
addSolarPanels(b, length, vec3(-lx, wy, -wz), vec3(-lx, ry, 0.0f), back_n);
addSolarPanels(b, length, vec3(-lx, wy, wz), vec3(-lx, ry, 0.0f), front_n);
}
}
/* Gables: walls up to the roof (inside too, a little in) */
const Vec3 gable = wall;
MeshBuilder_gridTriangle(b, vec3(-lx, wy, -wz), vec3(-lx, wy, wz), vec3(-lx, ry, 0),
vec3(-1, 0, 0), gable, CELL_SIZE);
MeshBuilder_gridTriangle(b, vec3(lx, wy, -wz), vec3(lx, wy, wz), vec3(lx, ry, 0),
vec3(1, 0, 0), gable, CELL_SIZE);
MeshBuilder_gridTriangle(b, vec3(-lx + 0.2f, wy, -wz), vec3(-lx + 0.2f, wy, wz), vec3(-lx + 0.2f, ry - 0.1f, 0),
vec3(1, 0, 0), vec3_scale(gable, 0.9f), CELL_SIZE);
MeshBuilder_gridTriangle(b, vec3(lx - 0.2f, wy, -wz), vec3(lx - 0.2f, wy, wz), vec3(lx - 0.2f, ry - 0.1f, 0),
vec3(-1, 0, 0), vec3_scale(gable, 0.9f), CELL_SIZE);
}
/* Brick walls: courses of mortar and the joints between the bricks, staggered,
only a shade lighter (the ink doesn't trace every one), on the four faces. */
#define COURSE 0.42f /* m up the wall per course (a few bricks: from afar it reads as brick) */
#define BRICK_LEN 0.9f
#define MORTAR 0.03f
static void addBrickCourses(MeshBuilder *b, float length, float width, float wall_height, Vec3 wall, float course, float brick_len) {
const Vec3 mortar = vec3_add(wall, vec3(0.06f, 0.06f, 0.05f));
const float lx = length * 0.5f, wz = width * 0.5f, out = 0.006f;
for (int face = 0; face < 4; face++) {
/* faces: +z, -z (length long), +x, -x (width long) */
float span = face < 2 ? length : width, half = span * 0.5f;
for (int c = 1; (float)c * course < wall_height - 0.1f; c++) {
float y = (float)c * course;
float y0 = y - MORTAR * 0.5f, y1 = y + MORTAR * 0.5f;
/* the course line, then the joints of the course above it */
for (int j = -1; j < (int)(span / brick_len) + 1; j++) {
float a0 = j < 0 ? -half : -half + ((float)j + (c % 2 ? 0.5f : 0.0f)) * brick_len;
float a1 = j < 0 ? half : a0 + MORTAR;
if (j >= 0 && (a0 < -half + 0.05f || a1 > half - 0.05f)) continue;
float ya = j < 0 ? y0 : y1, yb = j < 0 ? y1 : fminf(y + course - MORTAR * 0.5f, wall_height - 0.02f);
Vec3 n, p0, p1, p2, p3;
switch (face) {
case 0: n = vec3(0, 0, 1); p0 = vec3(a0, ya, wz + out); p1 = vec3(a1, ya, wz + out); p2 = vec3(a1, yb, wz + out); p3 = vec3(a0, yb, wz + out); break;
case 1: n = vec3(0, 0, -1); p0 = vec3(a1, ya, -wz - out); p1 = vec3(a0, ya, -wz - out); p2 = vec3(a0, yb, -wz - out); p3 = vec3(a1, yb, -wz - out); break;
case 2: n = vec3(1, 0, 0); p0 = vec3(lx + out, ya, a1); p1 = vec3(lx + out, ya, a0); p2 = vec3(lx + out, yb, a0); p3 = vec3(lx + out, yb, a1); break;
default: n = vec3(-1, 0, 0); p0 = vec3(-lx - out, ya, a0); p1 = vec3(-lx - out, ya, a1); p2 = vec3(-lx - out, yb, a1); p3 = vec3(-lx - out, yb, a0); break;
}
GLuint i0 = MeshBuilder_vertex(b, p0, n, mortar), i1 = MeshBuilder_vertex(b, p1, n, mortar);
GLuint i2 = MeshBuilder_vertex(b, p2, n, mortar), i3 = MeshBuilder_vertex(b, p3, n, mortar);
MeshBuilder_quad(b, i0, i1, i2, i3);
}
}
}
}
static void addBuilding(MeshBuilder *b, float length, float width, float wall_height,
float roof_height, BuildingLook look) {
const Vec3 wall = wallColor(look);
const Vec3 brick[FACE_COUNT] = { wall, wall, wall, wall, wall, wall };
shapes_addBox(b, vec3(0.0f, wall_height * 0.5f, 0.0f), vec3(length, wall_height, width), brick);
if (look == BUILDING_BRICK) addBrickCourses(b, length, width, wall_height, wall, COURSE, BRICK_LEN);
addRoof(b, length, width, wall_height, roof_height, look);
}
static const Vec3 COLOR_FRAME_LIGHT = { 0.93f, 0.91f, 0.86f };
static const Vec3 COLOR_FRAME_DARK = { 0.40f, 0.29f, 0.21f };
/* A window standing out of a wall facing +Z (side 1) or -Z (side -1): glass,
frame, cross bars and a sill. */
static void addWindow(MeshBuilder *b, float cx, float cy, float w, float h, float z, float side, Vec3 frame) {
const float fw = 0.08f, out = 0.06f;
addBlock(b, vec3(cx, cy, z + side * 0.005f), vec3(w, h, 0.01f), COLOR_WINDOW); /* glass */
addBlock(b, vec3(cx, cy + h * 0.5f, z + side * out * 0.5f), vec3(w + fw * 2.0f, fw, out), frame); /* frame */
addBlock(b, vec3(cx, cy - h * 0.5f, z + side * out * 0.5f), vec3(w + fw * 2.0f, fw, out), frame);
addBlock(b, vec3(cx - w * 0.5f, cy, z + side * out * 0.5f), vec3(fw, h, out), frame);
addBlock(b, vec3(cx + w * 0.5f, cy, z + side * out * 0.5f), vec3(fw, h, out), frame);
addBlock(b, vec3(cx, cy, z + side * 0.02f), vec3(0.04f, h, 0.03f), frame); /* cross bars */
addBlock(b, vec3(cx, cy + h * 0.12f, z + side * 0.02f), vec3(w, 0.04f, 0.03f), frame);
addBlock(b, vec3(cx, cy - h * 0.5f - fw * 0.5f - 0.02f, z + side * 0.07f), vec3(w + 0.26f, 0.05f, 0.14f), /* sill */
vec3_scale(frame, 0.85f));
}
/* A door in a frame, with panels, a knob and a step. */
static void addDoor(MeshBuilder *b, float cx, float w, float h, float z, float side, Vec3 frame) {
const float fw = 0.09f;
addBlock(b, vec3(cx, h * 0.5f, z + side * 0.02f), vec3(w, h, 0.04f), COLOR_DOOR); /* door */
for (int k = 0; k < 2; k++) /* panels */
addBlock(b, vec3(cx, h * (0.3f + 0.42f * (float)k), z + side * 0.045f), vec3(w * 0.65f, h * 0.3f, 0.015f),
vec3_scale(COLOR_DOOR, 0.8f));
addBlock(b, vec3(cx + w * 0.36f, h * 0.48f, z + side * 0.07f), vec3(0.05f, 0.05f, 0.05f), COLOR_STEEL); /* knob */
addBlock(b, vec3(cx, h + fw * 0.5f, z + side * 0.05f), vec3(w + fw * 2.0f, fw, 0.1f), frame); /* frame */
addBlock(b, vec3(cx - w * 0.5f - fw * 0.5f, h * 0.5f, z + side * 0.05f), vec3(fw, h, 0.1f), frame);
addBlock(b, vec3(cx + w * 0.5f + fw * 0.5f, h * 0.5f, z + side * 0.05f), vec3(fw, h, 0.1f), frame);
addBlock(b, vec3(cx, 0.06f, z + side * 0.16f), vec3(w + 0.5f, 0.12f, 0.32f), (Vec3){ 0.56f, 0.54f, 0.52f }); /* step */
}
/* ---------- Public shapes ---------- */
Mesh Shapes_box(Vec3 size, Palette palette) {
MeshBuilder b;
MeshBuilder_init(&b);
shapes_addBox(&b, vec3_zero(), size, PALETTE_FACES[palette]);
return Mesh_fromBuilder(&b);
}
Mesh Shapes_cylinder(float diameter, float height, int segments, Palette palette) {
MeshBuilder b;
MeshBuilder_init(&b);
shapes_addTube(&b, -height * 0.5f, diameter * 0.5f, diameter * 0.5f, height, segments, paletteColor(palette), true);
return Mesh_fromBuilder(&b);
}
#define FRUIT_LAYER 0.38f /* fruit tree crowns are layered more tightly */
static float layerStep(TreeSize size) { return size.fruit ? FRUIT_LAYER : 0.6f; }
float Shapes_treeHeight(TreeSize size) {
return size.trunk_height + (float)(size.leaf_layers - 1) * layerStep(size) + 0.8f;
}
static const Vec3 COLOR_FRUIT_LEAVES = { 0.42f, 0.66f, 0.30f };
static const Vec3 COLOR_APPLE_RED = { 0.84f, 0.20f, 0.16f };
unsigned Shapes_treeSeed(float x, float z) {
return (unsigned)(int)(x * 811.0f) ^ ((unsigned)(int)(z * 677.0f) << 10);
}
Mesh Shapes_tree(TreeSize size) {
const int segments = 12;
MeshBuilder b;
MeshBuilder_init(&b);
shapes_addTube(&b, 0.0f, size.trunk_radius, size.trunk_radius, size.trunk_height, segments, COLOR_BARK, false);
if (size.cut) shapes_addCutEnd(&b, size.trunk_radius); /* (not hollow) */
else shapes_addTrunkFoot(&b, size.trunk_radius, 0.0f, 0.45f, size.seed, segments);
/* Leaf cones, stacked: a pine shrinks towards the top, a fruit tree is round */
const Vec3 leaves = size.fruit ? COLOR_FRUIT_LEAVES : COLOR_LEAVES;
const int full = size.fruit ? TREE_FRUIT_LAYERS : size.leaf_layers;
unsigned seed = 7u + (unsigned)(size.trunk_height * 100.0f);
const Vec3 dead = { 0.48f, 0.38f, 0.24f };
for (int l = 0; l < size.leaf_layers; l++) {
float y = size.trunk_height + (float)l * layerStep(size) - (size.fruit ? 0.3f : 0.0f);
float r = size.fruit ? size.leaf_radius * (0.45f + 0.55f * sinf(PI_F * ((float)l + 0.7f) / ((float)full + 0.4f)))
: size.leaf_radius * (1.0f - (float)l / (float)size.leaf_layers);
Vec3 tip = vec3(0.0f, y + 0.8f, 0.0f);
Vec3 leaves_l = leaves;
if (size.old) { /* (old: some layers gone, some dead and thin, the top broken off) */
unsigned h = (unsigned)l * 2654435761u ^ 0x9e3779b9u;
h ^= h >> 13;
int k = (int)(h % 7u);
if (k == 0 || l >= size.leaf_layers - 3) continue;
if (k <= 2) { leaves_l = dead; r *= 0.75f; }
else if (k == 3) leaves_l = vec3_lerp(leaves, dead, 0.5f);
if (l >= size.leaf_layers - 6) r *= 0.7f;
}
for (int i = 0; i < segments; i++) {
float a0 = 2.0f * PI_F * (float)i / (float)segments;
float a1 = 2.0f * PI_F * (float)(i + 1) / (float)segments;
Vec3 p0 = vec3(cosf(a0) * r, y, sinf(a0) * r);
Vec3 p1 = vec3(cosf(a1) * r, y, sinf(a1) * r);
Vec3 n = vec3_normalize(vec3_cross(vec3_sub(p0, tip), vec3_sub(p1, tip)));
if (n.y < 0.0f) n = vec3_scale(n, -1.0f); /* cone surface faces up/out */
MeshBuilder_gridTriangle(&b, tip, p0, p1, n, leaves_l, CELL_SIZE);
/* Underside closes the cone so it casts a shadow */
MeshBuilder_gridTriangle(&b, vec3(0.0f, y, 0.0f), p0, p1, vec3(0, -1, 0),
leaves_l, CELL_SIZE);
}
/* A few apples hanging at the rim */
for (int k = 0; size.fruit && k < 3; k++) {
float a = 2.0f * PI_F * scatter(&seed);
addBlock(&b, vec3(cosf(a) * r * 0.92f, y + 0.02f, sinf(a) * r * 0.92f), vec3(0.09f, 0.09f, 0.09f), COLOR_APPLE_RED);
}
}
if (size.old) { /* the trunk on up through the crown, bare, snapped off up top: a jagged end */
float top = size.trunk_height + (float)(size.leaf_layers - 2) * layerStep(size);
shapes_addTube(&b, size.trunk_height, size.trunk_radius * 0.8f, size.trunk_radius * 0.3f, top - size.trunk_height, segments, COLOR_BARK, false);
for (int k = 0; k < 4; k++) {
float a = 1.7f * (float)k;
addBlock(&b, vec3(cosf(a) * size.trunk_radius * 0.18f, top + 0.15f + 0.12f * (float)(k % 2), sinf(a) * size.trunk_radius * 0.18f),
vec3(size.trunk_radius * 0.18f, 0.35f + 0.15f * (float)k, size.trunk_radius * 0.18f), (Vec3){ 0.62f, 0.56f, 0.44f }); /* (splinters) */
}
for (int k = 0; k < 3; k++) { /* dead branches sticking out, bare */
float a = 2.3f * (float)k + 0.4f, y = size.trunk_height + 1.0f + 2.6f * (float)k;
Vec3 from = vec3(cosf(a) * size.trunk_radius * 0.6f, y, sinf(a) * size.trunk_radius * 0.6f);
Vec3 to = vec3(cosf(a) * (size.leaf_radius * 0.9f), y + 0.6f, sinf(a) * (size.leaf_radius * 0.9f));
size_t f2 = b.vertex_count;
float len = vec3_length(vec3_sub(to, from));
shapes_addTube(&b, 0.0f, 0.07f, 0.03f, len, 6, COLOR_BARK, false);
Vec3 ay = vec3_normalize(vec3_sub(to, from)); /* (the tube's +Y along the branch) */
Vec3 x = vec3_normalize(vec3_cross(ay, vec3(0.0f, 1.0f, 0.0f))), z = vec3_cross(x, ay);
Mat4 m = mat4_identity();
m.m[0] = x.x; m.m[1] = x.y; m.m[2] = x.z;
m.m[4] = ay.x; m.m[5] = ay.y; m.m[6] = ay.z;
m.m[8] = z.x; m.m[9] = z.y; m.m[10] = z.z;
m.m[12] = from.x; m.m[13] = from.y; m.m[14] = from.z;
transformSince(&b, f2, m);
}
}
return Mesh_fromBuilder(&b);
}
#define ROAD_STEP 1.0f /* meters between rows along a road */
#define LINE_LIFT 0.02f /* paint on the road (the road itself is the ground) */
static const Vec3 COLOR_ROAD_PAINT = { 0.93f, 0.92f, 0.86f };
/* Rows along the polyline, ROAD_STEP apart; corners get the mitered direction. */
static RoadRow *roadRows(const Vec3 *points, int count, int *n_out) {
float total = 0.0f;
for (int i = 0; i + 1 < count; i++)
total += vec3_length(vec3(points[i + 1].x - points[i].x, 0.0f, points[i + 1].z - points[i].z));
int cap = (int)(total / ROAD_STEP) + 2 * count + 2, n = 0;
RoadRow *rows = malloc((size_t)cap * sizeof(*rows));
if (!rows) return NULL;
float along = 0.0f;
for (int i = 0; i + 1 < count; i++) {
Vec3 d = vec3_sub(points[i + 1], points[i]);
d.y = 0.0f;
float len = vec3_length(d);
if (len < 1e-3f) continue;
d = vec3_scale(d, 1.0f / len);
int steps = (int)ceilf(len / ROAD_STEP);
bool last = i + 2 == count;
for (int k = 0; k <= steps && n < cap; k++) {
if (k == steps && !last) break; /* the next segment starts there */
float t = (float)k / (float)steps;
Vec3 dir = d;
float miter = 1.0f;
if (k == 0 && i > 0) { /* corner: halfway between both directions, a bit wider */
Vec3 prev = vec3_sub(points[i], points[i - 1]);
prev.y = 0.0f;
if (vec3_length(prev) > 1e-3f) {
dir = vec3_normalize(vec3_add(vec3_normalize(prev), d));
miter = 1.0f / fmaxf(vec3_dot(dir, d), 0.5f);
}
}
rows[n++] = (RoadRow){ vec3_lerp(points[i], points[i + 1], t),
vec3_scale(vec3(-dir.z, 0.0f, dir.x), miter), along + len * t };
}
along += len;
}
*n_out = n;
return rows;
}
static GLuint roadVertex(MeshBuilder *b, const RoadRow *row, float across, float lift, Vec3 color, ShapeGround g) {
Vec3 q = vec3_add(row->p, vec3_scale(row->side, across));
float x = q.x - g.origin.x, z = q.z - g.origin.z;
return MeshBuilder_vertex(b, vec3(x, groundAt(g, x, z) + lift, z), vec3(0.0f, 1.0f, 0.0f), color);
}
/* A painted line: a thin strip over the road, solid or dashed. */
static void addRoadLine(MeshBuilder *b, const RoadRow *rows, int n, RoadLine line, ShapeGround g) {
const float period = line.dash + line.gap, half = line.width * 0.5f;
for (int r = 1; r < n; r++) {
if (line.gap > 0.0f && fmodf((rows[r - 1].along + rows[r].along) * 0.5f, period) > line.dash) continue;
GLuint a = roadVertex(b, &rows[r - 1], line.offset - half, LINE_LIFT, COLOR_ROAD_PAINT, g);
GLuint c = roadVertex(b, &rows[r - 1], line.offset + half, LINE_LIFT, COLOR_ROAD_PAINT, g);
GLuint e = roadVertex(b, &rows[r], line.offset + half, LINE_LIFT, COLOR_ROAD_PAINT, g);
GLuint f = roadVertex(b, &rows[r], line.offset - half, LINE_LIFT, COLOR_ROAD_PAINT, g);
MeshBuilder_quad(b, a, c, e, f);
}
}
Mesh Shapes_roadLines(const Vec3 *points, int count, const RoadLine *lines, int line_count, ShapeGround g) {
MeshBuilder b;
MeshBuilder_init(&b);
int n = 0;
RoadRow *rows = count >= 2 ? roadRows(points, count, &n) : NULL;
if (!rows) return Mesh_fromBuilder(&b);
for (int i = 0; i < line_count; i++) addRoadLine(&b, rows, n, lines[i], g);
free(rows);
return Mesh_fromBuilder(&b);
}
/* ---------- Gravel and stones ---------- */
static const Vec3 STONE_COLORS[] = {
{ 0.64f, 0.63f, 0.60f }, { 0.78f, 0.73f, 0.62f }, { 0.47f, 0.45f, 0.44f }, { 0.70f, 0.56f, 0.44f }, { 0.86f, 0.84f, 0.80f },
};
/* A lumpy rock: a squashed sphere with a ragged radius. */
void shapes_addRock(MeshBuilder *b, Vec3 center, Vec3 half, unsigned *seed, Vec3 color) {
enum { RINGS = 4, SEGS = 7 };
GLuint top = MeshBuilder_vertex(b, vec3_add(center, vec3(0.0f, half.y * (0.8f + 0.3f * scatter(seed)), 0.0f)), vec3(0, 1, 0), color);
GLuint first = (GLuint)b->vertex_count;
for (int r = 1; r < RINGS; r++) {
float th = PI_F * (float)r / (float)RINGS;
for (int k = 0; k < SEGS; k++) {
float ph = 2.0f * PI_F * ((float)k + 0.3f * scatter(seed)) / (float)SEGS;
Vec3 d = vec3(sinf(th) * cosf(ph), cosf(th), sinf(th) * sinf(ph));
float rad = 0.72f + 0.45f * scatter(seed);
Vec3 p = vec3(d.x * half.x * rad, d.y * half.y * rad, d.z * half.z * rad);
Vec3 shade = vec3_scale(color, 0.9f + 0.2f * scatter(seed));
MeshBuilder_vertex(b, vec3_add(center, p), vec3_normalize(vec3(d.x / half.x, d.y / half.y, d.z / half.z)), shade);
}
}
GLuint bottom = MeshBuilder_vertex(b, vec3_add(center, vec3(0.0f, -half.y * 0.85f, 0.0f)), vec3(0, -1, 0), color);
for (int k = 0; k < SEGS; k++) {
GLuint k1 = (GLuint)((k + 1) % SEGS);
MeshBuilder_triangle(b, top, first + (GLuint)k, first + k1);
for (int r = 0; r + 1 < RINGS - 1; r++) {
GLuint a = first + (GLuint)(r * SEGS);
MeshBuilder_quad(b, a + (GLuint)k, a + k1, a + SEGS + k1, a + SEGS + (GLuint)k);
}
GLuint last = first + (GLuint)((RINGS - 2) * SEGS);
MeshBuilder_triangle(b, bottom, last + (GLuint)k, last + k1);
}
}
Mesh Shapes_stone(Vec3 size, unsigned seed) {
MeshBuilder b;
MeshBuilder_init(&b);
if (seed == 0) seed = 1;
Vec3 color = STONE_COLORS[(seed >> 3) % (sizeof(STONE_COLORS) / sizeof(STONE_COLORS[0]))];
shapes_addRock(&b, vec3_zero(), vec3_scale(size, 0.5f), &seed, color);
return Mesh_fromBuilder(&b);
}
Mesh Shapes_gravelStones(const Vec3 *points, int count, float width, float density, bool road, ShapeGround g) {
MeshBuilder b;
MeshBuilder_init(&b);
int n = 0;
RoadRow *rows = count >= 2 ? roadRows(points, count, &n) : NULL;
if (!rows) return Mesh_fromBuilder(&b);
unsigned seed = 0x5EEDu ^ (unsigned)(points[0].x * 13.0f) ^ ((unsigned)(points[0].z * 7.0f) << 8);
/* Loose stones: fewer in the ruts and the grassy middle, bigger ones at the edges */
for (int r = 1; r < n; r++) {
float len = rows[r].along - rows[r - 1].along;
int stones = (int)(density * len * width + scatter(&seed));
for (int k = 0; k < stones; k++) {
float u = scatter(&seed) - 0.5f, t = scatter(&seed), a = fabsf(u);
bool rut = road && fabsf(a - 0.24f) < 0.06f, middle = road && a < 0.08f;
if ((rut || middle) && scatter(&seed) < 0.7f) continue;
float s = (a > 0.42f ? 0.07f + 0.08f * scatter(&seed) : 0.03f + 0.05f * scatter(&seed));
Vec3 row_p = vec3_lerp(rows[r - 1].p, rows[r].p, t), side = vec3_lerp(rows[r - 1].side, rows[r].side, t);
Vec3 q = vec3_add(row_p, vec3_scale(side, width * u));
float x = q.x - g.origin.x, z = q.z - g.origin.z;
Vec3 c = STONE_COLORS[(unsigned)(scatter(&seed) * 5.0f) % 5u];
float yaw = scatter(&seed) * PI_F;
Vec3 half = vec3(s * 0.5f, s * 0.22f, s * (0.3f + 0.15f * scatter(&seed)));
size_t first = b.vertex_count;
shapes_addRock(&b, vec3_zero(), half, &seed, c);
transformSince(&b, first, mat4_mul(mat4_translate(vec3(x, groundAt(g, x, z) + half.y * 0.4f, z)), mat4_rotate_y(yaw)));
}
}
free(rows);
return Mesh_fromBuilder(&b);
}
Mesh Shapes_building(BuildingSize size, bool with_details) {
MeshBuilder b;
MeshBuilder_init(&b);
addBuilding(&b, size.length, size.width, size.wall_height, size.roof_height, size.look);
if (with_details) {
const float lx = size.length * 0.5f;
const float z = size.width * 0.5f + 0.01f; /* just in front of the wall */
/* Windows on both long walls, a row per storey; none where the door is */
const Vec3 frame = size.look == BUILDING_BRICK ? COLOR_FRAME_DARK : COLOR_FRAME_LIGHT;
const float z0 = size.width * 0.5f;
const float win_w = 1.0f, win_h = 1.2f;
const int storeys = size.wall_height >= 4.6f ? 2 : 1;
const int count = size.length >= 7.0f ? (int)(size.length / 3.2f) : 2;
const float dw = 1.0f, dh = 2.1f;
const float dx = size.door_set ? size.door_x : -lx + 0.5f * size.length / (float)count;
for (int f = 0; f < storeys; f++) {
float wy = size.wall_height * ((float)f + 0.55f) / (float)storeys;
for (int i = 0; i < count; i++) {
float cx = -lx + ((float)i + 0.5f) * size.length / (float)count;
bool by_door = f == 0 && fabsf(cx - dx) < win_w * 0.5f + dw * 0.5f + 0.4f;
if (!by_door) addWindow(&b, cx, wy, win_w, win_h, z0, 1.0f, frame);
addWindow(&b, cx, wy, win_w, win_h, -z0, -1.0f, frame);
}
}
addDoor(&b, dx, dw, dh, z0, 1.0f, frame);
(void)z;
}
return Mesh_fromBuilder(&b);
}
Vec3 Shapes_roofColor(BuildingLook look) {
return look == BUILDING_BRICK ? COLOR_ROOF : look == BUILDING_PLASTER ? COLOR_LIGHT_ROOF
: look == BUILDING_PLASTER_DARK ? COLOR_DARK_ROOF : look == BUILDING_HALL_RED ? COLOR_RED_ROOF : COLOR_HALL_ROOF;
}
Mesh Shapes_structure(const ShapeSlab *slabs, int n_slabs, const ShapeOpening *openings, int n_openings,
const ShapeRoof *roofs, int n_roofs) {
MeshBuilder b;
MeshBuilder_init(&b);
for (int i = 0; i < n_slabs; i++) {
const ShapeSlab *s = &slabs[i];
Vec3 size = vec3_sub(s->max, s->min);
if (size.x <= 0.0f || size.y <= 0.0f || size.z <= 0.0f) continue;
addBlock(&b, vec3_scale(vec3_add(s->min, s->max), 0.5f), size, s->color);
if (s->brick) {
size_t first = b.vertex_count;
addBrickCourses(&b, size.x, size.z, size.y, s->color, COURSE * 0.5f, BRICK_LEN * 0.5f); /* (seen close up: finer) */
transformSince(&b, first, mat4_translate(vec3((s->min.x + s->max.x) * 0.5f, s->min.y, (s->min.z + s->max.z) * 0.5f)));
}
}
for (int i = 0; i < n_openings; i++) {
const ShapeOpening *o = &openings[i];
size_t first = b.vertex_count;
/* made facing +Z at the origin (on the wall face), then turned and moved there */
switch (o->kind) {
case OPENING_WINDOW:
addWindow(&b, 0.0f, 0.0f, o->w, o->h, 0.0f, 1.0f, o->frame);
break;
case OPENING_PANE: /* seen from inside: the glass and a frame, no sill */
addBlock(&b, vec3(0.0f, 0.0f, 0.005f), vec3(o->w, o->h, 0.01f), COLOR_WINDOW);
addBlock(&b, vec3(0.0f, o->h * 0.5f, 0.02f), vec3(o->w + 0.12f, 0.06f, 0.04f), o->frame);
addBlock(&b, vec3(0.0f, -o->h * 0.5f, 0.02f), vec3(o->w + 0.12f, 0.06f, 0.04f), o->frame);
addBlock(&b, vec3(-o->w * 0.5f, 0.0f, 0.02f), vec3(0.06f, o->h, 0.04f), o->frame);
addBlock(&b, vec3(o->w * 0.5f, 0.0f, 0.02f), vec3(0.06f, o->h, 0.04f), o->frame);
break;
case OPENING_DOOR:
addDoor(&b, 0.0f, o->w, o->h, 0.0f, 1.0f, o->frame);
break;
case OPENING_FRAME: { /* an open doorway: just the frame round it */
const float fw = 0.09f;
addBlock(&b, vec3(0.0f, o->h + fw * 0.5f, 0.02f), vec3(o->w + fw * 2.0f, fw, 0.06f), o->frame);
addBlock(&b, vec3(-o->w * 0.5f - fw * 0.5f, o->h * 0.5f, 0.02f), vec3(fw, o->h, 0.06f), o->frame);
addBlock(&b, vec3(o->w * 0.5f + fw * 0.5f, o->h * 0.5f, 0.02f), vec3(fw, o->h, 0.06f), o->frame);
break;
}
}
transformSince(&b, first, mat4_mul(mat4_translate(o->at), mat4_rotate_y(o->yaw)));
}
for (int i = 0; i < n_roofs; i++) {
const ShapeRoof *r = &roofs[i];
size_t first = b.vertex_count;
addRoof(&b, r->length, r->width, 0.0f, r->roof_height, r->look);
if (r->valley > 0.0f) { /* its ridge runs on into the roof across it: a valley each side, no eaves */
const float lx = r->length * 0.5f, wz = r->width * 0.5f, rh = r->roof_height;
const Vec3 color = Shapes_roofColor(r->look);
for (int side = -1; side <= 1; side += 2) {
const float sd = (float)side;
Vec3 n = vec3_normalize(vec3(0.0f, wz, sd * rh)), up = vec3_scale(n, 0.05f); /* (on a level with the tiles) */
MeshBuilder_gridTriangle(&b, vec3_add(vec3(lx, 0.0f, sd * wz), up), vec3_add(vec3(lx, rh, 0.0f), up),
vec3_add(vec3(lx + r->valley, rh, 0.0f), up), n, color, CELL_SIZE * 2.0f);
MeshBuilder_gridTriangle(&b, vec3(lx, 0.0f, sd * wz), vec3(lx, rh, 0.0f), vec3(lx + r->valley, rh, 0.0f),
vec3_scale(n, -1.0f), vec3_scale(color, 0.55f), CELL_SIZE * 2.0f);
}
size_t cap = b.vertex_count;
shapes_addTube(&b, lx, 0.11f, 0.11f, r->valley, 8, vec3_scale(color, 0.75f), true);
transformSince(&b, cap, mat4_mul(mat4_translate(vec3(0.0f, rh + 0.03f, 0.0f)), mat4_rotate_z(PI_F / 2.0f))); /* (up the tube -> +X: on into the roof across, not out past the far gable) */
}
Mat4 turn = r->across ? mat4_rotate_y(PI_F * 0.5f) : mat4_identity(); /* (ridge along Z) */
transformSince(&b, first, mat4_mul(mat4_translate(vec3(r->center.x, r->wall_top, r->center.z)), turn));
}
return Mesh_fromBuilder(&b);
}