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