diff --git a/game/engine/LogDisplay.lua b/game/engine/LogDisplay.lua index 0bdb1b0eeb79149e97ba6cb3e22e8248eb3ea466..c9494209df995aba1804a90236341fefc3ce6998 100644 --- a/game/engine/LogDisplay.lua +++ b/game/engine/LogDisplay.lua @@ -18,6 +18,7 @@ end function _M:call(str, ...) local lines = str:format(...):splitLines(self.w - 4, self.font) for i = #lines, 1, -1 do + print("[LOG]", lines[i]) table.insert(self.log, 1, lines[i]) end while #self.log > self.max do diff --git a/game/engine/Map.lua b/game/engine/Map.lua index 2058ed42bcde83c76d31e09884c92a0aef302c9e..56bb00e7dced5ad80dd7e12d4981b428c68bcd50 100644 --- a/game/engine/Map.lua +++ b/game/engine/Map.lua @@ -40,7 +40,6 @@ function _M:init(w, h) self.remembers = {} for i = 0, w * h - 1 do self.map[i] = {} end getmetatable(self).__call = _M.call - getmetatable(self).__gc = _M.close local mapbool = function(t, x, y, v) if x < 0 or y < 0 or x >= self.w or y >= self.h then return end if v ~= nil then @@ -53,11 +52,47 @@ function _M:init(w, h) setmetatable(self.remembers, {__call = mapbool}) self.surface = core.display.newSurface(self.viewport.width, self.viewport.height) - self.fov = function(x, y, d) core.fov.calc(x, y, d,_M.opaque, _M.apply, self) end - self.fov_lite = function(x, y, d) core.fov.calc(x, y, d,_M.opaque, _M.applyLite, self) end + self._fov = core.fov.new(_M.opaque, _M.apply, self) + self._fov_lite = core.fov.new(_M.opaque, _M.applyLite, self) self.changed = true end +--- Runs the FOV algorithm on the map +-- @param x source point of the ligth +-- @param y source point of the ligth +-- @param d radius of the light +function _M:fov(x, y, d) + -- Reset seen grids + if self.clean_fov then + self.clean_fov = false + for i = 0, self.w * self.h - 1 do self.seens[i] = nil end + end + self._fov(x, y, d) + + -- Also seen the source itself + self.seens(x, y, true) + self.lites(x, y, true) + self.remembers(x, y, true) +end + +--- Runs the FOV algorithm on the map, ligthing grids to allow rememberance +-- @param x source point of the ligth +-- @param y source point of the ligth +-- @param d radius of the light +function _M:fovLite(x, y, d) + -- Reset seen grids + if self.clean_fov then + self.clean_fov = false + for i = 0, self.w * self.h - 1 do self.seens[i] = nil end + self._fov_lite(x, y, d) + end + + -- Also seen the source itself + self.seens(x, y, true) + self.lites(x, y, true) + self.remembers(x, y, true) +end + --- Sets/gets a value from the map -- It is defined as the function metamethod, so one can simply do: mymap(x, y, Map.TERRAIN) -- @param x position @@ -96,7 +131,8 @@ end function _M:display() -- If nothing changed, return the same surface as before if not self.changed then return self.surface end - self.changed = false print("redraw") + self.changed = false + self.clean_fov = true -- Erase and the display the map self.surface:erase() @@ -120,7 +156,6 @@ function _M:display() end end end - self.seens[z] = nil end end else -- Version with multi display @@ -148,18 +183,6 @@ function _M:display() return self.surface end ---- Closes all stuff used by the map --- No need to call it manually usualy -function _M:close() - self.tiles:close() --- self.fovLite:close() --- self.fovLite = nil --- self.fov:close() --- self.fov = nil - return true -end - - --- Sets checks if a grid lets sigth pass through -- Used by FOV code function _M:opaque(x, y) diff --git a/game/engine/Zone.lua b/game/engine/Zone.lua index 5956847b13c7f86b74771002ad132767f30c32f6..a1c9280efc7007260f77ae56fab5331ac17a626e 100644 --- a/game/engine/Zone.lua +++ b/game/engine/Zone.lua @@ -81,5 +81,8 @@ function _M:getLevel(game, lev) generator:generate() end + -- Clean up things + collectgarbage("collect") + return level end diff --git a/game/modules/tome/class/Game.lua b/game/modules/tome/class/Game.lua index 74435a796df095f2ddc2a03e1d32b1c436ce0f87..784bb412fc6c4d674680698d89b9f7096c4f9308 100644 --- a/game/modules/tome/class/Game.lua +++ b/game/modules/tome/class/Game.lua @@ -55,11 +55,9 @@ function _M:display() if self.level and self.level.map then if self.level.map.changed then - self.level.map.seens(self.player.x, self.player.y, true) - self.level.map.lites(self.player.x, self.player.y, true) - self.level.map.remembers(self.player.x, self.player.y, true) - self.level.map.fov(self.player.x, self.player.y, 20) - self.level.map.fov_lite(self.player.x, self.player.y, 4) + self.level.map:fov(self.player.x, self.player.y, 20) + self.level.map:fovLite(self.player.x, self.player.y, 4) +-- core.fov.calc_circle(15,15, 7, self.level.map.opaque, self.level.map.apply, self.level.map) end local s = self.level.map:display() if s then diff --git a/game/modules/tome/class/NPC.lua b/game/modules/tome/class/NPC.lua index 4d5a760265c6cb2a190016dbbed97c70e9fdf9aa..2b1a4ee9bc780a0622ab34c8240a890d9e3cf1d6 100644 --- a/game/modules/tome/class/NPC.lua +++ b/game/modules/tome/class/NPC.lua @@ -8,5 +8,5 @@ function _M:init(t) end function _M:act() --- self:move(self.x + 1, self.y) + self:move(self.x + 1, self.y) end diff --git a/game/modules/tome/data/zones/ancient_ruins/zone.lua b/game/modules/tome/data/zones/ancient_ruins/zone.lua index d3e90f78ab0e9936b6f963f1631e97f40f464aff..716a0e70c5421b0bb384327330a6303a9e9a8827 100644 --- a/game/modules/tome/data/zones/ancient_ruins/zone.lua +++ b/game/modules/tome/data/zones/ancient_ruins/zone.lua @@ -7,7 +7,7 @@ return { level_npcs = {5, 10}, generator = { map = { - class= "engine.generator.map.Rooms", + class= "engine.generator.map.Empty", floor = "FLOOR", wall = "WALL", up = "UP", diff --git a/premake4.lua b/premake4.lua index 585179ebd4eddc1133d5fa12836fc8a99a99b7fc..e71164e9c75471984d3c8ec0d8c6d7c9edc762c5 100644 --- a/premake4.lua +++ b/premake4.lua @@ -6,6 +6,7 @@ solution "TEngine" "src", "src/lua", "src/luasocket", + "src/fov", "src/physfs", "src/physfs/zlib123", "/usr/include/SDL", @@ -31,7 +32,7 @@ project "TEngine" language "C" targetname "t-engine" files { "src/*.c", } - links { "physfs", "lua", "luasocket" } + links { "physfs", "lua", "fov", "luasocket" } defines { "_DEFAULT_VIDEOMODE_FLAGS_='SDL_HWSURFACE|SDL_DOUBLEBUF'" } configuration "macosx" @@ -101,3 +102,10 @@ project "luasocket" "src/luasocket/wsocket.c", "src/luasocket/mime.c", } + +project "fov" + kind "StaticLib" + language "C" + targetname "fov" + + files { "src/fov/*.c", } diff --git a/src/core_lua.c b/src/core_lua.c index ccfd4dcbf7063f4df00aa812a5e6e18c1a3e0f21..912a0e7985bf32cff1b57cead3cff24be0871c6d 100644 --- a/src/core_lua.c +++ b/src/core_lua.c @@ -1,4 +1,4 @@ -#include "fov.h" +#include "fov/fov.h" #include "lua.h" #include "lauxlib.h" #include "lualib.h" @@ -15,25 +15,157 @@ * FOV * ****************************************************************** ******************************************************************/ +struct lua_fov +{ + fov_settings_type fov_settings; + int apply_ref; + int opaque_ref; + int map_ref; +}; + +static void map_seen(void *m, int x, int y, int dx, int dy, int radius, void *src) +{ + struct lua_fov *fov = (struct lua_fov *)m; + radius--; + if (dx*dx + dy*dy <= radius*radius + 1) + { // circular view - can be changed if you like + + lua_rawgeti(L, LUA_REGISTRYINDEX, fov->apply_ref); + lua_rawgeti(L, LUA_REGISTRYINDEX, fov->map_ref); + lua_pushnumber(L, x); + lua_pushnumber(L, y); + lua_call(L, 3, 0); + } +} + +static bool map_opaque(void *m, int x, int y) +{ + struct lua_fov *fov = (struct lua_fov *)m; + + lua_rawgeti(L, LUA_REGISTRYINDEX, fov->opaque_ref); + lua_rawgeti(L, LUA_REGISTRYINDEX, fov->map_ref); + lua_pushnumber(L, x); + lua_pushnumber(L, y); + lua_call(L, 3, 1); + bool res = lua_toboolean(L, -1); + lua_pop(L, 1); + return res; +} + +static int lua_new_fov(lua_State *L) +{ + int map_ref = luaL_ref(L, LUA_REGISTRYINDEX); + int apply_ref = luaL_ref(L, LUA_REGISTRYINDEX); + int opaque_ref = luaL_ref(L, LUA_REGISTRYINDEX); + + struct lua_fov *fov = (struct lua_fov*)lua_newuserdata(L, sizeof(struct lua_fov)); + auxiliar_setclass(L, "fov{core}", -1); + fov->apply_ref = apply_ref; + fov->opaque_ref = opaque_ref; + fov->map_ref = map_ref; + fov_settings_init(&(fov->fov_settings)); + fov_settings_set_shape(&(fov->fov_settings), FOV_SHAPE_CIRCLE); + fov_settings_set_opacity_test_function(&(fov->fov_settings), map_opaque); + fov_settings_set_apply_lighting_function(&(fov->fov_settings), map_seen); + + return 1; +} + +static int lua_free_fov(lua_State *L) +{ + struct lua_fov *fov = (struct lua_fov*)auxiliar_checkclass(L, "fov{core}", 1); + fov_settings_free(&(fov->fov_settings)); + luaL_unref(L, LUA_REGISTRYINDEX, fov->apply_ref); + luaL_unref(L, LUA_REGISTRYINDEX, fov->opaque_ref); + luaL_unref(L, LUA_REGISTRYINDEX, fov->map_ref); + lua_pushnumber(L, 1); + printf("fov gc\n"); + return 1; +} + static int lua_fov(lua_State *L) +{ + struct lua_fov *fov = (struct lua_fov*)auxiliar_checkclass(L, "fov{core}", 1); + int x = luaL_checknumber(L, 2); + int y = luaL_checknumber(L, 3); + int radius = luaL_checknumber(L, 4); + + fov_circle(&(fov->fov_settings), fov, NULL, x, y, radius+1); + return 0; +} + +static int lua_fov_calc_circle(lua_State *L) { int x = luaL_checknumber(L, 1); int y = luaL_checknumber(L, 2); int radius = luaL_checknumber(L, 3); int map_ref = luaL_ref(L, LUA_REGISTRYINDEX); int apply_ref = luaL_ref(L, LUA_REGISTRYINDEX); - int check_ref = luaL_ref(L, LUA_REGISTRYINDEX); + int opaque_ref = luaL_ref(L, LUA_REGISTRYINDEX); + + struct lua_fov fov; + + fov_settings_init(&(fov.fov_settings)); + fov_circle(&(fov.fov_settings), &fov, NULL, x, y, radius+1); + fov_settings_free(&(fov.fov_settings)); - do_fov(x, y, radius, map_ref, check_ref, apply_ref); + luaL_unref(L, LUA_REGISTRYINDEX, fov.apply_ref); + luaL_unref(L, LUA_REGISTRYINDEX, fov.opaque_ref); + luaL_unref(L, LUA_REGISTRYINDEX, fov.map_ref); - luaL_unref(L, LUA_REGISTRYINDEX, apply_ref); - luaL_unref(L, LUA_REGISTRYINDEX, check_ref); - luaL_unref(L, LUA_REGISTRYINDEX, map_ref); + return 0; +} + +static int lua_fov_calc_beam(lua_State *L) +{ + int x = luaL_checknumber(L, 1); + int y = luaL_checknumber(L, 2); + int radius = luaL_checknumber(L, 3); + int direction = luaL_checknumber(L, 4); + float angle = luaL_checknumber(L, 5); + int map_ref = luaL_ref(L, LUA_REGISTRYINDEX); + int apply_ref = luaL_ref(L, LUA_REGISTRYINDEX); + int opaque_ref = luaL_ref(L, LUA_REGISTRYINDEX); + int dir = 0; + + switch (direction) + { + case 1: dir = FOV_SOUTHWEST; break; + case 2: dir = FOV_SOUTH; break; + case 3: dir = FOV_SOUTHEAST; break; + case 4: dir = FOV_WEST; break; + case 6: dir = FOV_EAST; break; + case 7: dir = FOV_NORTHWEST; break; + case 8: dir = FOV_NORTH; break; + case 9: dir = FOV_NORTHEAST; break; + } + + struct lua_fov fov; + + fov_settings_init(&(fov.fov_settings)); + fov_beam(&(fov.fov_settings), &fov, NULL, x, y, radius+1, dir, angle); + fov_settings_free(&(fov.fov_settings)); + + luaL_unref(L, LUA_REGISTRYINDEX, fov.apply_ref); + luaL_unref(L, LUA_REGISTRYINDEX, fov.opaque_ref); + luaL_unref(L, LUA_REGISTRYINDEX, fov.map_ref); + + return 0; } static const struct luaL_reg fovlib[] = { - {"calc", lua_fov}, + {"new", lua_new_fov}, + {"calc_circle", lua_fov_calc_circle}, + {"calc_beam", lua_fov_calc_beam}, + {NULL, NULL}, +}; + +static const struct luaL_reg fov_reg[] = +{ + {"__gc", lua_free_fov}, + {"close", lua_free_fov}, + {"__call", lua_fov}, {NULL, NULL}, }; @@ -413,7 +545,7 @@ static const struct luaL_reg rnglib[] = int luaopen_core(lua_State *L) { -// auxiliar_newclass(L, "fov{core}", fov_reg); + auxiliar_newclass(L, "fov{core}", fov_reg); auxiliar_newclass(L, "sdl{surface}", sdl_surface_reg); auxiliar_newclass(L, "sdl{font}", sdl_font_reg); luaL_openlib(L, "core.fov", fovlib, 0); diff --git a/src/fov.c b/src/fov.c deleted file mode 100644 index 140b511829ef351ab8d5ef26c06e5c5903e938bf..0000000000000000000000000000000000000000 --- a/src/fov.c +++ /dev/null @@ -1,105 +0,0 @@ -#include "fov.h" -#include "types.h" -#include "script.h" -#define f(x,y) for (x = 0; x < y; ++x) - -int ccw(int x1, int y1, int x2, int y2, int x3, int y3) { // positive iff they are counterclockwise - return (x1*y2 + x2*y3 + x3*y1 - x1*y3 - x2*y1 - x3*y2); -} - -static bool check(int map_ref, int check_ref, int x, int y) -{ - lua_rawgeti(L, LUA_REGISTRYINDEX, check_ref); - lua_rawgeti(L, LUA_REGISTRYINDEX, map_ref); - lua_pushnumber(L, x); - lua_pushnumber(L, y); - lua_call(L, 3, 1); - bool res = lua_toboolean(L, -1); - lua_pop(L, 1); - return res; -} - -static void apply(int map_ref, int apply_ref, int px, int py, int cx, int cy, int dis) -{ - if ((cx-px)*(cx-px) + (cy-py)*(cy-py) <= dis*dis + 1) { // circular view - can be changed if you like - lua_rawgeti(L, LUA_REGISTRYINDEX, apply_ref); - lua_rawgeti(L, LUA_REGISTRYINDEX, map_ref); - lua_pushnumber(L, cx); - lua_pushnumber(L, cy); - lua_pushnumber(L, dis); - lua_call(L, 4, 0); - } -} - -// runs in O(N), "point" (read: unit length segment) to "point" line of sight that also checks intermediate "point"s. -// Gives identical results to the other algorithm, amazingly. Both are equivalent to checking for digital lines. -// you see those inner loops? Amortized time. Each while loop is entered at most N times, total. -static void trace(int src_x, int src_y, int dir, int n, int h, int map_ref, int check_ref, int apply_ref) -{ - int topx[n+2], topy[n+2], botx[n+2], boty[n+2]; // convex hull of obstructions - int curt = 0, curb = 0; // size of top and bottom convex hulls - int s[2][2] = {{0, 0}, {0, 0}}; // too lazy to think of real variable names, four critical points on the convex hulls - these four points determine what is visible - topx[0] = botx[0] = boty[0] = 0, topy[0] = 1; - int ad1 = 1, ad2[2] = {0, 0}, eps[2] = {0, n-1}; - for (; ad1 <= n; ++ad1) { - int i; - f(i,2) { - eps[i] += h; // good old Bresenham - if (eps[i] >= n) { - eps[i] -= n; - ++ad2[i]; - } - } - f(i,2) if (ccw(topx[s[!i][1]], topy[s[!i][1]], botx[s[i][0]], boty[s[i][0]], ad1, ad2[i]+i) <= 0) return; // the relevant region is no longer visible. If we don't exit the loop now, strange things happen. - int cx[2] = {ad1, ad1}, cy[2] = {ad2[0], ad2[1]}; - f(i,2) { - if (dir&1) cx[i] = -cx[i]; - if (dir&2) cy[i] = -cy[i]; - if (dir&4) cx[i] ^= cy[i], cy[i] ^= cx[i], cx[i] ^= cy[i]; - cx[i] += src_x, cy[i] += src_y; - - if (ccw(topx[s[i][1]], topy[s[i][1]], botx[s[!i][0]], boty[s[!i][0]], ad1, ad2[i]+1-i) > 0) { - apply(map_ref, apply_ref, src_x, src_y, cx[i], cy[i], n); - } - } - - if (check(map_ref, check_ref, cx[0], cy[0])) { // new obstacle, update convex hull - ++curb; - botx[curb] = ad1, boty[curb] = ad2[0]+1; - if (ccw(botx[s[0][0]], boty[s[0][0]], topx[s[1][1]], topy[s[1][1]], ad1, ad2[0]+1) >= 0) return; // the obstacle obscures everything - if (ccw(topx[s[0][1]], topy[s[0][1]], botx[s[1][0]], boty[s[1][0]], ad1, ad2[0]+1) >= 0) { - s[1][0] = curb; // updating visible region - while (s[0][1] < curt && ccw(topx[s[0][1]], topy[s[0][1]], topx[s[0][1]+1], topy[s[0][1]+1], ad1, ad2[0]+1) >= 0) ++s[0][1]; - } - while (curb > 1 && ccw(botx[curb-2], boty[curb-2], botx[curb-1], boty[curb-1], ad1, ad2[0]+1) >= 0) { // not convex anymore, delete a point - if (s[1][0] == curb) --s[1][0]; // s[0][0] won't be a problem - --curb; - botx[curb] = botx[curb+1], boty[curb] = boty[curb+1]; - } - } - - if (check(map_ref, check_ref, cx[1], cy[1])) { // same as above - ++curt; - topx[curt] = ad1, topy[curt] = ad2[1]; - if (ccw(botx[s[1][0]], boty[s[1][0]], topx[s[0][1]], topy[s[0][1]], ad1, ad2[1]) >= 0) return; - if (ccw(topx[s[1][1]], topy[s[1][1]], botx[s[0][0]], boty[s[0][0]], ad1, ad2[1]) >= 0) { - s[1][1] = curt; - while (s[0][0] < curb && ccw(botx[s[0][0]], boty[s[0][0]], botx[s[0][0]+1], boty[s[0][0]+1], ad1, ad2[1]) <= 0) ++s[0][0]; - } - while (curt > 1 && ccw(topx[curt-2], topy[curt-2], topx[curt-1], topy[curt-1], ad1, ad2[1]) <= 0) { - if (s[1][1] == curt) --s[1][1]; - --curt; - topx[curt] = topx[curt+1], topy[curt] = topy[curt+1]; - } - } - } -} - -void do_fov(int x, int y, int distance, int map_ref, int check_ref, int apply_ref) -{ - int dir; - f(dir, 8) { - int i; - f(i,distance+1) trace(x, y, dir, distance, i, map_ref, check_ref, apply_ref); - } -} diff --git a/src/fov.h b/src/fov.h deleted file mode 100644 index aad4d02206f5171671279bc9745a18fd0c2a46f6..0000000000000000000000000000000000000000 --- a/src/fov.h +++ /dev/null @@ -1,6 +0,0 @@ -#ifndef _FOV_H_ -#define _FOV_H_ - -void do_fov(int x, int y, int distance, int map_ref, int check_ref, int apply_ref); - -#endif diff --git a/src/fov/fov.c b/src/fov/fov.c new file mode 100644 index 0000000000000000000000000000000000000000..51a1b7d30d03761551e787da8413183fc7221b88 --- /dev/null +++ b/src/fov/fov.c @@ -0,0 +1,427 @@ +/* + * Copyright (C) 2006, Greg McIntyre + * All rights reserved. See the file named COPYING in the distribution + * for more details. + */ + +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#define __USE_ISOC99 1 +#include <math.h> +#include <float.h> +#include <assert.h> +#include "fov.h" + +/* ++---++---++---++---+ +| || || || | +| || || || | +| || || || | ++---++---++---++---+ 2 ++---++---++---+##### +| || || |##### +| || || |##### +| || || |##### ++---++---++---+#####X 1 <-- y ++---++---++---++---+ +| || || || | +| @ || || || | <-- srcy centre -> dy = 0.5 = y - 0.5 +| || || || | ++---++---++---++---+ 0 +0 1 2 3 4 + ^ ^ + | | + srcx x -> dx = 3.5 = x + 0.5 +centre + +Slope from @ to X. + ++---++---++---++---+ +| || || || | +| || || || | +| || || || | ++---++---++---++---+ 2 ++---++---++---++---+ +| || || || | +| || || || | +| || || || | ++---++---++---+X---+ 1 <-- y ++---++---++---+##### +| || || |##### +| @ || || |##### <-- srcy centre -> dy = 0.5 = y - 0.5 +| || || |##### ++---++---++---+##### 0 +0 1 2 3 + ^ ^ + | | + srcx x -> dx = 2.5 = x - 0.5 +centre + +Slope from @ to X +*/ + + +/* Types ---------------------------------------------------------- */ + +/** \cond INTERNAL */ +typedef struct { + /*@observer@*/ fov_settings_type *settings; + /*@observer@*/ void *map; + /*@observer@*/ void *source; + int source_x; + int source_y; + unsigned radius; +} fov_private_data_type; +/** \endcond */ + +/* Options -------------------------------------------------------- */ + +void fov_settings_init(fov_settings_type *settings) { + settings->shape = FOV_SHAPE_CIRCLE_PRECALCULATE; + settings->corner_peek = FOV_CORNER_NOPEEK; + settings->opaque_apply = FOV_OPAQUE_APPLY; + settings->opaque = NULL; + settings->apply = NULL; + settings->heights = NULL; + settings->numheights = 0; +} + +void fov_settings_set_shape(fov_settings_type *settings, + fov_shape_type value) { + settings->shape = value; +} + +void fov_settings_set_corner_peek(fov_settings_type *settings, + fov_corner_peek_type value) { + settings->corner_peek = value; +} + +void fov_settings_set_opaque_apply(fov_settings_type *settings, + fov_opaque_apply_type value) { + settings->opaque_apply = value; +} + +void fov_settings_set_opacity_test_function(fov_settings_type *settings, + bool (*f)(void *map, + int x, int y)) { + settings->opaque = f; +} + +void fov_settings_set_apply_lighting_function(fov_settings_type *settings, + void (*f)(void *map, + int x, int y, + int dx, int dy, int radius, + void *src)) { + settings->apply = f; +} + +/* Circular FOV --------------------------------------------------- */ + +/*@null@*/ static unsigned *precalculate_heights(unsigned maxdist) { + unsigned i; + unsigned *result = (unsigned *)malloc((maxdist+2)*sizeof(unsigned)); + if (result) { + for (i = 0; i <= maxdist; ++i) { + result[i] = (unsigned)sqrtf((float)(maxdist*maxdist - i*i)); + } + result[maxdist+1] = 0; + } + return result; +} + +static unsigned height(fov_settings_type *settings, int x, + unsigned maxdist) { + unsigned **newheights; + + if (maxdist > settings->numheights) { + newheights = (unsigned **)calloc((size_t)maxdist, sizeof(unsigned*)); + if (newheights != NULL) { + if (settings->heights != NULL && settings->numheights > 0) { + /* Copy the pointers to the heights arrays we've already + * calculated. Once copied out, we can free the old + * array of pointers. */ + memcpy(newheights, settings->heights, + settings->numheights*sizeof(unsigned*)); + free(settings->heights); + } + settings->heights = newheights; + settings->numheights = maxdist; + } + } + if (settings->heights) { + if (settings->heights[maxdist-1] == NULL) { + settings->heights[maxdist-1] = precalculate_heights(maxdist); + } + if (settings->heights[maxdist-1] != NULL) { + return settings->heights[maxdist-1][abs(x)]; + } + } + return 0; +} + +void fov_settings_free(fov_settings_type *settings) { + unsigned i; + if (settings != NULL) { + if (settings->heights != NULL && settings->numheights > 0) { + /*@+forloopexec@*/ + for (i = 0; i < settings->numheights; ++i) { + unsigned *h = settings->heights[i]; + if (h != NULL) { + free(h); + } + settings->heights[i] = NULL; + } + /*@=forloopexec@*/ + free(settings->heights); + settings->heights = NULL; + settings->numheights = 0; + } + } +} + +/* Slope ---------------------------------------------------------- */ + +static float fov_slope(float dx, float dy) { + if (dx <= -FLT_EPSILON || dx >= FLT_EPSILON) { + return dy/dx; + } else { + return 0.0; + } +} + +/* Octants -------------------------------------------------------- */ + +#define FOV_DEFINE_OCTANT(signx, signy, rx, ry, nx, ny, nf, apply_edge, apply_diag) \ + static void fov_octant_##nx##ny##nf( \ + fov_private_data_type *data, \ + int dx, \ + float start_slope, \ + float end_slope) { \ + int x, y, dy, dy0, dy1; \ + unsigned h; \ + int prev_blocked = -1; \ + float end_slope_next; \ + fov_settings_type *settings = data->settings; \ + \ + if (dx == 0) { \ + fov_octant_##nx##ny##nf(data, dx+1, start_slope, end_slope); \ + return; \ + } else if ((unsigned)dx > data->radius) { \ + return; \ + } \ + \ + dy0 = (int)(0.5f + ((float)dx)*start_slope); \ + dy1 = (int)(0.5f + ((float)dx)*end_slope); \ + \ + rx = data->source_##rx signx dx; \ + ry = data->source_##ry signy dy0; \ + \ + if (!apply_diag && dy1 == dx) { \ + /* We do diagonal lines on every second octant, so they don't get done twice. */ \ + --dy1; \ + } \ + \ + switch (settings->shape) { \ + case FOV_SHAPE_CIRCLE_PRECALCULATE: \ + h = height(settings, dx, data->radius); \ + break; \ + case FOV_SHAPE_CIRCLE: \ + h = (unsigned)sqrtf((float)(data->radius*data->radius - dx*dx)); \ + break; \ + case FOV_SHAPE_OCTAGON: \ + h = (data->radius - dx)<<1; \ + break; \ + default: \ + h = data->radius; \ + break; \ + }; \ + if ((unsigned)dy1 > h) { \ + if (h == 0) { \ + return; \ + } \ + dy1 = (int)h; \ + } \ + \ + /*fprintf(stderr, "(%2d) = [%2d .. %2d] (%f .. %f), h=%d,edge=%d\n", \ + dx, dy0, dy1, ((float)dx)*start_slope, \ + 0.5f + ((float)dx)*end_slope, h, apply_edge);*/ \ + \ + for (dy = dy0; dy <= dy1; ++dy) { \ + ry = data->source_##ry signy dy; \ + \ + if (settings->opaque(data->map, x, y)) { \ + if (settings->opaque_apply == FOV_OPAQUE_APPLY && (apply_edge || dy > 0)) { \ + settings->apply(data->map, x, y, x - data->source_x, y - data->source_y, data->radius, data->source); \ + } \ + if (prev_blocked == 0) { \ + end_slope_next = fov_slope((float)dx + 0.5f, (float)dy - 0.5f); \ + fov_octant_##nx##ny##nf(data, dx+1, start_slope, end_slope_next); \ + } \ + prev_blocked = 1; \ + } else { \ + if (apply_edge || dy > 0) { \ + settings->apply(data->map, x, y, x - data->source_x, y - data->source_y, data->radius, data->source); \ + } \ + if (prev_blocked == 1) { \ + start_slope = fov_slope((float)dx - 0.5f, (float)dy - 0.5f); \ + } \ + prev_blocked = 0; \ + } \ + } \ + \ + if (prev_blocked == 0) { \ + fov_octant_##nx##ny##nf(data, dx+1, start_slope, end_slope); \ + } \ + } + +FOV_DEFINE_OCTANT(+,+,x,y,p,p,n,true,true) +FOV_DEFINE_OCTANT(+,+,y,x,p,p,y,true,false) +FOV_DEFINE_OCTANT(+,-,x,y,p,m,n,false,true) +FOV_DEFINE_OCTANT(+,-,y,x,p,m,y,false,false) +FOV_DEFINE_OCTANT(-,+,x,y,m,p,n,true,true) +FOV_DEFINE_OCTANT(-,+,y,x,m,p,y,true,false) +FOV_DEFINE_OCTANT(-,-,x,y,m,m,n,false,true) +FOV_DEFINE_OCTANT(-,-,y,x,m,m,y,false,false) + + +/* Circle --------------------------------------------------------- */ + +static void _fov_circle(fov_private_data_type *data) { + /* + * Octants are defined by (x,y,r) where: + * x = [p]ositive or [n]egative x increment + * y = [p]ositive or [n]egative y increment + * r = [y]es or [n]o for reflecting on axis x = y + * + * \pmy|ppy/ + * \ | / + * \ | / + * mpn\|/ppn + * ----@---- + * mmn/|\pmn + * / | \ + * / | \ + * /mmy|mpy\ + */ + fov_octant_ppn(data, 1, (float)0.0f, (float)1.0f); + fov_octant_ppy(data, 1, (float)0.0f, (float)1.0f); + fov_octant_pmn(data, 1, (float)0.0f, (float)1.0f); + fov_octant_pmy(data, 1, (float)0.0f, (float)1.0f); + fov_octant_mpn(data, 1, (float)0.0f, (float)1.0f); + fov_octant_mpy(data, 1, (float)0.0f, (float)1.0f); + fov_octant_mmn(data, 1, (float)0.0f, (float)1.0f); + fov_octant_mmy(data, 1, (float)0.0f, (float)1.0f); +} + +void fov_circle(fov_settings_type *settings, + void *map, + void *source, + int source_x, + int source_y, + unsigned radius) { + fov_private_data_type data; + + data.settings = settings; + data.map = map; + data.source = source; + data.source_x = source_x; + data.source_y = source_y; + data.radius = radius; + + _fov_circle(&data); +} + +/** + * Limit x to the range [a, b]. + */ +static float betweenf(float x, float a, float b) { + if (x - a < FLT_EPSILON) { /* x < a */ + return a; + } else if (x - b > FLT_EPSILON) { /* x > b */ + return b; + } else { + return x; + } +} + +#define BEAM_DIRECTION(d, p1, p2, p3, p4, p5, p6, p7, p8) \ + if (direction == d) { \ + end_slope = betweenf(a, 0.0f, 1.0f); \ + fov_octant_##p1(&data, 1, 0.0f, end_slope); \ + fov_octant_##p2(&data, 1, 0.0f, end_slope); \ + if (a - 1.0f > FLT_EPSILON) { /* a > 1.0f */ \ + start_slope = betweenf(2.0f - a, 0.0f, 1.0f); \ + fov_octant_##p3(&data, 1, start_slope, 1.0f); \ + fov_octant_##p4(&data, 1, start_slope, 1.0f); \ + } \ + if (a - 2.0f > FLT_EPSILON) { /* a > 2.0f */ \ + end_slope = betweenf(a - 2.0f, 0.0f, 1.0f); \ + fov_octant_##p5(&data, 1, 0.0f, end_slope); \ + fov_octant_##p6(&data, 1, 0.0f, end_slope); \ + } \ + if (a - 3.0f > FLT_EPSILON) { /* a > 3.0f */ \ + start_slope = betweenf(4.0f - a, 0.0f, 1.0f); \ + fov_octant_##p7(&data, 1, start_slope, 1.0f); \ + fov_octant_##p8(&data, 1, start_slope, 1.0f); \ + } \ + } + +#define BEAM_DIRECTION_DIAG(d, p1, p2, p3, p4, p5, p6, p7, p8) \ + if (direction == d) { \ + start_slope = betweenf(1.0f - a, 0.0f, 1.0f); \ + fov_octant_##p1(&data, 1, start_slope, 1.0f); \ + fov_octant_##p2(&data, 1, start_slope, 1.0f); \ + if (a - 1.0f > FLT_EPSILON) { /* a > 1.0f */ \ + end_slope = betweenf(a - 1.0f, 0.0f, 1.0f); \ + fov_octant_##p3(&data, 1, 0.0f, end_slope); \ + fov_octant_##p4(&data, 1, 0.0f, end_slope); \ + } \ + if (a - 2.0f > FLT_EPSILON) { /* a > 2.0f */ \ + start_slope = betweenf(3.0f - a, 0.0f, 1.0f); \ + fov_octant_##p5(&data, 1, start_slope, 1.0f); \ + fov_octant_##p6(&data, 1, start_slope, 1.0f); \ + } \ + if (a - 3.0f > FLT_EPSILON) { /* a > 3.0f */ \ + end_slope = betweenf(a - 3.0f, 0.0f, 1.0f); \ + fov_octant_##p7(&data, 1, 0.0f, end_slope); \ + fov_octant_##p8(&data, 1, 0.0f, end_slope); \ + } \ + } + +void fov_beam(fov_settings_type *settings, void *map, void *source, + int source_x, int source_y, unsigned radius, + fov_direction_type direction, float angle) { + + fov_private_data_type data; + float start_slope, end_slope, a; + + data.settings = settings; + data.map = map; + data.source = source; + data.source_x = source_x; + data.source_y = source_y; + data.radius = radius; + + if (angle <= 0.0f) { + return; + } else if (angle >= 360.0f) { + _fov_circle(&data); + return; + } + + /* Calculate the angle as a percentage of 45 degrees, halved (for + * each side of the centre of the beam). e.g. angle = 180.0f means + * half the beam is 90.0 which is 2x45, so the result is 2.0. + */ + a = angle/90.0f; + + BEAM_DIRECTION(FOV_EAST, ppn, pmn, ppy, mpy, pmy, mmy, mpn, mmn); + BEAM_DIRECTION(FOV_WEST, mpn, mmn, pmy, mmy, ppy, mpy, ppn, pmn); + BEAM_DIRECTION(FOV_NORTH, mpy, mmy, mmn, pmn, mpn, ppn, pmy, ppy); + BEAM_DIRECTION(FOV_SOUTH, pmy, ppy, mpn, ppn, mmn, pmn, mmy, mpy); + BEAM_DIRECTION_DIAG(FOV_NORTHEAST, pmn, mpy, mmy, ppn, mmn, ppy, mpn, pmy); + BEAM_DIRECTION_DIAG(FOV_NORTHWEST, mmn, mmy, mpn, mpy, pmy, pmn, ppy, ppn); + BEAM_DIRECTION_DIAG(FOV_SOUTHEAST, ppn, ppy, pmy, pmn, mpn, mpy, mmn, mmy); + BEAM_DIRECTION_DIAG(FOV_SOUTHWEST, pmy, mpn, ppy, mmn, ppn, mmy, pmn, mpy); +} diff --git a/src/fov/fov.h b/src/fov/fov.h new file mode 100644 index 0000000000000000000000000000000000000000..24b3cdaf96fe19716f00a95d49f200696df24a40 --- /dev/null +++ b/src/fov/fov.h @@ -0,0 +1,245 @@ +/* + * Copyright (C) 2006-2007, Greg McIntyre. All rights reserved. See the file + * named COPYING in the distribution for more details. + */ + +/** + * \mainpage Field of View Library + * + * \section about About + * + * This is a C library which implements a course-grained lighting + * algorithm suitable for tile-based games such as roguelikes. + * + * \section copyright Copyright + * + * \verbinclude COPYING + * + * \section thanks Thanks + * + * Thanks to Björn Bergström + * <bjorn.bergstrom@hyperisland.se> for the algorithm. + * + */ + +/** + * \file fov.h + * Field-of-view algorithm for dynamically casting light/shadow on a + * low resolution 2D raster. + */ +#ifndef LIBFOV_HEADER +#define LIBFOV_HEADER + +#include <stdbool.h> +#include <stddef.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/** Eight-way directions. */ +typedef enum { + FOV_EAST = 0, + FOV_NORTHEAST, + FOV_NORTH, + FOV_NORTHWEST, + FOV_WEST, + FOV_SOUTHWEST, + FOV_SOUTH, + FOV_SOUTHEAST +} fov_direction_type; + +/** Values for the shape setting. */ +typedef enum { + FOV_SHAPE_CIRCLE_PRECALCULATE, + FOV_SHAPE_SQUARE, + FOV_SHAPE_CIRCLE, + FOV_SHAPE_OCTAGON +} fov_shape_type; + +/** Values for the corner peek setting. */ +typedef enum { + FOV_CORNER_NOPEEK, + FOV_CORNER_PEEK +} fov_corner_peek_type; + +/** Values for the opaque apply setting. */ +typedef enum { + FOV_OPAQUE_APPLY, + FOV_OPAQUE_NOAPPLY +} fov_opaque_apply_type; + +/** @cond INTERNAL */ +typedef /*@null@*/ unsigned *height_array_t; +/** @endcond */ + +typedef struct { + /** Opacity test callback. */ + /*@null@*/ bool (*opaque)(void *map, int x, int y); + + /** Lighting callback to set lighting on a map tile. */ + /*@null@*/ void (*apply)(void *map, int x, int y, int dx, int dy, int radius, void *src); + + /** Shape setting. */ + fov_shape_type shape; + + /** Whether to peek around corners. */ + fov_corner_peek_type corner_peek; + + /** Whether to call apply on opaque tiles. */ + fov_opaque_apply_type opaque_apply; + + /** \cond INTERNAL */ + + /** Pre-calculated data. \internal */ + /*@null@*/ height_array_t *heights; + + /** Size of pre-calculated data. \internal */ + unsigned numheights; + + /** \endcond */ +} fov_settings_type; + +/** The opposite direction to that given. */ +#define fov_direction_opposite(direction) ((fov_direction_type)(((direction)+4)&0x7)) + +/** + * Set all the default options. You must call this option when you + * create a new settings data structure. + * + * These settings are the defaults used: + * + * - shape: FOV_SHAPE_CIRCLE_PRECALCULATE + * - corner_peek: FOV_CORNER_NOPEEK + * - opaque_apply: FOV_OPAQUE_APPLY + * + * Callbacks still need to be set up after calling this function. + * + * \param settings Pointer to data structure containing settings. + */ +void fov_settings_init(fov_settings_type *settings); + +/** + * Set the shape of the field of view. + * + * \param settings Pointer to data structure containing settings. + * \param value One of the following values, where R is the radius: + * + * - FOV_SHAPE_CIRCLE_PRECALCULATE \b (default): Limit the FOV to a + * circle with radius R by precalculating, which consumes more memory + * at the rate of 4*(R+2) bytes per R used in calls to fov_circle. + * Each radius is only calculated once so that it can be used again. + * Use fov_free() to free this precalculated data's memory. + * + * - FOV_SHAPE_CIRCLE: Limit the FOV to a circle with radius R by + * calculating on-the-fly. + * + * - FOV_SHAPE_OCTOGON: Limit the FOV to an octogon with maximum radius R. + * + * - FOV_SHAPE_SQUARE: Limit the FOV to an R*R square. + */ +void fov_settings_set_shape(fov_settings_type *settings, fov_shape_type value); + +/** + * <em>NOT YET IMPLEMENTED</em>. + * + * Set whether sources will peek around corners. + * + * \param settings Pointer to data structure containing settings. + * \param value One of the following values: + * + * - FOV_CORNER_PEEK \b (default): Renders: +\verbatim + ........ + ........ + ........ + ..@# + ...# +\endverbatim + * - FOV_CORNER_NOPEEK: Renders: +\verbatim + ...... + ..... + .... + ..@# + ...# +\endverbatim + */ +void fov_settings_set_corner_peek(fov_settings_type *settings, fov_corner_peek_type value); + +/** + * Whether to call the apply callback on opaque tiles. + * + * \param settings Pointer to data structure containing settings. + * \param value One of the following values: + * + * - FOV_OPAQUE_APPLY \b (default): Call apply callback on opaque tiles. + * - FOV_OPAQUE_NOAPPLY: Do not call the apply callback on opaque tiles. + */ +void fov_settings_set_opaque_apply(fov_settings_type *settings, fov_opaque_apply_type value); + +/** + * Set the function used to test whether a map tile is opaque. + * + * \param settings Pointer to data structure containing settings. + * \param f The function called to test whether a map tile is opaque. + */ +void fov_settings_set_opacity_test_function(fov_settings_type *settings, bool (*f)(void *map, int x, int y)); + +/** + * Set the function used to apply lighting to a map tile. + * + * \param settings Pointer to data structure containing settings. + * \param f The function called to apply lighting to a map tile. + */ +void fov_settings_set_apply_lighting_function(fov_settings_type *settings, void (*f)(void *map, int x, int y, int dx, int dy, int radius, void *src)); + +/** + * Free any memory that may have been cached in the settings + * structure. + * + * \param settings Pointer to data structure containing settings. + */ +void fov_settings_free(fov_settings_type *settings); + +/** + * Calculate a full circle field of view from a source at (x,y). + * + * \param settings Pointer to data structure containing settings. + * \param map Pointer to map data structure to be passed to callbacks. + * \param source Pointer to data structure holding source of light. + * \param source_x x-axis coordinate from which to start. + * \param source_y y-axis coordinate from which to start. + * \param radius Euclidean distance from (x,y) after which to stop. + */ +void fov_circle(fov_settings_type *settings, void *map, void *source, + int source_x, int source_y, unsigned radius +); + +/** + * Calculate a field of view from source at (x,y), pointing + * in the given direction and with the given angle. The larger + * the angle, the wider, "less focused" the beam. Each side of the + * line pointing in the direction from the source will be half the + * angle given such that the angle specified will be represented on + * the raster. + * + * \param settings Pointer to data structure containing settings. + * \param map Pointer to map data structure to be passed to callbacks. + * \param source Pointer to data structure holding source of light. + * \param source_x x-axis coordinate from which to start. + * \param source_y y-axis coordinate from which to start. + * \param radius Euclidean distance from (x,y) after which to stop. + * \param direction One of eight directions the beam of light can point. + * \param angle The angle at the base of the beam of light, in degrees. + */ +void fov_beam(fov_settings_type *settings, void *map, void *source, + int source_x, int source_y, unsigned radius, + fov_direction_type direction, float angle +); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif diff --git a/src/main.c b/src/main.c index f389ccf11a6aab45c8c50c0fc5a9cbc8a4c3f3a8..ce9731bf4f0b95ee07eb4ab7f5205eb49dace8f6 100644 --- a/src/main.c +++ b/src/main.c @@ -193,7 +193,7 @@ int main(int argc, char *argv[]) } SDL_WM_SetCaption("T4Engine", NULL); SDL_EnableUNICODE(TRUE); - SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, 10); + SDL_EnableKeyRepeat(300, 10); TTF_Init(); // And run the lua engine scripts