diff --git a/game/engines/default/engine/Module.lua b/game/engines/default/engine/Module.lua index 79dfad26e984c99ab320e2d928d1066e42e74c69..182c2e41b308adaecf470470d8af5414d4042cf1 100644 --- a/game/engines/default/engine/Module.lua +++ b/game/engines/default/engine/Module.lua @@ -383,6 +383,10 @@ function _M:instanciate(mod, name, new_game, no_reboot) -- Turn based by default core.game.setRealtime(0) + -- FOV Shape + core.fov.set_vision_shape("circle") + core.fov.set_permissiveness("square") + -- Init the module directories mod.load("setup") diff --git a/game/engines/default/engine/utils.lua b/game/engines/default/engine/utils.lua index 7337ac5b764391543e95f5f95b8e3e2aaa625bc3..8860843fa1d59358747326ce55af092ff55ce78a 100644 --- a/game/engines/default/engine/utils.lua +++ b/game/engines/default/engine/utils.lua @@ -1106,6 +1106,7 @@ end -- "firstpeek" is the least permissive setting that allows @ to see r below: -- @## -- ..r +-- Default is "square" function core.fov.set_permissiveness(val) val = type(val) == "string" and (string.lower(val) == "square" and 0.0 or string.lower(val) == "diamond" and 0.5 or @@ -1116,6 +1117,27 @@ function core.fov.set_permissiveness(val) if type(val) ~= "number" then return end val = util.bound(val, 0.0, 0.5) core.fov.set_permissiveness_base(val) + return 2*val +end + +--- Sets the vision shape or distance metric for field of vision, talent ranges, AoEs, etc. +-- @param should be a string: circle, circle_round (same as circle), circle_floor, circle_ceil, circle_plus1, octagon, diamond, square. +-- See "src/fov/fov.h" to see how each shape calculates distance and height. +-- "circle_round" is aesthetically pleasing, "octagon" is a traditional roguelike FoV shape, and "circle_plus1" is similar to both "circle_round" and "octagon" +-- Default is "circle_round" +function core.fov.set_vision_shape(val) + sval = type(val) == "string" and string.lower(val) + val = sval and ((sval == "circle" or sval == "circle_round") and 0 or + sval == "circle_floor" and 1 or + sval == "circle_ceil" and 2 or + sval == "circle_plus1" and 3 or + sval == "octagon" and 4 or + sval == "diamond" and 5 or + sval == "square" and 6) or + type(tonumber(val)) == "number" and tonumber(val) + + if type(val) ~= "number" then return end + core.fov.set_vision_shape_base(val) return val end diff --git a/src/fov.c b/src/fov.c index 82115d49cabf7ead410dd2d9fd1a7613c511cf56..ee13492a0775e076392304d492964f5cc88281f9 100644 --- a/src/fov.c +++ b/src/fov.c @@ -43,6 +43,7 @@ ******************************************************************/ static const char FOV_PERMISSIVE_KEY = 'k'; +static const char FOV_VISION_SHAPE_KEY = 'k'; struct lua_fovcache { @@ -83,23 +84,43 @@ static int lua_fov_get_permissiveness(lua_State *L) lua_settable(L, LUA_REGISTRYINDEX); } } + +static int lua_fov_set_vision_shape(lua_State *L) +{ + int val = luaL_checknumber(L, 1); + lua_pushlightuserdata(L, (void *)&FOV_VISION_SHAPE_KEY); // push address as guaranteed unique key + lua_pushnumber(L, val); + lua_settable(L, LUA_REGISTRYINDEX); + return 0; +} + +static int lua_fov_get_vision_shape(lua_State *L) +{ + lua_pushlightuserdata(L, (void *)&FOV_VISION_SHAPE_KEY); // push address as guaranteed unique key + lua_gettable(L, LUA_REGISTRYINDEX); /* retrieve value */ + if (lua_isnil(L, -1)) { + lua_pop(L, 1); // remove nil + lua_pushlightuserdata(L, (void *)&FOV_VISION_SHAPE_KEY); // push address as guaranteed unique key + lua_pushnumber(L, FOV_SHAPE_CIRCLE_ROUND); + lua_settable(L, LUA_REGISTRYINDEX); + } +} + 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; if (x < 0 || y < 0 || x >= fov->w || y >= fov->h) return; - if (dx*dx + dy*dy <= radius*radius + radius) // <-- use shape of FoV. Also, is this check really necessary? TODO: verify and delete if unnecessary - { - // circular view - can be changed if you like - lua_rawgeti(fov->L, LUA_REGISTRYINDEX, fov->apply_ref); - if (fov->cache) lua_rawgeti(fov->L, LUA_REGISTRYINDEX, fov->cache_ref); - else lua_pushnil(fov->L); - lua_pushnumber(fov->L, x); - lua_pushnumber(fov->L, y); - lua_pushnumber(fov->L, dx); - lua_pushnumber(fov->L, dy); - lua_pushnumber(fov->L, dx*dx + dy*dy); - lua_call(fov->L, 6, 0); - } + + // circular view - can be changed if you like + lua_rawgeti(fov->L, LUA_REGISTRYINDEX, fov->apply_ref); + if (fov->cache) lua_rawgeti(fov->L, LUA_REGISTRYINDEX, fov->cache_ref); + else lua_pushnil(fov->L); + lua_pushnumber(fov->L, x); + lua_pushnumber(fov->L, y); + lua_pushnumber(fov->L, dx); + lua_pushnumber(fov->L, dy); + lua_pushnumber(fov->L, dx*dx + dy*dy); + lua_call(fov->L, 6, 0); } static bool map_opaque(void *m, int x, int y) @@ -155,6 +176,8 @@ static int lua_fov_calc_circle(lua_State *L) fov_settings_set_apply_lighting_function(&(fov.fov_settings), map_seen); lua_fov_get_permissiveness(L); fov.fov_settings.permissiveness = luaL_checknumber(L, -1); + lua_fov_get_vision_shape(L); + fov.fov_settings.shape = luaL_checknumber(L, -1); fov_circle(&(fov.fov_settings), &fov, NULL, x, y, radius); map_seen(&fov, x, y, 0, 0, radius, NULL); @@ -213,6 +236,8 @@ static int lua_fov_calc_beam(lua_State *L) fov_settings_set_apply_lighting_function(&(fov.fov_settings), map_seen); lua_fov_get_permissiveness(L); fov.fov_settings.permissiveness = luaL_checknumber(L, -1); + lua_fov_get_vision_shape(L); + fov.fov_settings.shape = luaL_checknumber(L, -1); fov_beam(&(fov.fov_settings), &fov, NULL, x, y, radius, dir, angle); map_seen(&fov, x, y, 0, 0, radius, NULL); @@ -259,6 +284,8 @@ static int lua_fov_calc_beam_any_angle(lua_State *L) fov_settings_set_apply_lighting_function(&(fov.fov_settings), map_seen); lua_fov_get_permissiveness(L); fov.fov_settings.permissiveness = luaL_checknumber(L, -1); + lua_fov_get_vision_shape(L); + fov.fov_settings.shape = luaL_checknumber(L, -1); fov_beam_any_angle(&(fov.fov_settings), &fov, NULL, x, y, radius, dx, dy, beam_angle); map_seen(&fov, x, y, 0, 0, radius, NULL); @@ -277,9 +304,49 @@ static int lua_distance(lua_State *L) double y1 = luaL_checknumber(L, 2); double x2 = luaL_checknumber(L, 3); double y2 = luaL_checknumber(L, 4); + bool ret_float = lua_toboolean(L, 5); + double dx = fabs(x2 - x1); + double dy = fabs(y2 - y1); + double dist; + lua_fov_get_vision_shape(L); + int shape = luaL_checknumber(L, -1); + + switch(shape) { + case FOV_SHAPE_CIRCLE_ROUND : + dist = sqrt(dx*dx + dy*dy) + 0.5; + break; + case FOV_SHAPE_CIRCLE_FLOOR : + dist = sqrt(dx*dx + dy*dy); + break; + case FOV_SHAPE_CIRCLE_CEIL : + if (ret_float) + dist = sqrt(dx*dx + dy*dy); + else + dist = ceil(sqrt(dx*dx + dy*dy)); + break; + case FOV_SHAPE_CIRCLE_PLUS1 : + dist = sqrt(dx*dx + dy*dy); + if (dist > 0.5) dist = dist + 1 - 1.0/dist; + break; + case FOV_SHAPE_OCTAGON : + dist = (dx > dy) ? (dx + 0.5*dy) : (dy + 0.5*dx); + break; + case FOV_SHAPE_DIAMOND : + dist = dx + dy; + break; + case FOV_SHAPE_SQUARE : + dist = (dx > dy) ? dx : dy; + break; + default : + dist = sqrt(dx*dx + dy*dy) + 0.5; + break; + } + + if (ret_float) + lua_pushnumber(L, dist); + else + lua_pushnumber(L, (int)dist); - // TODO: switch/case based on FoV shape. Use rounded circle for now - lua_pushnumber(L, (int)(sqrt((x1-x2)*(x1-x2) + (y1-y2)*(y1-y2)) + 0.5f)); return 1; } @@ -355,101 +422,101 @@ typedef struct { static void map_default_seen(void *m, int x, int y, int dx, int dy, int radius, void *src) { + // TODO: understand how this function uses distances and use "lua_distance" (i.e., core.fov.distance) if necessary default_fov *def = (default_fov*)src; struct lua_fov *fov = (struct lua_fov *)m; float sqdist = dx*dx + dy*dy; float dist = sqrtf(sqdist); - if (dx*dx + dy*dy <= radius*radius + radius) // <-- use FoV shape. Also, is this check really necessary? TODO: verify and delete if unnecessary - { - // Distance Map - if (def->do_dmap) - { - lua_pushnumber(fov->L, x + y * def->w); - lua_pushnumber(fov->L, def->turn + radius - dist); - lua_rawset(fov->L, STACK_DMAP); - } - - // Apply - if (def->do_apply) - { - lua_pushvalue(fov->L, STACK_APPLY); - lua_pushnumber(fov->L, x); - lua_pushnumber(fov->L, y); - lua_pushnumber(fov->L, dx); - lua_pushnumber(fov->L, dy); - lua_pushnumber(fov->L, sqdist); - lua_call(fov->L, 5, 0); - } + if (x < 0 || y < 0 || x >= fov->w || y >= fov->h) return; - // Get entity + // Distance Map + if (def->do_dmap) + { lua_pushnumber(fov->L, x + y * def->w); - lua_rawget(fov->L, STACK_MAP); - if (!lua_istable(fov->L, -1)) { lua_pop(fov->L, 1); return; } - lua_pushnumber(fov->L, def->entity); - lua_rawget(fov->L, -2); - if (!lua_istable(fov->L, -1)) { lua_pop(fov->L, 2); return; } - - // Check if dead - lua_pushstring(fov->L, "dead"); - lua_gettable(fov->L, -2); - if (lua_toboolean(fov->L, -1)) { lua_pop(fov->L, 3); return; } - lua_pop(fov->L, 1); - - // Set sqdist in the actor for faster sorting - lua_pushstring(fov->L, "__sqdist"); - lua_pushnumber(fov->L, sqdist); - lua_rawset(fov->L, -3); + lua_pushnumber(fov->L, def->turn + radius - dist); + lua_rawset(fov->L, STACK_DMAP); + } - // Make a table to hold data - lua_newtable(fov->L); - lua_pushstring(fov->L, "x"); + // Apply + if (def->do_apply) + { + lua_pushvalue(fov->L, STACK_APPLY); lua_pushnumber(fov->L, x); - lua_rawset(fov->L, -3); - lua_pushstring(fov->L, "y"); lua_pushnumber(fov->L, y); - lua_rawset(fov->L, -3); - lua_pushstring(fov->L, "dx"); lua_pushnumber(fov->L, dx); - lua_rawset(fov->L, -3); - lua_pushstring(fov->L, "dy"); lua_pushnumber(fov->L, dy); - lua_rawset(fov->L, -3); - lua_pushstring(fov->L, "sqdist"); lua_pushnumber(fov->L, sqdist); - lua_rawset(fov->L, -3); - - // Set the actor table - lua_pushvalue(fov->L, -2); - lua_pushvalue(fov->L, -2); - lua_rawset(fov->L, STACK_ACTOR); - - // Set the dist table - def->dist_idx++; - lua_pushnumber(fov->L, def->dist_idx); - lua_pushvalue(fov->L, -3); - lua_rawset(fov->L, STACK_DIST); + lua_call(fov->L, 5, 0); + } - // Call seen_by, if possible - lua_pushstring(fov->L, "updateFOV"); - lua_gettable(fov->L, -3); + // Get entity + lua_pushnumber(fov->L, x + y * def->w); + lua_rawget(fov->L, STACK_MAP); + if (!lua_istable(fov->L, -1)) { lua_pop(fov->L, 1); return; } + lua_pushnumber(fov->L, def->entity); + lua_rawget(fov->L, -2); + if (!lua_istable(fov->L, -1)) { lua_pop(fov->L, 2); return; } + + // Check if dead + lua_pushstring(fov->L, "dead"); + lua_gettable(fov->L, -2); + if (lua_toboolean(fov->L, -1)) { lua_pop(fov->L, 3); return; } + lua_pop(fov->L, 1); + + // Set sqdist in the actor for faster sorting + lua_pushstring(fov->L, "__sqdist"); + lua_pushnumber(fov->L, sqdist); + lua_rawset(fov->L, -3); + + // Make a table to hold data + lua_newtable(fov->L); + lua_pushstring(fov->L, "x"); + lua_pushnumber(fov->L, x); + lua_rawset(fov->L, -3); + lua_pushstring(fov->L, "y"); + lua_pushnumber(fov->L, y); + lua_rawset(fov->L, -3); + lua_pushstring(fov->L, "dx"); + lua_pushnumber(fov->L, dx); + lua_rawset(fov->L, -3); + lua_pushstring(fov->L, "dy"); + lua_pushnumber(fov->L, dy); + lua_rawset(fov->L, -3); + lua_pushstring(fov->L, "sqdist"); + lua_pushnumber(fov->L, sqdist); + lua_rawset(fov->L, -3); + + // Set the actor table + lua_pushvalue(fov->L, -2); + lua_pushvalue(fov->L, -2); + lua_rawset(fov->L, STACK_ACTOR); + + // Set the dist table + def->dist_idx++; + lua_pushnumber(fov->L, def->dist_idx); + lua_pushvalue(fov->L, -3); + lua_rawset(fov->L, STACK_DIST); + + // Call seen_by, if possible + lua_pushstring(fov->L, "updateFOV"); + lua_gettable(fov->L, -3); + lua_pushvalue(fov->L, -3); + lua_pushvalue(fov->L, STACK_SELF); + lua_pushnumber(fov->L, sqdist); + lua_call(fov->L, 3, 0); + + // Call seen_by, if possible + lua_pushstring(fov->L, "seen_by"); + lua_gettable(fov->L, -3); + if (lua_isfunction(fov->L, -1)) + { lua_pushvalue(fov->L, -3); lua_pushvalue(fov->L, STACK_SELF); - lua_pushnumber(fov->L, sqdist); - lua_call(fov->L, 3, 0); - - // Call seen_by, if possible - lua_pushstring(fov->L, "seen_by"); - lua_gettable(fov->L, -3); - if (lua_isfunction(fov->L, -1)) - { - lua_pushvalue(fov->L, -3); - lua_pushvalue(fov->L, STACK_SELF); - lua_call(fov->L, 2, 0); - } - else lua_pop(fov->L, 1); - - lua_pop(fov->L, 3); + lua_call(fov->L, 2, 0); } + else lua_pop(fov->L, 1); + + lua_pop(fov->L, 3); } static bool map_default_opaque(void *m, int x, int y) @@ -497,6 +564,8 @@ static int lua_fov_calc_default_fov(lua_State *L) fov_settings_set_apply_lighting_function(&(fov.fov_settings), map_default_seen); lua_fov_get_permissiveness(L); fov.fov_settings.permissiveness = luaL_checknumber(L, -1); + lua_fov_get_vision_shape(L); + fov.fov_settings.shape = luaL_checknumber(L, -1); fov_circle(&(fov.fov_settings), &fov, &def, x, y, radius); map_default_seen(&fov, x, y, 0, 0, radius, &def); @@ -539,10 +608,13 @@ static int lua_fov_line_init(lua_State *L) bool start_at_end = lua_toboolean(L, 8); fov.w = w; fov.h = h; + fov_settings_init(&(fov.fov_settings)); fov_settings_set_opacity_test_function(&(fov.fov_settings), map_opaque); lua_fov_get_permissiveness(L); fov.fov_settings.permissiveness = luaL_checknumber(L, -1); + lua_fov_get_vision_shape(L); + fov.fov_settings.shape = luaL_checknumber(L, -1); fov_line_data *line = (fov_line_data*)lua_newuserdata(L, sizeof(fov_line_data)); @@ -715,6 +787,7 @@ static const struct luaL_reg fovlib[] = {"line_base", lua_fov_line_init}, {"line_import", lua_fov_line_import}, {"set_permissiveness_base", lua_fov_set_permissiveness}, + {"set_vision_shape_base", lua_fov_set_vision_shape}, {NULL, NULL}, }; diff --git a/src/fov/fov.c b/src/fov/fov.c index 04cbc79888039576f0f6773c84d24ff22d462265..037ad6540841d1f932e7b95a4ad68ab51d7c5d35 100644 --- a/src/fov/fov.c +++ b/src/fov/fov.c @@ -82,7 +82,7 @@ typedef struct { /* Options -------------------------------------------------------- */ void fov_settings_init(fov_settings_type *settings) { - settings->shape = FOV_SHAPE_CIRCLE_PRECALCULATE; + settings->shape = FOV_SHAPE_CIRCLE_ROUND; settings->corner_peek = FOV_CORNER_NOPEEK; settings->opaque_apply = FOV_OPAQUE_APPLY; settings->opaque = NULL; @@ -238,23 +238,32 @@ static float fov_slope(float dx, float dy) { } \ \ switch (settings->shape) { \ - case FOV_SHAPE_CIRCLE_PRECALCULATE: \ + case FOV_SHAPE_CIRCLE_ROUND : \ h = height(settings, dx, data->radius); \ break; \ - case FOV_SHAPE_CIRCLE: \ - h = (unsigned)(sqrt((data->radius)*(data->radius) + data->radius - dx*dx)); \ + case FOV_SHAPE_CIRCLE_FLOOR : \ + h = (unsigned)(sqrt((data->radius)*(data->radius) + 2*data->radius - dx*dx)); \ + break; \ + case FOV_SHAPE_CIRCLE_CEIL : \ + h = (unsigned)(sqrt((data->radius)*(data->radius) - dx*dx)); \ + break; \ + case FOV_SHAPE_CIRCLE_PLUS1 : \ + h = (unsigned)(sqrt((data->radius)*(data->radius) + 1 - dx*dx)); \ break; \ case FOV_SHAPE_OCTAGON: \ - h = (data->radius - dx)<<1; \ + h = 2u*(data->radius - (unsigned)dx) + 1u; \ break; \ - default: \ + case FOV_SHAPE_DIAMOND : \ + h = data->radius - (unsigned)dx; \ + break; \ + case FOV_SHAPE_SQUARE : \ h = data->radius; \ break; \ + default : \ + h = (unsigned)(sqrt((data->radius)*(data->radius) + data->radius - dx*dx)); \ + break; \ }; \ if ((unsigned)dy1 > h) { \ - if (h == 0) { \ - return; \ - } \ dy1 = (int)h; \ } \ \ @@ -469,82 +478,92 @@ void fov_beam(fov_settings_type *settings, void *map, void *source, end_slope = betweenf(angle_end, 0.0f, 1.0f); \ fov_octant_##p1(&data, 1, start_slope, end_slope, true, true); \ \ - if (angle_end - 1.0 > FLT_EPSILON) { \ + if (angle_end - 1.0f > FLT_EPSILON) { \ start_slope = betweenf(2.0f - angle_end, 0.0f, 1.0f); \ fov_octant_##p2(&data, 1, start_slope, 1.0f, true, false); \ \ - if (angle_end - 2.0 > 2.0f * FLT_EPSILON) { \ + if (angle_end - 2.0f > 2.0f * FLT_EPSILON) { \ end_slope = betweenf(angle_end - 2.0f, 0.0f, 1.0f); \ fov_octant_##p3(&data, 1, 0.0f, end_slope, false, true); \ \ - if (angle_end - 3.0 > 3.0f * FLT_EPSILON) { \ + if (angle_end - 3.0f > 3.0f * FLT_EPSILON) { \ start_slope = betweenf(4.0f - angle_end, 0.0f, 1.0f); \ fov_octant_##p4(&data, 1, start_slope, 1.0f, true, false); \ \ - if (angle_end - 4.0 > 4.0f * FLT_EPSILON) { \ + if (angle_end - 4.0f > 4.0f * FLT_EPSILON) { \ end_slope = betweenf(angle_end - 4.0f, 0.0f, 1.0f); \ fov_octant_##p5(&data, 1, 0.0f, end_slope, false, true); \ \ - if (angle_end - 5.0 > 5.0f * FLT_EPSILON) { \ + if (angle_end - 5.0f > 5.0f * FLT_EPSILON) { \ start_slope = betweenf(6.0f - angle_end, 0.0f, 1.0f); \ fov_octant_##p6(&data, 1, start_slope, 1.0f, true, false); \ \ - if (angle_end - 6.0 > 6.0f * FLT_EPSILON) { \ + if (angle_end - 6.0f > 6.0f * FLT_EPSILON) { \ end_slope = betweenf(angle_end - 6.0f, 0.0f, 1.0f); \ fov_octant_##p7(&data, 1, 0.0f, end_slope, false, true); \ \ - if (angle_end - 7.0 > 7.0f * FLT_EPSILON) { \ + if (angle_end - 7.0f > 7.0f * FLT_EPSILON) { \ start_slope = betweenf(8.0f - angle_end, 0.0f, 1.0f); \ - fov_octant_##p8(&data, 1, start_slope, 1.0f, false, false); \ - }}}}}}} + fov_octant_##p8(&data, 1, start_slope, 1.0f, true, false); \ + \ + if (angle_end - 8.0f > 8.0f * FLT_EPSILON) { \ + end_slope = betweenf(angle_end - 8.0f, 0.0f, 1.0f); \ + start_slope = betweenf(angle_end - 8.0f, 0.0f, 1.0f); \ + fov_octant_##p1(&data, 1, 0.0f, end_slope, false, false); \ + }}}}}}}} #define BEAM_ANY_DIRECTION_DIAG(offset, p1, p2, p3, p4, p5, p6, p7, p8) \ angle_begin -= offset; \ angle_end -= offset; \ - start_slope = betweenf(1.0 - angle_end, 0.0f, 1.0f); \ - end_slope = 1.0 - angle_begin; \ + start_slope = betweenf(1.0f - angle_end, 0.0f, 1.0f); \ + end_slope = 1.0f - angle_begin; \ fov_octant_##p1(&data, 1, start_slope, end_slope, true, true); \ \ - if (angle_end - 1.0 > FLT_EPSILON) { \ + if (angle_end - 1.0f > FLT_EPSILON) { \ end_slope = betweenf(angle_end - 1.0f, 0.0f, 1.0f); \ fov_octant_##p2(&data, 1, 0.0f, end_slope, false, true); \ \ - if (angle_end - 2.0 > 2.0f * FLT_EPSILON) { \ + if (angle_end - 2.0f > 2.0f * FLT_EPSILON) { \ start_slope = betweenf(3.0f - angle_end, 0.0f, 1.0f); \ fov_octant_##p3(&data, 1, start_slope, 1.0f, true, false); \ \ - if (angle_end - 3.0 > 3.0f * FLT_EPSILON) { \ + if (angle_end - 3.0f > 3.0f * FLT_EPSILON) { \ end_slope = betweenf(angle_end - 3.0f, 0.0f, 1.0f); \ fov_octant_##p4(&data, 1, 0.0f, end_slope, false, true); \ \ - if (angle_end - 4.0 > 4.0f * FLT_EPSILON) { \ + if (angle_end - 4.0f > 4.0f * FLT_EPSILON) { \ start_slope = betweenf(5.0f - angle_end, 0.0f, 1.0f); \ fov_octant_##p5(&data, 1, start_slope, 1.0f, true, false); \ \ - if (angle_end - 5.0 > 5.0f * FLT_EPSILON) { \ + if (angle_end - 5.0f > 5.0f * FLT_EPSILON) { \ end_slope = betweenf(angle_end - 5.0f, 0.0f, 1.0f); \ fov_octant_##p6(&data, 1, 0.0f, end_slope, false, true); \ \ - if (angle_end - 6.0 > 6.0f * FLT_EPSILON) { \ + if (angle_end - 6.0f > 6.0f * FLT_EPSILON) { \ start_slope = betweenf(7.0f - angle_end, 0.0f, 1.0f); \ fov_octant_##p7(&data, 1, start_slope, 1.0f, true, false); \ \ - if (angle_end - 7.0 > 7.0f * FLT_EPSILON) { \ + if (angle_end - 7.0f > 7.0f * FLT_EPSILON) { \ end_slope = betweenf(angle_end - 7.0f, 0.0f, 1.0f); \ - fov_octant_##p8(&data, 1, 0.0f, end_slope, false, false); \ -}}}}}}} + fov_octant_##p8(&data, 1, 0.0f, end_slope, false, true); \ + \ + if (angle_end - 8.0f > 8.0f * FLT_EPSILON) { \ + start_slope = betweenf(9.0f - angle_end, 0.0f, 1.0f); \ + fov_octant_##p1(&data, 1, start_slope, 1.0f, false, false); \ +}}}}}}}} void fov_beam_any_angle(fov_settings_type *settings, void *map, void *source, int source_x, int source_y, unsigned radius, float dx, float dy, float beam_angle) { /* Note: angle_begin and angle_end are misnomers, since FoV calculation uses slopes, not angles. - * Hence, it may not be clear that we implicitly use a tan(x) ~ 4/pi*x approximation - * for x in range (0, pi/4) radians, or (0, 45) degrees. We can (and will) make this much - * more precise, which is why the function now uses parameters dx, dy instead of dir_angle. - * TODO: make this more precise by not using approximations */ + * We previously used a tan(x) ~ 4/pi*x approximation * for x in range (0, pi/4) radians, or 45 degrees. + * We no longer use this approximation. Angles and slopes are calculated precisely, + * so this function can be used for numerically precise purposes if desired. + */ + fov_private_data_type data; - float start_slope, end_slope, angle_begin, angle_end, dir_angle; + float start_slope, end_slope, angle_begin, angle_end, x_start, y_start, x_end, y_end; data.settings = settings; data.map = map; @@ -560,20 +579,45 @@ void fov_beam_any_angle(fov_settings_type *settings, void *map, void *source, return; } - dir_angle = RtoD*atan2(dy, dx); - while (dir_angle >= 360.0f) { - dir_angle -= 360.0f; + beam_angle = 0.5f * DtoR * beam_angle; + x_start = cos(beam_angle)*dx + sin(beam_angle)*dy; + y_start = cos(beam_angle)*dy - sin(beam_angle)*dx; + x_end = cos(beam_angle)*dx - sin(beam_angle)*dy; + y_end = cos(beam_angle)*dy + sin(beam_angle)*dx; + + if (y_start > 0.0f) { + if (x_start > 0.0f) { /* octant 1 */ /* octant 2 */ + angle_begin = ( y_start < x_start) ? (y_start / x_start) : (2.0f - x_start / y_start); + } + else { /* octant 3 */ /* octant 4 */ + angle_begin = (-x_start < y_start) ? (2.0f - x_start / y_start) : (4.0f + y_start / x_start); + } + } else { + if (x_start < 0.0f) { /* octant 5 */ /* octant 6 */ + angle_begin = (-y_start < -x_start) ? (4.0f + y_start / x_start) : (6.0f - x_start / y_start); + } + else { /* octant 7 */ /* octant 8 */ + angle_begin = ( x_start < -y_start) ? (6.0f - x_start / y_start) : (8.0f + y_start / x_start); + } } - while (dir_angle < 0.0f) { - dir_angle += 360.0f; + if (y_end > 0.0f) { + if (x_end > 0.0f) { /* octant 1 */ /* octant 2 */ + angle_end = ( y_end < x_end) ? (y_end / x_end) : (2.0f - x_end / y_end); + } + else { /* octant 3 */ /* octant 4 */ + angle_end = (-x_end < y_end) ? (2.0f - x_end / y_end) : (4.0f + y_end / x_end); + } + } else { + if (x_end < 0.0f) { /* octant 5 */ /* octant 6 */ + angle_end = (-y_end < -x_end) ? (4.0f + y_end / x_end) : (6.0f - x_end / y_end); + } + else { /* octant 7 */ /* octant 8 */ + angle_end = ( x_end < -y_end) ? (6.0f - x_end / y_end) : (8.0f + y_end / x_end); + } } - /* Calculate the angles as a percentage of 45 degrees */ - angle_begin = (dir_angle - 0.5*beam_angle) / 45.0f; - angle_end = (dir_angle + 0.5*beam_angle) / 45.0f; - if (angle_begin < 0.0f) { - angle_begin += 8.0f; + if (angle_end < angle_begin) { angle_end += 8.0f; } diff --git a/src/fov/fov.h b/src/fov/fov.h index 8c3ab0b8c8f8f2aeba4555449fd7d56e35be4aa7..fe614767ff9baef46951cc5c7688dc27d41eaa82 100644 --- a/src/fov/fov.h +++ b/src/fov/fov.h @@ -55,12 +55,15 @@ typedef enum { FOV_SOUTHEAST } fov_direction_type; -/** Values for the shape setting. */ +/** Values for the shape setting. Distance Y (given x, radius r) Square distance check */ typedef enum { - FOV_SHAPE_CIRCLE_PRECALCULATE, - FOV_SHAPE_SQUARE, - FOV_SHAPE_CIRCLE, - FOV_SHAPE_OCTAGON + FOV_SHAPE_CIRCLE_ROUND, /* floor(sqrt(x^2 + y^2) + 0.5) sqrt(r^2 + r - x^2) x^2 + y^2 <= r^2 + r */ + FOV_SHAPE_CIRCLE_FLOOR, /* floor(sqrt(x^2 + y^2)) sqrt(r^2 + 2*r - x^2) x^2 + y^2 <= r^2 + 2*r */ + FOV_SHAPE_CIRCLE_CEIL, /* ceil(sqrt(x^2 + y^2)) sqrt(r^2 - x^2) x^2 + y^2 <= r^2 */ + FOV_SHAPE_CIRCLE_PLUS1, /* floor(d + 1 - 1.0/d) sqrt(r^2 + 1 - x^2) x^2 + y^2 <= r^2 + 1 */ + FOV_SHAPE_OCTAGON, /* max(x, y) + min(x, y)/2 2*(r - x) + 1 */ + FOV_SHAPE_DIAMOND, /* x + y r - x */ + FOV_SHAPE_SQUARE /* max(x, y) r */ } fov_shape_type; /** Values for the corner peek setting. */