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_props.c raw
/*
* The small things as meshes: bottles, food, tools, wood, puddles, the
* fireplace and the trash, furniture, the hose, the dog and the ladder.
*/
#include "render/shapes_internal.h"
static const Vec3 COLOR_GLASS = { 0.42f, 0.24f, 0.08f }; /* brown beer bottle */
static const Vec3 COLOR_LABEL = { 0.93f, 0.86f, 0.62f };
static const Vec3 COLOR_CAP = { 0.85f, 0.70f, 0.25f };
static const Vec3 COLOR_BEER = { 0.62f, 0.44f, 0.14f }; /* spilled, soaking into the ground */
static const Vec3 COLOR_CLEAR = { 0.78f, 0.86f, 0.88f }; /* vodka bottle */
static const Vec3 COLOR_RED = { 0.80f, 0.20f, 0.18f };
static const Vec3 COLOR_WOOD = { 0.62f, 0.44f, 0.26f };
static const Vec3 COLOR_CUT = { 0.90f, 0.76f, 0.52f }; /* fresh sawn wood */
static const Vec3 COLOR_ORANGE = { 0.95f, 0.50f, 0.15f }; /* chainsaw */
static const Vec3 COLOR_DARK = { 0.18f, 0.16f, 0.16f };
static const Vec3 COLOR_WATER = { 0.62f, 0.80f, 0.90f }; /* water bottle */
static const Vec3 COLOR_BLUE = { 0.20f, 0.40f, 0.75f };
static const Vec3 COLOR_APPLE = { 0.82f, 0.18f, 0.14f };
static const Vec3 COLOR_BREAD = { 0.78f, 0.55f, 0.28f };
Mesh Shapes_bottle(float diameter, float height, BottleStyle style) {
const bool spirits = style == BOTTLE_SPIRITS, water = style == BOTTLE_WATER;
const int seg = 14;
const float r = diameter * 0.5f, neck = r * 0.38f;
const float base = -height * 0.5f;
const float body_h = height * (spirits ? 0.66f : 0.58f), shoulder_h = height * (spirits ? 0.08f : 0.14f);
const float neck_h = height * (spirits ? 0.20f : 0.24f);
Vec3 glass = water ? COLOR_WATER : (spirits ? COLOR_CLEAR : COLOR_GLASS);
Vec3 cap = water ? COLOR_BLUE : (spirits ? COLOR_RED : COLOR_CAP), label = COLOR_LABEL;
if (style == BOTTLE_COLA) { glass = (Vec3){ 0.20f, 0.11f, 0.07f }; cap = COLOR_RED; label = (Vec3){ 0.78f, 0.12f, 0.10f }; }
if (style == BOTTLE_ORANGE) { glass = (Vec3){ 0.96f, 0.56f, 0.14f }; cap = COLOR_ORANGE; label = (Vec3){ 0.20f, 0.36f, 0.72f }; }
if (style == BOTTLE_MATE) { glass = (Vec3){ 0.55f, 0.33f, 0.10f }; cap = COLOR_BLUE; label = (Vec3){ 0.93f, 0.84f, 0.50f }; }
MeshBuilder b;
MeshBuilder_init(&b);
shapes_addTube(&b, base, r, r, body_h * 0.25f, seg, glass, true); /* bottom */
shapes_addTube(&b, base + body_h * 0.25f, r * 1.02f, r * 1.02f, body_h * 0.5f, seg, label, false);
shapes_addTube(&b, base + body_h * 0.75f, r, r, body_h * 0.25f, seg, glass, false);
shapes_addTube(&b, base + body_h, r, neck, shoulder_h, seg, glass, false); /* shoulder */
shapes_addTube(&b, base + body_h + shoulder_h, neck, neck, neck_h, seg, glass, false);
shapes_addTube(&b, base + body_h + shoulder_h + neck_h, neck * 1.15f, neck * 1.15f,
height - body_h - shoulder_h - neck_h, seg, cap, true); /* cap */
return Mesh_fromBuilder(&b);
}
Mesh Shapes_apple(float diameter) {
const float r = diameter * 0.5f;
MeshBuilder b;
MeshBuilder_init(&b);
shapes_addTube(&b, -r, r * 0.75f, r, r * 0.6f, 10, COLOR_APPLE, true);
shapes_addTube(&b, -r + r * 0.6f, r, r * 0.8f, r * 1.0f, 10, COLOR_APPLE, true);
shapes_addTube(&b, r * 0.6f, 0.006f, 0.004f, r * 0.6f, 4, COLOR_BARK, false); /* stem */
return Mesh_fromBuilder(&b);
}
Mesh Shapes_toy(int variant, Vec3 size) {
MeshBuilder b;
MeshBuilder_init(&b);
if (variant == 0) { /* a rubber ball: red, a yellow band round it */
const Vec3 red = { 0.85f, 0.18f, 0.15f }, yellow = { 0.95f, 0.82f, 0.22f };
const float r = size.x * 0.5f;
const int rings = 6;
for (int k = 0; k < rings; k++) { /* stacked bands, a sphere */
float a0 = -PI_F * 0.5f + PI_F * (float)k / (float)rings, a1 = -PI_F * 0.5f + PI_F * (float)(k + 1) / (float)rings;
shapes_addTube(&b, r * sinf(a0), fmaxf(r * cosf(a0), 0.002f), fmaxf(r * cosf(a1), 0.002f), r * (sinf(a1) - sinf(a0)), 10,
k == rings / 2 || k == rings / 2 - 1 ? yellow : red, k == 0 || k == rings - 1);
}
} else { /* a rope toy: a short thick twisted rope (along x), a knot at each end */
const Vec3 blue = { 0.28f, 0.40f, 0.70f }, white = { 0.90f, 0.88f, 0.82f };
const float r = size.y * 0.5f, len = size.x;
size_t first = b.vertex_count;
shapes_addTube(&b, -len * 0.5f, r * 0.7f, r * 0.7f, len, 8, blue, true);
transformSince(&b, first, mat4_rotate_z(-PI_F * 0.5f));
for (int e = -1; e <= 1; e += 2) {
first = b.vertex_count;
shapes_addTube(&b, -r * 1.2f, r * 1.1f, r * 1.1f, r * 2.4f, 8, white, true);
transformSince(&b, first, mat4_mul(mat4_translate(vec3((float)e * (len * 0.5f - r), 0.0f, 0.0f)), mat4_rotate_z(-PI_F * 0.5f)));
}
for (int k = -2; k <= 2; k++) /* the twist */
addBlock(&b, vec3((float)k * len * 0.12f, 0.0f, 0.0f), vec3(0.015f, r * 1.5f, r * 1.5f), white);
}
return Mesh_fromBuilder(&b);
}
/* A sleeve flat on the ground from the shoulder at (x, z), out and down at this angle off straight out. */
static void addFlatSleeve(MeshBuilder *b, float side, float angle, Vec3 color, Vec3 cuff) {
const float len = 0.48f, wide = 0.13f, y = 0.013f;
float yaw = side > 0.0f ? -angle : PI_F + angle; /* (lays local +X along (side cos a, 0, -sin a)) */
Mat4 at = mat4_mul(mat4_translate(vec3(side * 0.24f, 0.0f, 0.22f)), mat4_rotate_y(yaw));
size_t first = b->vertex_count;
addBlock(b, vec3(len * 0.5f, y, 0.0f), vec3(len, 0.022f, wide), color);
addBlock(b, vec3(len - 0.03f, y + 0.002f, 0.0f), vec3(0.06f, 0.024f, wide * 0.9f), cuff);
transformSince(b, first, at);
}
Mesh Shapes_pullover(int lay, Vec3 color, float gap) {
MeshBuilder b;
MeshBuilder_init(&b);
Vec3 dark = vec3_scale(color, 0.78f); /* (the ribbed band, cuffs and collar) */
if (lay == 0) { /* spread out: the body, the band at the bottom, the collar, both sleeves out */
addBlock(&b, vec3(0.0f, 0.012f, 0.0f), vec3(0.5f, 0.024f, 0.62f), color);
addBlock(&b, vec3(0.0f, 0.014f, -0.28f), vec3(0.5f, 0.026f, 0.06f), dark);
addBlock(&b, vec3(0.0f, 0.016f, 0.28f), vec3(0.17f, 0.028f, 0.06f), dark);
addFlatSleeve(&b, 1.0f, 0.55f, color, dark);
addFlatSleeve(&b, -1.0f, 0.55f, color, dark);
} else if (lay == 1) { /* over an edge: rolled over its top, the front (+Z) and the back hanging down, the sleeves in front */
float in = gap * 0.5f + 0.015f;
addBlock(&b, vec3(0.0f, 0.016f, 0.0f), vec3(0.46f, 0.032f, gap + 0.06f), color);
addBlock(&b, vec3(0.0f, -0.15f, in), vec3(0.46f, 0.32f, 0.024f), color);
addBlock(&b, vec3(0.0f, -0.30f, in + 0.002f), vec3(0.46f, 0.05f, 0.026f), dark);
addBlock(&b, vec3(0.0f, -0.13f, -in), vec3(0.46f, 0.28f, 0.024f), color);
for (int s = -1; s <= 1; s += 2) {
size_t first = b.vertex_count;
addBlock(&b, vec3(0.0f, -0.22f, 0.0f), vec3(0.11f, 0.44f, 0.022f), color);
addBlock(&b, vec3(0.0f, -0.42f, 0.0f), vec3(0.1f, 0.05f, 0.024f), dark);
transformSince(&b, first, mat4_mul(mat4_translate(vec3((float)s * 0.17f, 0.0f, in + 0.024f)), mat4_rotate_z((float)s * 0.08f)));
}
} else { /* folded up */
addBlock(&b, vec3(0.0f, 0.03f, 0.0f), vec3(0.32f, 0.06f, 0.26f), color);
addBlock(&b, vec3(0.0f, 0.03f, 0.125f), vec3(0.322f, 0.04f, 0.014f), dark);
addBlock(&b, vec3(0.0f, 0.061f, -0.02f), vec3(0.14f, 0.004f, 0.05f), dark);
}
return Mesh_fromBuilder(&b);
}
Mesh Shapes_bread(Vec3 size) {
MeshBuilder b;
MeshBuilder_init(&b);
addBlock(&b, vec3(0.0f, -size.y * 0.15f, 0.0f), vec3(size.x, size.y * 0.7f, size.z), COLOR_BREAD);
addBlock(&b, vec3(0.0f, size.y * 0.35f, 0.0f), vec3(size.x * 0.85f, size.y * 0.3f, size.z * 0.8f), COLOR_BREAD);
return Mesh_fromBuilder(&b);
}
Mesh Shapes_appleCrate(Vec3 size) {
MeshBuilder b;
MeshBuilder_init(&b);
addBlock(&b, vec3(0.0f, -0.05f, 0.0f), vec3(size.x, size.y - 0.1f, size.z), COLOR_WOOD);
for (int i = 0; i < 9; i++) { /* apples heaped on top */
float x = ((float)(i % 3) - 1.0f) * size.x * 0.28f, z = ((float)(i / 3) - 1.0f) * size.z * 0.28f;
float y = size.y * 0.5f - 0.1f + (i == 4 ? 0.06f : 0.0f);
size_t first = b.vertex_count;
shapes_addTube(&b, y, 0.05f, 0.05f, 0.09f, 8, COLOR_APPLE, true);
for (size_t v = first; v < b.vertex_count; v++) { /* move it off the axis */
b.vertices[v].position.x += x;
b.vertices[v].position.z += z;
}
}
return Mesh_fromBuilder(&b);
}
Mesh Shapes_barrel(float diameter, float height) {
const float r = diameter * 0.5f, base = -height * 0.5f;
MeshBuilder b;
MeshBuilder_init(&b);
shapes_addTube(&b, base, r * 0.9f, r, height * 0.5f, 16, COLOR_WOOD, true);
shapes_addTube(&b, base + height * 0.5f, r, r * 0.9f, height * 0.5f, 16, COLOR_WOOD, false);
shapes_addTube(&b, base + height * 0.2f, r * 0.98f, r * 0.98f, 0.04f, 16, COLOR_DARK, false); /* hoops */
shapes_addTube(&b, base + height * 0.78f, r * 0.98f, r * 0.98f, 0.04f, 16, COLOR_DARK, false);
shapes_addTube(&b, height * 0.5f - 0.02f, r * 0.86f, r * 0.86f, 0.01f, 16, COLOR_WATER, true); /* water */
return Mesh_fromBuilder(&b);
}
Mesh Shapes_log(float diameter, float length) {
const float r = diameter * 0.5f, half = length * 0.5f;
MeshBuilder b;
MeshBuilder_init(&b);
shapes_addTube(&b, -half, r, r, length, 14, COLOR_BARK, false);
shapes_addTube(&b, -half - 0.004f, r * 0.96f, r * 0.96f, 0.008f, 14, COLOR_CUT, true); /* sawn ends */
shapes_addTube(&b, half - 0.004f, r * 0.96f, r * 0.96f, 0.008f, 14, COLOR_CUT, true);
return Mesh_fromBuilder(&b);
}
Mesh Shapes_woodpile(float length, float depth) {
MeshBuilder b;
MeshBuilder_init(&b);
Vec3 wood[FACE_COUNT];
for (int f = 0; f < FACE_COUNT; f++) wood[f] = vec3_scale(COLOR_BARK, f == FACE_TOP ? 1.0f : 0.8f);
for (int k = -1; k <= 1; k += 2) /* the sleepers, centered on the pivot height */
shapes_addBox(&b, vec3(0.0f, 0.0f, (float)k * depth * 0.3f), vec3(length, 0.08f, 0.1f), wood);
return Mesh_fromBuilder(&b);
}
Mesh Shapes_firewood(float diameter, float length, int parts) {
const float r = diameter * 0.5f, half = length * 0.5f;
const float sweep = 2.0f * PI_F / (float)parts;
const int segments = parts == 2 ? 10 : 6;
/* The wedge spans angles 0..sweep in the XZ plane; shift it onto its box center */
const Vec3 shift = parts == 2 ? vec3(0.0f, 0.0f, -r * 0.5f) : vec3(-r * 0.5f, 0.0f, -r * 0.5f);
MeshBuilder b;
MeshBuilder_init(&b);
/* Bark */
GLuint ring = (GLuint)b.vertex_count;
for (int end = 0; end <= 1; end++) {
float y = end ? half : -half;
for (int i = 0; i <= segments; i++) {
float a = sweep * (float)i / (float)segments;
Vec3 n = vec3(cosf(a), 0.0f, sinf(a));
MeshBuilder_vertex(&b, vec3_add(vec3(r * n.x, y, r * n.z), shift), n, COLOR_BARK);
}
}
for (int i = 0; i < segments; i++) {
GLuint a = ring + (GLuint)i, top = a + (GLuint)(segments + 1);
MeshBuilder_quad(&b, a, a + 1, top + 1, top);
}
/* Split faces: from the heart out to the bark at both edges of the wedge */
const float edges[2] = { 0.0f, sweep };
for (int k = 0; k < 2; k++) {
Vec3 out = vec3(cosf(edges[k]), 0.0f, sinf(edges[k]));
Vec3 n = k == 0 ? vec3(0.0f, 0.0f, -1.0f) : vec3(-sinf(sweep), 0.0f, cosf(sweep));
GLuint c0 = MeshBuilder_vertex(&b, vec3_add(vec3(0.0f, -half, 0.0f), shift), n, COLOR_CUT);
GLuint c1 = MeshBuilder_vertex(&b, vec3_add(vec3(0.0f, half, 0.0f), shift), n, COLOR_CUT);
GLuint o1 = MeshBuilder_vertex(&b, vec3_add(vec3(r * out.x, half, r * out.z), shift), n, COLOR_CUT);
GLuint o0 = MeshBuilder_vertex(&b, vec3_add(vec3(r * out.x, -half, r * out.z), shift), n, COLOR_CUT);
MeshBuilder_quad(&b, c0, c1, o1, o0);
}
/* Sawn ends */
for (int end = 0; end <= 1; end++) {
float y = end ? half : -half;
Vec3 n = vec3(0.0f, end ? 1.0f : -1.0f, 0.0f);
GLuint center = MeshBuilder_vertex(&b, vec3_add(vec3(0.0f, y, 0.0f), shift), n, COLOR_CUT);
GLuint first = (GLuint)b.vertex_count;
for (int i = 0; i <= segments; i++) {
float a = sweep * (float)i / (float)segments;
MeshBuilder_vertex(&b, vec3_add(vec3(r * cosf(a), y, r * sinf(a)), shift), n, COLOR_CUT);
}
for (int i = 0; i < segments; i++)
MeshBuilder_triangle(&b, center, first + (GLuint)i, first + (GLuint)i + 1);
}
return Mesh_fromBuilder(&b);
}
Mesh Shapes_branch(float length) {
MeshBuilder b;
MeshBuilder_init(&b);
addBlock(&b, vec3(0.0f, 0.0f, 0.0f), vec3(0.04f, length, 0.04f), COLOR_BARK);
addBlock(&b, vec3(0.0f, length * 0.15f, 0.08f), vec3(0.025f, 0.025f, 0.16f), COLOR_BARK);
addBlock(&b, vec3(0.07f, -length * 0.1f, 0.0f), vec3(0.14f, 0.025f, 0.025f), COLOR_BARK);
addBlock(&b, vec3(0.0f, length * 0.42f, 0.0f), vec3(0.14f, 0.12f, 0.14f), COLOR_LEAVES);
addBlock(&b, vec3(0.0f, length * 0.15f, 0.17f), vec3(0.1f, 0.08f, 0.1f), COLOR_LEAVES);
return Mesh_fromBuilder(&b);
}
Mesh Shapes_shears(float length) {
const float half = length * 0.5f;
MeshBuilder b;
MeshBuilder_init(&b);
addBlock(&b, vec3(-0.018f, -half * 0.35f, 0.0f), vec3(0.022f, length * 0.6f, 0.025f), COLOR_RED); /* handles */
addBlock(&b, vec3( 0.018f, -half * 0.35f, 0.0f), vec3(0.022f, length * 0.6f, 0.025f), COLOR_RED);
addBlock(&b, vec3(-0.008f, half * 0.65f, 0.0f), vec3(0.016f, length * 0.35f, 0.008f), COLOR_STEEL); /* blades */
addBlock(&b, vec3( 0.008f, half * 0.65f, 0.0f), vec3(0.016f, length * 0.35f, 0.008f), COLOR_STEEL);
return Mesh_fromBuilder(&b);
}
Mesh Shapes_chainsaw(void) {
MeshBuilder b;
MeshBuilder_init(&b);
addBlock(&b, vec3(0.0f, 0.0f, 0.2f), vec3(0.16f, 0.18f, 0.32f), COLOR_ORANGE); /* motor, at the back */
addBlock(&b, vec3(0.0f, 0.12f, 0.24f), vec3(0.04f, 0.06f, 0.2f), COLOR_DARK); /* top handle */
addBlock(&b, vec3(0.0f, -0.05f, -0.17f), vec3(0.025f, 0.07f, 0.42f), COLOR_STEEL); /* bar, pointing forward (-Z), low on the motor: lying, it nearly touches the ground */
addBlock(&b, vec3(0.0f, 0.0f, 0.03f), vec3(0.18f, 0.03f, 0.03f), COLOR_DARK); /* front grip */
return Mesh_fromBuilder(&b);
}
Mesh Shapes_axe(float length) {
MeshBuilder b;
MeshBuilder_init(&b);
Vec3 wood[FACE_COUNT], steel[FACE_COUNT];
for (int f = 0; f < FACE_COUNT; f++) { wood[f] = COLOR_WOOD; steel[f] = COLOR_STEEL; }
const float half = length * 0.5f;
shapes_addBox(&b, vec3(0.0f, 0.0f, 0.0f), vec3(0.03f, length, 0.035f), wood); /* handle */
shapes_addBox(&b, vec3(0.0f, half - 0.06f, 0.06f), vec3(0.022f, 0.09f, 0.11f), steel); /* blade */
shapes_addBox(&b, vec3(0.0f, half - 0.06f, 0.125f), vec3(0.012f, 0.14f, 0.02f), steel); /* edge */
shapes_addBox(&b, vec3(0.0f, half - 0.06f, -0.02f), vec3(0.04f, 0.07f, 0.04f), steel); /* back */
return Mesh_fromBuilder(&b);
}
/* Uneven flat disc just above y = 0. */
#define DECAL_LIFT 0.03f /* puddles sit this far above the ground (and above roads) */
#define DECAL_RINGS 3
#define DECAL_SEGS 14
static float flatAtZero(const void *ctx, float x, float z) {
(void)ctx;
(void)x;
(void)z;
return 0.0f;
}
/* A ragged disc lying on the ground: rings of points that each follow it. */
static void addPuddle(MeshBuilder *b, float radius, Vec3 color, unsigned *seed, ShapeGround g) {
const Vec3 up = { 0.0f, 1.0f, 0.0f };
float rim[DECAL_SEGS];
for (int i = 0; i < DECAL_SEGS; i++) rim[i] = radius * (0.7f + 0.5f * scatter(seed));
GLuint center = MeshBuilder_vertex(b, vec3(0.0f, groundAt(g, 0.0f, 0.0f) + DECAL_LIFT, 0.0f), up, color);
GLuint first = (GLuint)b->vertex_count;
for (int k = 1; k <= DECAL_RINGS; k++) {
for (int i = 0; i < DECAL_SEGS; i++) {
float a = (float)i / (float)DECAL_SEGS * 2.0f * PI_F, r = rim[i] * (float)k / (float)DECAL_RINGS;
float x = cosf(a) * r, z = sinf(a) * r;
MeshBuilder_vertex(b, vec3(x, groundAt(g, x, z) + DECAL_LIFT, z), up, color);
}
}
for (int i = 0; i < DECAL_SEGS; i++) {
GLuint next = (GLuint)((i + 1) % DECAL_SEGS);
MeshBuilder_triangle(b, center, first + (GLuint)i, first + next);
for (int k = 1; k < DECAL_RINGS; k++) {
GLuint in = first + (GLuint)((k - 1) * DECAL_SEGS), out = in + DECAL_SEGS;
MeshBuilder_quad(b, in + (GLuint)i, out + (GLuint)i, out + next, in + next);
}
}
}
Mesh Shapes_vomit(float radius, unsigned seed, ShapeGround g) {
const Vec3 up = { 0.0f, 1.0f, 0.0f };
const Vec3 sick = { 0.74f, 0.68f, 0.30f }, chunk = { 0.56f, 0.46f, 0.22f };
MeshBuilder b;
MeshBuilder_init(&b);
addPuddle(&b, radius, sick, &seed, g);
for (int i = 0; i < 10; i++) {
float a = scatter(&seed) * 2.0f * PI_F, d = radius * 0.8f * scatter(&seed);
float x = cosf(a) * d, z = sinf(a) * d;
Vec3 c = vec3(x, groundAt(g, x, z) + DECAL_LIFT + 0.008f, z);
float size = radius * (0.06f + 0.08f * scatter(&seed));
GLuint p0 = MeshBuilder_vertex(&b, vec3_add(c, vec3(size, 0.0f, 0.0f)), up, chunk);
GLuint p1 = MeshBuilder_vertex(&b, vec3_add(c, vec3(-size * 0.5f, 0.0f, size)), up, chunk);
GLuint p2 = MeshBuilder_vertex(&b, vec3_add(c, vec3(-size * 0.5f, 0.0f, -size)), up, chunk);
MeshBuilder_triangle(&b, p0, p1, p2);
}
return Mesh_fromBuilder(&b);
}
/* A tree stump, centered: bark round the sides, roots flaring into the ground, the cut on top. */
#define FOOT_FLARE 0.12f /* a trunk is this much wider at the ground ... */
#define FOOT_HEIGHT 0.45f /* ... narrowing to its own width this high */
static float footRadius(float r, float h) { return r * (1.0f + FOOT_FLARE * (1.0f - clampf(h / FOOT_HEIGHT, 0.0f, 1.0f))); }
void shapes_addTrunkFoot(MeshBuilder *b, float r, float ground_y, float top, unsigned seed, int segments) {
float h = fminf(top - ground_y, FOOT_HEIGHT);
shapes_addTube(b, ground_y - 0.05f, footRadius(r, 0.0f) * 1.02f, footRadius(r, h), h + 0.05f, segments, COLOR_BARK, false);
int roots = 4 + (int)(scatter(&seed) * 3.0f);
for (int k = 0; k < roots; k++) { /* flaring out, into the ground */
float a = ((float)k + 0.4f * scatter(&seed)) / (float)roots * 2.0f * PI_F;
float len = r * (0.9f + 0.6f * scatter(&seed)), thick = r * (0.35f + 0.15f * scatter(&seed));
size_t first = b->vertex_count;
shapes_addTube(b, 0.0f, thick, thick * 0.3f, len, 6, COLOR_BARK, false);
Mat4 m = mat4_mul(mat4_translate(vec3(cosf(a) * r * 0.75f, ground_y + thick * 0.5f, sinf(a) * r * 0.75f)),
mat4_mul(mat4_rotate_y(-a), mat4_rotate_z(-PI_F * 0.5f - 0.35f)));
transformSince(b, first, m);
}
}
Mesh Shapes_stump(float diameter, float height, unsigned seed) {
const float r = diameter * 0.5f, half = height * 0.5f;
MeshBuilder b;
MeshBuilder_init(&b);
shapes_addTrunkFoot(&b, r, -half, half, seed, 12); /* (the very foot the tree had: no pop when it's cut) */
if (height > FOOT_HEIGHT) shapes_addTube(&b, -half + FOOT_HEIGHT, r, r, height - FOOT_HEIGHT, 12, COLOR_BARK, false);
const float top = footRadius(r, height);
shapes_addTube(&b, half - 0.004f, top * 0.95f, top * 0.95f, 0.008f, 12, COLOR_CUT, true);
shapes_addTube(&b, half - 0.002f, top * 0.35f, top * 0.35f, 0.008f, 10, vec3_scale(COLOR_CUT, 0.85f), true); /* the heart */
return Mesh_fromBuilder(&b);
}
/* The cut end of a felled trunk (at y = 0, facing down). */
void shapes_addCutEnd(MeshBuilder *b, float r) {
shapes_addTube(b, -0.008f, r * 0.97f, r * 0.97f, 0.008f, 12, COLOR_CUT, true);
shapes_addTube(b, -0.010f, r * 0.35f, r * 0.35f, 0.004f, 10, vec3_scale(COLOR_CUT, 0.85f), true);
}
/* What's left of a stump chopped out: dug-up earth and root ends in it (following the ground). */
Mesh Shapes_roots(float diameter, unsigned seed, ShapeGround g) {
const Vec3 earth = { 0.26f, 0.19f, 0.12f };
const float r = diameter * 0.5f;
MeshBuilder b;
MeshBuilder_init(&b);
addPuddle(&b, r * 1.6f, earth, &seed, g);
int roots = 5 + (int)(scatter(&seed) * 3.0f);
for (int k = 0; k < roots; k++) {
float a = ((float)k + 0.5f * scatter(&seed)) / (float)roots * 2.0f * PI_F;
float from = r * (0.1f + 0.3f * scatter(&seed)), len = r * (1.6f + 1.6f * scatter(&seed));
float thick = r * (0.16f + 0.16f * scatter(&seed));
float x = cosf(a) * from, z = sinf(a) * from;
size_t first = b.vertex_count;
shapes_addTube(&b, 0.0f, thick, thick * 0.35f, len, 6, COLOR_BARK, true);
Mat4 m = mat4_mul(mat4_translate(vec3(x, groundAt(g, x, z) + thick * 0.2f, z)),
mat4_mul(mat4_rotate_y(-a), mat4_rotate_z(-PI_F * 0.5f - 0.08f))); /* lying, half in the earth */
transformSince(&b, first, m);
}
return Mesh_fromBuilder(&b);
}
Mesh Shapes_water(float side) {
const Vec3 water = { 0.30f, 0.52f, 0.46f };
const float h = side * 0.5f;
MeshBuilder b;
MeshBuilder_init(&b);
MeshBuilder_gridQuad(&b, vec3(-h, 0.0f, -h), vec3(h, 0.0f, -h), vec3(h, 0.0f, h), vec3(-h, 0.0f, h),
vec3(0.0f, 1.0f, 0.0f), water, CELL_SIZE * 2.0f);
return Mesh_fromBuilder(&b);
}
#define STINK_LINES 3
#define STINK_HEIGHT 0.6f
#define STINK_STEPS 10
#define STINK_SIDES 5
/* Cartoon stink: a few thin wavy lines rising from the ground. */
Mesh Shapes_stink(float radius, unsigned seed, ShapeGround g) {
const Vec3 color = { 0.72f, 0.88f, 0.42f };
MeshBuilder b;
MeshBuilder_init(&b);
for (int line = 0; line < STINK_LINES; line++) {
float a = ((float)line + 0.6f * scatter(&seed)) / (float)STINK_LINES * 2.0f * PI_F, d = radius * (0.35f + 0.4f * scatter(&seed));
float bx = cosf(a) * d, bz = sinf(a) * d, phase = scatter(&seed) * 6.0f;
float base = groundAt(g, bx, bz) + 0.04f + 0.06f * scatter(&seed);
float height = STINK_HEIGHT * (0.7f + 0.5f * scatter(&seed));
Vec3 sway = vec3(cosf(a + 1.3f), 0.0f, sinf(a + 1.3f));
GLuint start = (GLuint)b.vertex_count;
for (int k = 0; k <= STINK_STEPS; k++) {
float t = (float)k / (float)STINK_STEPS;
float wave = 0.05f * sinf(t * 11.0f + phase) * (0.4f + t);
float r = 0.022f * (1.0f - 0.55f * t);
Vec3 c = vec3(bx + sway.x * wave, base + height * t, bz + sway.z * wave);
for (int i = 0; i < STINK_SIDES; i++) {
float s = (float)i / (float)STINK_SIDES * 2.0f * PI_F;
Vec3 n = vec3(cosf(s), 0.0f, sinf(s));
MeshBuilder_vertex(&b, vec3_add(c, vec3_scale(n, r)), n, color);
}
}
for (int k = 0; k < STINK_STEPS; k++)
for (int i = 0; i < STINK_SIDES; i++) {
GLuint p = start + (GLuint)(k * STINK_SIDES), q = p + STINK_SIDES;
GLuint ni = (GLuint)((i + 1) % STINK_SIDES);
MeshBuilder_quad(&b, p + (GLuint)i, p + ni, q + ni, q + (GLuint)i);
}
}
return Mesh_fromBuilder(&b);
}
Mesh Shapes_shards(float diameter, unsigned seed) {
MeshBuilder b;
MeshBuilder_init(&b);
addPuddle(&b, diameter * 3.0f, COLOR_BEER, &seed, (ShapeGround){ .height = flatAtZero });
/* Glass pieces lying around, some tipped up on an edge */
for (int i = 0; i < 9; i++) {
float a = scatter(&seed) * 2.0f * PI_F, d = diameter * (0.5f + 4.0f * scatter(&seed));
Vec3 c = vec3(cosf(a) * d, 0.012f, sinf(a) * d);
float size = diameter * (0.45f + 0.8f * scatter(&seed)), turn = scatter(&seed) * 2.0f * PI_F;
Vec3 p[3];
for (int k = 0; k < 3; k++) {
float t = turn + (float)k * 2.1f + scatter(&seed) * 0.6f;
p[k] = vec3_add(c, vec3(cosf(t) * size, (k == 0 ? scatter(&seed) * size : 0.0f), sinf(t) * size));
}
Vec3 n = vec3_normalize(vec3_cross(vec3_sub(p[1], p[0]), vec3_sub(p[2], p[0])));
if (n.y < 0.0f) n = vec3_scale(n, -1.0f);
MeshBuilder_gridTriangle(&b, p[0], p[1], p[2], n, i == 0 ? COLOR_LABEL : COLOR_GLASS, CELL_SIZE);
}
return Mesh_fromBuilder(&b);
}
/* ---------- Camp and cleaning ---------- */
static const Vec3 COLOR_STONE = { 0.56f, 0.54f, 0.52f };
static const Vec3 COLOR_ASH = { 0.30f, 0.28f, 0.27f };
static const Vec3 COLOR_SACK = { 0.12f, 0.12f, 0.14f };
static const Vec3 COLOR_BIN = { 0.24f, 0.44f, 0.32f };
static const Vec3 COLOR_BIN_LID = { 0.16f, 0.22f, 0.20f };
/* Colors above 2 are drawn as they are, without light or shade (see scene.frag). */
#define GLOW(r, g, b) { (r) + 2.0f, (g) + 2.0f, (b) + 2.0f }
/* ---------- Bent and turned parts ---------- */
/* Rotation taking +Y to dir, then moved to origin. */
static Mat4 alongY(Vec3 dir, Vec3 origin) {
Vec3 y = vec3_normalize(dir);
Vec3 helper = fabsf(y.y) > 0.9f ? vec3(1, 0, 0) : vec3(0, 1, 0);
Vec3 x = vec3_normalize(vec3_cross(helper, y)), z = vec3_cross(x, y);
Mat4 m = mat4_identity();
m.m[0] = x.x; m.m[1] = x.y; m.m[2] = x.z;
m.m[4] = y.x; m.m[5] = y.y; m.m[6] = y.z;
m.m[8] = z.x; m.m[9] = z.y; m.m[10] = z.z;
m.m[12] = origin.x; m.m[13] = origin.y; m.m[14] = origin.z;
return m;
}
/* A round rod from a to b. */
static void addRod(MeshBuilder *b, Vec3 a, Vec3 to, float r0, float r1, int segments, Vec3 color) {
size_t first = b->vertex_count;
Vec3 d = vec3_sub(to, a);
shapes_addTube(b, 0.0f, r0, r1, vec3_length(d), segments, color, true);
transformSince(b, first, alongY(d, a));
}
static const Vec3 COLOR_IRON = { 0.22f, 0.21f, 0.20f };
Vec3 Shapes_tripodFoot(float diameter, int k) {
float a = 2.0f * PI_F * ((float)k + 0.25f) / 3.0f, out = diameter * 0.5f + TRIPOD_OUT;
return vec3(cosf(a) * out, 0.0f, sinf(a) * out);
}
Mesh Shapes_fireplace(float diameter) {
const float r = diameter * 0.5f;
const int stones = 15;
unsigned seed = 91u;
MeshBuilder b;
MeshBuilder_init(&b);
/* Three long wooden poles meeting over the fire, their feet well away from
it (they don't burn so easily), crossing at the top and sticking out past
the lashing (see Shapes_tripodFoot for where they stand) */
const Vec3 pole = { 0.50f, 0.36f, 0.22f };
for (int k = 0; k < 3; k++) {
Vec3 foot = Shapes_tripodFoot(diameter, k), top = vec3(foot.x * 0.03f, GRILL_TOP + 0.05f, foot.z * 0.03f);
Vec3 past = vec3_add(top, vec3_scale(vec3_normalize(vec3_sub(top, foot)), TRIPOD_PAST));
addRod(&b, foot, past, 0.045f, 0.035f, 7, pole);
}
addRod(&b, vec3(0.0f, GRILL_TOP - 0.02f, 0.0f), vec3(0.0f, GRILL_TOP + 0.14f, 0.0f), 0.075f, 0.075f, 8, (Vec3){ 0.55f, 0.48f, 0.34f }); /* the lashing */
shapes_addTube(&b, 0.0f, r * 0.8f, r * 0.75f, 0.04f, 14, COLOR_ASH, true);
for (int i = 0; i < stones; i++) {
float a = 2.0f * PI_F * ((float)i + 0.3f * scatter(&seed)) / (float)stones;
float s = 0.2f + 0.1f * scatter(&seed), h = 0.15f + 0.09f * scatter(&seed);
Vec3 c = vec3(cosf(a) * (r - s * 0.4f), h * 0.4f, sinf(a) * (r - s * 0.4f));
Vec3 shade = vec3_scale(COLOR_STONE, 0.8f + 0.35f * scatter(&seed));
shapes_addRock(&b, c, vec3(s * 0.55f, h * 0.55f, s * 0.45f), &seed, shade); /* (field stones, not bricks) */
}
return Mesh_fromBuilder(&b);
}
Mesh Shapes_flames(void) {
static const Vec3 OUTER = GLOW(1.00f, 0.42f, 0.10f), INNER = GLOW(1.00f, 0.80f, 0.30f);
static const float TONGUES[][4] = { /* x, z, radius, height */
{ 0.00f, 0.00f, 0.22f, 0.85f }, { 0.14f, 0.06f, 0.14f, 0.55f }, { -0.12f, 0.10f, 0.15f, 0.62f },
{ 0.03f, -0.15f, 0.13f, 0.50f }, { -0.10f, -0.08f, 0.12f, 0.45f },
};
MeshBuilder b;
MeshBuilder_init(&b);
for (unsigned i = 0; i < sizeof(TONGUES) / sizeof(TONGUES[0]); i++) {
const float *t = TONGUES[i];
const GLuint first = (GLuint)b.vertex_count;
shapes_addTube(&b, 0.05f, t[2], 0.0f, t[3], 7, OUTER, false);
for (GLuint v = first; v < (GLuint)b.vertex_count; v++) {
b.vertices[v].position.x += t[0];
b.vertices[v].position.z += t[1];
}
if (i == 0) shapes_addTube(&b, 0.05f, t[2] * 0.55f, 0.0f, t[3] * 0.7f, 7, INNER, false);
}
return Mesh_fromBuilder(&b);
}
Mesh Shapes_sausage(float length, bool grilled) {
const Vec3 raw = { 0.86f, 0.56f, 0.50f }, done = { 0.56f, 0.28f, 0.15f }, stripe = { 0.30f, 0.14f, 0.08f };
const float r = 0.014f, half = length * 0.5f;
MeshBuilder b;
MeshBuilder_init(&b);
shapes_addTube(&b, -half, r * 0.6f, r, r, 8, grilled ? done : raw, true);
shapes_addTube(&b, -half + r, r, r, length - 2.0f * r, 8, grilled ? done : raw, false);
shapes_addTube(&b, half - r, r, r * 0.6f, r, 8, grilled ? done : raw, true);
for (int k = 0; grilled && k < 3; k++)
addBlock(&b, vec3(0.0f, -half * 0.5f + (float)k * half * 0.5f, r * 0.9f), vec3(r * 1.6f, 0.006f, 0.006f), stripe);
return Mesh_fromBuilder(&b);
}
Mesh Shapes_coolBox(Vec3 size) {
const Vec3 body = { 0.30f, 0.52f, 0.78f }, lid = { 0.92f, 0.92f, 0.90f };
MeshBuilder b;
MeshBuilder_init(&b);
addBlock(&b, vec3(0.0f, -size.y * 0.1f, 0.0f), vec3(size.x, size.y * 0.8f, size.z), body);
addBlock(&b, vec3(0.0f, size.y * 0.4f, 0.0f), vec3(size.x * 1.02f, size.y * 0.2f, size.z * 1.02f), lid);
return Mesh_fromBuilder(&b);
}
Mesh Shapes_bagCarton(Vec3 size) {
const Vec3 carton = { 0.66f, 0.50f, 0.32f };
MeshBuilder b;
MeshBuilder_init(&b);
addBlock(&b, vec3_zero(), size, carton);
/* a roll of bags lying on top */
const GLuint first = (GLuint)b.vertex_count;
shapes_addTube(&b, -size.x * 0.3f, 0.06f, 0.06f, size.x * 0.6f, 10, COLOR_SACK, true);
for (GLuint v = first; v < (GLuint)b.vertex_count; v++) { /* lay it along X */
Vec3 p = b.vertices[v].position, n = b.vertices[v].normal;
b.vertices[v].position = vec3(p.y, size.y * 0.5f + 0.06f + p.x, p.z);
b.vertices[v].normal = vec3(n.y, n.x, n.z);
}
return Mesh_fromBuilder(&b);
}
Vec3 Shapes_trashSize(Palette kind) {
switch (kind) {
case PALETTE_WARM: return vec3(0.12f, 0.10f, 0.12f); /* crumpled paper */
case PALETTE_MODERN: return vec3(0.07f, 0.12f, 0.07f); /* a can */
case PALETTE_STANDARD: return vec3(0.09f, 0.06f, 0.24f); /* a squashed plastic bottle */
default: return vec3(0.45f, 0.03f, 0.10f); /* a broken board */
}
}
Mesh Shapes_butt(void) {
MeshBuilder b;
MeshBuilder_init(&b);
addBlock(&b, vec3(-0.006f, 0.0f, 0.0f), vec3(0.018f, 0.011f, 0.011f), vec3(0.86f, 0.6f, 0.34f)); /* filter */
addBlock(&b, vec3(0.009f, 0.0f, 0.0f), vec3(0.012f, 0.01f, 0.01f), vec3(0.55f, 0.53f, 0.5f)); /* burnt end */
return Mesh_fromBuilder(&b);
}
Mesh Shapes_trash(Palette kind, unsigned seed) {
const Vec3 size = Shapes_trashSize(kind);
MeshBuilder b;
MeshBuilder_init(&b);
switch (kind) {
case PALETTE_WARM: { /* crumpled paper: a lumpy little ball */
const Vec3 paper = { 0.90f, 0.88f, 0.80f };
for (int k = 0; k < 4; k++) {
Vec3 c = vec3((scatter(&seed) - 0.5f) * size.x * 0.4f, (scatter(&seed) - 0.5f) * size.y * 0.4f,
(scatter(&seed) - 0.5f) * size.z * 0.4f);
addBlock(&b, c, vec3_scale(size, 0.55f + 0.2f * scatter(&seed)), vec3_scale(paper, 0.9f + 0.1f * scatter(&seed)));
}
break;
}
case PALETTE_MODERN: { /* a can, a bit dented */
const Vec3 cans[] = { { 0.80f, 0.16f, 0.14f }, { 0.20f, 0.42f, 0.70f }, { 0.86f, 0.74f, 0.20f } };
const Vec3 metal = { 0.78f, 0.78f, 0.80f };
Vec3 color = cans[seed % 3u];
shapes_addTube(&b, -size.y * 0.5f, size.x * 0.5f, size.x * 0.46f, size.y * 0.9f, 9, color, true);
shapes_addTube(&b, size.y * 0.4f, size.x * 0.46f, size.x * 0.40f, size.y * 0.1f, 9, metal, true);
break;
}
case PALETTE_STANDARD: { /* a squashed plastic bottle */
const Vec3 plastic = { 0.66f, 0.84f, 0.90f }, cap = { 0.20f, 0.40f, 0.80f };
addBlock(&b, vec3(0.0f, 0.0f, -size.z * 0.08f), vec3(size.x, size.y, size.z * 0.84f), plastic);
addBlock(&b, vec3(0.0f, 0.0f, size.z * 0.42f), vec3(size.x * 0.35f, size.y * 0.6f, size.z * 0.16f), cap);
break;
}
default: { /* a broken board */
const Vec3 wood = { 0.50f, 0.38f, 0.26f };
addBlock(&b, vec3_zero(), size, wood);
addBlock(&b, vec3(size.x * 0.45f, 0.0f, size.z * 0.15f), vec3(size.x * 0.1f, size.y, size.z * 0.6f), vec3_scale(wood, 0.8f));
break;
}
}
return Mesh_fromBuilder(&b);
}
Vec3 Shapes_bagSize(float fullness) {
float d = 0.24f + 0.26f * fullness;
return vec3(d, 0.26f + 0.32f * fullness, d);
}
/* A unit block in white (tinted when drawn): wood chips, sawdust. */
Mesh Shapes_chip(void) {
MeshBuilder b;
MeshBuilder_init(&b);
addBlock(&b, vec3_zero(), vec3(1.0f, 1.0f, 1.0f), vec3(1.0f, 1.0f, 1.0f));
return Mesh_fromBuilder(&b);
}
Mesh Shapes_puff(void) {
MeshBuilder b;
MeshBuilder_init(&b);
const int around = 10, up = 7;
GLuint ring[8][11];
for (int j = 0; j <= up; j++) {
float v = PI_F * (float)j / (float)up;
for (int i = 0; i <= around; i++) {
float u = 2.0f * PI_F * (float)i / (float)around;
Vec3 n = vec3(sinf(v) * cosf(u), cosf(v), sinf(v) * sinf(u));
ring[j][i] = MeshBuilder_vertex(&b, vec3_scale(n, 0.5f), n, vec3(1.0f, 1.0f, 1.0f));
}
}
for (int j = 0; j < up; j++)
for (int i = 0; i < around; i++) MeshBuilder_quad(&b, ring[j][i], ring[j][i + 1], ring[j + 1][i + 1], ring[j + 1][i]);
return Mesh_fromBuilder(&b);
}
Mesh Shapes_bag(float fullness) {
const Vec3 size = Shapes_bagSize(fullness);
const float r = size.x * 0.5f, h = size.y;
MeshBuilder b;
MeshBuilder_init(&b);
shapes_addTube(&b, -h * 0.5f, r * 0.7f, r, h * 0.25f, 10, COLOR_SACK, true);
shapes_addTube(&b, -h * 0.25f, r, r * 0.85f, h * 0.45f, 10, COLOR_SACK, false);
shapes_addTube(&b, h * 0.2f, r * 0.85f, r * 0.15f, h * 0.22f, 10, COLOR_SACK, false);
shapes_addTube(&b, h * 0.42f, r * 0.15f, r * 0.25f, h * 0.08f, 6, COLOR_SACK, true); /* the knot */
return Mesh_fromBuilder(&b);
}
Mesh Shapes_bin(Vec3 size) {
MeshBuilder b;
MeshBuilder_init(&b);
addBlock(&b, vec3(0.0f, -size.y * 0.05f, 0.0f), vec3(size.x, size.y * 0.9f, size.z), COLOR_BIN);
addBlock(&b, vec3(0.0f, size.y * 0.45f, 0.0f), vec3(size.x * 1.04f, size.y * 0.1f, size.z * 1.04f), COLOR_BIN_LID);
return Mesh_fromBuilder(&b);
}
Mesh Shapes_bush(float diameter, float height, unsigned seed) {
const Vec3 green = { 0.30f, 0.52f, 0.24f };
const int segments = 10, layers = 8;
MeshBuilder b;
MeshBuilder_init(&b);
/* A few squashed domes side by side, each a stack of flat cones */
for (int k = 0; k < 3; k++) {
float r = diameter * (0.32f + 0.12f * scatter(&seed)), a = 2.0f * PI_F * scatter(&seed);
float off = diameter * 0.18f * (float)(k > 0), h = height * (0.75f + 0.25f * scatter(&seed));
Vec3 c = vec3(cosf(a) * off, 0.0f, sinf(a) * off);
Vec3 tint = vec3_scale(green, 0.85f + 0.3f * scatter(&seed));
for (int l = 0; l < layers; l++) {
float y = h * (float)l / (float)layers;
float lr = r * sqrtf(fmaxf(sinf(PI_F * ((float)l + 0.6f) / ((float)layers + 0.4f)), 0.0f)) + 0.05f;
Vec3 tip = vec3(c.x, y + h * 0.16f, c.z);
for (int i = 0; i < segments; i++) {
float a0 = 2.0f * PI_F * (float)i / (float)segments, a1 = 2.0f * PI_F * (float)(i + 1) / (float)segments;
Vec3 p0 = vec3(c.x + cosf(a0) * lr, y, c.z + sinf(a0) * lr), p1 = vec3(c.x + cosf(a1) * lr, y, c.z + sinf(a1) * lr);
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);
MeshBuilder_gridTriangle(&b, tip, p0, p1, n, tint, CELL_SIZE);
}
}
}
return Mesh_fromBuilder(&b);
}
Mesh Shapes_marker(float length, float depth) {
static const Vec3 color = GLOW(1.0f, 0.85f, 0.35f);
const float w = 0.07f, hx = length * 0.5f, hz = depth * 0.5f, dash = 0.25f;
MeshBuilder b;
MeshBuilder_init(&b);
const Vec3 up = { 0.0f, 1.0f, 0.0f };
/* Dashes along the four sides */
for (int side = 0; side < 4; side++) {
bool along_x = side < 2;
float len = along_x ? length : depth, fixed = side % 2 ? 1.0f : -1.0f;
for (float t = 0.0f; t < len - 0.01f; t += dash * 2.0f) {
float a0 = -len * 0.5f + t, a1 = fminf(a0 + dash, len * 0.5f);
Vec3 p0 = along_x ? vec3(a0, 0.0f, fixed * hz - w * 0.5f) : vec3(fixed * hx - w * 0.5f, 0.0f, a0);
Vec3 p1 = along_x ? vec3(a1, 0.0f, fixed * hz + w * 0.5f) : vec3(fixed * hx + w * 0.5f, 0.0f, a1);
GLuint i0 = MeshBuilder_vertex(&b, vec3(p0.x, 0.0f, p0.z), up, color);
GLuint i1 = MeshBuilder_vertex(&b, vec3(p1.x, 0.0f, p0.z), up, color);
GLuint i2 = MeshBuilder_vertex(&b, vec3(p1.x, 0.0f, p1.z), up, color);
GLuint i3 = MeshBuilder_vertex(&b, vec3(p0.x, 0.0f, p1.z), up, color);
MeshBuilder_quad(&b, i0, i3, i2, i1);
}
}
return Mesh_fromBuilder(&b);
}
Mesh Shapes_grill(void) {
MeshBuilder b;
MeshBuilder_init(&b);
/* Chain links down to a hook, three short chains out to the grate */
const float hook = -(GRILL_DROP - 0.28f), grate = -GRILL_DROP, ring = 0.42f;
for (float y = 0.0f; y > hook; y -= 0.07f)
addBlock(&b, vec3(0.0f, y - 0.035f, 0.0f), (int)(-y / 0.07f) % 2 ? vec3(0.012f, 0.06f, 0.035f) : vec3(0.035f, 0.06f, 0.012f), COLOR_IRON);
for (int k = 0; k < 3; k++) {
float a = 2.0f * PI_F * (float)k / 3.0f;
addRod(&b, vec3(0.0f, hook, 0.0f), vec3(cosf(a) * ring, grate, sinf(a) * ring), 0.008f, 0.008f, 4, COLOR_IRON);
}
/* The grate: a ring and bars across */
for (int k = 0; k < 14; k++) {
float a0 = 2.0f * PI_F * (float)k / 14.0f, a1 = 2.0f * PI_F * (float)(k + 1) / 14.0f;
addRod(&b, vec3(cosf(a0) * ring, grate, sinf(a0) * ring), vec3(cosf(a1) * ring, grate, sinf(a1) * ring), 0.012f, 0.012f, 4, COLOR_IRON);
}
for (int k = -3; k <= 3; k++) {
float x = (float)k * ring / 3.6f, half = sqrtf(fmaxf(ring * ring - x * x, 0.0f));
addRod(&b, vec3(x, grate, -half), vec3(x, grate, half), 0.007f, 0.007f, 4, COLOR_IRON);
}
return Mesh_fromBuilder(&b);
}
/* ---------- Furniture ---------- */
static const Vec3 COLOR_TIMBER = { 0.62f, 0.46f, 0.30f };
static const Vec3 COLOR_PLYWOOD = { 0.80f, 0.68f, 0.48f };
static const Vec3 COLOR_ENAMEL = { 0.90f, 0.88f, 0.84f };
static const Vec3 COLOR_BLACK = { 0.10f, 0.10f, 0.11f };
static const Vec3 COLOR_CAR = { 0.62f, 0.20f, 0.17f };
static const Vec3 COLOR_TYRE = { 0.12f, 0.12f, 0.13f };
/* A table top on four legs. */
static void addTable(MeshBuilder *b, Vec3 size, float top, Vec3 color) {
const float hx = size.x * 0.5f - 0.05f, hz = size.z * 0.5f - 0.05f;
addBlock(b, vec3(0.0f, size.y - top * 0.5f, 0.0f), vec3(size.x, top, size.z), color);
for (int k = 0; k < 4; k++)
addBlock(b, vec3(k % 2 ? hx : -hx, (size.y - top) * 0.5f, k / 2 ? hz : -hz), vec3(0.06f, size.y - top, 0.06f), color);
}
/* A wheel turned to roll along Z. */
static void addWheel(MeshBuilder *b, Vec3 center, float radius, float width) {
size_t first = b->vertex_count;
shapes_addTube(b, -width * 0.5f, radius, radius, width, 14, COLOR_TYRE, true);
shapes_addTube(b, -width * 0.52f, radius * 0.55f, radius * 0.55f, width * 1.04f, 10, COLOR_STEEL, true);
transformSince(b, first, mat4_mul(mat4_translate(center), mat4_rotate_z(PI_F / 2.0f)));
}
/* A thin stick from a to c (a pole, a guy line, a peg). */
static void addStick(MeshBuilder *b, Vec3 a, Vec3 c, float thick, Vec3 color) {
Vec3 d = vec3_sub(c, a);
float len = vec3_length(d);
if (len < 1e-4f) return;
Vec3 y = vec3_scale(d, 1.0f / len);
Vec3 x = vec3_normalize(fabsf(y.y) < 0.9f ? vec3_cross(y, vec3(0.0f, 1.0f, 0.0f)) : vec3_cross(y, vec3(1.0f, 0.0f, 0.0f)));
Vec3 z = vec3_cross(x, y);
Mat4 m = mat4_identity();
m.m[0] = x.x; m.m[1] = x.y; m.m[2] = x.z;
m.m[4] = y.x; m.m[5] = y.y; m.m[6] = y.z;
m.m[8] = z.x; m.m[9] = z.y; m.m[10] = z.z;
Vec3 mid = vec3_scale(vec3_add(a, c), 0.5f);
m.m[12] = mid.x; m.m[13] = mid.y; m.m[14] = mid.z;
size_t first = b->vertex_count;
addBlock(b, vec3_zero(), vec3(thick, len, thick), color);
transformSince(b, first, m);
}
/* The dome over a hw x hl footprint, h high (rows along z from the back; only
the rows up to `upto` - the cover going over), a little wrinkled when low. */
#define DOME_N 10
static void addDome(MeshBuilder *b, float hw, float hl, float h, Vec3 color, float upto) {
GLuint idx[DOME_N + 1][DOME_N + 1];
for (int j = 0; j <= DOME_N; j++)
for (int i = 0; i <= DOME_N; i++) {
float u = -1.0f + 2.0f * (float)i / DOME_N, v = -1.0f + 2.0f * (float)j / DOME_N;
float fu = powf(fmaxf(0.0f, 1.0f - u * u), 0.55f), fv = powf(fmaxf(0.0f, 1.0f - v * v), 0.55f);
float y = h * fu * fv + (h < 0.15f ? 0.02f * sinf(7.0f * u + 3.0f * v) * fu * fv : 0.0f) + 0.012f;
Vec3 n = vec3_normalize(vec3(u * h / hw, 1.0f, v * h / hl));
Vec3 shade = vec3_scale(color, 0.9f + 0.1f * fu * fv);
idx[j][i] = MeshBuilder_vertex(b, vec3(u * hw, y, v * hl), n, shade);
}
for (int j = 0; j < DOME_N; j++) {
if ((float)(j + 1) / DOME_N > upto + 1e-3f) break;
for (int i = 0; i < DOME_N; i++) MeshBuilder_quad(b, idx[j][i], idx[j][i + 1], idx[j + 1][i + 1], idx[j + 1][i]);
}
}
void Shapes_tentPeg(int big, int k, float *x, float *z) {
const float hw = TENT_W(big) * 0.5f, hl = TENT_L(big) * 0.5f;
static const float AT[10][2] = { { -1.0f, -1.0f }, { 1.0f, -1.0f }, { 1.0f, 1.0f }, { -1.0f, 1.0f }, /* the corners */
{ 0.0f, -1.45f }, { 0.0f, 1.45f }, /* back and front (the door) */
{ -1.35f, 0.0f }, { 1.35f, 0.0f }, { -0.55f, 1.4f }, { 0.55f, 1.4f } };
k = k < 0 ? 0 : k > 9 ? 9 : k;
*x = AT[k][0] * (hw + (k < 4 ? 0.12f : 0.0f));
*z = AT[k][1] * (hl + (k < 4 ? 0.12f : 0.0f));
}
Mesh Shapes_tent(int big, int stage, float progress, int pegs, Vec3 inner, Vec3 fly) {
MeshBuilder b;
MeshBuilder_init(&b);
const float hw = TENT_W(big) * 0.5f, hl = TENT_L(big) * 0.5f, h = TENT_H(big);
const Vec3 sheet = { 0.22f, 0.24f, 0.25f }, pole = { 0.62f, 0.64f, 0.66f }, peg = { 0.7f, 0.7f, 0.72f }, door = vec3_scale(fly, 0.45f);
addBlock(&b, vec3(0.0f, 0.005f, 0.0f), vec3(hw * 2.0f + 0.06f, 0.01f, hl * 2.0f + 0.06f), sheet); /* the ground sheet */
float up = stage == 0 ? 0.04f : stage == 1 ? fmaxf(0.04f, h * 0.95f * progress) : h * 0.95f;
addDome(&b, hw, hl, up, inner, 1.0f);
if (stage >= 1) { /* the poles, corner to corner, crossing at the top */
float ph = stage == 1 ? fmaxf(0.06f, h * progress) : h;
for (int s = -1; s <= 1; s += 2) {
Vec3 last = vec3((float)s * -hw, 0.0f, -hl);
for (int k = 1; k <= 10; k++) {
float t = (float)k / 10.0f;
Vec3 p = vec3((float)s * (-hw + 2.0f * hw * t), ph * sinf(PI_F * t), -hl + 2.0f * hl * t);
addStick(&b, last, p, 0.022f, pole);
last = p;
}
}
}
if (stage >= 2) /* the cover, from the back over the top to the door */
addDome(&b, hw * 1.08f, hl * 1.14f, h * 1.05f, fly, stage == 2 ? progress : 1.0f);
if (stage >= 3) { /* the pegs in so far, with their lines up to the cover */
int n = stage == 3 ? pegs : TENT_PEGS(big);
for (int k = 0; k < n && k < TENT_PEGS(big); k++) {
float x, z;
Shapes_tentPeg(big, k, &x, &z);
addStick(&b, vec3(x, -0.08f, z), vec3(x, 0.05f, z), 0.02f, peg);
if (k >= 4) { /* a guy line from the cover's edge up there */
float ex = x * 0.55f, ez = z * 0.62f, u = ex / (hw * 1.08f), v = ez / (hl * 1.14f);
float ey = h * 1.05f * powf(fmaxf(0.0f, 1.0f - u * u), 0.55f) * powf(fmaxf(0.0f, 1.0f - v * v), 0.55f);
addStick(&b, vec3(x, 0.05f, z), vec3(ex, ey, ez), 0.008f, (Vec3){ 0.85f, 0.4f, 0.15f });
}
}
}
if (stage >= 4) { /* the door: zipped shut, a darker arch in the front */
size_t first = b.vertex_count;
addBlock(&b, vec3(0.0f, h * 0.32f, 0.0f), vec3(hw * 0.75f, h * 0.62f, 0.01f), door);
transformSince(&b, first, mat4_mul(mat4_translate(vec3(0.0f, 0.0f, hl * 1.02f)), mat4_rotate_x(-0.55f)));
}
return Mesh_fromBuilder(&b);
}
/* A shape drawn side on (points (z, y), going round, convex) made solid from
x0 to x1: the two sides and the faces between the points. */
static void addProfile(MeshBuilder *b, const float (*pt)[2], int n, float x0, float x1, Vec3 color) {
float cz = 0.0f, cy = 0.0f;
for (int i = 0; i < n; i++) { cz += pt[i][0]; cy += pt[i][1]; }
cz /= (float)n; cy /= (float)n;
for (int i = 0; i < n; i++) {
const float *p = pt[i], *q = pt[(i + 1) % n];
Vec3 n_out = vec3_normalize(vec3(0.0f, -(q[0] - p[0]), q[1] - p[1])); /* (perpendicular in the side view) */
if (vec3_dot(n_out, vec3(0.0f, (p[1] + q[1]) * 0.5f - cy, (p[0] + q[0]) * 0.5f - cz)) < 0.0f) n_out = vec3_scale(n_out, -1.0f);
MeshBuilder_gridQuad(b, vec3(x0, p[1], p[0]), vec3(x1, p[1], p[0]), vec3(x1, q[1], q[0]), vec3(x0, q[1], q[0]), n_out, color, 0.6f);
for (int s = 0; s < 2; s++) {
float x = s ? x1 : x0;
MeshBuilder_gridTriangle(b, vec3(x, cy, cz), vec3(x, p[1], p[0]), vec3(x, q[1], q[0]), vec3(s ? 1.0f : -1.0f, 0.0f, 0.0f), color, 0.6f);
}
}
}
/* Volkram's old estate car (see shapes.h): rounded off, a faded paint, rust
at the arches, roof rails; the cabin glass all round with the pillars and
the roof over it. Nose to +Z. */
static void carBody(MeshBuilder *bp, Vec3 color, bool boot_open);
Mesh Shapes_car(Vec3 color, bool boot_open) {
MeshBuilder b;
MeshBuilder_init(&b);
carBody(&b, color, boot_open);
return Mesh_fromBuilder(&b);
}
/* The ambulance: the same estate, white, a red band round it, the blue lights on the roof. */
Mesh Shapes_ambulance(void) {
MeshBuilder b;
MeshBuilder_init(&b);
const Vec3 white = { 0.93f, 0.93f, 0.90f }, red = { 0.86f, 0.22f, 0.16f }, blue = { 0.25f, 0.45f, 0.95f };
carBody(&b, white, false);
const float hw = CAR_WIDTH * 0.5f;
for (int s = -1; s <= 1; s += 2) addBlock(&b, vec3((float)s * (hw + 0.012f), 0.76f, -0.1f), vec3(0.01f, 0.13f, CAR_LENGTH * 0.9f), red);
addBlock(&b, vec3(0.0f, 0.76f, CAR_LENGTH * 0.5f + 0.012f), vec3(CAR_WIDTH * 0.8f, 0.1f, 0.01f), red);
addBlock(&b, vec3(0.0f, 1.63f, -0.15f), vec3(CAR_WIDTH * 0.62f, 0.09f, 0.22f), (Vec3){ 0.2f, 0.2f, 0.22f }); /* the light bar */
for (int s = -1; s <= 1; s += 2) addBlock(&b, vec3((float)s * CAR_WIDTH * 0.2f, 1.71f, -0.15f), vec3(0.3f, 0.09f, 0.18f), blue);
for (int s = -1; s <= 1; s += 2) addBlock(&b, vec3((float)s * 0.25f, 1.585f, -1.2f), vec3(0.35f, 0.012f, 0.35f), red); /* (a cross on the roof, more or less) */
return Mesh_fromBuilder(&b);
}
static void carBody(MeshBuilder *bp, Vec3 color, bool boot_open) {
MeshBuilder b = *bp;
const float w = CAR_WIDTH, hw = w * 0.5f, l = CAR_LENGTH, wheel = 0.31f, back = -l * 0.5f;
const Vec3 glass = { 0.34f, 0.42f, 0.48f }, dark = { 0.12f, 0.12f, 0.12f }, trim = { 0.30f, 0.30f, 0.29f };
const Vec3 rust = { 0.45f, 0.27f, 0.16f }, chrome = { 0.62f, 0.63f, 0.62f };
const float BODY[][2] = { { 2.12f, 0.36f }, { 2.30f, 0.48f }, { 2.32f, 0.66f }, { 2.24f, 0.82f }, { 1.95f, 0.90f },
{ 0.80f, 0.97f }, { -2.26f, 0.97f }, { -2.32f, 0.82f }, { -2.32f, 0.50f }, { -2.18f, 0.36f } };
addProfile(&b, BODY, (int)(sizeof BODY / sizeof BODY[0]), -hw, hw, color);
addBlock(&b, vec3(0.0f, 0.34f, 0.0f), vec3(w * 0.94f, 0.08f, l * 0.92f), dark); /* under it */
/* the cabin: glass all round ... */
const float GLASS[][2] = { { 0.80f, 0.96f }, { 0.04f, 1.52f }, { -2.06f, 1.52f }, { -2.27f, 1.38f }, { -2.27f, 0.96f } };
addProfile(&b, GLASS, 5, -hw * 0.88f, hw * 0.88f, glass);
/* ... the roof, the pillars, the sills over it */
const float ROOF[][2] = { { 0.08f, 1.50f }, { 0.02f, 1.57f }, { -2.06f, 1.57f }, { -2.16f, 1.50f } };
addProfile(&b, ROOF, 4, -hw * 0.89f, hw * 0.89f, color);
for (int s = -1; s <= 1; s += 2) {
const float x = (float)s * hw * 0.885f;
addStick(&b, vec3(x, 0.97f, 0.80f), vec3(x, 1.53f, 0.05f), 0.055f, color); /* windscreen pillar */
addBlock(&b, vec3(x, 1.24f, -0.62f), vec3(0.03f, 0.56f, 0.09f), color); /* middle pillar */
addBlock(&b, vec3(x, 1.24f, -1.48f), vec3(0.03f, 0.56f, 0.12f), color); /* back door's pillar */
addStick(&b, vec3(x, 0.97f, -2.26f), vec3(x, 1.52f, -2.14f), 0.08f, color); /* the last one, at the back */
addStick(&b, vec3(x * 0.92f, 1.61f, -0.1f), vec3(x * 0.92f, 1.61f, -1.95f), 0.025f, trim); /* roof rails */
addBlock(&b, vec3(x * 0.92f, 1.585f, -0.2f), vec3(0.03f, 0.04f, 0.05f), trim);
addBlock(&b, vec3(x * 0.92f, 1.585f, -1.85f), vec3(0.03f, 0.04f, 0.05f), trim);
for (int k = 0; k < 2; k++) { /* the wheel arches, dark, a bit of rust round them */
float z = k ? l * 0.31f : -l * 0.31f;
addBlock(&b, vec3((float)s * (hw + 0.005f), 0.52f, z), vec3(0.01f, 0.32f, 0.86f), dark);
addBlock(&b, vec3((float)s * (hw + 0.008f), 0.70f, z + (k ? 0.30f : -0.32f)), vec3(0.01f, 0.07f, 0.16f), rust);
}
addBlock(&b, vec3((float)s * (hw + 0.006f), 0.66f, 0.15f), vec3(0.01f, 0.03f, 1.9f), trim); /* a rubbing strip along the doors */
addBlock(&b, vec3((float)s * (hw + 0.006f), 0.92f, 0.4f), vec3(0.012f, 0.025f, 0.14f), chrome); /* door handles */
addBlock(&b, vec3((float)s * (hw + 0.006f), 0.92f, -0.85f), vec3(0.012f, 0.025f, 0.14f), chrome);
addBlock(&b, vec3((float)s * w * 0.33f, 0.66f, l * 0.5f + 0.01f), vec3(0.30f, 0.14f, 0.03f), (Vec3){ 0.93f, 0.91f, 0.78f }); /* headlights */
addBlock(&b, vec3((float)s * w * 0.40f, 0.78f, back - 0.01f), vec3(0.16f, 0.24f, 0.03f), (Vec3){ 0.66f, 0.14f, 0.11f }); /* tail lights */
addBlock(&b, vec3((float)s * (hw + 0.08f), 1.04f, 0.62f), vec3(0.1f, 0.09f, 0.15f), dark); /* mirrors */
}
addBlock(&b, vec3(0.0f, 0.66f, l * 0.5f + 0.02f), vec3(w * 0.32f, 0.12f, 0.03f), dark); /* the grille */
addBlock(&b, vec3(0.0f, 0.44f, l * 0.5f + 0.05f), vec3(w * 0.96f, 0.12f, 0.09f), trim); /* bumpers */
addBlock(&b, vec3(0.0f, 0.44f, back - 0.05f), vec3(w * 0.96f, 0.12f, 0.09f), trim);
addBlock(&b, vec3(0.0f, 0.62f, back - 0.022f), vec3(0.42f, 0.11f, 0.01f), (Vec3){ 0.92f, 0.92f, 0.88f }); /* the number plate */
for (int k = 0; k < 4; k++)
addWheel(&b, vec3(k % 2 ? w * 0.43f : -w * 0.43f, wheel, k / 2 ? l * 0.31f : -l * 0.31f), wheel, 0.22f);
if (boot_open) { /* the hatch up (hinged at the roof's back edge), the boot open: dark inside */
addBlock(&b, vec3(0.0f, 1.17f, back + 0.02f), vec3(w * 0.80f, 0.44f, 0.02f), dark);
size_t first = b.vertex_count;
addBlock(&b, vec3(0.0f, 0.0f, -0.40f), vec3(w * 0.84f, 0.04f, 0.80f), color);
addBlock(&b, vec3(0.0f, -0.025f, -0.36f), vec3(w * 0.7f, 0.02f, 0.5f), glass);
transformSince(&b, first, mat4_mul(mat4_translate(vec3(0.0f, 1.55f, -2.12f)), mat4_rotate_x(-0.35f)));
} else {
addBlock(&b, vec3(0.0f, 1.20f, back + 0.035f), vec3(w * 0.84f, 0.40f, 0.02f), glass); /* the back window */
}
*bp = b;
}
/* The car seen from inside (the body round you would hide it all): the
bonnet, the dashboard, the wheel, the pillars, the roof, the seats. */
Mesh Shapes_carInside(Vec3 color) {
MeshBuilder b;
MeshBuilder_init(&b);
const float w = CAR_WIDTH, hw = w * 0.5f, l = CAR_LENGTH;
const Vec3 dash = { 0.18f, 0.17f, 0.16f }, roof = { 0.70f, 0.67f, 0.60f };
const float BONNET[][2] = { { 2.24f, 0.82f }, { 1.95f, 0.90f }, { 0.80f, 0.97f }, { 0.80f, 0.86f }, { 2.24f, 0.78f } };
addProfile(&b, BONNET, 5, -hw, hw, color); /* the bonnet */
addBlock(&b, vec3(0.0f, 0.90f, 0.70f), vec3(w * 0.92f, 0.12f, 0.28f), dash); /* the dashboard */
addBlock(&b, vec3(0.0f, 0.74f, 0.74f), vec3(w * 0.92f, 0.2f, 0.2f), dash); /* ... its underside */
addBlock(&b, vec3(0.0f, 0.58f, 0.86f), vec3(w * 0.92f, 0.52f, 0.06f), dash); /* the footwell's front */
size_t first = b.vertex_count; /* the wheel, the driver's side */
shapes_addTube(&b, -0.02f, 0.19f, 0.19f, 0.03f, 18, dash, false);
shapes_addTube(&b, -0.02f, 0.03f, 0.03f, 0.04f, 8, dash, true);
transformSince(&b, first, mat4_mul(mat4_translate(vec3(0.38f, 0.96f, 0.46f)), mat4_rotate_x(PI_F * 0.5f - 0.45f)));
for (int s = -1; s <= 1; s += 2) /* the pillars beside the windscreen, leaning back with it */
addStick(&b, vec3((float)s * hw * 0.86f, 0.97f, 0.78f), vec3((float)s * hw * 0.86f, 1.52f, 0.06f), 0.055f, color);
const float back = -l * 0.5f, cab0 = back + 0.12f;
const Vec3 door = vec3_scale(color, 0.78f), seat = { 0.30f, 0.28f, 0.26f }, floor = { 0.14f, 0.13f, 0.12f };
addBlock(&b, vec3(0.0f, 1.53f, (0.05f - 2.1f) * 0.5f), vec3(w * 0.9f, 0.04f, 2.15f), roof); /* the roof lining */
addBlock(&b, vec3(0.0f, 1.48f, 0.12f), vec3(0.25f, 0.05f, 0.06f), dash); /* the mirror */
addBlock(&b, vec3(0.0f, 0.33f, (cab0 + 0.9f) * 0.5f), vec3(w * 0.9f, 0.04f, 0.9f - cab0), floor); /* the floor, on under the dashboard */
for (int s = -1; s <= 1; s += 2) {
float x = (float)s * (hw - 0.04f);
addBlock(&b, vec3(x, 0.66f, (cab0 + 0.9f) * 0.5f), vec3(0.06f, 0.64f, 0.9f - cab0), door); /* the doors, up to the windows */
addBlock(&b, vec3(x, 1.24f, -0.62f), vec3(0.07f, 0.56f, 0.12f), door); /* the middle pillar ... */
addBlock(&b, vec3(x, 1.24f, -1.48f), vec3(0.07f, 0.56f, 0.14f), door); /* ... the back door's ... */
addBlock(&b, vec3(x, 1.24f, cab0 + 0.1f), vec3(0.07f, 0.56f, 0.2f), door); /* ... and the last one */
addBlock(&b, vec3((float)s * 0.38f, 0.47f, 0.0f), vec3(0.5f, 0.12f, 0.5f), seat); /* the front seats */
first = b.vertex_count;
addBlock(&b, vec3(0.0f, 0.30f, 0.0f), vec3(0.48f, 0.6f, 0.1f), seat);
addBlock(&b, vec3(0.0f, 0.68f, 0.0f), vec3(0.22f, 0.16f, 0.1f), seat); /* (the headrest) */
transformSince(&b, first, mat4_mul(mat4_translate(vec3((float)s * 0.38f, 0.5f, -0.3f)), mat4_rotate_x(0.22f))); /* (leaning back) */
}
addBlock(&b, vec3(0.0f, 0.47f, -0.98f), vec3(w * 0.84f, 0.12f, 0.5f), seat); /* the back seat */
first = b.vertex_count;
addBlock(&b, vec3(0.0f, 0.31f, 0.0f), vec3(w * 0.84f, 0.62f, 0.1f), seat);
transformSince(&b, first, mat4_mul(mat4_translate(vec3(0.0f, 0.5f, -1.28f)), mat4_rotate_x(0.18f)));
addBlock(&b, vec3(0.0f, 0.6f, back + 0.55f), vec3(w * 0.84f, 0.04f, 0.8f), floor); /* the boot, full */
static const Vec3 LOAD[] = { { 0.62f, 0.16f, 0.12f }, { 0.22f, 0.42f, 0.72f }, { 0.70f, 0.18f, 0.16f }, { 0.30f, 0.45f, 0.25f }, { 0.85f, 0.75f, 0.2f } };
for (int k = 0; k < 5; k++)
addBlock(&b, vec3(-0.6f + 0.3f * (float)k, 0.78f, back + 0.45f + 0.1f * (float)(k % 2)), vec3(0.27f, 0.32f, 0.36f), LOAD[k]);
addBlock(&b, vec3(0.0f, 1.2f, back + 0.06f), vec3(w * 0.84f, 0.44f, 0.04f), (Vec3){ 0.34f, 0.42f, 0.48f }); /* the back window from inside */
addBlock(&b, vec3(0.0f, 0.75f, back + 0.05f), vec3(w * 0.86f, 0.42f, 0.04f), door); /* the hatch below it */
return Mesh_fromBuilder(&b);
}
Mesh Shapes_rain(const Vec3 *drops, int n, float len, Vec3 lean) {
MeshBuilder b;
MeshBuilder_init(&b);
const Vec3 color = { 0.78f, 0.82f, 0.88f };
for (int k = 0; k < n; k++) {
Vec3 p = drops[k], d = vec3(lean.x * len, len, lean.z * len); /* (leaning with the wind: the top upwind) */
Vec3 side = vec3(0.0f, 0.0f, 0.014f), across = vec3(0.014f, 0.0f, 0.0f);
GLuint i0 = MeshBuilder_vertex(&b, vec3_sub(p, side), vec3(0, 0, 1), color);
GLuint i1 = MeshBuilder_vertex(&b, vec3_add(p, side), vec3(0, 0, 1), color);
GLuint i2 = MeshBuilder_vertex(&b, vec3_add(vec3_add(p, d), side), vec3(0, 0, 1), color);
GLuint i3 = MeshBuilder_vertex(&b, vec3_sub(vec3_add(p, d), side), vec3(0, 0, 1), color);
MeshBuilder_quad(&b, i0, i1, i2, i3);
MeshBuilder_quad(&b, i3, i2, i1, i0); /* (seen from both sides) */
GLuint j0 = MeshBuilder_vertex(&b, vec3_sub(p, across), vec3(1, 0, 0), color);
GLuint j1 = MeshBuilder_vertex(&b, vec3_add(p, across), vec3(1, 0, 0), color);
GLuint j2 = MeshBuilder_vertex(&b, vec3_add(vec3_add(p, d), across), vec3(1, 0, 0), color);
GLuint j3 = MeshBuilder_vertex(&b, vec3_sub(vec3_add(p, d), across), vec3(1, 0, 0), color);
MeshBuilder_quad(&b, j0, j1, j2, j3);
MeshBuilder_quad(&b, j3, j2, j1, j0);
}
return Mesh_fromBuilder(&b);
}
/* A crate of bottles, centred: beer (brown bottles, gold caps, a red crate),
water (green glass, blue caps, a blue crate), soda (cola and orange, a green
crate) or mate (amber, an orange crate); each slot shows what's in it (a
bottle of another kind in its own colours, an empty one without its cap). */
Mesh Shapes_crate(int variant, const unsigned char *slots, int n) {
MeshBuilder b;
MeshBuilder_init(&b);
const float x = CRATE_SIZE_X, h = CRATE_SIZE_Y, z = CRATE_SIZE_Z, wall = 0.015f;
static const Vec3 CRATE[4] = { { 0.62f, 0.16f, 0.12f }, { 0.22f, 0.42f, 0.72f }, { 0.20f, 0.48f, 0.28f }, { 0.90f, 0.62f, 0.15f } };
static const Vec3 GLASS[6] = { { 0, 0, 0 }, { 0.38f, 0.22f, 0.08f }, { 0.55f, 0.75f, 0.62f }, { 0.20f, 0.11f, 0.07f }, { 0.96f, 0.56f, 0.14f }, { 0.55f, 0.33f, 0.10f } };
static const Vec3 CAP[6] = { { 0, 0, 0 }, { 0.85f, 0.70f, 0.25f }, { 0.20f, 0.35f, 0.75f }, { 0.78f, 0.12f, 0.10f }, { 0.95f, 0.50f, 0.15f }, { 0.20f, 0.35f, 0.75f } };
variant = variant < 0 ? 0 : variant > 3 ? 3 : variant;
const Vec3 crate = CRATE[variant];
const Vec3 hole = vec3_scale(crate, 0.45f);
addBlock(&b, vec3(0.0f, -h * 0.5f + 0.01f, 0.0f), vec3(x, 0.02f, z), crate);
addBlock(&b, vec3(0.0f, 0.0f, z * 0.5f - wall * 0.5f), vec3(x, h, wall), crate);
addBlock(&b, vec3(0.0f, 0.0f, -z * 0.5f + wall * 0.5f), vec3(x, h, wall), crate);
addBlock(&b, vec3(x * 0.5f - wall * 0.5f, 0.0f, 0.0f), vec3(wall, h, z), crate);
addBlock(&b, vec3(-x * 0.5f + wall * 0.5f, 0.0f, 0.0f), vec3(wall, h, z), crate);
for (int s = -1; s <= 1; s += 2) /* the handle holes in the ends */
addBlock(&b, vec3((float)s * (x * 0.5f + 0.001f), h * 0.32f, 0.0f), vec3(0.004f, 0.035f, 0.11f), hole);
const bool big = variant == 1 || variant == 2; /* (water and soda: 12 big bottles; beer and mate: 20 small) */
const int cols = big ? 4 : 5, rows = big ? 3 : 4;
for (int k = 0; k < n && k < cols * rows; k++) {
int kind = CRATE_KIND(slots[k]);
if (kind < BOTTLE_KIND_BEER || kind > BOTTLE_KIND_MATE) continue;
float cx = -x * 0.5f + x * ((float)(k % cols) + 0.5f) / (float)cols, cz = -z * 0.5f + z * ((float)(k / cols) + 0.5f) / (float)rows;
size_t first = b.vertex_count;
shapes_addTube(&b, h * 0.5f - 0.07f, 0.024f, 0.012f, 0.1f, 6, GLASS[kind], false); /* the neck out of the top */
if (CRATE_SIPS(slots[k]) > 0) shapes_addTube(&b, h * 0.5f + 0.03f, 0.014f, 0.014f, 0.012f, 6, CAP[kind], true);
else shapes_addTube(&b, h * 0.5f + 0.03f, 0.013f, 0.012f, 0.004f, 6, vec3_scale(GLASS[kind], 0.6f), true); /* (an empty: the open mouth) */
transformSince(&b, first, mat4_translate(vec3(cx, 0.0f, cz)));
}
return Mesh_fromBuilder(&b);
}
Mesh Shapes_umbrella(float length, float radius, int shape, Vec3 color) {
const bool open = shape != UMBRELLA_FOLDED, inside_out = shape == UMBRELLA_INSIDE_OUT;
MeshBuilder b;
MeshBuilder_init(&b);
const float half = length * 0.5f;
const Vec3 shaft = { 0.12f, 0.11f, 0.10f }, handle = { 0.30f, 0.18f, 0.10f };
shapes_addTube(&b, -half + 0.08f, 0.007f, 0.007f, length - 0.08f, 6, shaft, true);
for (int k = 0; k < 6; k++) { /* the crook: a little hook of a handle */
float a0 = PI_F * (float)k / 6.0f, a1 = PI_F * (float)(k + 1) / 6.0f;
Vec3 p0 = vec3(0.035f - 0.035f * cosf(a0), -half + 0.08f - 0.035f * sinf(a0), 0.0f);
Vec3 p1 = vec3(0.035f - 0.035f * cosf(a1), -half + 0.08f - 0.035f * sinf(a1), 0.0f);
Vec3 mid = vec3_scale(vec3_add(p0, p1), 0.5f), d = vec3_sub(p1, p0);
addBlock(&b, mid, vec3(fabsf(d.x) + 0.016f, fabsf(d.y) + 0.016f, 0.016f), handle);
}
const int panels = 8;
if (open) { /* the canopy: panels from the tip down to the rim, both sides (seen from under it too) */
Vec3 tip = vec3(0.0f, half + 0.02f, 0.0f);
for (int k = 0; k < panels; k++) {
float a0 = 2.0f * PI_F * (float)k / (float)panels, a1 = 2.0f * PI_F * (float)(k + 1) / (float)panels;
/* (inside out: the rim blown up above the tip, narrower, a bit ragged) */
float rr0 = inside_out ? radius * (0.8f + 0.1f * (float)(k % 3)) : radius, rr1 = inside_out ? radius * (0.8f + 0.1f * (float)((k + 1) % 3)) : radius;
float lift = inside_out ? 0.55f * radius : -0.2f * radius;
Vec3 r0 = vec3(cosf(a0) * rr0, half + lift + (inside_out ? 0.05f * (float)(k % 2) : 0.0f), sinf(a0) * rr0);
Vec3 r1 = vec3(cosf(a1) * rr1, half + lift + (inside_out ? 0.05f * (float)((k + 1) % 2) : 0.0f), sinf(a1) * rr1);
Vec3 c = k % 2 ? vec3_scale(color, 0.82f) : color;
Vec3 n = vec3_normalize(vec3_cross(vec3_sub(r1, tip), vec3_sub(r0, tip)));
GLuint t0 = MeshBuilder_vertex(&b, tip, n, c), i0 = MeshBuilder_vertex(&b, r0, n, c), i1 = MeshBuilder_vertex(&b, r1, n, c);
MeshBuilder_triangle(&b, t0, i1, i0);
Vec3 under = vec3_scale(c, 0.7f), m = vec3_scale(n, -1.0f);
GLuint u0 = MeshBuilder_vertex(&b, tip, m, under), u1 = MeshBuilder_vertex(&b, r0, m, under), u2 = MeshBuilder_vertex(&b, r1, m, under);
MeshBuilder_triangle(&b, u0, u1, u2);
}
} else { /* folded round the shaft */
shapes_addTube(&b, -half + 0.25f * length, 0.012f, 0.038f, 0.45f * length, panels, color, false);
shapes_addTube(&b, -half + 0.70f * length, 0.038f, 0.008f, 0.27f * length, panels, color, false);
}
return Mesh_fromBuilder(&b);
}
/* A stage going up: stakes and string, then posts and beams, the deck, the roof and the
back wall, then paint and banners. Each stage shows bit by bit (part). */
Mesh Shapes_stage(int stage, float part, float width, float depth, Vec3 accent) {
MeshBuilder b;
MeshBuilder_init(&b);
const float hx = width * 0.5f, hz = depth * 0.5f, deck = STAGE_DECK, top = 3.2f;
const Vec3 STAKE = { 0.72f, 0.60f, 0.40f }, STRING = { 0.92f, 0.30f, 0.20f }, POST = COLOR_TIMBER, BOARD = COLOR_PLYWOOD;
const Vec3 corner[4] = { { -hx, 0.0f, -hz }, { hx, 0.0f, -hz }, { hx, 0.0f, hz }, { -hx, 0.0f, hz } };
if (stage <= 0) { /* marked out: a stake at each corner, string round it (more of it cleared as it goes) */
for (int k = 0; k < 4; k++) addBlock(&b, vec3(corner[k].x, 0.3f, corner[k].z), vec3(0.05f, 0.6f, 0.05f), STAKE);
addBlock(&b, vec3(0.0f, 0.5f, -hz), vec3(width, 0.01f, 0.01f), STRING);
addBlock(&b, vec3(0.0f, 0.5f, hz), vec3(width, 0.01f, 0.01f), STRING);
addBlock(&b, vec3(-hx, 0.5f, 0.0f), vec3(0.01f, 0.01f, depth), STRING);
addBlock(&b, vec3(hx, 0.5f, 0.0f), vec3(0.01f, 0.01f, depth), STRING);
return Mesh_fromBuilder(&b);
}
/* the frame: posts (back ones taller), beams round the top and under the deck */
int posts = stage == 1 ? (int)(part * 6.0f + 1.0f) : 6;
const Vec3 at[6] = { { -hx, 0, -hz }, { 0, 0, -hz }, { hx, 0, -hz }, { -hx, 0, hz }, { 0, 0, hz }, { hx, 0, hz } };
for (int k = 0; k < posts && k < 6; k++) {
float h = at[k].z < 0.0f ? top + 0.4f : top;
addBlock(&b, vec3(at[k].x, h * 0.5f, at[k].z), vec3(0.16f, h, 0.16f), POST);
}
if (stage > 1 || part > 0.5f) {
addBlock(&b, vec3(0.0f, top + 0.3f, -hz), vec3(width + 0.2f, 0.14f, 0.12f), POST); /* top beams */
addBlock(&b, vec3(0.0f, top - 0.1f, hz), vec3(width + 0.2f, 0.14f, 0.12f), POST);
}
if (stage > 1 || part > 0.8f) {
addBlock(&b, vec3(0.0f, deck - 0.08f, -hz), vec3(width, 0.12f, 0.1f), POST); /* under the deck */
addBlock(&b, vec3(0.0f, deck - 0.08f, hz), vec3(width, 0.12f, 0.1f), POST);
}
if (stage >= 2) { /* the deck: boards across, laid front to back */
int boards = (int)(width / 0.3f), laid = stage == 2 ? (int)((float)boards * part) : boards;
for (int k = 0; k < laid; k++) {
float x = -hx + 0.15f + 0.3f * (float)k;
addBlock(&b, vec3(x, deck - 0.02f, 0.0f), vec3(0.28f, 0.04f, depth), k % 2 ? BOARD : vec3_scale(BOARD, 0.92f));
}
if (stage > 2) addBlock(&b, vec3(0.0f, deck * 0.5f - 0.02f, hz), vec3(width, deck - 0.04f, 0.03f), vec3_scale(BOARD, 0.8f)); /* the front skirt */
}
if (stage >= 3) { /* the back wall and the roof */
float wall = stage == 3 ? fminf(1.0f, part * 2.0f) : 1.0f, roof = stage == 3 ? fmaxf(0.0f, part * 2.0f - 1.0f) : 1.0f;
float wh = (top + 0.3f - deck) * wall;
Vec3 back = stage >= 4 ? vec3_lerp(BOARD, accent, 0.75f) : BOARD;
if (wh > 0.05f) addBlock(&b, vec3(0.0f, deck + wh * 0.5f, -hz + 0.05f), vec3(width, wh, 0.04f), back);
if (roof > 0.0f) {
size_t first = b.vertex_count;
addBlock(&b, vec3(0.0f, 0.0f, 0.0f), vec3(width + 0.4f, 0.05f, (depth + 0.6f) * roof), stage >= 4 ? vec3_scale(accent, 0.6f) : BOARD);
/* (sloping down to the front, from the back's top) */
float slope = atanf(0.4f / depth);
transformSince(&b, first, mat4_mul(mat4_translate(vec3(0.0f, top + 0.25f, -hz - 0.3f + (depth + 0.6f) * roof * 0.5f)), mat4_rotate_x(slope)));
}
}
if (stage >= 4) { /* paint and banners: triangles along the front beam, a band of paint on the deck's front */
int flags = stage == 4 ? (int)(part * 10.0f) : 10;
for (int k = 0; k < flags; k++) {
float x = -hx + width * ((float)k + 0.5f) / 10.0f;
Vec3 c = k % 2 ? accent : vec3(1.0f - accent.x * 0.5f, 0.85f, 0.3f + accent.z * 0.5f);
addBlock(&b, vec3(x, top - 0.35f, hz + 0.04f), vec3(width / 14.0f, 0.3f, 0.02f), c);
}
if (stage > 4 || part > 0.5f) addBlock(&b, vec3(0.0f, deck * 0.5f, hz + 0.02f), vec3(width, 0.12f, 0.02f), accent);
}
return Mesh_fromBuilder(&b);
}
/* A hat: a dome of rings (a cap: a brim out in front; a beanie: taller, a cuff). */
Mesh Shapes_hat(int kind, Vec3 color) {
MeshBuilder b;
MeshBuilder_init(&b);
const bool beanie = kind == 2;
const float r = beanie ? 0.105f : 0.1f, h = beanie ? 0.12f : 0.085f;
const int rings = 5;
for (int k = 0; k < rings; k++) { /* the crown: a dome */
float a0 = 0.5f * PI_F * (float)k / (float)rings, a1 = 0.5f * PI_F * (float)(k + 1) / (float)rings;
shapes_addTube(&b, -0.04f + h * sinf(a0), r * cosf(a0), fmaxf(r * cosf(a1), 0.004f), h * (sinf(a1) - sinf(a0)), 12,
vec3_scale(color, 0.92f + 0.08f * (float)k / (float)rings), k == rings - 1);
}
if (beanie) shapes_addTube(&b, -0.045f, r * 1.04f, r * 1.04f, 0.035f, 12, vec3_scale(color, 0.85f), false); /* the cuff */
else addBlock(&b, vec3(0.0f, -0.036f, r + 0.045f), vec3(0.15f, 0.008f, 0.11f), vec3_scale(color, 0.8f)); /* the brim */
return Mesh_fromBuilder(&b);
}
/* Camping things, centred, lying along X (see shapes.h). */
Mesh Shapes_gear(int kind, Vec3 color) {
MeshBuilder b;
MeshBuilder_init(&b);
const float len[] = { 0.42f, 0.6f, 0.5f, 0.6f }, rad[] = { 0.13f, 0.09f, 0.08f, 0.1f };
float l = len[kind & 3], r = rad[kind & 3];
Vec3 dark = vec3_scale(color, 0.65f);
size_t first = b.vertex_count;
shapes_addTube(&b, -l * 0.5f, r, r, l, 12, color, true);
if (kind == 1) { /* a foam mat rolled up: two straps */
shapes_addTube(&b, -l * 0.3f, r * 1.04f, r * 1.04f, 0.03f, 12, dark, false);
shapes_addTube(&b, l * 0.27f, r * 1.04f, r * 1.04f, 0.03f, 12, dark, false);
} else { /* a stuff sack: the drawstring end gathered, a toggle */
shapes_addTube(&b, l * 0.5f, r * 0.8f, r * 0.25f, 0.04f, 12, dark, true);
shapes_addTube(&b, l * 0.5f + 0.04f, 0.015f, 0.015f, 0.03f, 6, (Vec3){ 0.1f, 0.1f, 0.1f }, true);
}
transformSince(&b, first, mat4_rotate_z(-PI_F * 0.5f));
return Mesh_fromBuilder(&b);
}
/* The scythe (centred, the snath along Y): the long wooden snath with its two grips, the blade at the
bottom end, curving off to one side (+X) and a little forward. */
Mesh Shapes_scythe(float length) {
MeshBuilder b;
MeshBuilder_init(&b);
const float h = length * 0.5f;
const Vec3 steel = { 0.62f, 0.64f, 0.68f };
shapes_addTube(&b, -h, 0.018f, 0.016f, length, 8, COLOR_WOOD, true);
for (int k = 0; k < 2; k++) { /* the grips sticking out */
float y = k ? h * 0.15f : h * 0.75f;
addBlock(&b, vec3(0.0f, y, 0.06f), vec3(0.025f, 0.025f, 0.12f), COLOR_WOOD);
}
for (int k = 0; k < 6; k++) { /* the blade: a curve of flat pieces */
float t = (float)k / 6.0f, x = 0.06f + 0.6f * t, z = 0.05f + 0.12f * sinf(t * 3.0f) - 0.1f * t;
addBlock(&b, vec3(x, -h + 0.03f, z), vec3(0.11f, 0.008f, 0.07f - 0.05f * t), steel);
}
return Mesh_fromBuilder(&b);
}
/* A vineyard snail (on y = 0, the head to +Z): the round shell with its whorl, the body under it. */
Mesh Shapes_snail(void) {
MeshBuilder b;
MeshBuilder_init(&b);
const Vec3 shell = { 0.72f, 0.58f, 0.38f }, line = { 0.48f, 0.36f, 0.22f }, body = { 0.66f, 0.62f, 0.52f };
addBlock(&b, vec3(0.0f, 0.006f, 0.008f), vec3(0.022f, 0.012f, 0.07f), body);
addBlock(&b, vec3(0.0f, 0.016f, 0.04f), vec3(0.012f, 0.012f, 0.012f), body); /* the head, the feelers */
addBlock(&b, vec3(0.006f, 0.026f, 0.046f), vec3(0.003f, 0.016f, 0.003f), body);
addBlock(&b, vec3(-0.006f, 0.026f, 0.046f), vec3(0.003f, 0.016f, 0.003f), body);
size_t first = b.vertex_count;
shapes_addTube(&b, -0.016f, 0.022f, 0.022f, 0.032f, 10, shell, true);
shapes_addTube(&b, 0.0165f, 0.012f, 0.012f, 0.003f, 10, line, true);
transformSince(&b, first, mat4_mul(mat4_translate(vec3(0.0f, 0.03f, -0.006f)), mat4_rotate_z(PI_F * 0.5f)));
return Mesh_fromBuilder(&b);
}
/* Kerry's art: a log stood on end, carved (on y = 0): kind 0 an owl, 1 a bear, 2 a mushroom man. */
Mesh Shapes_sculpture(int kind, float diameter, float height) {
MeshBuilder b;
MeshBuilder_init(&b);
const float r = diameter * 0.5f;
const Vec3 wood = { 0.70f, 0.52f, 0.32f }, dark = { 0.38f, 0.26f, 0.15f }, bark = { 0.36f, 0.27f, 0.19f };
shapes_addTube(&b, 0.0f, r * 1.05f, r * 1.0f, 0.12f, 12, bark, false); /* (the foot left rough) */
if (kind == 0) { /* an owl: a body narrowing up, a round head, the ear tufts, two big eyes */
shapes_addTube(&b, 0.12f, r, r * 0.8f, height * 0.6f, 12, wood, false);
shapes_addTube(&b, 0.12f + height * 0.6f, r * 0.85f, r * 0.75f, height * 0.28f - 0.12f, 12, wood, true);
for (int s = -1; s <= 1; s += 2) {
addBlock(&b, vec3((float)s * r * 0.45f, height - 0.06f, 0.0f), vec3(r * 0.25f, 0.14f, r * 0.3f), wood);
addBlock(&b, vec3((float)s * r * 0.33f, height * 0.78f, r * 0.72f), vec3(r * 0.32f, r * 0.32f, 0.03f), dark);
}
addBlock(&b, vec3(0.0f, height * 0.7f, r * 0.8f), vec3(r * 0.14f, r * 0.24f, 0.05f), dark); /* the beak */
} else if (kind == 1) { /* a bear sitting up: a big belly, the head, ears, the snout */
shapes_addTube(&b, 0.12f, r, r * 0.9f, height * 0.55f, 12, wood, false);
shapes_addTube(&b, 0.12f + height * 0.55f, r * 0.75f, r * 0.6f, height * 0.33f - 0.12f, 12, wood, true);
for (int s = -1; s <= 1; s += 2) addBlock(&b, vec3((float)s * r * 0.5f, height * 0.9f, 0.0f), vec3(r * 0.25f, r * 0.25f, r * 0.2f), wood);
addBlock(&b, vec3(0.0f, height * 0.72f, r * 0.62f), vec3(r * 0.35f, r * 0.28f, r * 0.3f), wood);
addBlock(&b, vec3(0.0f, height * 0.74f, r * 0.8f), vec3(r * 0.12f, r * 0.1f, 0.03f), dark);
addBlock(&b, vec3(0.0f, height * 0.35f, r * 0.9f), vec3(r * 1.1f, r * 0.25f, r * 0.2f), wood); /* the paws */
} else { /* a mushroom man: a stem with a face, a wide cap */
shapes_addTube(&b, 0.12f, r * 0.8f, r * 0.7f, height * 0.62f, 12, wood, false);
shapes_addTube(&b, 0.12f + height * 0.62f, r * 1.5f, r * 0.2f, height * 0.38f - 0.12f, 12, dark, true);
for (int s = -1; s <= 1; s += 2) addBlock(&b, vec3((float)s * r * 0.25f, height * 0.5f, r * 0.7f), vec3(r * 0.12f, r * 0.12f, 0.03f), dark);
addBlock(&b, vec3(0.0f, height * 0.38f, r * 0.68f), vec3(r * 0.4f, r * 0.06f, 0.03f), dark);
}
return Mesh_fromBuilder(&b);
}
/* The marker over who to go to next: a diamond (two pyramids), centred, this tall. */
Mesh Shapes_questMarker(float height, Vec3 color) {
MeshBuilder b;
MeshBuilder_init(&b);
const float r = height * 0.32f, h = height * 0.5f;
for (int e = 0; e < 4; e++)
for (int up = 0; up < 2; up++) {
float a0 = (float)e * PI_F * 0.5f, a1 = a0 + PI_F * 0.5f;
Vec3 p0 = vec3(r * cosf(a0), 0.0f, r * sinf(a0)), p1 = vec3(r * cosf(a1), 0.0f, r * sinf(a1)), tip = vec3(0.0f, up ? h : -h, 0.0f);
Vec3 n = vec3_normalize(vec3_cross(vec3_sub(p1, p0), vec3_sub(tip, p0)));
Vec3 mid = vec3_scale(vec3_add(vec3_add(p0, p1), tip), 1.0f / 3.0f);
if (vec3_dot(n, mid) < 0.0f) n = vec3_scale(n, -1.0f);
Vec3 c = vec3_scale(color, up ? 1.0f : 0.8f);
MeshBuilder_triangle(&b, MeshBuilder_vertex(&b, p0, n, c), MeshBuilder_vertex(&b, p1, n, c), MeshBuilder_vertex(&b, tip, n, c));
}
return Mesh_fromBuilder(&b);
}
/* A party pavilion standing on y = 0 (centred): four thin legs, a valance round the top,
a pyramid roof of white canvas (both sides drawn: it's seen from under it too). */
Mesh Shapes_pavilion(float side, float eaves, float peak, Vec3 canvas) {
MeshBuilder b;
MeshBuilder_init(&b);
const float h = side * 0.5f, leg = 0.045f, skirt = 0.24f;
const Vec3 steel = { 0.72f, 0.73f, 0.75f }, under = vec3_scale(canvas, 0.8f);
for (int i = -1; i <= 1; i += 2)
for (int k = -1; k <= 1; k += 2) {
addBlock(&b, vec3((float)i * (h - leg), eaves * 0.5f, (float)k * (h - leg)), vec3(leg, eaves, leg), steel);
addBlock(&b, vec3((float)i * (h - leg), 0.01f, (float)k * (h - leg)), vec3(0.16f, 0.02f, 0.16f), steel); /* (foot plates) */
}
for (int s = -1; s <= 1; s += 2) { /* the valance: four strips of canvas hanging from the eaves */
addBlock(&b, vec3(0.0f, eaves - skirt * 0.5f, (float)s * h), vec3(side, skirt, 0.01f), canvas);
addBlock(&b, vec3((float)s * h, eaves - skirt * 0.5f, 0.0f), vec3(0.01f, skirt, side), canvas);
}
const Vec3 top = vec3(0.0f, peak, 0.0f);
const float cx[4] = { -h, h, h, -h }, cz[4] = { -h, -h, h, h };
for (int e = 0; e < 4; e++) { /* each face: outside up, the inside a shade darker */
Vec3 a = vec3(cx[e], eaves, cz[e]), c = vec3(cx[(e + 1) % 4], eaves, cz[(e + 1) % 4]);
Vec3 n = vec3_normalize(vec3_cross(vec3_sub(c, a), vec3_sub(top, a)));
if (n.y < 0.0f) n = vec3_scale(n, -1.0f);
GLuint i0 = MeshBuilder_vertex(&b, a, n, canvas), i1 = MeshBuilder_vertex(&b, c, n, canvas), i2 = MeshBuilder_vertex(&b, top, n, canvas);
MeshBuilder_triangle(&b, i0, i1, i2);
Vec3 m = vec3_scale(n, -1.0f), down = vec3(0.0f, -0.01f, 0.0f);
GLuint j0 = MeshBuilder_vertex(&b, vec3_add(a, down), m, under), j1 = MeshBuilder_vertex(&b, vec3_add(c, down), m, under);
GLuint j2 = MeshBuilder_vertex(&b, vec3_add(top, down), m, under);
MeshBuilder_triangle(&b, j0, j1, j2);
}
return Mesh_fromBuilder(&b);
}
/* Building material (centred): a bundle of deck boards strapped together,
a box of nails, a box of screws, a bucket of paint. */
Mesh Shapes_material(int kind, Vec3 size) {
MeshBuilder b;
MeshBuilder_init(&b);
const Vec3 card = { 0.66f, 0.52f, 0.34f }, strap = { 0.12f, 0.12f, 0.14f };
if (kind == 0) { /* 2 x 4 boards, a hair apart, the grain a shade different each */
float w = size.z / 2.0f, h = size.y / 4.0f;
for (int k = 0; k < 8; k++) {
float shade = 0.92f + 0.05f * (float)((k * 5) % 3);
Vec3 c = vec3(0.80f * shade, 0.64f * shade, 0.42f * shade);
addBlock(&b, vec3(0.0f, -size.y * 0.5f + h * ((float)(k / 2) + 0.5f), -size.z * 0.5f + w * ((float)(k % 2) + 0.5f)),
vec3(size.x, h - 0.004f, w - 0.004f), c);
}
for (int k = -1; k <= 1; k += 2) addBlock(&b, vec3((float)k * size.x * 0.3f, 0.0f, 0.0f), vec3(0.025f, size.y + 0.006f, size.z + 0.006f), strap);
} else if (kind == 1 || kind == 2) { /* a cardboard box, its label: grey nails, a blue box of screws */
Vec3 label = kind == 1 ? vec3(0.55f, 0.56f, 0.58f) : vec3(0.18f, 0.36f, 0.62f);
addBlock(&b, vec3_zero(), size, kind == 1 ? card : vec3(0.85f, 0.82f, 0.74f));
addBlock(&b, vec3(0.0f, 0.0f, size.z * 0.5f + 0.002f), vec3(size.x * 0.8f, size.y * 0.6f, 0.004f), label);
addBlock(&b, vec3(0.0f, size.y * 0.5f + 0.002f, 0.0f), vec3(size.x * 0.5f, 0.004f, size.z * 0.5f), label);
} else { /* a white bucket, its lid, the handle */
float r = size.x * 0.5f;
shapes_addTube(&b, -size.y * 0.5f, r * 0.88f, r, size.y * 0.92f, 16, vec3(0.92f, 0.92f, 0.9f), true);
shapes_addTube(&b, size.y * 0.42f, r * 1.02f, r * 1.02f, size.y * 0.08f, 16, vec3(0.85f, 0.35f, 0.12f), true);
addBlock(&b, vec3(0.0f, size.y * 0.5f + 0.012f, 0.0f), vec3(size.x * 0.9f, 0.012f, 0.012f), strap);
}
return Mesh_fromBuilder(&b);
}
/* An old tin can as ashtray: the butts stick out of the ash, more and more,
a little heap of them when it's full. */
static void buildAshtray(MeshBuilder *b, Vec3 size, int butts) {
const Vec3 tin = { 0.62f, 0.62f, 0.60f }, filter = { 0.86f, 0.6f, 0.34f }, ash = { 0.25f, 0.24f, 0.22f };
shapes_addTube(b, 0.0f, size.x * 0.5f, size.x * 0.48f, size.y, 10, tin, true);
float fill = fminf((float)butts / 14.0f, 1.0f);
addBlock(b, vec3(0.0f, size.y * (0.7f + 0.28f * fill), 0.0f), vec3(size.x * 0.8f, 0.006f, size.x * 0.8f), ash);
int shown = butts < 12 ? butts : 12;
for (int k = 0; k < shown; k++) {
float a = (float)k * 2.39996f, r = 0.012f + 0.022f * sqrtf((float)k / 12.0f);
float up = size.y * (0.72f + 0.28f * fill) + 0.006f + 0.008f * (float)(k / 6); /* (full: piled up) */
addBlock(b, vec3(r * cosf(a), up, r * sinf(a)), vec3(0.018f, 0.012f, 0.011f), filter);
}
}
Mesh Shapes_ashtray(Vec3 size, int butts) {
MeshBuilder b;
MeshBuilder_init(&b);
buildAshtray(&b, size, butts);
return Mesh_fromBuilder(&b);
}
Mesh Shapes_furniture(FurnitureKind kind, Vec3 size) {
MeshBuilder b;
MeshBuilder_init(&b);
const float hx = size.x * 0.5f, hz = size.z * 0.5f;
switch (kind) {
case FURN_WORKBENCH:
addTable(&b, size, 0.07f, COLOR_TIMBER);
addBlock(&b, vec3(-hx + 0.25f, size.y + 0.06f, hz - 0.12f), vec3(0.14f, 0.12f, 0.16f), COLOR_IRON); /* vice */
addBlock(&b, vec3(0.1f, size.y + 0.02f, 0.0f), vec3(0.28f, 0.03f, 0.03f), COLOR_TIMBER); /* hammer */
addBlock(&b, vec3(0.24f, size.y + 0.03f, 0.0f), vec3(0.04f, 0.05f, 0.1f), COLOR_IRON);
addBlock(&b, vec3(hx - 0.35f, size.y + 0.09f, -0.05f), vec3(0.4f, 0.18f, 0.25f), COLOR_CAR); /* toolbox */
addBlock(&b, vec3(0.0f, 0.12f, 0.0f), vec3(size.x - 0.15f, 0.03f, size.z - 0.15f), COLOR_TIMBER); /* shelf below */
break;
case FURN_TOOLWALL: {
addBlock(&b, vec3(0.0f, size.y * 0.5f, 0.0f), size, COLOR_PLYWOOD);
unsigned seed = 5u;
for (int k = 0; k < 7; k++) { /* tools hanging on hooks */
float x = -hx + 0.3f + (size.x - 0.6f) * (float)k / 6.0f, h = 0.25f + 0.3f * scatter(&seed);
addBlock(&b, vec3(x, size.y * 0.55f, hz + 0.03f), vec3(0.035f, h, 0.03f), k % 3 ? COLOR_TIMBER : COLOR_STEEL);
addBlock(&b, vec3(x, size.y * 0.55f + h * 0.5f, hz + 0.04f), vec3(0.12f, 0.05f, 0.04f), COLOR_IRON);
}
break;
}
case FURN_SHELF: {
const Vec3 boxes[] = { { 0.75f, 0.55f, 0.30f }, { 0.30f, 0.50f, 0.65f }, { 0.85f, 0.75f, 0.35f } };
for (int k = 0; k < 2; k++) addBlock(&b, vec3(k ? hx - 0.03f : -hx + 0.03f, size.y * 0.5f, 0.0f), vec3(0.05f, size.y, size.z), COLOR_TIMBER);
for (int l = 0; l < 4; l++) {
float y = 0.1f + (size.y - 0.15f) * (float)l / 3.0f;
addBlock(&b, vec3(0.0f, y, 0.0f), vec3(size.x, 0.03f, size.z), COLOR_TIMBER);
for (int k = 0; k < 3 && l < 3; k++)
addBlock(&b, vec3(-hx + 0.3f + (float)k * (size.x - 0.6f) / 2.0f, y + 0.13f, 0.0f), vec3(0.3f, 0.23f, size.z * 0.7f), boxes[(k + l) % 3]);
}
break;
}
case FURN_STOVE:
addBlock(&b, vec3(0.0f, size.y * 0.5f, 0.0f), size, COLOR_ENAMEL);
addBlock(&b, vec3(0.0f, size.y + 0.01f, 0.0f), vec3(size.x - 0.04f, 0.02f, size.z - 0.04f), COLOR_BLACK);
for (int k = 0; k < 4; k++) { /* burners */
size_t first = b.vertex_count;
shapes_addTube(&b, size.y + 0.02f, 0.09f, 0.09f, 0.015f, 10, COLOR_IRON, true);
transformSince(&b, first, mat4_translate(vec3((k % 2 ? 0.17f : -0.17f) * size.x / 0.6f, 0.0f, k / 2 ? 0.14f : -0.14f)));
}
addBlock(&b, vec3(0.0f, size.y * 0.42f, hz + 0.01f), vec3(size.x * 0.75f, size.y * 0.45f, 0.02f), COLOR_BLACK); /* oven door */
break;
case FURN_COUNTER:
addBlock(&b, vec3(0.0f, (size.y - 0.04f) * 0.5f, 0.0f), vec3(size.x, size.y - 0.04f, size.z), COLOR_PLYWOOD);
addBlock(&b, vec3(0.0f, size.y - 0.02f, 0.0f), vec3(size.x + 0.04f, 0.04f, size.z + 0.03f), COLOR_TIMBER);
addBlock(&b, vec3(hx * 0.4f, size.y + 0.001f, 0.02f), vec3(0.5f, 0.01f, size.z * 0.6f), COLOR_STEEL); /* sink */
for (int k = 0; k < 3; k++) /* cupboard doors */
addBlock(&b, vec3(-hx + size.x * ((float)k + 0.5f) / 3.0f, size.y * 0.45f, hz + 0.005f), vec3(size.x / 3.0f - 0.06f, size.y * 0.7f, 0.01f), COLOR_TIMBER);
break;
case FURN_TABLE:
addTable(&b, size, 0.05f, COLOR_TIMBER);
break;
case FURN_CHAIR:
addTable(&b, vec3(size.x, SEAT_HEIGHT, size.z), 0.04f, COLOR_TIMBER);
addBlock(&b, vec3(0.0f, SEAT_HEIGHT + (size.y - SEAT_HEIGHT) * 0.5f, -hz + 0.02f), vec3(size.x, size.y - SEAT_HEIGHT, 0.04f), COLOR_TIMBER);
break;
case FURN_BENCH: { /* a split log on two blocks */
addBlock(&b, vec3(-hx + 0.2f, 0.14f, 0.0f), vec3(0.14f, 0.28f, size.z * 0.9f), COLOR_TIMBER);
addBlock(&b, vec3(hx - 0.2f, 0.14f, 0.0f), vec3(0.14f, 0.28f, size.z * 0.9f), COLOR_TIMBER);
size_t first = b.vertex_count;
shapes_addTube(&b, -hx, size.z * 0.5f, size.z * 0.5f, size.x, 10, COLOR_BARK, true);
transformSince(&b, first, mat4_mul(mat4_translate(vec3(0.0f, SEAT_HEIGHT - size.z * 0.5f, 0.0f)), mat4_rotate_z(-PI_F / 2.0f)));
addBlock(&b, vec3(0.0f, SEAT_HEIGHT - 0.01f, 0.0f), vec3(size.x, 0.02f, size.z * 0.75f), COLOR_PLYWOOD); /* the cut face */
break;
}
case FURN_CAR: { /* an old saloon, length along Z, nose to +Z */
const float l = size.z, w = size.x, wheel = 0.32f;
addBlock(&b, vec3(0.0f, 0.62f, 0.0f), vec3(w, 0.55f, l), COLOR_CAR); /* body */
addBlock(&b, vec3(0.0f, 1.12f, -0.15f), vec3(w * 0.86f, 0.46f, l * 0.48f), COLOR_CAR); /* cabin */
addBlock(&b, vec3(0.0f, 1.12f, -0.15f), vec3(w * 0.87f, 0.32f, l * 0.40f), (Vec3){ 0.30f, 0.40f, 0.52f }); /* windows */
addBlock(&b, vec3(0.0f, 0.55f, l * 0.5f + 0.02f), vec3(w * 0.9f, 0.12f, 0.06f), COLOR_STEEL); /* bumpers */
addBlock(&b, vec3(0.0f, 0.55f, -l * 0.5f - 0.02f), vec3(w * 0.9f, 0.12f, 0.06f), COLOR_STEEL);
for (int k = 0; k < 4; k++)
addWheel(&b, vec3(k % 2 ? w * 0.42f : -w * 0.42f, wheel, k / 2 ? l * 0.32f : -l * 0.32f), wheel, 0.2f);
addBlock(&b, vec3(0.0f, 0.9f, l * 0.36f), vec3(w * 0.7f, 0.04f, l * 0.25f), COLOR_CAR); /* bonnet, propped up */
break;
}
case FURN_TIRES:
for (int k = 0; k < 4; k++) {
shapes_addTube(&b, 0.22f * (float)k, size.x * 0.5f, size.x * 0.5f, 0.2f, 14, COLOR_TYRE, true);
}
break;
case FURN_TOILET:
if (size.y < 1.2f) { /* a toilet in a house: the cistern at the back (z = -hz), the bowl and seat in front */
const Vec3 white = { 0.93f, 0.93f, 0.90f }, seat = { 0.86f, 0.84f, 0.80f };
addBlock(&b, vec3(0.0f, 0.62f, -hz + 0.09f), vec3(size.x * 0.95f, 0.36f, 0.17f), white); /* cistern */
addBlock(&b, vec3(size.x * 0.25f, 0.82f, -hz + 0.09f), vec3(0.06f, 0.02f, 0.04f), COLOR_STEEL); /* flush button */
size_t first = b.vertex_count;
shapes_addTube(&b, 0.0f, 0.13f, 0.17f, 0.38f, 12, white, true); /* bowl */
transformSince(&b, first, mat4_mul(mat4_translate(vec3(0.0f, 0.0f, 0.06f)), mat4_scale(vec3(1.0f, 1.0f, 1.3f))));
first = b.vertex_count;
shapes_addTube(&b, 0.38f, 0.18f, 0.18f, 0.025f, 12, seat, true); /* seat */
transformSince(&b, first, mat4_mul(mat4_translate(vec3(0.0f, 0.0f, 0.06f)), mat4_scale(vec3(1.0f, 1.0f, 1.3f))));
break;
}
{ /* a portable toilet: blue cabin, white roof, door at the front */
const Vec3 blue = { 0.22f, 0.42f, 0.74f }, white = { 0.92f, 0.92f, 0.90f };
addBlock(&b, vec3(0.0f, size.y * 0.47f, 0.0f), vec3(size.x, size.y * 0.94f, size.z), blue);
addBlock(&b, vec3(0.0f, size.y * 0.97f, 0.0f), vec3(size.x * 1.04f, size.y * 0.06f, size.z * 1.04f), white);
addBlock(&b, vec3(0.0f, size.y * 0.45f, hz + 0.01f), vec3(size.x * 0.78f, size.y * 0.82f, 0.02f), vec3_scale(blue, 0.8f));
addBlock(&b, vec3(size.x * 0.28f, size.y * 0.47f, hz + 0.03f), vec3(0.03f, 0.12f, 0.03f), white); /* handle */
addBlock(&b, vec3(-size.x * 0.12f, size.y * 0.75f, hz + 0.025f), vec3(0.22f, 0.07f, 0.01f), (Vec3){ 0.2f, 0.7f, 0.3f }); /* free */
addRod(&b, vec3(-size.x * 0.3f, size.y, -hz * 0.5f), vec3(-size.x * 0.3f, size.y + 0.25f, -hz * 0.5f), 0.04f, 0.04f, 6, white); /* vent */
break;
}
case FURN_TAP: { /* on the wall (back at z = -hz): pipe, valve, a reel of hose below */
const Vec3 brass = { 0.78f, 0.62f, 0.26f }, green = { 0.20f, 0.52f, 0.24f };
addBlock(&b, vec3(0.0f, TAP_HEIGHT + 0.05f, -hz + 0.01f), vec3(0.12f, 0.16f, 0.02f), COLOR_STEEL);
addRod(&b, vec3(0.0f, TAP_HEIGHT + 0.05f, -hz), vec3(0.0f, TAP_HEIGHT + 0.05f, -hz + 0.12f), 0.015f, 0.015f, 6, brass);
addRod(&b, vec3(0.0f, TAP_HEIGHT + 0.05f, -hz + 0.12f), vec3(0.0f, TAP_HEIGHT, -hz + 0.13f), 0.013f, 0.013f, 6, brass);
addBlock(&b, vec3(0.0f, TAP_HEIGHT + 0.1f, -hz + 0.08f), vec3(0.08f, 0.02f, 0.02f), (Vec3){ 0.7f, 0.15f, 0.12f }); /* valve */
size_t first = b.vertex_count;
shapes_addTube(&b, -0.07f, 0.22f, 0.22f, 0.14f, 14, green, true); /* the coiled hose on its reel */
transformSince(&b, first, mat4_mul(mat4_translate(vec3(0.0f, 0.4f, -hz + 0.1f)), mat4_rotate_x(PI_F / 2.0f)));
break;
}
case FURN_BAR: { /* a bar counter (its front to +Z): dark wood, a lighter top, two beer taps, glasses */
const Vec3 wood = { 0.30f, 0.19f, 0.12f }, top = { 0.52f, 0.36f, 0.22f }, brass = { 0.78f, 0.64f, 0.30f }, glass = { 0.80f, 0.86f, 0.84f };
addBlock(&b, vec3(0.0f, (size.y - 0.05f) * 0.5f, 0.0f), vec3(size.x, size.y - 0.05f, size.z), wood);
addBlock(&b, vec3(0.0f, size.y - 0.025f, 0.05f), vec3(size.x + 0.06f, 0.05f, size.z + 0.1f), top);
for (int k = -1; k <= 1; k += 2) {
addBlock(&b, vec3((float)k * 0.4f, size.y + 0.2f, -hz + 0.15f), vec3(0.06f, 0.4f, 0.06f), brass);
addBlock(&b, vec3((float)k * 0.4f, size.y + 0.36f, -hz + 0.22f), vec3(0.04f, 0.04f, 0.14f), brass);
}
for (int k = 0; k < 5; k++) addBlock(&b, vec3(-hx + 0.4f + 0.25f * (float)k, size.y + 0.07f, hz - 0.1f), vec3(0.07f, 0.14f, 0.07f), glass);
break;
}
case FURN_SMOKES: { /* an old cigarette machine on the wall (back at z = -hz): a row of packs behind glass, pull knobs, the coin slot, the tray */
const Vec3 body = { 0.66f, 0.13f, 0.12f }, cream = { 0.90f, 0.86f, 0.74f }, glass = { 0.30f, 0.38f, 0.44f };
const float bottom = 0.95f, h = 0.78f, d = 0.22f, front = -hz + d;
addBlock(&b, vec3(0.0f, bottom + h * 0.5f, -hz + d * 0.5f), vec3(size.x, h, d), body);
addBlock(&b, vec3(0.0f, bottom + h - 0.06f, front + 0.005f), vec3(size.x - 0.06f, 0.07f, 0.01f), cream); /* the name plate */
addBlock(&b, vec3(0.0f, bottom + h * 0.6f, front + 0.005f), vec3(size.x - 0.08f, 0.26f, 0.01f), glass);
static const Vec3 PACKS[] = { { 0.85f, 0.85f, 0.82f }, { 0.75f, 0.12f, 0.10f }, { 0.20f, 0.30f, 0.62f }, { 0.88f, 0.75f, 0.20f }, { 0.25f, 0.50f, 0.28f } };
const int n = 5;
for (int k = 0; k < n; k++) {
float x = -size.x * 0.5f + 0.06f + (size.x - 0.12f) * ((float)k + 0.5f) / (float)n;
addBlock(&b, vec3(x, bottom + h * 0.6f, front + 0.012f), vec3(0.055f, 0.08f, 0.01f), PACKS[k]); /* a pack on show */
addBlock(&b, vec3(x, bottom + h * 0.33f, front + 0.02f), vec3(0.05f, 0.025f, 0.04f), COLOR_STEEL); /* its knob */
}
addBlock(&b, vec3(size.x * 0.32f, bottom + h * 0.16f, front + 0.008f), vec3(0.05f, 0.08f, 0.015f), COLOR_STEEL); /* coin slot */
addBlock(&b, vec3(-size.x * 0.08f, bottom + 0.05f, front + 0.03f), vec3(size.x * 0.5f, 0.05f, 0.06f), COLOR_IRON); /* the tray */
break;
}
case FURN_ASHTRAY:
buildAshtray(&b, size, 3);
break;
case FURN_GATE: { /* a wooden gate leaf, painted green: boards, two rails and a brace */
const Vec3 green = { 0.24f, 0.45f, 0.28f }, dark = { 0.17f, 0.34f, 0.21f };
int boards = (int)(size.x / 0.14f);
for (int k = 0; k < boards; k++) {
float x = -hx + ((float)k + 0.5f) * size.x / (float)boards;
addBlock(&b, vec3(x, size.y * 0.5f, 0.0f), vec3(size.x / (float)boards - 0.012f, size.y, size.z), k % 2 ? green : vec3_scale(green, 0.94f));
}
for (int r = 0; r < 2; r++)
addBlock(&b, vec3(0.0f, size.y * (0.2f + 0.6f * (float)r), hz + 0.02f), vec3(size.x - 0.06f, 0.14f, 0.04f), dark);
float rise = size.y * 0.6f, run = size.x - 0.12f, len = sqrtf(rise * rise + run * run);
size_t first = b.vertex_count;
addBlock(&b, vec3_zero(), vec3(len, 0.12f, 0.04f), dark);
transformSince(&b, first, mat4_mul(mat4_translate(vec3(0.0f, size.y * 0.5f, hz + 0.02f)), mat4_rotate_z(atan2f(rise, run))));
addBlock(&b, vec3(hx - 0.12f, size.y * 0.5f, hz + 0.05f), vec3(0.05f, 0.18f, 0.05f), COLOR_IRON); /* handle */
break;
}
case FURN_COUNT:
break;
}
return Mesh_fromBuilder(&b);
}
/* Ivy climbing a wall (the wall at z = 0, the plant in front of it, facing
+Z): a few stems wandering up from the ground, leaves all along them,
thicker low down, spreading out under the eaves. */
Mesh Shapes_ivy(float width, float height, unsigned seed) {
static const Vec3 LEAF[] = { { 0.22f, 0.42f, 0.20f }, { 0.28f, 0.50f, 0.24f }, { 0.18f, 0.36f, 0.18f }, { 0.32f, 0.52f, 0.22f } };
MeshBuilder b;
MeshBuilder_init(&b);
if (seed == 0) seed = 1;
const int stems = 5 + (int)(scatter(&seed) * 4.0f);
for (int s = 0; s < stems; s++) {
float x = (scatter(&seed) - 0.5f) * width * 0.6f, top = height * (0.65f + 0.35f * scatter(&seed));
for (float y = 0.05f; y < top; y += 0.11f) {
x += (scatter(&seed) - 0.5f) * 0.18f;
x = clampf(x, -width * 0.5f, width * 0.5f);
float spread = 0.4f + 0.6f * smoothstepf(top * 0.55f, top, y) + 0.3f * (1.0f - smoothstepf(0.0f, 1.2f, y));
int leaves = 3 + (int)(scatter(&seed) * 3.0f);
for (int k = 0; k < leaves; k++) {
float lx = clampf(x + (scatter(&seed) - 0.5f) * spread * 2.0f, -width * 0.5f, width * 0.5f);
float ly = y + (scatter(&seed) - 0.5f) * 0.1f, d = 0.02f + 0.05f * scatter(&seed);
float sz = 0.12f + 0.09f * scatter(&seed);
addBlock(&b, vec3(lx, ly, d), vec3(sz, sz * 0.8f, 0.025f), LEAF[(unsigned)(scatter(&seed) * 4.0f) % 4u]);
}
if ((int)(y / 0.11f) % 3 == 0) /* the stem */
addBlock(&b, vec3(x, y, 0.015f), vec3(0.025f, 0.13f, 0.02f), (Vec3){ 0.36f, 0.28f, 0.18f });
}
}
return Mesh_fromBuilder(&b);
}
Mesh Shapes_hoseNozzle(void) {
const Vec3 yellow = { 0.90f, 0.72f, 0.18f }, green = { 0.20f, 0.52f, 0.24f };
MeshBuilder b;
MeshBuilder_init(&b);
addRod(&b, vec3(0.0f, -0.1f, 0.0f), vec3(0.0f, 0.04f, 0.0f), 0.022f, 0.022f, 8, green);
addRod(&b, vec3(0.0f, 0.04f, 0.0f), vec3(0.0f, 0.12f, 0.0f), 0.018f, 0.012f, 8, yellow);
return Mesh_fromBuilder(&b);
}
Mesh Shapes_hosePiece(void) {
const Vec3 green = { 0.20f, 0.52f, 0.24f };
MeshBuilder b;
MeshBuilder_init(&b);
shapes_addTube(&b, 0.0f, 0.016f, 0.016f, 1.0f, 6, green, false);
return Mesh_fromBuilder(&b);
}
Mesh Shapes_hoseTape(void) {
const Vec3 grey = { 0.62f, 0.63f, 0.64f };
MeshBuilder b;
MeshBuilder_init(&b);
shapes_addTube(&b, 0.0f, 0.021f, 0.021f, 1.0f, 6, grey, false);
return Mesh_fromBuilder(&b);
}
Mesh Shapes_tapeRoll(void) {
const Vec3 grey = { 0.62f, 0.63f, 0.64f }, card = { 0.72f, 0.60f, 0.42f };
MeshBuilder b;
MeshBuilder_init(&b);
shapes_addTube(&b, -0.024f, 0.052f, 0.052f, 0.048f, 16, grey, false); /* the tape ... */
shapes_addTube(&b, -0.025f, 0.036f, 0.036f, 0.05f, 16, card, false); /* ... on its cardboard core */
for (int s = -1; s <= 1; s += 2) /* (the sides: rings of tape) */
for (int k = 0; k < 16; k++) {
float a = 2.0f * PI_F * (float)k / 16.0f;
addBlock(&b, vec3(0.044f * cosf(a), (float)s * 0.024f, 0.044f * sinf(a)), vec3(0.022f, 0.002f, 0.022f), grey);
}
return Mesh_fromBuilder(&b);
}
Mesh Shapes_wateringCan(void) {
const Vec3 green = { 0.30f, 0.56f, 0.38f };
MeshBuilder b;
MeshBuilder_init(&b);
shapes_addTube(&b, -0.14f, 0.12f, 0.11f, 0.26f, 14, green, true); /* body */
addRod(&b, vec3(0.0f, -0.06f, 0.1f), vec3(0.0f, 0.16f, 0.36f), 0.022f, 0.014f, 6, green); /* spout */
size_t rose = b.vertex_count;
shapes_addTube(&b, 0.155f, 0.035f, 0.045f, 0.03f, 8, green, true); /* rose */
transformSince(&b, rose, mat4_translate(vec3(0.0f, 0.0f, 0.36f)));
addRod(&b, vec3(0.0f, 0.12f, -0.08f), vec3(0.0f, 0.2f, 0.0f), 0.012f, 0.012f, 6, green); /* handle */
addRod(&b, vec3(0.0f, 0.2f, 0.0f), vec3(0.0f, 0.12f, 0.08f), 0.012f, 0.012f, 6, green);
return Mesh_fromBuilder(&b);
}
/* ---------- Alice, the dachshund ---------- */
static const Vec3 COLOR_COAT = { 0.55f, 0.30f, 0.14f };
static const Vec3 COLOR_EARS = { 0.36f, 0.19f, 0.09f };
/* A tube lying along Z (from z0 to z1) at height y. */
static void addTubeZ(MeshBuilder *b, float y, float z0, float z1, float r0, float r1, Vec3 color) {
size_t first = b->vertex_count;
shapes_addTube(b, 0.0f, r0, r1, fabsf(z1 - z0), 10, color, true);
transformSince(b, first, alongY(vec3(0.0f, 0.0f, z1 - z0), vec3(0.0f, y, z0)));
}
Mesh Shapes_dogBody(void) {
MeshBuilder b;
MeshBuilder_init(&b);
addTubeZ(&b, 0.21f, 0.29f, -0.27f, 0.085f, 0.095f, COLOR_COAT); /* long body */
addRod(&b, vec3(0.0f, 0.22f, -0.24f), vec3(0.0f, 0.31f, -0.33f), 0.07f, 0.06f, 8, COLOR_COAT); /* neck */
addBlock(&b, vec3(0.0f, 0.33f, -0.36f), vec3(0.11f, 0.1f, 0.13f), COLOR_COAT); /* head */
addBlock(&b, vec3(0.0f, 0.31f, -0.46f), vec3(0.065f, 0.06f, 0.1f), COLOR_COAT); /* snout */
addBlock(&b, vec3(0.0f, 0.325f, -0.51f), vec3(0.03f, 0.025f, 0.02f), COLOR_BLACK); /* nose */
for (int s = -1; s <= 1; s += 2) {
addBlock(&b, vec3((float)s * 0.065f, 0.29f, -0.34f), vec3(0.02f, 0.11f, 0.075f), COLOR_EARS); /* floppy ears */
addBlock(&b, vec3((float)s * 0.035f, 0.35f, -0.425f), vec3(0.015f, 0.015f, 0.01f), COLOR_BLACK); /* eyes */
}
return Mesh_fromBuilder(&b);
}
/* ---------- Telescopic ladder ---------- */
#define LADDER_PITCH_IN 0.066f /* rung spacing slid together ... */
#define LADDER_PITCH_OUT 0.30f /* ... and all the way out */
#define LADDER_FOOT 0.12f /* the lowest rung above the feet */
#define LADDER_CAP 0.18f /* rails past the top rung */
float Shapes_ladderRung(float extend, int k) {
float pitch = LADDER_PITCH_IN + (LADDER_PITCH_OUT - LADDER_PITCH_IN) * clampf(extend, 0.0f, 1.0f);
return LADDER_FOOT + pitch * (float)k;
}
float Shapes_ladderLength(float extend) {
return Shapes_ladderRung(extend, LADDER_RUNGS - 1) + LADDER_CAP;
}
/* The folding ladder: two sections on a hinge. Folded, the upper one lies
along the lower (inside its rails, a little in front); unfolding, it
swings up and over to stand straight on top of it. */
#define FOLD_PITCH 0.28f
#define FOLD_SECTION (LADDER_FOOT + FOLD_PITCH * (float)(FOLD_RUNGS - 1) + 0.14f)
#define FOLD_INSET 0.045f
#define FOLD_FRONT 0.05f
static float foldAngle(float unfold) { return PI_F * (1.0f - clampf(unfold, 0.0f, 1.0f)); }
float Shapes_foldLadderLength(float unfold) {
return fmaxf(FOLD_SECTION, FOLD_SECTION * (1.0f + cosf(foldAngle(unfold))));
}
float Shapes_foldLadderRung(int k) {
return k < FOLD_RUNGS ? LADDER_FOOT + FOLD_PITCH * (float)k
: FOLD_SECTION + LADDER_FOOT + FOLD_PITCH * (float)(k - FOLD_RUNGS);
}
/* One section along +Y from 0, rails half apart. */
static void foldSection(MeshBuilder *b, float half, bool lower) {
const Vec3 alu = { 0.80f, 0.70f, 0.42f }, joint = { 0.22f, 0.20f, 0.18f }; /* (yellowish: tell it from the other one) */
for (int s = -1; s <= 1; s += 2) {
float x = (float)s * half;
addBlock(b, vec3(x, FOLD_SECTION * 0.5f, 0.0f), vec3(0.04f, FOLD_SECTION, 0.06f), alu);
if (lower) addBlock(b, vec3(x, 0.02f, 0.0f), vec3(0.06f, 0.04f, 0.07f), joint); /* rubber feet */
else addBlock(b, vec3(x, FOLD_SECTION - 0.015f, 0.0f), vec3(0.05f, 0.03f, 0.06f), joint); /* end caps */
}
for (int k = 0; k < FOLD_RUNGS; k++)
addBlock(b, vec3(0.0f, LADDER_FOOT + FOLD_PITCH * (float)k, 0.0f), vec3(2.0f * half, 0.03f, 0.045f), alu);
}
Mesh Shapes_foldLadder(float unfold) {
const Vec3 joint = { 0.22f, 0.20f, 0.18f };
const float half = LADDER_WIDTH * 0.5f;
MeshBuilder b;
MeshBuilder_init(&b);
foldSection(&b, half, true);
for (int s = -1; s <= 1; s += 2) /* the hinges */
addBlock(&b, vec3((float)s * (half - FOLD_INSET * 0.5f), FOLD_SECTION, FOLD_FRONT * 0.5f), vec3(0.07f, 0.08f, 0.1f), joint);
size_t first = b.vertex_count;
foldSection(&b, half - FOLD_INSET, false);
transformSince(&b, first, mat4_mul(mat4_translate(vec3(0.0f, FOLD_SECTION, FOLD_FRONT)), mat4_rotate_x(foldAngle(unfold))));
return Mesh_fromBuilder(&b);
}
/* The step ladder: open, the legs stand 30 degrees apart, each 15 off upright;
the treads are set at that angle so they're level when it stands. */
#define STEP_LEN 1.95f
#define STEP_FIRST 0.27f
#define STEP_PITCH 0.29f
#define STEP_SPREAD 0.5236f
#define STEP_SHUT 0.03f /* (folded: the back leg lies just behind the front one) */
float Shapes_stepLadderLength(void) { return STEP_LEN; }
float Shapes_stepLadderRung(int k) {
return k >= STEP_TREADS ? STEP_LEN - 0.03f : STEP_FIRST + STEP_PITCH * (float)k;
}
float Shapes_stepLadderSpread(float open) { return STEP_SHUT + (STEP_SPREAD - STEP_SHUT) * clampf(open, 0.0f, 1.0f); }
Mesh Shapes_stepLadder(float open) {
const Vec3 alu = { 0.74f, 0.76f, 0.78f }, tread = { 0.55f, 0.57f, 0.60f }, plastic = { 0.85f, 0.32f, 0.12f },
rubber = { 0.15f, 0.15f, 0.16f };
const float half = LADDER_WIDTH * 0.5f, level = STEP_SPREAD * 0.5f;
MeshBuilder b;
MeshBuilder_init(&b);
/* the front leg: rails, rubber feet, treads (level when it stands) */
for (int s = -1; s <= 1; s += 2) {
float x = (float)s * half;
addBlock(&b, vec3(x, STEP_LEN * 0.5f, 0.0f), vec3(0.03f, STEP_LEN, 0.07f), alu);
addBlock(&b, vec3(x, 0.02f, 0.0f), vec3(0.045f, 0.04f, 0.085f), rubber);
}
for (int k = 0; k < STEP_TREADS; k++) {
size_t first = b.vertex_count;
addBlock(&b, vec3_zero(), vec3(2.0f * half - 0.02f, 0.025f, 0.11f), tread);
transformSince(&b, first, mat4_mul(mat4_translate(vec3(0.0f, Shapes_stepLadderRung(k), -0.015f)), mat4_rotate_x(-level)));
}
{ /* the top: a plastic platform over the hinge */
size_t first = b.vertex_count;
addBlock(&b, vec3(0.0f, 0.0f, -0.05f), vec3(2.0f * half + 0.06f, 0.05f, 0.2f), plastic);
transformSince(&b, first, mat4_mul(mat4_translate(vec3(0.0f, STEP_LEN - 0.02f, 0.0f)), mat4_rotate_x(-level)));
}
/* the back leg: rails and two braces, swung out from the hinge */
size_t first = b.vertex_count;
const float back = STEP_LEN - 0.05f;
for (int s = -1; s <= 1; s += 2) {
float x = (float)s * (half - 0.02f);
addBlock(&b, vec3(x, back * 0.5f, 0.0f), vec3(0.03f, back, 0.05f), alu);
addBlock(&b, vec3(x, back - 0.02f, 0.0f), vec3(0.045f, 0.04f, 0.07f), rubber);
}
for (int k = 1; k <= 2; k++)
addBlock(&b, vec3(0.0f, back * (float)k / 3.0f, 0.0f), vec3(2.0f * half - 0.06f, 0.025f, 0.025f), alu);
transformSince(&b, first, mat4_mul(mat4_translate(vec3(0.0f, STEP_LEN - 0.04f, STEP_HINGE_Z)),
mat4_rotate_x(PI_F - Shapes_stepLadderSpread(open))));
return Mesh_fromBuilder(&b);
}
Mesh Shapes_ladder(float extend) {
const Vec3 alu = { 0.76f, 0.78f, 0.80f }, joint = { 0.20f, 0.20f, 0.22f };
const float half = LADDER_WIDTH * 0.5f, top = Shapes_ladderLength(extend);
MeshBuilder b;
MeshBuilder_init(&b);
for (int s = -1; s <= 1; s += 2) {
float x = (float)s * half;
addBlock(&b, vec3(x, 0.02f, 0.0f), vec3(0.06f, 0.04f, 0.07f), joint); /* rubber feet */
/* each section of the rails reaches from its rung to the next; thinner further up */
for (int k = 0; k < LADDER_RUNGS; k++) {
float y0 = k == 0 ? 0.0f : Shapes_ladderRung(extend, k) - 0.04f;
float y1 = k == LADDER_RUNGS - 1 ? top : Shapes_ladderRung(extend, k + 1) + 0.01f;
float w = 0.034f - 0.0012f * (float)k;
addBlock(&b, vec3(x, (y0 + y1) * 0.5f, 0.0f), vec3(w, y1 - y0, w * 1.2f), alu);
addBlock(&b, vec3(x, Shapes_ladderRung(extend, k) - 0.02f, 0.0f), vec3(w + 0.014f, 0.035f, w * 1.2f + 0.014f), joint); /* the latch */
}
addBlock(&b, vec3(x, top - 0.015f, 0.0f), vec3(0.05f, 0.03f, 0.06f), joint); /* end caps */
}
for (int k = 0; k < LADDER_RUNGS; k++)
addBlock(&b, vec3(0.0f, Shapes_ladderRung(extend, k), 0.0f), vec3(LADDER_WIDTH, 0.03f, 0.045f), alu);
return Mesh_fromBuilder(&b);
}
/* ---------- The big tree's rope, the phone line ---------- */
/* A rope from a to c (local), sagging this far in the middle; coil: a few loops lying at c. */
Mesh Shapes_rope(Vec3 a, Vec3 c, float sag, bool coil) {
MeshBuilder b;
MeshBuilder_init(&b);
const Vec3 color = { 0.86f, 0.62f, 0.22f };
const int n = 14;
Vec3 was = a;
for (int k = 1; k <= n; k++) {
float t = (float)k / (float)n;
Vec3 p = vec3_add(vec3_lerp(a, c, t), vec3(0.0f, -sag * 4.0f * t * (1.0f - t), 0.0f));
addStick(&b, was, p, 0.018f, color);
was = p;
}
for (int k = 0; coil && k < 3; k++) { /* (the rest of it in loops on the ground) */
float r = 0.22f + 0.03f * (float)k;
Vec3 last = vec3_add(c, vec3(r, 0.02f * (float)k + 0.01f, 0.0f));
for (int i = 1; i <= 10; i++) {
float an = 6.2832f * (float)i / 10.0f;
Vec3 p = vec3_add(c, vec3(cosf(an) * r, 0.02f * (float)k + 0.01f, sinf(an) * r));
addStick(&b, last, p, 0.016f, color);
last = p;
}
}
return Mesh_fromBuilder(&b);
}
/* The throw line: a little sand bag with the cord coiled beside it. */
Mesh Shapes_throwBag(void) {
MeshBuilder b;
MeshBuilder_init(&b);
addBlock(&b, vec3(0.0f, 0.05f, 0.0f), vec3(0.09f, 0.1f, 0.09f), (Vec3){ 0.78f, 0.22f, 0.18f });
Vec3 last = vec3(0.16f, 0.015f, 0.0f);
for (int i = 1; i <= 10; i++) {
float an = 6.2832f * (float)i / 10.0f;
Vec3 p = vec3(0.16f * cosf(an), 0.015f, 0.16f * sinf(an));
addStick(&b, last, p, 0.014f, (Vec3){ 0.86f, 0.62f, 0.22f });
last = p;
}
return Mesh_fromBuilder(&b);
}
/* A wooden phone pole this high (mast: the tall one with two cross arms), its foot at the origin; the arms across X. */
Mesh Shapes_phonePole(float height, bool mast) {
MeshBuilder b;
MeshBuilder_init(&b);
const Vec3 wood = { 0.38f, 0.30f, 0.22f }, porcelain = { 0.88f, 0.88f, 0.84f };
shapes_addTube(&b, 0.0f, mast ? 0.16f : 0.12f, mast ? 0.11f : 0.09f, height, 10, wood, true);
for (int arm = 0; arm < (mast ? 2 : 1); arm++) {
float y = height - 0.4f - 0.6f * (float)arm;
addBlock(&b, vec3(0.0f, y, 0.0f), vec3(1.4f, 0.1f, 0.1f), wood);
for (int k = -1; k <= 1; k++) addBlock(&b, vec3(0.6f * (float)k, y + 0.1f, 0.0f), vec3(0.06f, 0.1f, 0.06f), porcelain);
}
if (mast) { /* (a box with the junctions, a step bolt or two) */
addBlock(&b, vec3(0.0f, 2.2f, 0.16f), vec3(0.3f, 0.45f, 0.14f), (Vec3){ 0.55f, 0.56f, 0.52f });
for (int k = 0; k < 6; k++) addBlock(&b, vec3(k % 2 ? 0.16f : -0.16f, 3.0f + 0.5f * (float)k, 0.0f), vec3(0.14f, 0.03f, 0.03f), COLOR_STEEL);
}
return Mesh_fromBuilder(&b);
}
/* The wires: three of them from pole to pole (n points, each the middle of a cross arm, local), sagging; down: torn,
hanging off the poles to the ground (ground_y: local height of the ground under each point). */
Mesh Shapes_wires(const Vec3 *points, int n, bool down, const float *ground_y) {
MeshBuilder b;
MeshBuilder_init(&b);
const Vec3 color = { 0.12f, 0.12f, 0.12f };
for (int i = 0; i + 1 < n; i++) {
Vec3 a0 = points[i], c0 = points[i + 1];
Vec3 d = vec3_sub(c0, a0);
Vec3 across = vec3_normalize(vec3(-d.z, 0.0f, d.x));
for (int w = -1; w <= 1; w++) {
Vec3 a = vec3_add(a0, vec3_scale(across, 0.6f * (float)w)), c = vec3_add(c0, vec3_scale(across, 0.6f * (float)w));
a.y += 0.15f; c.y += 0.15f;
if (down) { /* (snapped in the middle: each half hangs down to the ground) */
Vec3 ga = vec3_lerp(a, c, 0.35f), gc = vec3_lerp(a, c, 0.65f);
ga.y = ground_y ? ground_y[i] + 0.03f : 0.03f;
gc.y = ground_y ? ground_y[i + 1] + 0.03f : 0.03f;
addStick(&b, a, vec3_lerp(a, ga, 0.6f), 0.012f, color);
addStick(&b, vec3_lerp(a, ga, 0.6f), ga, 0.012f, color);
addStick(&b, c, vec3_lerp(c, gc, 0.6f), 0.012f, color);
addStick(&b, vec3_lerp(c, gc, 0.6f), gc, 0.012f, color);
continue;
}
Vec3 was = a;
for (int k = 1; k <= 12; k++) {
float t = (float)k / 12.0f;
Vec3 p = vec3_add(vec3_lerp(a, c, t), vec3(0.0f, -0.7f * 4.0f * t * (1.0f - t), 0.0f));
addStick(&b, was, p, 0.012f, color);
was = p;
}
}
}
return Mesh_fromBuilder(&b);
}