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/world/clothes.c raw
#include "world/clothes.h"
#include "people/ai.h"
#include "people/hands.h"
#include "render/shapes.h"
#include <stdio.h>
#include <string.h>
#define CHAIR_REACH 1.0f /* m: a chair this near in front: over its back */
#define LOOK_REACH 1.9f /* m: what you look at this near from the eye */
#define EDGE_LOW 0.35f /* m over the feet: a top this high (up to EDGE_HIGH) takes it ... */
#define EDGE_HIGH 1.5f
#define EDGE_DROP 0.25f /* ... hung over it when it drops this much ... */
#define EDGE_THICK 0.42f /* ... within this far past the edge (a railing, a wall top, a bench); else laid on it */
#define CHAIR_GAP 0.05f /* a chair's back this thick */
#define AWAY 0.6f /* m: on the ground this far in front */
#define SAT_CHANCE 0.8f /* sitting down on a chair: over its back this often (x tidiness) */
static Vec3 flat(Vec3 v, Vec3 or_else) {
Vec3 f = vec3_normalize(vec3(v.x, 0.0f, v.z));
return vec3_length(f) > 0.5f ? f : or_else;
}
static bool isChair(const Object *o) { return o && o->type == OBJ_FURNITURE && o->variant == FURN_CHAIR; }
/* The top of a chair's back (its back at local -Z, see Shapes_furniture). */
static Vec3 chairBack(const Object *chair) {
Vec3 local = vec3(0.0f, chair->size.y, -chair->size.z * 0.5f + 0.02f);
return vec3_add(chair->position, mat4_mul_dir(mat4_rotate_y(chair->rotation.y), local));
}
static void overChair(const Object *chair, ClothSpot *s) {
s->lay = CLOTH_HUNG;
s->host = (Object *)chair;
s->at = chairBack(chair);
s->yaw = chair->rotation.y;
s->gap = CHAIR_GAP;
s->where = "over the chair's back";
}
/* The nearest chair in front within reach. */
static const Object *chairInFront(const World *world, const Object *who, Vec3 look) {
const Object *best = NULL;
float best_d = CHAIR_REACH;
for (int i = 0; i < world->count; i++) {
const Object *o = world->objects[i];
if (!isChair(o)) continue;
Vec3 to = vec3_sub(chairBack(o), who->position);
to.y = 0.0f;
float d = vec3_length(to);
if (d < best_d && (d < 0.3f || vec3_dot(vec3_scale(to, 1.0f / d), look) > 0.4f)) { best = o; best_d = d; }
}
return best;
}
static bool notThere(const Object *o, const void *ctx) {
return o == ctx || o->is_character || o->is_held || o->type == OBJ_DOG || o->type == OBJ_CLOTHES;
}
/* Hung over it: a railing (thin), a wall top, a bench, a chair. */
static const char *overIt(const Object *o, bool thin) {
if (o->type == OBJ_FURNITURE && o->variant == FURN_BENCH) return "over the bench";
if (isChair(o)) return "over the chair";
if (o->type == OBJ_WALL || o->type == OBJ_HOUSE || o->type == OBJ_BARN) return thin ? "over the railing" : "over the wall";
return "over it";
}
static const char *onTopOf(const Object *o) {
if (o->type == OBJ_FURNITURE && o->variant == FURN_TABLE) return "on the table";
if (o->type == OBJ_FURNITURE && o->variant == FURN_BENCH) return "on the bench";
if (isChair(o)) return "on the chair";
return "on top of it";
}
bool Clothes_spot(const World *world, const Object *who, Vec3 look, ClothSpot *s) {
memset(s, 0, sizeof(*s));
Vec3 facing = Object_facing(who), ahead = flat(look, facing);
float feet = who->position.y;
if (who->sitting) {
if (isChair(who->seat)) { overChair(who->seat, s); return true; }
if (who->seat) return false; /* (a bench or a log: no) */
s->lay = CLOTH_SPREAD;
s->at = vec3_add(who->position, vec3_scale(facing, 0.05f));
s->at.y = World_groundHeight(world, s->at.x, s->at.z, feet + 0.3f);
s->yaw = yaw_facing(facing);
s->where = "on the ground";
return true;
}
const Object *chair = chairInFront(world, who, ahead);
if (chair) { overChair(chair, s); return true; }
Vec3 eye = vec3_add(who->position, vec3(0.0f, who->look.height * 0.92f, 0.0f));
Object *hit = NULL;
Vec3 n = vec3_zero();
float t = World_rayCast(world, eye, look, LOOK_REACH, notThere, who, &hit, &n);
if (t >= 0.0f && hit) {
Vec3 h = vec3_add(eye, vec3_scale(look, t));
Vec3 in = flat(vec3_scale(n, -1.0f), ahead); /* (into it) */
Vec3 edge = vec3_add(h, vec3_scale(in, 0.02f));
float top = World_groundHeight(world, edge.x, edge.z, h.y + 0.6f), up = top - feet;
if (up > EDGE_LOW && up < EDGE_HIGH && top < h.y + 0.6f) {
float thick = EDGE_THICK; /* (how thick the top is: where it drops away past the edge) */
for (float d = 0.04f; d < EDGE_THICK; d += 0.04f) {
Vec3 q = vec3_add(h, vec3_scale(in, d));
if (top - World_groundHeight(world, q.x, q.z, top + 0.05f) > EDGE_DROP) { thick = d; break; }
}
s->yaw = yaw_facing(vec3_scale(in, -1.0f)); /* (the front towards you) */
if (thick < EDGE_THICK) {
Vec3 mid = vec3_add(h, vec3_scale(in, thick * 0.5f));
s->lay = CLOTH_HUNG;
s->at = vec3(mid.x, top, mid.z);
s->gap = thick;
s->where = overIt(hit, thick <= 0.12f);
} else {
s->lay = CLOTH_FOLDED;
s->at = vec3(h.x + in.x * 0.18f, top, h.z + in.z * 0.18f);
s->where = onTopOf(hit);
}
return true;
}
}
float away = t >= 0.0f ? clampf(t * sqrtf(fmaxf(1.0f - look.y * look.y, 0.0f)) - 0.35f, 0.25f, AWAY) : AWAY;
s->lay = CLOTH_SPREAD;
s->at = vec3_add(who->position, vec3_scale(ahead, away));
s->at.y = World_groundHeight(world, s->at.x, s->at.z, feet + 0.5f);
s->yaw = yaw_facing(ahead);
s->where = "on the ground";
return fabsf(s->at.y - feet) < 0.5f;
}
bool Clothes_putDown(World *world, Object *who, const ClothSpot *s) {
Mesh mesh = Shapes_pullover((int)s->lay, who->look.jacket, fmaxf(s->gap, 0.04f));
Object *c = who->pullover;
if (!c) {
c = Object_createDecal(mesh, s->at);
if (!c || !World_add(world, c)) return false;
c->type = OBJ_CLOTHES;
snprintf(c->name, sizeof(c->name), "Pullover");
who->pullover = c;
} else {
Mesh_destroy(&c->mesh);
c->mesh = mesh;
c->position = s->at;
}
c->variant = (int)s->lay;
c->cloth_gap = fmaxf(s->gap, 0.04f);
c->rotation = vec3(0.0f, s->yaw, 0.0f);
c->cloth_of = who;
c->cloth_host = s->host;
c->cloth_where = s->where;
if (who->look.top == TOP_JACKET) who->look.top = TOP_SHIRT;
who->pullover_down = true;
who->pullover_sat = false;
who->look.jacket_tied = false;
Hands_touch(who, vec3_add(s->at, vec3(0.0f, s->lay == CLOTH_HUNG ? 0.0f : 0.05f, 0.0f)));
return true;
}
void Clothes_takeBack(Object *who, bool wear) {
Object *c = who->pullover;
if (c && who->pullover_down) {
Hands_touch(who, c->position);
Mesh_destroy(&c->mesh); /* (nothing to see where it was) */
c->cloth_host = NULL;
}
who->pullover_down = false;
who->pullover_sat = false;
if (wear) who->look.top = TOP_JACKET;
who->look.jacket_tied = who->look.top != TOP_JACKET;
}
const char *Clothes_whereText(const char *saved) {
static const char *const ALL[] = { "over the chair's back", "on the ground", "over the railing", "over the wall", "over the bench",
"over the chair", "over it", "on the table", "on the bench", "on the chair", "on top of it" };
for (size_t i = 0; saved && i < sizeof ALL / sizeof ALL[0]; i++)
if (!strcmp(saved, ALL[i])) return ALL[i];
return "somewhere";
}
void Clothes_rebuild(Object *c) {
const Object *who = c->cloth_of;
if (c->type != OBJ_CLOTHES || !who || !who->pullover_down || who->pullover != c) return;
Mesh mesh = Shapes_pullover(c->variant, who->look.jacket, fmaxf(c->cloth_gap, 0.04f));
if (!mesh.vao) return;
Mesh_destroy(&c->mesh);
c->mesh = mesh;
}
const char *Clothes_where(const Object *who) {
return who->pullover_down && who->pullover ? who->pullover->cloth_where : NULL;
}
Object *Clothes_alongRay(const World *world, Vec3 eye, Vec3 dir, float reach) {
Object *best = NULL;
float best_off = 0.35f;
for (int i = 0; i < world->count; i++) {
Object *c = world->objects[i];
if (c->type != OBJ_CLOTHES || !c->mesh.vao) continue;
Vec3 mid = vec3_add(c->position, vec3(0.0f, c->variant == CLOTH_HUNG ? -0.15f : 0.03f, 0.0f));
Vec3 rel = vec3_sub(mid, eye);
float along = vec3_dot(rel, dir);
if (along < 0.0f || along > reach) continue;
float off = vec3_length(vec3_sub(rel, vec3_scale(dir, along)));
if (off < best_off) { best = c; best_off = off; }
}
return best;
}
static float dice(World *world) {
world->rng = world->rng * 1103515245u + 12345u;
return (float)((world->rng >> 8) & 0xFFFFu) / 65536.0f;
}
/* How likely they bother (the tidy and the proud more, the drunk less). */
static float tidiness(const Object *o) {
return clampf(0.35f + 0.35f * o->ai.character.likes[ORDER_TRASH] + 0.2f * o->temperament.pride - 0.3f * fminf(o->drunk, 1.0f), 0.05f, 0.9f);
}
/* Sat down (settled): theirs over the chair's back, or under them on the ground. */
static void satDown(World *world, Object *o) {
if (o->pullover_down || o->look.top == TOP_JACKET || o->chill > 0.4f) return;
bool chair = isChair(o->seat);
if (o->seat && !chair) return;
if (dice(world) > tidiness(o) * (chair ? SAT_CHANCE : 1.0f)) return;
ClothSpot s;
if (!Clothes_spot(world, o, Object_facing(o), &s) || !Clothes_putDown(world, o, &s)) return;
o->pullover_sat = true;
if (!chair && o->ai.speech_time <= 0.0f && dice(world) < 0.3f) Ai_say(o, "Not sitting in the dirt.");
}
void Clothes_update(World *world) {
for (int i = 0; i < world->count; i++) {
Object *o = world->objects[i];
if (o->type == OBJ_CLOTHES && o->mesh.vao && isChair(o->cloth_host)) {
o->position = chairBack(o->cloth_host);
o->rotation.y = o->cloth_host->rotation.y;
continue;
}
if (o->type != OBJ_HUMAN) continue;
bool settled = o->sitting && !o->sit_moving;
if (settled && !o->was_sitting) satDown(world, o);
else if (!o->sitting && o->was_sitting && o->pullover_sat && o->pullover_down) Clothes_takeBack(o, o->chill > 0.6f);
o->was_sitting = settled;
}
}