#include #include #include #include #include #include #include #define max(x, y) ({ \ __typeof__(x) __x = x, __y = y; \ (__x < __y ? __y : __x); \ }) #define min(x, y) ({ \ __typeof__(x) __x = x, __y = y; \ (__x > __y ? __y : __x); \ }) enum { TILE_KIND_WALL, TILE_KIND_EMPTY }; typedef uint32_t Tile; #define LEVEL_WIDTH 40 #define LEVEL_HEIGHT 25 #define TILE_SIZE 20 static Tile level[LEVEL_WIDTH * LEVEL_HEIGHT]; enum { ENTITY_KIND_FAUCET }; #define TILE_AT(x, y) (*({ \ uint32_t __x = x; \ uint32_t __y = y; \ level + (__x + LEVEL_WIDTH * __y); \ })) typedef uint32_t Entity_Kind; typedef struct Entity { Entity_Kind kind; uint16_t x, y; union { struct { uint32_t flow; uint32_t tick; } faucet; } entity; } Entity; #define MAX_ENTITIES 10 static uint32_t entity_count; static Entity entities[MAX_ENTITIES]; #define CYCLE_MS 10 #define CYCLES_PER_SEC 100 static int last_frame_time; static int time_left_over; cpSpace *space; cpBody *scenery; typedef struct Neighbor { uint32_t j; cpFloat q; cpFloat q2; struct Neighbor *next; } Neighbor; typedef struct Water_Particle { cpShape *shape; cpFloat rho; cpFloat rho_near; cpFloat press; cpFloat press_near; Neighbor *neighbors; } Water_Particle; #define MAX_WATER 10000 uint32_t water_count; Water_Particle water[MAX_WATER]; #define MAX_NEIGHBORS 100000 uint32_t neighbor_count; Neighbor neighbors[MAX_NEIGHBORS]; #define WATER_RADIUS 6.0 #define ROI_RADIUS 18.0 #define ROI_RADIUS_SQUARE (ROI_RADIUS * ROI_RADIUS) #define K 1.0 #define K_NEAR 2.0 #define REST_DENSITY 0.1 #define WATER_GROUP 1 void draw_tile(uint32_t x, uint32_t y) { Tile tile = TILE_AT(x, y); if (tile == TILE_KIND_WALL) { glColor3f(1, 0, 0); glBegin(GL_QUADS); glVertex2f((x + 0) * TILE_SIZE, (y + 0) * TILE_SIZE); glVertex2f((x + 1) * TILE_SIZE, (y + 0) * TILE_SIZE); glVertex2f((x + 1) * TILE_SIZE, (y + 1) * TILE_SIZE); glVertex2f((x + 0) * TILE_SIZE, (y + 1) * TILE_SIZE); glEnd(); glColor3f(1, 1, 1); } else if (tile == TILE_KIND_EMPTY) { } else { abort(); } } void draw_entity(uint32_t i) { Entity_Kind kind = entities[i].kind; cpFloat x = entities[i].x; cpFloat y = entities[i].y; if (kind == ENTITY_KIND_FAUCET) { glColor3f(0, 1, 1); glBegin(GL_QUADS); glVertex2f((x + 0.25f) * TILE_SIZE, (y + 0.25f) * TILE_SIZE); glVertex2f((x + 0.75f) * TILE_SIZE, (y + 0.25f) * TILE_SIZE); glVertex2f((x + 0.75f) * TILE_SIZE, (y + 0.75f) * TILE_SIZE); glVertex2f((x + 0.25f) * TILE_SIZE, (y + 0.75f) * TILE_SIZE); glEnd(); glColor3f(1, 1, 1); } else { abort(); } } void draw_water(uint32_t i) { cpVect p = water[i].shape->body->p; glColor3f(0, 0, 1); glBegin(GL_QUADS); glVertex2f(p.x - WATER_RADIUS, p.y - WATER_RADIUS); glVertex2f(p.x + WATER_RADIUS, p.y - WATER_RADIUS); glVertex2f(p.x + WATER_RADIUS, p.y + WATER_RADIUS); glVertex2f(p.x - WATER_RADIUS, p.y + WATER_RADIUS); glEnd(); } /* typedef struct Density_Closure { cpFloat d; cpFloat dn; } Density_Closure; int calculateDensity( void *obj1, void *obj2, __attribute__((unused)) void *data) { cpShape *dropletShape = obj1; cpShape *otherDropletShape = obj2; Density_Closure *c = data; uintptr_t i = (uintptr_t)dropletShape->data; uintptr_t j = (uintptr_t)otherDropletShape->data; if (j <= i) return 0; // names from Brandon Pelfrey's tutorial... urgh cpVect rij = cpvsub(dropletShape->body->p, otherDropletShape->body->p); cpFloat dist2 = cpvdot(rij, rij); // Chipmunk has cpvlengthsq but it's not inline?! if (dist2 < ROI_RADIUS_SQUARE) { cpFloat length = sqrt(dist2); cpFloat q = 1 - length / ROI_RADIUS; cpFloat q2 = q * q; cpFloat q3 = q2 * q; c->d += q2; c->dn += q3; water[j].rho += q2; water[j].rho_near += q3; if (neighbor_count >= MAX_NEIGHBORS) { printf("exceeded MAX_NEIGHBORS\n"); return 0; } Neighbor *n = neighbors + (neighbor_count++); n->j = j; n->q = q; n->q2 = q2; n->next = water[i].neighbors; water[i].neighbors = n; } return 0; } */ void update(void) { for (uint32_t i = 0; i < entity_count; ++i) { if (entities[i].kind == ENTITY_KIND_FAUCET) { ++entities[i].entity.faucet.tick; if (water_count < MAX_WATER && entities[i].entity.faucet.tick == entities[i].entity.faucet.flow) { entities[i].entity.faucet.tick = 0; cpBody *dropletBody = cpBodyNew(1.0, INFINITY); dropletBody->p = cpv( TILE_SIZE * (entities[i].x + 0.5) + rand() % 3 - 1, TILE_SIZE * (entities[i].y + 0.5) + rand() % 3 - 1); cpSpaceAddBody(space, dropletBody); cpShape *dropletShape = cpCircleShapeNew( dropletBody, WATER_RADIUS, cpvzero); dropletShape->e = 1.0; dropletShape->u = 0.0; //dropletShape->group = WATER_GROUP; dropletShape->data = (void *)(uintptr_t)water_count; cpSpaceAddShape(space, dropletShape); water[water_count].shape = dropletShape; ++water_count; } } else { abort(); } } /* for (uint32_t i = 0; i < water_count; ++i) { water[i].rho = 0.0f; water[i].rho_near = 0.0f; water[i].neighbors = NULL; cpBodyResetForces(water[i].shape->body); } neighbor_count = 0; for (uint32_t i = 0; i < water_count; ++i) { Density_Closure closure = { .d = 0.0f, .dn = 0.0f }; cpShape *droplet = water[i].shape; cpBB box = cpBBNew( droplet->body->p.x - ROI_RADIUS, droplet->body->p.y - ROI_RADIUS, droplet->body->p.x + ROI_RADIUS, droplet->body->p.y + ROI_RADIUS); cpSpaceHashQuery( space->activeShapes, droplet, box, calculateDensity, &closure); water[i].rho += closure.d; water[i].rho_near += closure.dn; water[i].press = K * (water[i].rho - REST_DENSITY); water[i].press_near = K_NEAR * water[i].rho_near; } for (uint32_t i = 0; i < water_count; ++i) { cpVect dX = cpvzero; Neighbor *n = water[i].neighbors; while (n) { cpVect rij = cpvsub( water[i].shape->body->p, water[n->j].shape->body->p); cpFloat dm = (water[i].press + water[n->j].press) * n->q + (water[i].press_near + water[n->j].press_near) * n->q2; cpFloat rijlensq = cpvdot(rij, rij); cpFloat rijlen = sqrt(rijlensq); cpVect D = cpvmult(rij, dm / rijlen); dX = cpvadd(dX, D); cpBodyApplyForce(water[n->j].shape->body, D, cpvzero); n = n->next; } cpBodyApplyForce(water[i].shape->body, cpvneg(dX), cpvzero); // printf("applying -(%f, %f) to %u\n", dX.x, dX.y, i); } */ cpSpaceStep(space, CYCLE_MS * 0.001); } void display(void) { int now = glutGet(GLUT_ELAPSED_TIME); int dt = now - last_frame_time + time_left_over; last_frame_time = now; while (dt >= CYCLE_MS) { update(); dt -= CYCLE_MS; } time_left_over = dt; glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho(0, 800, 500, 0, -1, 1); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); for (uint32_t y = 0; y < LEVEL_HEIGHT; ++y) { for (uint32_t x = 0; x < LEVEL_WIDTH; ++x) { draw_tile(x, y); } } for (uint32_t i = 0; i < entity_count; ++i) { draw_entity(i); } for (uint32_t i = 0; i < water_count; ++i) { draw_water(i); } glutSwapBuffers(); } void reshape(int width, int height) { glViewport(0, 0, width, height); } void idle(void) { glutPostRedisplay(); } cpVect WALL_VERTS[4] = { { 0, TILE_SIZE }, { TILE_SIZE, TILE_SIZE }, { TILE_SIZE, 0 }, { 0, 0 } }; void read_level(char const *path) { FILE *f = fopen(path, "rb"); if (!f) { fprintf(stderr, "can't open %s\n", path); exit(EXIT_FAILURE); } for (uint32_t y = 0; y < LEVEL_HEIGHT; ++y) { for (uint32_t x = 0; x < LEVEL_WIDTH; ++x) { int c = fgetc(f); if (c == '#') { TILE_AT(x, y) = TILE_KIND_WALL; cpShape *shape = cpPolyShapeNew( scenery, 4, WALL_VERTS, cpv(x * TILE_SIZE, y * TILE_SIZE)); cpSpaceAddStaticShape(space, shape); } else { TILE_AT(x, y) = TILE_KIND_EMPTY; if (c == ' ') { } else if (c == 'F') { if (entity_count == MAX_ENTITIES) { fprintf(stderr, "exceeded max entities (%c) in %s\n", c, path); exit(EXIT_FAILURE); } entities[entity_count].kind = ENTITY_KIND_FAUCET; entities[entity_count].x = x; entities[entity_count].y = y; entities[entity_count].entity.faucet.flow = 10; entities[entity_count].entity.faucet.tick = 0; entity_count ++; } else { fprintf(stderr, "syntax error (%c) in %s\n", c, path); exit(EXIT_FAILURE); } } } int c = fgetc(f); if (c != '\n') { fprintf(stderr, "syntax error (%c) in %s\n", c, path); exit(EXIT_FAILURE); } } } int main(int argc, char** argv) { glutInit(&argc, argv); glutInitDisplayMode(GLUT_RGB | GLUT_ALPHA | GLUT_DOUBLE | GLUT_DEPTH); glutInitWindowSize(800, 500); glutCreateWindow("WT"); glutDisplayFunc(display); glutReshapeFunc(reshape); glutIdleFunc(idle); cpInitChipmunk(); space = cpSpaceNew(); space->gravity = cpv(0.0, 5.0 * TILE_SIZE); space->elasticIterations = 2; scenery = cpBodyNew(INFINITY, INFINITY); read_level("level.txt"); last_frame_time = glutGet(GLUT_ELAPSED_TIME); glutMainLoop(); return EXIT_SUCCESS; }