200 lines
8.0 KiB
C
200 lines
8.0 KiB
C
#include "islands.h"
|
|
#include "engineCamera.h"
|
|
#include "enginePhysics.h"
|
|
#include "engineRectangle.h"
|
|
#include "engineVector.h"
|
|
#include <math.h>
|
|
#include <raylib.h>
|
|
#include <raymath.h>
|
|
|
|
// How far an island can drift from the camera before it's considered "left
|
|
// behind" and gets regenerated fresh outside the current view, the same
|
|
// recycling idea used for enemies/birds so the world stays populated as the
|
|
// player explores.
|
|
#define ISLAND_RECYCLE_DISTANCE 800.0f
|
|
#define ISLAND_MIN_SPAWN_DISTANCE 80
|
|
#define ISLAND_MAX_SPAWN_DISTANCE 600
|
|
// Concentric elevation bands, each a fraction of the coastline radius at
|
|
// that angle — same idea as a real elevation-banded map (shore/lowland/
|
|
// highland rings), just three tiers instead of five since this renders at
|
|
// pixel-art scale, not map scale.
|
|
#define ISLAND_GRASS_SCALE 0.72f
|
|
#define ISLAND_HILL_SCALE 0.35f
|
|
// Native resolution the shape is painted at. Higher than the 16x16 ship/bird
|
|
// sprites — islands are the biggest thing on screen now, and matching their
|
|
// texel density would make each "pixel" a huge, coarse block; this keeps
|
|
// the pixel-art look while the individual pixels stay small relative to the
|
|
// island's size.
|
|
#define ISLAND_IMAGE_SIZE 128
|
|
|
|
// A coastline is two overlapping cosine lobes (a broad one for bays/
|
|
// peninsulas, a finer one for coastal roughness) plus light jitter, instead
|
|
// of a single jittered circle — the same kind of shape irregularity a real
|
|
// procedural terrain generator uses, just far cheaper.
|
|
static void GenerateCoastline(float shoreRadiusPx[ISLAND_SHAPE_POINTS], float baseRadius) {
|
|
float amp1 = (float)GetRandomValue(15, 30) / 100.0f;
|
|
float freq1 = (float)GetRandomValue(2, 3);
|
|
float phase1 = (float)GetRandomValue(0, 628) / 100.0f;
|
|
float amp2 = (float)GetRandomValue(5, 15) / 100.0f;
|
|
float freq2 = (float)GetRandomValue(5, 8);
|
|
float phase2 = (float)GetRandomValue(0, 628) / 100.0f;
|
|
|
|
for (int i = 0; i < ISLAND_SHAPE_POINTS; i++) {
|
|
float angle = (2.0f * PI * (float)i) / (float)ISLAND_SHAPE_POINTS;
|
|
float shapeFactor = 1.0f
|
|
+ amp1 * cosf(freq1 * angle + phase1)
|
|
+ amp2 * cosf(freq2 * angle + phase2);
|
|
if (shapeFactor < 0.5f) shapeFactor = 0.5f;
|
|
|
|
float jitter = (float)GetRandomValue(90, 110) / 100.0f;
|
|
shoreRadiusPx[i] = baseRadius * shapeFactor * jitter;
|
|
}
|
|
|
|
// Average each radius with its neighbours a couple of times so the
|
|
// outline reads as an irregular but rounded coastline instead of a spiky
|
|
// star.
|
|
for (int pass = 0; pass < 2; pass++) {
|
|
float smoothed[ISLAND_SHAPE_POINTS];
|
|
for (int i = 0; i < ISLAND_SHAPE_POINTS; i++) {
|
|
int prev = (i - 1 + ISLAND_SHAPE_POINTS) % ISLAND_SHAPE_POINTS;
|
|
int next = (i + 1) % ISLAND_SHAPE_POINTS;
|
|
smoothed[i] = (shoreRadiusPx[prev] + shoreRadiusPx[i] + shoreRadiusPx[next]) / 3.0f;
|
|
}
|
|
for (int i = 0; i < ISLAND_SHAPE_POINTS; i++) shoreRadiusPx[i] = smoothed[i];
|
|
}
|
|
}
|
|
|
|
static float SampleRadius(const float shoreRadiusPx[ISLAND_SHAPE_POINTS], float angle) {
|
|
float t = angle / (2.0f * PI) * ISLAND_SHAPE_POINTS;
|
|
int i0 = ((int)t) % ISLAND_SHAPE_POINTS;
|
|
int i1 = (i0 + 1) % ISLAND_SHAPE_POINTS;
|
|
float frac = t - (int)t;
|
|
return shoreRadiusPx[i0] * (1.0f - frac) + shoreRadiusPx[i1] * frac;
|
|
}
|
|
|
|
static Texture2D GenerateIslandTexture(const float shoreRadiusPx[ISLAND_SHAPE_POINTS],
|
|
Color shoreColor, Color grassColor, Color hillColor) {
|
|
Image image = GenImageColor(ISLAND_IMAGE_SIZE, ISLAND_IMAGE_SIZE, BLANK);
|
|
float center = ISLAND_IMAGE_SIZE / 2.0f;
|
|
|
|
for (int y = 0; y < ISLAND_IMAGE_SIZE; y++) {
|
|
for (int x = 0; x < ISLAND_IMAGE_SIZE; x++) {
|
|
float dx = (x + 0.5f) - center;
|
|
float dy = (y + 0.5f) - center;
|
|
float dist = sqrtf(dx * dx + dy * dy);
|
|
|
|
float angle = atan2f(dy, dx);
|
|
if (angle < 0.0f) angle += 2.0f * PI;
|
|
|
|
float shoreRadius = SampleRadius(shoreRadiusPx, angle);
|
|
|
|
if (dist <= shoreRadius * ISLAND_HILL_SCALE) {
|
|
ImageDrawPixel(&image, x, y, hillColor);
|
|
} else if (dist <= shoreRadius * ISLAND_GRASS_SCALE) {
|
|
ImageDrawPixel(&image, x, y, grassColor);
|
|
} else if (dist <= shoreRadius) {
|
|
ImageDrawPixel(&image, x, y, shoreColor);
|
|
}
|
|
}
|
|
}
|
|
|
|
Texture2D texture = LoadTextureFromImage(image);
|
|
UnloadImage(image);
|
|
SetTextureFilter(texture, TEXTURE_FILTER_POINT);
|
|
return texture;
|
|
}
|
|
|
|
static void GenerateIsland(Island *island, Vector2 position) {
|
|
if (island->sprite.texture.id != 0) {
|
|
UnloadTexture(island->sprite.texture);
|
|
}
|
|
|
|
island->position = position;
|
|
|
|
float worldDiameter = (float)GetRandomValue(220, 380);
|
|
|
|
float basePixelRadius = (float)GetRandomValue(30, 44);
|
|
float shoreRadiusPx[ISLAND_SHAPE_POINTS];
|
|
GenerateCoastline(shoreRadiusPx, basePixelRadius);
|
|
|
|
// The collision radius has to track the coastline's actual average
|
|
// extent, not a fraction of the destRec/canvas size — the canvas has a
|
|
// fair amount of open water margin around the shore (needed so the
|
|
// harmonic wobble and jitter don't clip off-canvas), so anything based on
|
|
// worldDiameter directly ends up much bigger than the visible island,
|
|
// making the ship "bump" into open water well before the shore.
|
|
float avgRadiusPx = 0.0f;
|
|
for (int i = 0; i < ISLAND_SHAPE_POINTS; i++) avgRadiusPx += shoreRadiusPx[i];
|
|
avgRadiusPx /= ISLAND_SHAPE_POINTS;
|
|
float pixelToWorld = worldDiameter / (float)ISLAND_IMAGE_SIZE;
|
|
// Slightly over the average so the collision circle covers the coastline
|
|
// at most angles instead of sitting exactly on it — the shape bulges out
|
|
// past the average at peninsulas, and a ship visually overlapping the
|
|
// shore reads worse than the collision boundary having a little slack.
|
|
island->radius = avgRadiusPx * pixelToWorld * 1.05f;
|
|
|
|
// Slight per-island tint variation so a cluster of islands doesn't look
|
|
// like the same stamp copy-pasted around.
|
|
Color shoreColor = (Color){
|
|
(unsigned char)Clamp(233 + GetRandomValue(-10, 10), 0, 255),
|
|
(unsigned char)Clamp(220 + GetRandomValue(-10, 10), 0, 255),
|
|
(unsigned char)Clamp(178 + GetRandomValue(-10, 10), 0, 255),
|
|
255};
|
|
Color grassColor = (Color){
|
|
(unsigned char)Clamp(80 + GetRandomValue(-15, 15), 0, 255),
|
|
(unsigned char)Clamp(168 + GetRandomValue(-15, 15), 0, 255),
|
|
(unsigned char)Clamp(84 + GetRandomValue(-15, 15), 0, 255),
|
|
255};
|
|
Color hillColor = (Color){
|
|
(unsigned char)Clamp(52 + GetRandomValue(-12, 12), 0, 255),
|
|
(unsigned char)Clamp(120 + GetRandomValue(-12, 15), 0, 255),
|
|
(unsigned char)Clamp(64 + GetRandomValue(-12, 12), 0, 255),
|
|
255};
|
|
|
|
Texture2D texture = GenerateIslandTexture(shoreRadiusPx, shoreColor, grassColor, hillColor);
|
|
|
|
island->sprite = (Sprite){
|
|
.texture = texture,
|
|
.frameSize = {ISLAND_IMAGE_SIZE, ISLAND_IMAGE_SIZE},
|
|
.sourceRec = {0.0f, 0.0f, ISLAND_IMAGE_SIZE, ISLAND_IMAGE_SIZE},
|
|
.destRec = {position.x, position.y, worldDiameter, worldDiameter},
|
|
.origin = {worldDiameter / 2.0f, worldDiameter / 2.0f},
|
|
.rotation = 0.0f,
|
|
.color = WHITE};
|
|
}
|
|
|
|
Islands CreateIslands(Camera2D *camera) {
|
|
Islands islands = {.count = MAX_ISLANDS};
|
|
Rectangle cameraRec = GetCameraRectangle(camera);
|
|
|
|
for (int i = 0; i < islands.count; i++) {
|
|
GenerateIsland(&islands.island[i],
|
|
GetPositionOutsideRectangle(cameraRec, ISLAND_MIN_SPAWN_DISTANCE, ISLAND_MAX_SPAWN_DISTANCE));
|
|
}
|
|
|
|
return islands;
|
|
}
|
|
|
|
void UpdateIslands(Islands *islands, Camera2D *camera) {
|
|
Rectangle cameraRec = GetCameraRectangle(camera);
|
|
|
|
for (int i = 0; i < islands->count; i++) {
|
|
if (GetDistanceBetweenTwoVectors(islands->island[i].position, camera->target) > ISLAND_RECYCLE_DISTANCE) {
|
|
GenerateIsland(&islands->island[i],
|
|
GetPositionOutsideRectangle(cameraRec, ISLAND_MIN_SPAWN_DISTANCE, ISLAND_MAX_SPAWN_DISTANCE));
|
|
}
|
|
}
|
|
}
|
|
|
|
void RenderIslands(Islands *islands) {
|
|
for (int i = 0; i < islands->count; i++) {
|
|
RenderSprite(&islands->island[i].sprite);
|
|
}
|
|
}
|
|
|
|
void UnloadIslands(Islands *islands) {
|
|
for (int i = 0; i < islands->count; i++) {
|
|
UnloadTexture(islands->island[i].sprite.texture);
|
|
}
|
|
}
|