diff options
| -rw-r--r-- | README.md | 1 | ||||
| -rwxr-xr-x | build.sh | 5 | ||||
| -rw-r--r-- | kon/konworld.c | 483 | ||||
| -rw-r--r-- | kon/konworld.h | 121 |
4 files changed, 608 insertions, 2 deletions
@@ -33,6 +33,7 @@ The engine modules are a `.h` and a `.c` pair, they depend on each other a bit, | tile | tilemaps and sprite sheets | | 3d | first person kit: mouse look body, walls from a tilemap, editable hill terrain (adds tile) | | sector | maps made of rooms (vertices, floors, ceilings, walls) with entities like doors and buttons, in a text format | +| world | one place to move bodies through sector maps, terrain, tilemaps and your own boxes, cylinders, spheres and planes (needs 3d, sector) | | pack | encrypted asset packs | | assets | named asset cache with reference counts, loose files or a pack | | image | stb_image so assets can load pngs | @@ -12,7 +12,7 @@ developer=0 usage() { echo "usage: build.sh [-d] [-D] [-p platform] [-m module,...] [-o objdir] [target ...]" - echo "modules: font toml tile pack ui debug assets image audio mic model 3d sector" + echo "modules: font toml tile pack ui debug assets image audio mic model 3d sector world" echo "platforms: native linux bsd windows" echo "targets: all run clean tools pack examples" exit 1 @@ -77,7 +77,7 @@ addobj() for m in $modules; do DEFS="$DEFS -DKON_MODULE_$(echo "$m" | tr 'a-z' 'A-Z')" case $m in - font|toml|tile|pack|ui|debug|3d|sector) addobj "kon$m" ;; + font|toml|tile|pack|ui|debug|3d|sector|world) addobj "kon$m" ;; assets) addobj konassets ;; model) addobj konmodel; addobj konassets ;; image) addobj stb_image ;; @@ -105,6 +105,7 @@ need() # modules that use other modules case " $modules " in *" debug "*) need font; need assets ;; esac case " $modules " in *" ui "*) need font ;; esac +case " $modules " in *" world "*) need 3d; need sector; need tile ;; esac case " $modules " in *" 3d "*) need tile ;; esac case " $modules " in *" model "*) need assets ;; esac case " $DEFS " in *" -DKON_MODULE_ASSETS "*) need pack ;; esac diff --git a/kon/konworld.c b/kon/konworld.c new file mode 100644 index 0000000..1806099 --- /dev/null +++ b/kon/konworld.c @@ -0,0 +1,483 @@ +/* SPDX-License-Identifier: MIT */ +/* + * konworld.c + * + * Copyright (c) 2026 KonAki + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "konworld.h" + +#include <math.h> +#include <stdlib.h> +#include <string.h> + +#define KON_SECTOR_SET 48 + +/*** shapes ***/ + +kon_collider_t kon_colliderBox(kon_vec3_t min, kon_vec3_t max) +{ + kon_collider_t c = {0}; + + c.type = KON_COLLIDER_BOX; + c.enabled = 1; + c.min = min; + c.max = max; + return c; +} + +kon_collider_t kon_colliderCylinder(kon_vec3_t bottom, float radius, float height) +{ + kon_collider_t c = {0}; + + c.type = KON_COLLIDER_CYLINDER; + c.enabled = 1; + c.center = bottom; + c.radius = radius; + c.height = height; + return c; +} + +kon_collider_t kon_colliderSphere(kon_vec3_t center, float radius) +{ + kon_collider_t c = {0}; + + c.type = KON_COLLIDER_SPHERE; + c.enabled = 1; + c.center = center; + c.radius = radius; + return c; +} + +kon_collider_t kon_colliderPlane(kon_vec3_t point, kon_vec3_t normal) +{ + kon_collider_t c = {0}; + + c.type = KON_COLLIDER_PLANE; + c.enabled = 1; + c.normal = kon_vec3Normalize(normal); + c.offset = kon_vec3Dot(c.normal, point); + return c; +} + +/*** the world ***/ + +void kon_worldInit(kon_world_t *world) +{ + memset(world, 0, sizeof(*world)); + world->tileHeight = 1.0f; + world->gravity = 20.0f; + world->stepHeight = 0.35f; + world->maxSlope = 1.0f; +} + +void kon_worldFree(kon_world_t *world) +{ + free(world->colliders); + world->colliders = NULL; + world->colliderCount = world->colliderCap = 0; +} + +int kon_worldAdd(kon_world_t *world, kon_collider_t collider) +{ + int i; + + for (i = 0; i < world->colliderCount; i++) { + if (world->colliders[i].type == KON_COLLIDER_NONE) { + world->colliders[i] = collider; + return i; + } + } + if (world->colliderCount == world->colliderCap) { + int cap = world->colliderCap ? world->colliderCap * 2 : 16; + kon_collider_t *grown = realloc(world->colliders, (size_t)cap * sizeof(*grown)); + + if (!grown) return -1; + world->colliders = grown; + world->colliderCap = cap; + } + world->colliders[world->colliderCount] = collider; + return world->colliderCount++; +} + +kon_collider_t *kon_worldCollider(kon_world_t *world, int index) +{ + if (index < 0 || index >= world->colliderCount || world->colliders[index].type == KON_COLLIDER_NONE) return NULL; + return &world->colliders[index]; +} + +void kon_worldRemove(kon_world_t *world, int index) +{ + if (index < 0 || index >= world->colliderCount) return; + memset(&world->colliders[index], 0, sizeof(world->colliders[index])); +} + +/*** ground ***/ + +static float kon_colliderTop_(const kon_collider_t *c, float x, float z) +{ + switch (c->type) { + case KON_COLLIDER_BOX: + if (x >= c->min.x && x <= c->max.x && z >= c->min.z && z <= c->max.z) return c->max.y; + break; + case KON_COLLIDER_CYLINDER: { + float dx = x - c->center.x, dz = z - c->center.z; + + if (dx * dx + dz * dz <= c->radius * c->radius) return c->center.y + c->height; + break; + } + case KON_COLLIDER_PLANE: + if (c->normal.y >= 0.7f) return (c->offset - c->normal.x * x - c->normal.z * z) / c->normal.y; + break; + default: + break; + } + return KON_NO_GROUND; +} + +float kon_worldGround(const kon_world_t *world, float x, float z, float feet, int hint, int *sector) +{ + float ground = KON_NO_GROUND; + int s = -1, i, any = 0; + + if (world->sectors) { + any = 1; + s = kon_sectorAt(world->sectors, x, z, hint); + if (s >= 0) ground = world->sectors->sectors[s].floor; + } + if (s < 0 && world->terrain) { + any = 1; + if (x >= 0.0f && z >= 0.0f && x <= (float)world->terrain->columns && z <= (float)world->terrain->rows) ground = kon_terrainHeight(world->terrain, x, z); + } + if (s < 0 && ground == KON_NO_GROUND && world->tiles) { + any = 1; + if (x >= 0.0f && z >= 0.0f && x < (float)world->tiles->columns && z < (float)world->tiles->rows) ground = 0.0f; + } + if (!any) ground = 0.0f; + + for (i = 0; i < world->colliderCount; i++) { + const kon_collider_t *c = &world->colliders[i]; + float top; + + if (!c->enabled || c->type == KON_COLLIDER_NONE) continue; + top = kon_colliderTop_(c, x, z); + if (top != KON_NO_GROUND && top <= feet + world->stepHeight && top > ground) ground = top; + } + if (sector) *sector = s; + return ground; +} + +/*** pushing out ***/ + +static float kon_clampf_(float v, float lo, float hi) +{ + return v < lo ? lo : (v > hi ? hi : v); +} + +/* the body can't step onto something that ends above its step, and something that starts above its head is no problem */ +static int kon_blocks_(const kon_world_t *world, const kon_body_t *body, float lo, float hi) +{ + return hi > body->position.y + world->stepHeight && lo < body->position.y + body->height; +} + +static int kon_pushSegment_(float *x, float *z, float r, float ax, float az, float bx, float bz, float nx, float nz) +{ + float ex = bx - ax, ez = bz - az, len2 = ex * ex + ez * ez; + float t = len2 > 0.0f ? ((*x - ax) * ex + (*z - az) * ez) / len2 : 0.0f; + float cx, cz, dx, dz, d; + + t = kon_clampf_(t, 0.0f, 1.0f); + cx = ax + ex * t; + cz = az + ez * t; + dx = *x - cx; + dz = *z - cz; + d = sqrtf(dx * dx + dz * dz); + if (d >= r) return 0; + if (d < 1.0e-5f) { + dx = nx; + dz = nz; + d = 0.0f; + } else { + dx /= d; + dz /= d; + } + *x += dx * (r - d); + *z += dz * (r - d); + return 1; +} + +static int kon_pushBox_(float *x, float *z, float r, float minX, float minZ, float maxX, float maxZ) +{ + float cx = kon_clampf_(*x, minX, maxX), cz = kon_clampf_(*z, minZ, maxZ); + float dx = *x - cx, dz = *z - cz, d2 = dx * dx + dz * dz; + + if (d2 >= r * r) return 0; + if (d2 > 1.0e-10f) { + float d = sqrtf(d2); + + *x += dx / d * (r - d); + *z += dz / d * (r - d); + } else { + /* the middle is inside, leave through the nearest side */ + float left = *x - minX, right = maxX - *x, up = *z - minZ, down = maxZ - *z; + float best = left; + + if (right < best) best = right; + if (up < best) best = up; + if (down < best) best = down; + if (best == left) *x = minX - r; + else if (best == right) *x = maxX + r; + else if (best == up) *z = minZ - r; + else *z = maxZ + r; + } + return 1; +} + +static int kon_pushCircle_(float *x, float *z, float r, float cx, float cz, float cr) +{ + float dx = *x - cx, dz = *z - cz, d = sqrtf(dx * dx + dz * dz), reach = r + cr; + + if (d >= reach) return 0; + if (d < 1.0e-5f) { + *x = cx + reach; + return 1; + } + *x = cx + dx / d * reach; + *z = cz + dz / d * reach; + return 1; +} + +/* is the way from one room into the next blocked for this body, because of the step, or because it would not fit */ +static int kon_gapBlocks_(const kon_world_t *world, const kon_body_t *body, const kon_sector_t *from, const kon_sector_t *to) +{ + float stand = body->position.y > to->floor ? body->position.y : to->floor; + + if (to->floor - body->position.y > world->stepHeight) return 1; + if (to->ceiling - stand < body->height) return 1; + if (from->ceiling - stand < body->height) return 1; + return 0; +} + +static int kon_sectorSet_(const kon_sectorMap_t *map, int start, int *set) +{ + int n = 0, ring, i, k, j; + + set[n++] = start; + for (ring = 0; ring < 2; ring++) { + int count = n; + + for (i = 0; i < count; i++) { + const kon_sector_t *sector = &map->sectors[set[i]]; + + for (k = 0; k < sector->count; k++) { + int p = map->edges[sector->first + k].portal; + + if (p < 0) continue; + for (j = 0; j < n; j++) if (set[j] == p) break; + if (j == n && n < KON_SECTOR_SET) set[n++] = p; + } + } + } + return n; +} + +/* one pass over everything the body can bump into. touching is set to the last collider that moved it */ +static int kon_pushAll_(const kon_world_t *world, kon_body_t *body, float *x, float *z) +{ + float r = body->radius, feet = body->position.y, top = feet + body->height; + int moved = 0, i; + + if (world->sectors && body->sector >= 0) { + const kon_sectorMap_t *map = world->sectors; + int set[KON_SECTOR_SET], n = kon_sectorSet_(map, body->sector, set), a, k; + + for (a = 0; a < n; a++) { + const kon_sector_t *sector = &map->sectors[set[a]]; + + for (k = 0; k < sector->count; k++) { + kon_vec2_t p = kon_sectorCorner(map, set[a], k), q = kon_sectorCorner(map, set[a], k + 1); + float ex = q.x - p.x, ez = q.y - p.y, len = sqrtf(ex * ex + ez * ez); + int portal = map->edges[sector->first + k].portal; + + if (len <= 0.0f) continue; + if (portal < 0) { + if (!(feet < sector->ceiling && top > sector->floor)) continue; + } else if (!kon_gapBlocks_(world, body, sector, &map->sectors[portal])) { + continue; + } + moved |= kon_pushSegment_(x, z, r, p.x, p.y, q.x, q.y, ez / len, -ex / len); + } + } + } + + if (world->tiles) { + const kon_tilemap_t *tiles = world->tiles; + int column, row, c0 = (int)floorf(*x - r), c1 = (int)floorf(*x + r), r0 = (int)floorf(*z - r), r1 = (int)floorf(*z + r); + + if (kon_blocks_(world, body, 0.0f, world->tileHeight)) { + for (row = r0; row <= r1; row++) { + for (column = c0; column <= c1; column++) { + int solid = 1; + + if (column >= 0 && row >= 0 && column < tiles->columns && row < tiles->rows) { + uint16_t id = kon_tilemapAt(tiles, column, row); + + solid = id < world->solidCount && world->solid[id]; + } + if (solid) moved |= kon_pushBox_(x, z, r, (float)column, (float)row, (float)column + 1.0f, (float)row + 1.0f); + } + } + } + } + + for (i = 0; i < world->colliderCount; i++) { + const kon_collider_t *c = &world->colliders[i]; + int hit = 0; + + if (!c->enabled) continue; + switch (c->type) { + case KON_COLLIDER_BOX: + if (kon_blocks_(world, body, c->min.y, c->max.y)) hit = kon_pushBox_(x, z, r, c->min.x, c->min.z, c->max.x, c->max.z); + break; + case KON_COLLIDER_CYLINDER: + if (kon_blocks_(world, body, c->center.y, c->center.y + c->height)) hit = kon_pushCircle_(x, z, r, c->center.x, c->center.z, c->radius); + break; + case KON_COLLIDER_SPHERE: + if (kon_blocks_(world, body, c->center.y - c->radius, c->center.y + c->radius)) hit = kon_pushCircle_(x, z, r, c->center.x, c->center.z, c->radius); + break; + case KON_COLLIDER_PLANE: + if (c->normal.y < 0.7f) { + float nh = sqrtf(c->normal.x * c->normal.x + c->normal.z * c->normal.z); + float d = c->normal.x * *x + c->normal.y * (feet + body->height * 0.5f) + c->normal.z * *z - c->offset; + + if (nh > 1.0e-4f && d < r * nh) { + float t = (r * nh - d) / (nh * nh); + + *x += c->normal.x * t; + *z += c->normal.z * t; + hit = 1; + } + } + break; + default: + break; + } + if (hit) body->touching = i; + moved |= hit; + } + return moved; +} + +/*** moving ***/ + +/* tries to stand at x, z. the body is only changed if it works */ +static int kon_try_(const kon_world_t *world, kon_body_t *body, float x, float z, float oldX, float oldZ) +{ + kon_body_t probe = *body; + float ground, dist; + int pass, s; + + probe.sector = body->sector; + for (pass = 0; pass < 4; pass++) { + if (!kon_pushAll_(world, &probe, &x, &z)) break; + } + ground = kon_worldGround(world, x, z, body->position.y, body->sector, &s); + if (ground == KON_NO_GROUND) return 0; + + if (world->terrain && s < 0 && kon_sectorAt(world->sectors, oldX, oldZ, -1) < 0) { + float before = kon_terrainHeight(world->terrain, oldX, oldZ), after = kon_terrainHeight(world->terrain, x, z); + + dist = sqrtf((x - oldX) * (x - oldX) + (z - oldZ) * (z - oldZ)); + if (after > body->position.y && after - before > world->maxSlope * dist + 1.0e-4f) return 0; + } + body->position.x = x; + body->position.z = z; + body->touching = probe.touching; + if (s >= 0) body->sector = s; + return 1; +} + +void kon_bodyInit(kon_body_t *body, float x, float y, float z, float radius, float height) +{ + memset(body, 0, sizeof(*body)); + body->position = KON_VEC3(x, y, z); + body->radius = radius; + body->height = height; + body->sector = -1; + body->touching = -1; +} + +void kon_bodyJump(kon_body_t *body, float speed) +{ + if (!body->onGround) return; + body->vy = speed; + body->onGround = 0; +} + +void kon_worldPlace(const kon_world_t *world, kon_body_t *body) +{ + int s; + float ground = kon_worldGround(world, body->position.x, body->position.z, body->position.y, body->sector, &s); + + if (s >= 0) body->sector = s; + if (ground != KON_NO_GROUND) body->position.y = ground; + body->vy = 0.0f; + body->onGround = 1; +} + +void kon_worldMove(kon_world_t *world, kon_body_t *body, float dx, float dz, double dt) +{ + float x = body->position.x, z = body->position.z, ground, ceiling = 1.0e30f; + int s; + + body->touching = -1; + if (dx != 0.0f || dz != 0.0f) { + if (!kon_try_(world, body, x + dx, z + dz, x, z)) { + if (!kon_try_(world, body, x + dx, z, x, z)) kon_try_(world, body, x, z + dz, x, z); + } + } + + ground = kon_worldGround(world, body->position.x, body->position.z, body->position.y, body->sector, &s); + if (s >= 0) { + body->sector = s; + ceiling = world->sectors->sectors[s].ceiling; + } + if (ground == KON_NO_GROUND) ground = body->position.y; + + if (body->vy <= 0.0f && body->position.y - ground <= world->stepHeight) { + body->position.y = ground; + body->vy = 0.0f; + body->onGround = 1; + } else { + body->vy -= world->gravity * (float)dt; + body->position.y += body->vy * (float)dt; + body->onGround = 0; + if (body->vy <= 0.0f && body->position.y <= ground) { + body->position.y = ground; + body->vy = 0.0f; + body->onGround = 1; + } + } + if (body->position.y + body->height > ceiling) { + body->position.y = ceiling - body->height; + if (body->vy > 0.0f) body->vy = 0.0f; + } +} diff --git a/kon/konworld.h b/kon/konworld.h new file mode 100644 index 0000000..4be1d1c --- /dev/null +++ b/kon/konworld.h @@ -0,0 +1,121 @@ +/* SPDX-License-Identifier: MIT */ +/* + * konworld.h + * + * Copyright (c) 2026 KonAki + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef KONWORLD_H +#define KONWORLD_H + +#include "konmath.h" +#include "kontile.h" +#include "kon3d.h" +#include "konsector.h" + +/*** definition ***/ + +/* one place to ask "can I stand here, can I walk there" for everything a 3d game is built from: the rooms of a sector map, + a terrain, a tilemap, and any shapes you add yourself. x and z are the plan, y is up. a world only points at the maps and the + terrain, they stay yours and can change between frames. nothing here knows what a door or a monster is, a door is a box that + the game moves */ + +#define KON_NO_GROUND (-1.0e30f) + +typedef enum kon_colliderType { + KON_COLLIDER_NONE = 0, + KON_COLLIDER_BOX, /* min and max, sides line up with the axes */ + KON_COLLIDER_CYLINDER, /* standing on the ground, center is the middle of its bottom */ + KON_COLLIDER_SPHERE, /* blocks only, you can't stand on one */ + KON_COLLIDER_PLANE /* everything behind the plane is solid. a plane that faces mostly up is a floor, a ramp if it is tilted */ +} kon_colliderType_t; + +typedef struct kon_collider { + int type; + int enabled; /* 0 and it is ignored, 1 */ + int user; /* yours, like the entity this belongs to */ + kon_vec3_t min, max; /* box */ + kon_vec3_t center; /* cylinder, sphere */ + float radius, height; /* cylinder, sphere (no height) */ + kon_vec3_t normal; /* plane, the side it faces, length 1. points p with dot(normal, p) < offset are solid */ + float offset; +} kon_collider_t; + +kon_collider_t kon_colliderBox(kon_vec3_t min, kon_vec3_t max); +kon_collider_t kon_colliderCylinder(kon_vec3_t bottom, float radius, float height); +kon_collider_t kon_colliderSphere(kon_vec3_t center, float radius); +kon_collider_t kon_colliderPlane(kon_vec3_t point, kon_vec3_t normal); + +typedef struct kon_world { + const kon_sectorMap_t *sectors; /* any of these three can be NULL */ + const kon_terrain_t *terrain; + const kon_tilemap_t *tiles; /* one unit per tile like the rest of kon3d */ + const uint8_t *solid; /* indexed by tile id like kon_tilemapMove */ + int solidCount; + float tileHeight; /* how tall the solid tiles are, 1 */ + + kon_collider_t *colliders; + int colliderCount, colliderCap; + + float gravity; /* 20 */ + float stepHeight; /* the biggest step you walk up without jumping, 0.35 */ + float maxSlope; /* rise over run you can climb on terrain, 1 is 45 degrees */ +} kon_world_t; + +/* a thing that walks. position is where the feet are, the body is a cylinder going up from there */ +typedef struct kon_body { + kon_vec3_t position; + float radius, height; /* 0.2 and 1.0 are fine for a person in this scale */ + float vy; /* up speed, only jumps and falls change it */ + int onGround; + int sector; /* the room it is in, -1 if none, kept up to date by kon_worldMove */ + int touching; /* the collider it was pushed by in the last move, -1 if none. a monster hitting a door, say */ +} kon_body_t; + +/*** the world ***/ + +void kon_worldInit(kon_world_t *world); +void kon_worldFree(kon_world_t *world); + +/* returns the index, or -1 if there is no memory. the index stays the same until the collider is removed */ +int kon_worldAdd(kon_world_t *world, kon_collider_t collider); + +/* NULL if the index is not a collider. change it any time, a door opens by raising min.y */ +kon_collider_t *kon_worldCollider(kon_world_t *world, int index); +void kon_worldRemove(kon_world_t *world, int index); + +/* the height of the highest thing to stand on at x, z that is not more than a step above feet, or KON_NO_GROUND outside the world. + hint is the sector you were in, -1 if you have none. sector gets the room the point is in, it can be NULL */ +float kon_worldGround(const kon_world_t *world, float x, float z, float feet, int hint, int *sector); + +/*** bodies ***/ + +void kon_bodyInit(kon_body_t *body, float x, float y, float z, float radius, float height); +void kon_bodyJump(kon_body_t *body, float speed); + +/* puts the feet on the ground under them right away */ +void kon_worldPlace(const kon_world_t *world, kon_body_t *body); + +/* moves by dx, dz and slides along whatever is in the way: walls, shapes, steps that are too high, slopes that are too steep, the edge + of the world. then gravity: steps up are taken at once and drops are falls. dt is seconds */ +void kon_worldMove(kon_world_t *world, kon_body_t *body, float dx, float dz, double dt); + +#endif |
