Compare commits
27 Commits
a223506a5f
...
e47b761a2c
Author | SHA1 | Date | |
---|---|---|---|
|
e47b761a2c | ||
|
844283c2fb | ||
|
09eac707c3 | ||
|
5e89710458 | ||
|
4bc1feb826 | ||
|
1c3973c6a2 | ||
|
da5bdb4fae | ||
|
ed2afec5a7 | ||
|
6812c7c13d | ||
|
8c0f43ec34 | ||
|
23fbd45564 | ||
|
a36459397e | ||
|
5f3920fdba | ||
|
f57525cea6 | ||
|
6b2901be28 | ||
|
9f0d15b9f6 | ||
|
b46331e08d | ||
|
d2938da8e2 | ||
|
9134e51817 | ||
|
d66eda1894 | ||
|
a88392b9e9 | ||
|
05f85062e8 | ||
|
d5aec5e6e1 | ||
|
62866d33ae | ||
|
ce7240d423 | ||
|
7a38f7bcf3 | ||
|
affaf7f557 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -12,6 +12,7 @@
|
||||
**/*.js
|
||||
**/*.wasm
|
||||
**/*.wasm.map
|
||||
**/*.data
|
||||
**/*.so
|
||||
**/*.dll
|
||||
**/*.7z
|
||||
|
@ -106,6 +106,7 @@ set(TWN_NONOPT_SOURCE_FILES
|
||||
src/rendering/twn_circles.c
|
||||
src/rendering/twn_skybox.c
|
||||
src/rendering/twn_models.c
|
||||
src/rendering/twn_lines.c
|
||||
)
|
||||
|
||||
set(TWN_SOURCE_FILES
|
||||
@ -167,8 +168,7 @@ function(give_options_without_warnings target)
|
||||
-g3
|
||||
-gdwarf
|
||||
-fno-omit-frame-pointer
|
||||
$<$<BOOL:${TWN_SANITIZE}>:-fstack-protector-all -fsanitize=undefined -fsanitize=address>
|
||||
$<$<BOOL:${EMSCRIPTEN}>:-gsource-map>)
|
||||
$<$<BOOL:${TWN_SANITIZE}>:-fstack-protector-all -fsanitize=undefined -fsanitize=address>)
|
||||
|
||||
set(LINK_FLAGS
|
||||
-Bsymbolic-functions
|
||||
|
@ -1,3 +1,3 @@
|
||||
[[deps]]
|
||||
source = "../../../common-data"
|
||||
source = "../../../data"
|
||||
name = "common-data"
|
||||
|
1
apps/demos/crawl/data/assets/LICENSES
Normal file
1
apps/demos/crawl/data/assets/LICENSES
Normal file
@ -0,0 +1 @@
|
||||
castledoors.png - https://opengameart.org/content/castle-door - CC-BY 3.0
|
@ -1,4 +1,3 @@
|
||||
require("string")
|
||||
require("level")
|
||||
require("render")
|
||||
|
||||
@ -29,19 +28,23 @@ function game_tick()
|
||||
input_action { control = "S", name = "walk_backward" }
|
||||
|
||||
if input_action_just_released { name = "turn_left" } then
|
||||
ctx.udata.player.direction = { x = ctx.udata.player.direction.z, y = ctx.udata.player.direction.y, z = -ctx.udata.player.direction.x }
|
||||
ctx.udata.player.direction = { x = ctx.udata.player.direction.z,
|
||||
y = ctx.udata.player.direction.y,
|
||||
z =-ctx.udata.player.direction.x }
|
||||
end
|
||||
|
||||
if input_action_just_released { name = "turn_right" } then
|
||||
ctx.udata.player.direction = { x = -ctx.udata.player.direction.z, y = ctx.udata.player.direction.y, z = ctx.udata.player.direction.x }
|
||||
ctx.udata.player.direction = { x =-ctx.udata.player.direction.z,
|
||||
y = ctx.udata.player.direction.y,
|
||||
z = ctx.udata.player.direction.x }
|
||||
end
|
||||
|
||||
local move = { x = 0, y = 0 }
|
||||
if input_action_just_released { name = "walk_forward" } then
|
||||
move = { x = move.x + ctx.udata.player.direction.z, y = move.y + ctx.udata.player.direction.x }
|
||||
move = { x = move.x + ctx.udata.player.direction.x, y = move.y + ctx.udata.player.direction.z }
|
||||
end
|
||||
if input_action_just_released { name = "walk_backward" } then
|
||||
move = { x = move.x - ctx.udata.player.direction.z, y = move.y - ctx.udata.player.direction.x }
|
||||
move = { x = move.x - ctx.udata.player.direction.x, y = move.y - ctx.udata.player.direction.z }
|
||||
end
|
||||
|
||||
if ctx.udata.level.grid[ctx.udata.player.position.y + move.y][ctx.udata.player.position.x + move.x].solid ~= nil then
|
||||
@ -57,9 +60,9 @@ function game_tick()
|
||||
|
||||
draw_camera {
|
||||
position = {
|
||||
x = ctx.udata.player.position_lerp.y * 2 + 1 - ctx.udata.player.direction.x / 2,
|
||||
y = 1,
|
||||
z = ctx.udata.player.position_lerp.x * 2 + 1 - ctx.udata.player.direction.z / 2,
|
||||
x = ctx.udata.player.position_lerp.x + 0.5 - ctx.udata.player.direction.x / 2,
|
||||
y = 0.5,
|
||||
z = ctx.udata.player.position_lerp.y + 0.5 - ctx.udata.player.direction.z / 2,
|
||||
},
|
||||
direction = ctx.udata.player.direction_lerp,
|
||||
}
|
||||
|
@ -3,14 +3,20 @@ function load_level(file)
|
||||
local f = file_read { file = file }
|
||||
|
||||
local result = {
|
||||
-- templates to fill the grid with
|
||||
classes = {
|
||||
-- predefined empty tile
|
||||
void = { },
|
||||
},
|
||||
-- symbol to class lookup table
|
||||
glossary = {
|
||||
[" "] = "void",
|
||||
},
|
||||
-- grid consists of expanded classes, of size dimensions
|
||||
grid = {},
|
||||
-- map consists of original rows of symbols that the grid is constructed from
|
||||
map = {},
|
||||
-- maximum extends of the map, unspecified tiles are filled with "void"
|
||||
size = { x = 0, y = 0 },
|
||||
}
|
||||
|
||||
@ -28,14 +34,10 @@ function load_level(file)
|
||||
section = line:sub(2); subsection = "none"
|
||||
-- decode map one line at a time
|
||||
elseif section == "map" then
|
||||
local l = #result.map + 1
|
||||
if result.size.x < #line then
|
||||
result.size.x = #line
|
||||
end
|
||||
result.map[l] = {}
|
||||
for i = 1, #line do
|
||||
result.map[l][i] = line:sub(i,i)
|
||||
end
|
||||
result.map[#result.map + 1] = line
|
||||
-- templates to expand
|
||||
elseif section == "classes" then
|
||||
-- properties
|
||||
@ -61,14 +63,16 @@ function load_level(file)
|
||||
from = limit + 1
|
||||
start, limit = string.find(f, "\n", from)
|
||||
end
|
||||
-- post process
|
||||
-- post process, expand map to grid
|
||||
for y = 1, #result.map do
|
||||
result.grid[y] = {}
|
||||
for x = 1, result.size.x do
|
||||
-- past defined for line
|
||||
local symbol
|
||||
if x > #result.map[y] then symbol = " "
|
||||
else symbol = result.map[y][x]
|
||||
if x > #result.map[y] then
|
||||
symbol = " "
|
||||
else
|
||||
symbol = result.map[y]:sub(x,x)
|
||||
end
|
||||
local class = result.classes[result.glossary[symbol]]
|
||||
if class["unique"] ~= nil then
|
||||
|
@ -1,56 +1,57 @@
|
||||
|
||||
-- if this is too wasteful, one could check nerby tiles to see whether faces could be visible
|
||||
-- more robust solution would be to travel the level from observer point of view
|
||||
function render_dungeon(dungeon)
|
||||
for y = 1, dungeon.size.y do
|
||||
for x = 1, dungeon.size.x do
|
||||
if dungeon.grid[y][x].wall_texture ~= nil then
|
||||
draw_quad {
|
||||
texture = dungeon.grid[y][x].wall_texture,
|
||||
v3 = { x = y * 2, y = 2, z = x * 2 },
|
||||
v2 = { x = y * 2, y = 0, z = x * 2 },
|
||||
v1 = { x = y * 2 + 2, y = 0, z = x * 2 },
|
||||
v0 = { x = y * 2 + 2, y = 2, z = x * 2 },
|
||||
v3 = { x = x, y = 1, z = y },
|
||||
v2 = { x = x, y = 0, z = y },
|
||||
v1 = { x = x + 1, y = 0, z = y },
|
||||
v0 = { x = x + 1, y = 1, z = y },
|
||||
texture_region = { w = 128, h = 128 },
|
||||
}
|
||||
draw_quad {
|
||||
texture = dungeon.grid[y][x].wall_texture,
|
||||
v3 = { x = y * 2 + 2, y = 2, z = x * 2 },
|
||||
v2 = { x = y * 2 + 2, y = 0, z = x * 2 },
|
||||
v1 = { x = y * 2 + 2, y = 0, z = x * 2 + 2 },
|
||||
v0 = { x = y * 2 + 2, y = 2, z = x * 2 + 2 },
|
||||
v3 = { x = x + 1, y = 1, z = y },
|
||||
v2 = { x = x + 1, y = 0, z = y },
|
||||
v1 = { x = x + 1, y = 0, z = y + 1 },
|
||||
v0 = { x = x + 1, y = 1, z = y + 1 },
|
||||
texture_region = { w = 128, h = 128 },
|
||||
}
|
||||
draw_quad {
|
||||
texture = dungeon.grid[y][x].wall_texture,
|
||||
v3 = { x = y * 2 + 2, y = 2, z = x * 2 + 2 },
|
||||
v2 = { x = y * 2 + 2, y = 0, z = x * 2 + 2 },
|
||||
v1 = { x = y * 2, y = 0, z = x * 2 + 2 },
|
||||
v0 = { x = y * 2, y = 2, z = x * 2 + 2 },
|
||||
v3 = { x = x + 1, y = 1, z = y + 1 },
|
||||
v2 = { x = x + 1, y = 0, z = y + 1 },
|
||||
v1 = { x = x, y = 0, z = y + 1 },
|
||||
v0 = { x = x, y = 1, z = y + 1 },
|
||||
texture_region = { w = 128, h = 128 },
|
||||
}
|
||||
draw_quad {
|
||||
texture = dungeon.grid[y][x].wall_texture,
|
||||
v3 = { x = y * 2, y = 2, z = x * 2 + 2 },
|
||||
v2 = { x = y * 2, y = 0, z = x * 2 + 2 },
|
||||
v1 = { x = y * 2, y = 0, z = x * 2 },
|
||||
v0 = { x = y * 2, y = 2, z = x * 2 },
|
||||
v3 = { x = x, y = 1, z = y + 1 },
|
||||
v2 = { x = x, y = 0, z = y + 1 },
|
||||
v1 = { x = x, y = 0, z = y },
|
||||
v0 = { x = x, y = 1, z = y },
|
||||
texture_region = { w = 128, h = 128 },
|
||||
}
|
||||
|
||||
elseif dungeon.grid[y][x].tile_texture ~= nil then
|
||||
draw_quad {
|
||||
texture = dungeon.grid[y][x].tile_texture,
|
||||
v0 = { x = y * 2 + 2, y = 0, z = x * 2 },
|
||||
v1 = { x = y * 2, y = 0, z = x * 2 },
|
||||
v2 = { x = y * 2, y = 0, z = x * 2 + 2 },
|
||||
v3 = { x = y * 2 + 2, y = 0, z = x * 2 + 2},
|
||||
v0 = { x = x + 1, y = 0, z = y },
|
||||
v1 = { x = x, y = 0, z = y },
|
||||
v2 = { x = x, y = 0, z = y + 1 },
|
||||
v3 = { x = x + 1, y = 0, z = y + 1},
|
||||
texture_region = { w = 128, h = 128 },
|
||||
}
|
||||
draw_quad {
|
||||
texture = dungeon.grid[y][x].tile_texture,
|
||||
v3 = { x = y * 2 + 2, y = 2, z = x * 2 },
|
||||
v2 = { x = y * 2, y = 2, z = x * 2 },
|
||||
v1 = { x = y * 2, y = 2, z = x * 2 + 2 },
|
||||
v0 = { x = y * 2 + 2, y = 2, z = x * 2 + 2},
|
||||
v3 = { x = x + 1, y = 1, z = y },
|
||||
v2 = { x = x, y = 1, z = y },
|
||||
v1 = { x = x, y = 1, z = y + 1 },
|
||||
v0 = { x = x + 1, y = 1, z = y + 1},
|
||||
texture_region = { w = 128, h = 128 },
|
||||
}
|
||||
end
|
||||
@ -59,25 +60,26 @@ function render_dungeon(dungeon)
|
||||
if dungeon.grid[y][x].face == "horizon" then
|
||||
draw_quad {
|
||||
texture = dungeon.grid[y][x].face_texture,
|
||||
v3 = { x = y * 2, y = 2, z = x * 2 + 1 },
|
||||
v2 = { x = y * 2, y = 0, z = x * 2 + 1 },
|
||||
v1 = { x = y * 2 + 2, y = 0, z = x * 2 + 1 },
|
||||
v0 = { x = y * 2 + 2, y = 2, z = x * 2 + 1 },
|
||||
texture_region = { w = 64, h = 96 },
|
||||
v3 = { x = x + 1, y = 1, z = y },
|
||||
v2 = { x = x + 1, y = 0, z = y },
|
||||
v1 = { x = x + 1, y = 0, z = y + 1 },
|
||||
v0 = { x = x + 1, y = 1, z = y + 1 },
|
||||
texture_region = { w = 64, h = 64 },
|
||||
}
|
||||
draw_quad {
|
||||
texture = dungeon.grid[y][x].face_texture,
|
||||
v3 = { x = y * 2 + 2, y = 2, z = x * 2 + 1 },
|
||||
v2 = { x = y * 2 + 2, y = 0, z = x * 2 + 1 },
|
||||
v1 = { x = y * 2, y = 0, z = x * 2 + 1 },
|
||||
v0 = { x = y * 2, y = 2, z = x * 2 + 1 },
|
||||
texture_region = { w = 64, h = 96 },
|
||||
v3 = { x = x, y = 1, z = y + 1 },
|
||||
v2 = { x = x, y = 0, z = y + 1 },
|
||||
v1 = { x = x, y = 0, z = y },
|
||||
v0 = { x = x, y = 1, z = y },
|
||||
texture_region = { w = 64, h = 64 },
|
||||
}
|
||||
|
||||
elseif dungeon.grid[y][x].face == "observer" then
|
||||
draw_billboard {
|
||||
texture = dungeon.grid[y][x].face_texture,
|
||||
position = { x = y * 2 + 1, y = 1, z = x * 2 + 1 },
|
||||
size = { x = 1, y = 1 },
|
||||
position = { x = x + 0.5, y = 0.5, z = y + 0.5 },
|
||||
size = { x = 0.5, y = 0.5 },
|
||||
}
|
||||
end
|
||||
end
|
||||
|
@ -1,3 +1,3 @@
|
||||
[[deps]]
|
||||
source = "../../../common-data" # where does it come from, might be an url
|
||||
source = "../../../data" # where does it come from, might be an url
|
||||
name = "common-data" # should be globally unique
|
||||
|
@ -13,7 +13,6 @@ set(SOURCE_FILES
|
||||
state.h
|
||||
|
||||
scenes/scene.c scenes/scene.h
|
||||
scenes/title.c scenes/title.h
|
||||
scenes/ingame.c scenes/ingame.h
|
||||
)
|
||||
|
||||
|
@ -1,3 +1,3 @@
|
||||
[[deps]]
|
||||
source = "../../../common-data" # where does it come from, might be an url
|
||||
name = "common-data" # should be globally unique
|
||||
source = "../../../data" # where does it come from, might be an url
|
||||
name = "common-data" # should be globally unique
|
||||
|
@ -1,6 +1,5 @@
|
||||
#include "state.h"
|
||||
#include "scenes/scene.h"
|
||||
#include "scenes/title.h"
|
||||
#include "scenes/ingame.h"
|
||||
|
||||
#include "twn_game_api.h"
|
||||
@ -18,7 +17,7 @@ void game_tick(void) {
|
||||
|
||||
State *state = ctx.udata;
|
||||
state->ctx = &ctx;
|
||||
state->scene = title_scene(state);
|
||||
state->scene = ingame_scene(state);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
#include "ingame.h"
|
||||
#include "title.h"
|
||||
#include "scene.h"
|
||||
|
||||
#include "twn_game_api.h"
|
||||
@ -13,10 +12,14 @@
|
||||
|
||||
|
||||
#define TERRAIN_FREQUENCY 0.15f
|
||||
#define TERRAIN_DISTANCE 64
|
||||
#define TERRAIN_RADIUS 128
|
||||
#define GRASS_RADIUS 16
|
||||
#define TERRAIN_DISTANCE (TERRAIN_RADIUS * 2)
|
||||
#define HALF_TERRAIN_DISTANCE ((float)TERRAIN_DISTANCE / 2)
|
||||
#define PLAYER_HEIGHT 0.6f
|
||||
#define TREE_DENSITY 0.03f
|
||||
|
||||
/* TODO: pregenerate grid of levels of detail */
|
||||
static float heightmap[TERRAIN_DISTANCE][TERRAIN_DISTANCE];
|
||||
|
||||
|
||||
@ -24,7 +27,9 @@ static void process_fly_mode(State *state) {
|
||||
SceneIngame *scn = (SceneIngame *)state->scene;
|
||||
|
||||
DrawCameraFromPrincipalAxesResult dir_and_up =
|
||||
draw_camera_from_principal_axes(scn->pos, scn->roll, scn->pitch, scn->yaw, (float)M_PI_2 * 0.8f, 1);
|
||||
draw_camera_from_principal_axes(scn->pos, scn->roll, scn->pitch, scn->yaw, (float)M_PI_2 * 0.8f, 1, TERRAIN_RADIUS * sqrtf(3));
|
||||
|
||||
scn->looking_direction = dir_and_up.direction;
|
||||
|
||||
const Vec3 right = m_vec_norm(m_vec_cross(dir_and_up.direction, dir_and_up.up));
|
||||
const float speed = 0.04f; /* TODO: put this in a better place */
|
||||
@ -81,13 +86,15 @@ static void process_ground_mode(State *state) {
|
||||
SceneIngame *scn = (SceneIngame *)state->scene;
|
||||
|
||||
DrawCameraFromPrincipalAxesResult dir_and_up =
|
||||
draw_camera_from_principal_axes(scn->pos, scn->roll, scn->pitch, scn->yaw, (float)M_PI_2 * 0.8f, 1);
|
||||
draw_camera_from_principal_axes(scn->pos, scn->roll, scn->pitch, scn->yaw, (float)M_PI_2 * 0.8f, 1, TERRAIN_RADIUS * sqrtf(3));
|
||||
|
||||
scn->looking_direction = dir_and_up.direction;
|
||||
|
||||
dir_and_up.direction.y = 0;
|
||||
dir_and_up.direction = vec3_norm(dir_and_up.direction);
|
||||
|
||||
const Vec3 right = m_vec_norm(m_vec_cross(dir_and_up.direction, dir_and_up.up));
|
||||
const float speed = 0.18f; /* TODO: put this in a better place */
|
||||
const float speed = 0.20f; /* TODO: put this in a better place */
|
||||
|
||||
Vec3 target = scn->pos;
|
||||
|
||||
@ -96,7 +103,7 @@ static void process_ground_mode(State *state) {
|
||||
float const height = height_at(scn, (Vec2){scn->pos.x, scn->pos.z});
|
||||
|
||||
if (target.y > height + PLAYER_HEIGHT)
|
||||
target.y = target.y - 0.4f;
|
||||
target.y = target.y - 0.6f;
|
||||
|
||||
if (target.y < height + PLAYER_HEIGHT)
|
||||
target.y = height + PLAYER_HEIGHT;
|
||||
@ -135,7 +142,7 @@ static void generate_terrain(SceneIngame *scn) {
|
||||
float y = floorf(scn->pos.z - HALF_TERRAIN_DISTANCE + (float)ly);
|
||||
|
||||
float height = stb_perlin_noise3((float)x * TERRAIN_FREQUENCY, (float)y * TERRAIN_FREQUENCY, 0, 0, 0, 0) * 3 - 1;
|
||||
height += stb_perlin_noise3((float)x * TERRAIN_FREQUENCY / 10, (float)y * TERRAIN_FREQUENCY / 10, 0, 0, 0, 0) * 10 - 1;
|
||||
height += stb_perlin_noise3((float)x * TERRAIN_FREQUENCY / 10, (float)y * TERRAIN_FREQUENCY / 10, 0, 0, 0, 0) * 20 - 1;
|
||||
|
||||
heightmap[lx][ly] = height;
|
||||
}
|
||||
@ -143,42 +150,89 @@ static void generate_terrain(SceneIngame *scn) {
|
||||
}
|
||||
|
||||
|
||||
static int32_t ceil_sqrt(int32_t const n) {
|
||||
int32_t res = 1;
|
||||
while(res * res < n)
|
||||
res++;
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
static uint32_t adler32(const void *buf, size_t buflength) {
|
||||
const uint8_t *buffer = (const uint8_t*)buf;
|
||||
|
||||
uint32_t s1 = 1;
|
||||
uint32_t s2 = 0;
|
||||
|
||||
for (size_t n = 0; n < buflength; n++) {
|
||||
s1 = (s1 + buffer[n]) % 65521;
|
||||
s2 = (s2 + s1) % 65521;
|
||||
}
|
||||
return (s2 << 16) | s1;
|
||||
}
|
||||
|
||||
|
||||
static void draw_terrain(SceneIngame *scn) {
|
||||
for (int ly = TERRAIN_DISTANCE - 1; ly > 0; ly--) {
|
||||
for (int lx = 0; lx < TERRAIN_DISTANCE - 1; lx++) {
|
||||
int32_t x = (int32_t)(floorf(scn->pos.x - HALF_TERRAIN_DISTANCE + (float)lx));
|
||||
int32_t y = (int32_t)(floorf(scn->pos.z - HALF_TERRAIN_DISTANCE + (float)ly));
|
||||
/* used to cull invisible tiles over field of view (to horizon) */
|
||||
Vec2 const d = vec2_norm((Vec2){ .x = scn->looking_direction.x, .y = scn->looking_direction.z });
|
||||
float const c = cosf((float)M_PI_2 * 0.8f * 0.8f);
|
||||
|
||||
/* draw terrain in circle */
|
||||
int32_t const rsi = (int32_t)TERRAIN_RADIUS * (int32_t)TERRAIN_RADIUS;
|
||||
for (int32_t iy = -(int32_t)TERRAIN_RADIUS; iy <= (int32_t)TERRAIN_RADIUS - 1; ++iy) {
|
||||
int32_t const dx = ceil_sqrt(rsi - (iy + (iy <= 0)) * (iy + (iy <= 0)));
|
||||
for (int32_t ix = -dx; ix < dx - 1; ++ix) {
|
||||
int32_t lx = ix + TERRAIN_RADIUS;
|
||||
int32_t ly = iy + TERRAIN_RADIUS;
|
||||
|
||||
float x = (float)(floorf(scn->pos.x - HALF_TERRAIN_DISTANCE + (float)lx));
|
||||
float y = (float)(floorf(scn->pos.z - HALF_TERRAIN_DISTANCE + (float)ly));
|
||||
|
||||
/* cull tiles outside of vision */
|
||||
if (vec2_dot(vec2_norm((Vec2){x - scn->pos.x + d.x * 2, y - scn->pos.z + d.y * 2}), d) < c)
|
||||
continue;
|
||||
|
||||
float d0 = heightmap[lx][ly];
|
||||
float d1 = heightmap[lx + 1][ly];
|
||||
float d2 = heightmap[lx + 1][ly - 1];
|
||||
float d3 = heightmap[lx][ly - 1];
|
||||
|
||||
draw_triangle("/assets/grass.png",
|
||||
draw_quad("/assets/grass2.png",
|
||||
(Vec3){ (float)x, d0, (float)y },
|
||||
(Vec3){ (float)x + 1, d1, (float)y },
|
||||
(Vec3){ (float)x, d3, (float)y - 1 },
|
||||
(Vec2){ 128, 128 },
|
||||
(Vec2){ 128, 0 },
|
||||
(Vec2){ 0, 128 },
|
||||
(Color){255, 255, 255, 255},
|
||||
(Color){255, 255, 255, 255},
|
||||
(Color){255, 255, 255, 255});
|
||||
|
||||
draw_triangle("/assets/grass.png",
|
||||
(Vec3){ (float)x + 1, d1, (float)y },
|
||||
(Vec3){ (float)x + 1, d2, (float)y - 1 },
|
||||
(Vec3){ (float)x, d3, (float)y - 1 },
|
||||
(Vec2){ 128, 0 },
|
||||
(Vec2){ 0, 0 },
|
||||
(Vec2){ 0, 128 },
|
||||
(Color){255, 255, 255, 255},
|
||||
(Color){255, 255, 255, 255},
|
||||
(Rect){ .w = 128, .h = 128 },
|
||||
(Color){255, 255, 255, 255});
|
||||
|
||||
draw_billboard("/assets/grasses/10.png",
|
||||
(Vec3){ (float)x, d0 + 0.15f, (float)y },
|
||||
(Vec2){0.3f, 0.3f},
|
||||
if (((float)(adler32(&((Vec2){x, y}), sizeof (Vec2)) % 100) / 100) <= TREE_DENSITY)
|
||||
draw_billboard("/assets/trreez.png",
|
||||
(Vec3){ (float)x, d0 + 1.95f, (float)y },
|
||||
(Vec2){2.f, 2.f},
|
||||
(Rect){0},
|
||||
(Color){255, 255, 255, 255}, true);
|
||||
}
|
||||
}
|
||||
|
||||
int32_t const rsi_g = (int32_t)GRASS_RADIUS * (int32_t)GRASS_RADIUS;
|
||||
for (int32_t iy = -(int32_t)GRASS_RADIUS; iy <= (int32_t)GRASS_RADIUS - 1; ++iy) {
|
||||
int32_t const dx = ceil_sqrt(rsi_g - (iy + (iy <= 0)) * (iy + (iy <= 0)));
|
||||
for (int32_t ix = -dx; ix < dx; ++ix) {
|
||||
int32_t lx = ix + TERRAIN_RADIUS;
|
||||
int32_t ly = iy + TERRAIN_RADIUS;
|
||||
|
||||
float x = (float)(floorf(scn->pos.x - HALF_TERRAIN_DISTANCE + (float)lx));
|
||||
float y = (float)(floorf(scn->pos.z - HALF_TERRAIN_DISTANCE + (float)ly));
|
||||
|
||||
float d = heightmap[lx][ly];
|
||||
|
||||
draw_billboard("/assets/grasses/25.png",
|
||||
(Vec3){
|
||||
(float)x + (float)((adler32(&((Vec2){x, y}), sizeof (Vec2))) % 32) / 64.0f,
|
||||
d + 0.2f,
|
||||
(float)y + (float)((adler32(&((Vec2){y, x}), sizeof (Vec2))) % 32) / 64.0f
|
||||
},
|
||||
(Vec2){0.4f, 0.4f},
|
||||
(Rect){0},
|
||||
(Color){255, 255, 255, 255}, true);
|
||||
}
|
||||
@ -230,7 +284,7 @@ static void ingame_tick(State *state) {
|
||||
draw_skybox("/assets/miramar/miramar_*.tga");
|
||||
|
||||
ctx.fog_color = (Color){ 140, 147, 160, 255 };
|
||||
ctx.fog_density = 0.03f;
|
||||
ctx.fog_density = 0.015f;
|
||||
}
|
||||
|
||||
|
||||
@ -248,7 +302,7 @@ Scene *ingame_scene(State *state) {
|
||||
|
||||
new_scene->mouse_captured = true;
|
||||
|
||||
m_audio(m_set(path, "music/mod65.xm"),
|
||||
m_audio(m_set(path, "music/woah.ogg"),
|
||||
m_opt(channel, "soundtrack"),
|
||||
m_opt(repeat, true));
|
||||
|
||||
|
@ -12,6 +12,8 @@
|
||||
typedef struct SceneIngame {
|
||||
Scene base;
|
||||
|
||||
Vec3 looking_direction;
|
||||
|
||||
Vec3 pos;
|
||||
float yaw;
|
||||
float pitch;
|
||||
|
@ -1,61 +0,0 @@
|
||||
#include "title.h"
|
||||
#include "ingame.h"
|
||||
|
||||
#include "twn_game_api.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
|
||||
static void title_tick(State *state) {
|
||||
SceneTitle *scn = (SceneTitle *)state->scene;
|
||||
(void)scn;
|
||||
|
||||
input_action("ui_accept", "RETURN");
|
||||
|
||||
if (input_action_just_pressed("ui_accept")) {
|
||||
switch_to(state, ingame_scene);
|
||||
return;
|
||||
}
|
||||
|
||||
m_sprite("/assets/title.png", ((Rect) {
|
||||
((float)ctx.resolution.x / 2) - ((float)320 / 2), 64, 320, 128 }));
|
||||
|
||||
/* draw the tick count as an example of dynamic text */
|
||||
size_t text_str_len = snprintf(NULL, 0, "%llu", (unsigned long long)state->ctx->frame_number) + 1;
|
||||
char *text_str = malloc(text_str_len);
|
||||
snprintf(text_str, text_str_len, "%llu", (unsigned long long)state->ctx->frame_number);
|
||||
|
||||
const char *font = "/fonts/kenney-pixel.ttf";
|
||||
float text_h = 32;
|
||||
float text_w = draw_text_width(text_str, text_h, font);
|
||||
|
||||
draw_rectangle(
|
||||
(Rect) {
|
||||
.x = 0,
|
||||
.y = 0,
|
||||
.w = (float)text_w,
|
||||
.h = (float)text_h,
|
||||
},
|
||||
(Color) { 0, 0, 0, 255 }
|
||||
);
|
||||
|
||||
draw_text(text_str, (Vec2){ 0, 0 }, text_h, (Color) { 255, 255, 255, 255 }, font);
|
||||
free(text_str);
|
||||
}
|
||||
|
||||
|
||||
static void title_end(State *state) {
|
||||
free(state->scene);
|
||||
}
|
||||
|
||||
|
||||
Scene *title_scene(State *state) {
|
||||
(void)state;
|
||||
|
||||
SceneTitle *new_scene = calloc(1, sizeof *new_scene);
|
||||
new_scene->base.tick = title_tick;
|
||||
new_scene->base.end = title_end;
|
||||
|
||||
return (Scene *)new_scene;
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
#ifndef TITLE_H
|
||||
#define TITLE_H
|
||||
|
||||
#include "../state.h"
|
||||
#include "scene.h"
|
||||
|
||||
|
||||
typedef struct SceneTitle {
|
||||
Scene base;
|
||||
} SceneTitle;
|
||||
|
||||
|
||||
Scene *title_scene(State *state);
|
||||
|
||||
|
||||
#endif
|
@ -13,4 +13,4 @@ set(SOURCE_FILES
|
||||
state.h
|
||||
)
|
||||
|
||||
use_townengine(${PROJECT_NAME} "${SOURCE_FILES}" ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
use_townengine("${SOURCE_FILES}" ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
|
@ -116,11 +116,13 @@ void game_tick(void) {
|
||||
input_action("up", "LCLICK");
|
||||
input_action("down", "RCLICK");
|
||||
|
||||
if (input_action_just_pressed("up"))
|
||||
if (input_action_pressed("up"))
|
||||
state->r += 1;
|
||||
if (input_action_just_pressed("down"))
|
||||
if (input_action_pressed("down") && state->r > 2)
|
||||
state->r -= 1;
|
||||
|
||||
draw_circle(mouse_snap, state->r * 8, (Color){125, 125, 125, 125});
|
||||
|
||||
int32_t const rsi = (int32_t)state->r * (int32_t)state->r;
|
||||
int32_t acc = 1;
|
||||
for (int32_t iy = (int32_t)state->r - 1; iy >= 0; --iy) {
|
||||
@ -133,9 +135,8 @@ void game_tick(void) {
|
||||
}
|
||||
}
|
||||
|
||||
draw_circle(mouse_snap, state->r * 8, (Color){125, 125, 125, 125});
|
||||
|
||||
benchmark(state);
|
||||
/* uncomment to see performance difference between variants */
|
||||
// benchmark(state);
|
||||
}
|
||||
|
||||
|
||||
|
BIN
apps/templates/c/data/twn.png
(Stored with Git LFS)
BIN
apps/templates/c/data/twn.png
(Stored with Git LFS)
Binary file not shown.
@ -17,11 +17,6 @@ void game_tick(void) {
|
||||
|
||||
struct state *state = ctx.udata;
|
||||
++state->counter;
|
||||
|
||||
m_sprite("twn.png",
|
||||
(Rect) { .w = 128, .h = 64, },
|
||||
m_opt(stretch, true)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
13
apps/templates/lua/.gitignore
vendored
13
apps/templates/lua/.gitignore
vendored
@ -3,10 +3,15 @@
|
||||
!*.*
|
||||
!*/
|
||||
|
||||
*.so
|
||||
*.dll
|
||||
*.exe
|
||||
*.trace
|
||||
**/*.so
|
||||
**/*.dll
|
||||
**/*.exe
|
||||
**/*.trace
|
||||
**/*.js
|
||||
**/*.wasm
|
||||
**/*.wasm.map
|
||||
**/*.data
|
||||
**/*.html
|
||||
|
||||
data/scripts/twnapi.lua
|
||||
build/
|
||||
|
@ -1,53 +0,0 @@
|
||||
---@type { frame_number: number, frame_duration: number, fog_density: number, fog_color: { r: number, g: number, b: number, a: number }, resolution: { x: number, y: number }, mouse_position: { x: number, y: number }, mouse_movement: { x: number, y: number }, random_seed: number, debug: boolean, initialization_needed: boolean, mouse_capture: boolean, udata: table }
|
||||
ctx = nil
|
||||
---@alias Control '"A"'|'"B"'|'"C"'|'"D"'|'"E"'|'"F"'|'"G"'|'"H"'|'"I"'|'"J"'|'"K"'|'"L"'|'"M"'|'"N"'|'"O"'|'"P"'|'"Q"'|'"R"'|'"S"'|'"T"'|'"U"'|'"V"'|'"W"'|'"X"'|'"Y"'|'"Z"'|'"1"'|'"2"'|'"3"'|'"4"'|'"5"'|'"6"'|'"7"'|'"8"'|'"9"'|'"0"'|'"RETURN"'|'"ESCAPE"'|'"BACKSPACE"'|'"TAB"'|'"SPACE"'|'"MINUS"'|'"EQUALS"'|'"LEFTBRACKET"'|'"RIGHTBRACKET"'|'"BACKSLASH"'|'"NONUSHASH"'|'"SEMICOLON"'|'"APOSTROPHE"'|'"GRAVE"'|'"COMMA"'|'"PERIOD"'|'"SLASH"'|'"CAPSLOCK"'|'"F1"'|'"F2"'|'"F3"'|'"F4"'|'"F5"'|'"F6"'|'"F7"'|'"F8"'|'"F9"'|'"F10"'|'"F11"'|'"F12"'|'"PRINTSCREEN"'|'"SCROLLLOCK"'|'"PAUSE"'|'"INSERT"'|'"HOME"'|'"PAGEUP"'|'"DELETE"'|'"END"'|'"PAGEDOWN"'|'"RIGHT"'|'"LEFT"'|'"DOWN"'|'"UP"'|'"NUMLOCKCLEAR"'|'"KP_DIVIDE"'|'"KP_MULTIPLY"'|'"KP_MINUS"'|'"KP_PLUS"'|'"KP_ENTER"'|'"KP_1"'|'"KP_2"'|'"KP_3"'|'"KP_4"'|'"KP_5"'|'"KP_6"'|'"KP_7"'|'"KP_8"'|'"KP_9"'|'"KP_0"'|'"KP_PERIOD"'|'"NONUSBACKSLASH"'|'"APPLICATION"'|'"POWER"'|'"KP_EQUALS"'|'"F13"'|'"F14"'|'"F15"'|'"F16"'|'"F17"'|'"F18"'|'"F19"'|'"F20"'|'"F21"'|'"F22"'|'"F23"'|'"F24"'|'"EXECUTE"'|'"HELP"'|'"MENU"'|'"SELECT"'|'"STOP"'|'"AGAIN"'|'"UNDO"'|'"CUT"'|'"COPY"'|'"PASTE"'|'"FIND"'|'"MUTE"'|'"VOLUMEUP"'|'"VOLUMEDOWN"'|'"KP_COMMA"'|'"KP_EQUALSAS400"'|'"INTERNATIONAL1"'|'"INTERNATIONAL2"'|'"INTERNATIONAL3"'|'"INTERNATIONAL4"'|'"INTERNATIONAL5"'|'"INTERNATIONAL6"'|'"INTERNATIONAL7"'|'"INTERNATIONAL8"'|'"INTERNATIONAL9"'|'"LANG1"'|'"LANG2"'|'"LANG3"'|'"LANG4"'|'"LANG5"'|'"LANG6"'|'"LANG7"'|'"LANG8"'|'"LANG9"'|'"ALTERASE"'|'"SYSREQ"'|'"CANCEL"'|'"CLEAR"'|'"PRIOR"'|'"RETURN2"'|'"SEPARATOR"'|'"OUT"'|'"OPER"'|'"CLEARAGAIN"'|'"CRSEL"'|'"EXSEL"'|'"KP_00"'|'"KP_000"'|'"THOUSANDSSEPARATOR"'|'"DECIMALSEPARATOR"'|'"CURRENCYUNIT"'|'"CURRENCYSUBUNIT"'|'"KP_LEFTPAREN"'|'"KP_RIGHTPAREN"'|'"KP_LEFTBRACE"'|'"KP_RIGHTBRACE"'|'"KP_TAB"'|'"KP_BACKSPACE"'|'"KP_A"'|'"KP_B"'|'"KP_C"'|'"KP_D"'|'"KP_E"'|'"KP_F"'|'"KP_XOR"'|'"KP_POWER"'|'"KP_PERCENT"'|'"KP_LESS"'|'"KP_GREATER"'|'"KP_AMPERSAND"'|'"KP_DBLAMPERSAND"'|'"KP_VERTICALBAR"'|'"KP_DBLVERTICALBAR"'|'"KP_COLON"'|'"KP_HASH"'|'"KP_SPACE"'|'"KP_AT"'|'"KP_EXCLAM"'|'"KP_MEMSTORE"'|'"KP_MEMRECALL"'|'"KP_MEMCLEAR"'|'"KP_MEMADD"'|'"KP_MEMSUBTRACT"'|'"KP_MEMMULTIPLY"'|'"KP_MEMDIVIDE"'|'"KP_PLUSMINUS"'|'"KP_CLEAR"'|'"KP_CLEARENTRY"'|'"KP_BINARY"'|'"KP_OCTAL"'|'"KP_DECIMAL"'|'"KP_HEXADECIMAL"'|'"LCTRL"'|'"LSHIFT"'|'"LALT"'|'"LGUI"'|'"RCTRL"'|'"RSHIFT"'|'"RALT"'|'"RGUI"'|'"MODE"'|'"KBDILLUMTOGGLE"'|'"KBDILLUMDOWN"'|'"KBDILLUMUP"'|'"EJECT"'|'"SLEEP"'|'"APP1"'|'"APP2"'|'"AUDIOREWIND"'|'"AUDIOFASTFORWARD"'|'"SOFTLEFT"'|'"SOFTRIGHT"'|'"CALL"'|'"ENDCALL"'|'"LEFT_MOUSE"'|'"RIGHT_MOUSE"'|'"MIDDLE_MOUSE"'|'"X1"'|'"X2"'
|
||||
---@param args { name: string, control: Control }
|
||||
function input_action(args) end
|
||||
---@param args { name: string }
|
||||
function input_action_pressed(args) end
|
||||
---@param args { name: string }
|
||||
function input_action_just_pressed(args) end
|
||||
---@param args { name: string }
|
||||
function input_action_just_released(args) end
|
||||
---@param args { name: string }
|
||||
function input_action_position(args) end
|
||||
---@param args { texture: string, rect: { x: number, y: number, w: number, h: number }, texture_region: { x: number, y: number, w: number, h: number }?, color: { r: number, g: number, b: number, a: number }?, rotation: number?, flip_x: boolean?, flip_y: boolean?, stretch: boolean? }
|
||||
function draw_sprite(args) end
|
||||
---@param args { rect: { x: number, y: number, w: number, h: number }, color: { r: number, g: number, b: number, a: number }? }
|
||||
function draw_rectangle(args) end
|
||||
---@param args { position: { x: number, y: number }, radius: number, color: { r: number, g: number, b: number, a: number }? }
|
||||
function draw_circle(args) end
|
||||
---@param args { string: string, position: { x: number, y: number }, height: number?, color: { r: number, g: number, b: number, a: number }?, font: string? }
|
||||
function draw_text(args) end
|
||||
---@param args { string: string, height: number?, font: string? }
|
||||
function draw_text_width(args) end
|
||||
---@param args { texture: string, corners: { x: number, y: number }, rect: { x: number, y: number, w: number, h: number }, border_thickness: number?, color: { r: number, g: number, b: number, a: number }? }
|
||||
function draw_nine_slice(args) end
|
||||
---@param args { start: { x: number, y: number }, finish: { x: number, y: number }, thickness: number?, color: { r: number, g: number, b: number, a: number }? }
|
||||
function draw_line(args) end
|
||||
---@param args { texture: string, v0: { x: number, y: number, z: number }, v1: { x: number, y: number, z: number }, v2: { x: number, y: number, z: number }, uv0: { x: number, y: number }, uv1: { x: number, y: number }, uv2: { x: number, y: number }, c0: { r: number, g: number, b: number, a: number }?, c1: { r: number, g: number, b: number, a: number }?, c2: { r: number, g: number, b: number, a: number }? }
|
||||
function draw_triangle(args) end
|
||||
---@param args { texture: string, v0: { x: number, y: number, z: number }, v1: { x: number, y: number, z: number }, v2: { x: number, y: number, z: number }, v3: { x: number, y: number, z: number }, texture_region: { x: number, y: number, w: number, h: number }, color: { r: number, g: number, b: number, a: number }? }
|
||||
function draw_quad(args) end
|
||||
---@param args { texture: string, position: { x: number, y: number, z: number }, size: { x: number, y: number }, color: { r: number, g: number, b: number, a: number }?, cylindrical: boolean? }
|
||||
function draw_billboard(args) end
|
||||
---@param args { position: { x: number, y: number, z: number }, direction: { x: number, y: number, z: number }?, up: { x: number, y: number, z: number }?, fov: number?, zoom: number? }
|
||||
function draw_camera(args) end
|
||||
---@param args { position: { x: number, y: number, z: number }, roll: number?, pitch: number?, yaw: number?, fov: number?, zoom: number? }
|
||||
function draw_camera_from_principal_axes(args) end
|
||||
---@param args { textures: string? }
|
||||
function draw_skybox(args) end
|
||||
---@param args { audio: string, channel: string?, loops: boolean?, volume: number?, panning: number? }
|
||||
function audio_play(args) end
|
||||
---@param args { channel: string, parameter: string, value: number }
|
||||
function audio_parameter(args) end
|
||||
---@param args { value: { x: number, y: number }, identity: string }
|
||||
function log_vec2(args) end
|
||||
---@param args { value: { x: number, y: number, z: number }, identity: string }
|
||||
function log_vec3(args) end
|
||||
---@param args { value: { x: number, y: number, w: number, h: number }, identity: string }
|
||||
function log_rect(args) end
|
||||
---@param args { profile: string }
|
||||
function profile_start(args) end
|
||||
---@param args { profile: string }
|
||||
function profile_end(args) end
|
@ -1,3 +1,3 @@
|
||||
[[deps]]
|
||||
source = "../../common-data" # where does it come from, might be an url
|
||||
name = "common-data" # should be globally unique
|
||||
source = "../../data" # where does it come from, might be an url
|
||||
name = "common-data" # should be globally unique
|
||||
|
@ -29,8 +29,8 @@ def to_lua_type_annot(typedesc):
|
||||
return "unknown"
|
||||
# raise BaseException("Unhandled type for annotation: %s" % typedesc)
|
||||
|
||||
print("---@meta twn")
|
||||
print("---@diagnostic disable")
|
||||
print("error(\"townengine lua api file is not supposed to be imported!\")")
|
||||
|
||||
type_annotations = {}
|
||||
type_annotations["ctx"] = r"{ %s, udata: table }" % \
|
||||
|
3
bin/twn
3
bin/twn
@ -57,6 +57,9 @@ case "$1" in
|
||||
fi
|
||||
;;
|
||||
|
||||
devcompl ) (cd "$TWNROOT" && "$toolpath"/twnbuild "--build_dir=$TWNROOT/build" "${@:2}")
|
||||
;;
|
||||
|
||||
* ) echo "Unknown command."
|
||||
;;
|
||||
esac
|
||||
|
@ -5,6 +5,7 @@ from os import getcwd
|
||||
from os.path import expandvars
|
||||
from pathlib import Path
|
||||
from sys import argv
|
||||
from functools import reduce
|
||||
import tomllib
|
||||
|
||||
#TODO: support for default pack override
|
||||
@ -16,15 +17,16 @@ has_clang = getoutput("command -v clang") != ""
|
||||
target_web = "--target=web" in argv
|
||||
|
||||
#TODO: infer what "native" means for current env
|
||||
build_dir = "build/web" if target_web else "build/native"
|
||||
build_dir += "/release" if "--release" in argv else "/debug"
|
||||
build_dir_arg = reduce(lambda c, n: c if c.startswith("--build_dir=") else n, argv + [""], "")
|
||||
build_dir = "build/web" if target_web else build_dir_arg.split("=")[1] if build_dir_arg else "build/native"
|
||||
build_dir += "" if build_dir_arg else "/release" if "--release" in argv else "/debug"
|
||||
|
||||
cmake = ["emcmake", "cmake"] if target_web else ["cmake"]
|
||||
# cmake configuration command
|
||||
command = []
|
||||
|
||||
# check whether clang is around (it's just better)
|
||||
if has_clang:
|
||||
if has_clang and not target_web:
|
||||
command += ["-DCMAKE_C_COMPILER=clang"]
|
||||
# check whether ninja is around (you better start running)
|
||||
if has_ninja:
|
||||
|
BIN
data/assets/dirt/1.png
(Stored with Git LFS)
Normal file
BIN
data/assets/dirt/1.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
data/assets/dirt/2.png
(Stored with Git LFS)
Normal file
BIN
data/assets/dirt/2.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
data/assets/grass2.png
(Stored with Git LFS)
Normal file
BIN
data/assets/grass2.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
data/assets/grasses/25.png
(Stored with Git LFS)
Normal file
BIN
data/assets/grasses/25.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
data/assets/trreez.png
(Stored with Git LFS)
Normal file
BIN
data/assets/trreez.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
data/music/woah.ogg
(Stored with Git LFS)
Normal file
BIN
data/music/woah.ogg
(Stored with Git LFS)
Normal file
Binary file not shown.
@ -53,6 +53,7 @@
|
||||
<li><b>T</b> for townengine; development and apis.
|
||||
<li><b>G</b> for gamedev; guides and FAQs on game making.
|
||||
</ul>
|
||||
<p>There's no reason not to pile up knowledge, refactoring is irrelevant.
|
||||
</blockquote>
|
||||
<p><a name="abi"></a><strong>T1.3 </strong><strong>Procedure Interface</strong>
|
||||
<blockquote>
|
||||
@ -76,5 +77,21 @@
|
||||
<li>Returns must be restricted/closed on being constant over frame, parameters or asset frame.
|
||||
</ul>
|
||||
</blockquote>
|
||||
<p><a name="dev-on"></a><strong>T1.4 </strong><strong>Developing</strong>
|
||||
<blockquote>
|
||||
<p>One of hard choices we employ is requirement on having more or less the same tools on all platforms in order to compile.
|
||||
If you're on Windows you need to get a MinGW environment. [cmake] and [python3] (>specify versions<) are also required.
|
||||
[clang] is preferred over [gcc], as it has superior lto, which matters a great deal with our architecture (literal day and night difference in frame time).
|
||||
[git] is used for version control. Git for Windows distribution could also be used for getting conforming cli environment, much recommended.
|
||||
<p>Inside $TWNROOT/bin folder the [twn] script is used for engine instrumentation.
|
||||
It could build and run your projects and even open this very wiki locally!
|
||||
You could execute [source hooks] inside $TWNROOT to add [twn] to your $PATH temporarily. Symlinking of it is also supported.
|
||||
Look inside the script to see what it can do, it's ever growing !
|
||||
<p>[twn init <template> <name>] could be used to quickly initialize a project.
|
||||
Look into $TWNROOT/apps/templates folder to see available templates.
|
||||
<p>Support for [clangd] completions is provided with [twn devcompl] command, it generates compile_commands.json inside $TWNROOT/build folder.
|
||||
Having workspace placed in $TWNROOT by your IDE should be enough to make it all work. Rerun [twn devcompl] if new files are added.
|
||||
<p>Web target requires [emsdk] as of now. Use [twn build --target=web] to use it.
|
||||
</blockquote>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -21,6 +21,7 @@
|
||||
<p style="margin:0">T1.1 <a href="about-townengine.html#introduction">Introduction</a></p>
|
||||
<p style="margin:0">T1.2 <a href="about-townengine.html#wiki">Wiki</a></p>
|
||||
<p style="margin:0">T1.3 <a href="about-townengine.html#abi">Procedure Interface</a></p>
|
||||
<p style="margin:0">T1.4 <a href="about-townengine.html#dev-on">Developing</a></p>
|
||||
</blockquote>
|
||||
<p style="margin-bottom:0"><a name="input-system"></a>T2. </strong><a href="input-system.html">Input System</strong></a></p>
|
||||
<blockquote style="margin-top:0">
|
||||
|
@ -1,7 +1,7 @@
|
||||
#ifndef TWN_AUDIO_H
|
||||
#define TWN_AUDIO_H
|
||||
|
||||
#include "twn_engine_api.h"
|
||||
#include "twn_api.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
#define TWN_CONTEXT_H
|
||||
|
||||
#include "twn_types.h"
|
||||
#include "twn_engine_api.h"
|
||||
#include "twn_api.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
@ -2,7 +2,7 @@
|
||||
#define TWN_DRAW_H
|
||||
|
||||
#include "twn_types.h"
|
||||
#include "twn_engine_api.h"
|
||||
#include "twn_api.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
@ -66,12 +66,12 @@ TWN_API void draw_triangle(char const *texture,
|
||||
Color c2); /* optional, default: all 255 */
|
||||
|
||||
TWN_API void draw_quad(char const *texture,
|
||||
Vec3 v0, /* upper-left */
|
||||
Vec3 v1, /* bottom-left */
|
||||
Vec3 v2, /* bottom-right */
|
||||
Vec3 v3, /* upper-right */
|
||||
Vec3 v0, /* upper-left */
|
||||
Vec3 v1, /* bottom-left */
|
||||
Vec3 v2, /* bottom-right */
|
||||
Vec3 v3, /* upper-right */
|
||||
Rect texture_region,
|
||||
Color color); /* optional, default: all 255 */
|
||||
Color color); /* optional, default: all 255 */
|
||||
|
||||
TWN_API void draw_billboard(char const *texture,
|
||||
Vec3 position,
|
||||
@ -86,11 +86,12 @@ TWN_API void draw_camera_2d(Vec2 position, /* optional, default: (0, 0) */
|
||||
|
||||
/* sets a perspective 3d camera to be used for all 3d commands */
|
||||
/* fov = 0 corresponds to orthographic projection */
|
||||
TWN_API void draw_camera(Vec3 position,
|
||||
TWN_API void draw_camera(Vec3 position, /* optional, default: (0, 0, 0) */
|
||||
Vec3 direction, /* optional, default: (0, 0, -1) */
|
||||
Vec3 up, /* optional, default: (0, 1, 0) */
|
||||
float fov, /* optional, default: PI / 6 * 3 (90 degrees) */
|
||||
float zoom); /* optional, default: 1 */
|
||||
float zoom, /* optional, default: 1 */
|
||||
float draw_distance); /* optional, default: 100 */
|
||||
|
||||
/* same as draw_camera(), but with first person controller in mind */
|
||||
/* direction and up vectors are inferred from roll, pitch and yaw parameters (in radians) */
|
||||
@ -100,12 +101,13 @@ typedef struct DrawCameraFromPrincipalAxesResult {
|
||||
Vec3 up;
|
||||
} DrawCameraFromPrincipalAxesResult;
|
||||
TWN_API DrawCameraFromPrincipalAxesResult
|
||||
draw_camera_from_principal_axes(Vec3 position,
|
||||
float roll, /* optional, default: 0 */
|
||||
float pitch, /* optional, default: 0 */
|
||||
float yaw, /* optional, default: 0 */
|
||||
float fov, /* optional, default: PI / 6 * 3 (90 degrees) */
|
||||
float zoom); /* optional, default: 1 */
|
||||
draw_camera_from_principal_axes(Vec3 position, /* optional, default: (0, 0, 0) */
|
||||
float roll, /* optional, default: 0 */
|
||||
float pitch, /* optional, default: 0 */
|
||||
float yaw, /* optional, default: 0 */
|
||||
float fov, /* optional, default: PI / 6 * 3 (90 degrees) */
|
||||
float zoom, /* optional, default: 1 */
|
||||
float draw_distance); /* optional, default: 100 */
|
||||
|
||||
/* expects '*' masks that will be expanded to 6 names: 'up', 'down', 'east', 'west', 'north' and 'south' */
|
||||
TWN_API void draw_skybox(const char *textures);
|
||||
|
@ -5,7 +5,7 @@
|
||||
#include "twn_input.h"
|
||||
#include "twn_draw.h"
|
||||
#include "twn_audio.h"
|
||||
#include "twn_engine_api.h"
|
||||
#include "twn_api.h"
|
||||
#include "twn_util.h"
|
||||
#include "twn_context.h"
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
#ifndef TWN_INPUT_H
|
||||
#define TWN_INPUT_H
|
||||
|
||||
#include "twn_engine_api.h"
|
||||
#include "twn_api.h"
|
||||
#include "twn_types.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
|
@ -2,7 +2,7 @@
|
||||
#define TWN_UTIL_H
|
||||
|
||||
#include "twn_types.h"
|
||||
#include "twn_engine_api.h"
|
||||
#include "twn_api.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
@ -29,10 +29,20 @@ static inline Vec2 vec2_scale(Vec2 a, float s) {
|
||||
return (Vec2) { a.x * s, a.y * s };
|
||||
}
|
||||
|
||||
static inline float vec2_dot(Vec2 a, Vec2 b) {
|
||||
return a.x * b.x + a.y * b.y;
|
||||
}
|
||||
|
||||
static inline float vec2_length(Vec2 a) {
|
||||
return sqrtf(a.x * a.x + a.y * a.y);
|
||||
}
|
||||
|
||||
static inline Vec2 vec2_norm(Vec2 a) {
|
||||
const float n = sqrtf(vec2_dot(a, a));
|
||||
/* TODO: do we need truncating over epsilon as cglm does? */
|
||||
return vec2_scale(a, 1.0f / n);
|
||||
}
|
||||
|
||||
static inline Vec3 vec3_add(Vec3 a, Vec3 b) {
|
||||
return (Vec3) { a.x + b.x, a.y + b.y, a.z + b.z };
|
||||
}
|
||||
|
@ -207,11 +207,12 @@
|
||||
"symbol": "camera",
|
||||
"header": "twn_draw.h",
|
||||
"params": [
|
||||
{ "name": "position", "type": "Vec3" },
|
||||
{ "name": "position", "type": "Vec3", "default": { "x": 0, "y": 0, "z": 0 } },
|
||||
{ "name": "direction", "type": "Vec3", "default": { "x": 0, "y": 0, "z": -1 } },
|
||||
{ "name": "up", "type": "Vec3", "default": { "x": 0, "y": 1, "z": 0 } },
|
||||
{ "name": "fov", "type": "float", "default": 1.57079632679 },
|
||||
{ "name": "zoom", "type": "float", "default": 1 }
|
||||
{ "name": "zoom", "type": "float", "default": 1 },
|
||||
{ "name": "draw_distance", "type": "float", "default": 100 }
|
||||
]
|
||||
},
|
||||
|
||||
@ -220,12 +221,13 @@
|
||||
"symbol": "camera_from_principal_axes",
|
||||
"header": "twn_draw.h",
|
||||
"params": [
|
||||
{ "name": "position", "type": "Vec3" },
|
||||
{ "name": "position", "type": "Vec3", "default": { "x": 0, "y": 0, "z": 0 } },
|
||||
{ "name": "roll", "type": "float", "default": 0 },
|
||||
{ "name": "pitch", "type": "float", "default": 0 },
|
||||
{ "name": "yaw", "type": "float", "default": 0 },
|
||||
{ "name": "fov", "type": "float", "default": 1.57079632679 },
|
||||
{ "name": "zoom", "type": "float", "default": 1 }
|
||||
{ "name": "zoom", "type": "float", "default": 1 },
|
||||
{ "name": "draw_distance", "type": "float", "default": 100 }
|
||||
],
|
||||
"return": {
|
||||
"fields": [
|
||||
|
@ -95,14 +95,15 @@ void finally_draw_billboard_batch(struct MeshBatch const *batch,
|
||||
const Vec2 uv3c = { xr + wr, yr };
|
||||
|
||||
for (size_t batch_n = 0; batch_n <= (primitives_len - 1) / QUAD_ELEMENT_BUFFER_LENGTH; batch_n++) {
|
||||
size_t const processing = MIN(primitives_len - batch_n * QUAD_ELEMENT_BUFFER_LENGTH, QUAD_ELEMENT_BUFFER_LENGTH);
|
||||
|
||||
/* emit vertex data */
|
||||
VertexBuffer const buffer = get_scratch_vertex_array();
|
||||
VertexBufferBuilder builder = build_vertex_buffer(
|
||||
buffer,
|
||||
sizeof (ElementIndexedBillboard) * MIN(primitives_len - batch_n * QUAD_ELEMENT_BUFFER_LENGTH, QUAD_ELEMENT_BUFFER_LENGTH));
|
||||
sizeof (ElementIndexedBillboard) * processing);
|
||||
|
||||
for (size_t i = 0; i < primitives_len; ++i) {
|
||||
for (size_t i = 0; i < processing; ++i) {
|
||||
struct SpaceBillboard const billboard = ((SpaceBillboard *)(void *)batch->primitives)[batch_n * QUAD_ELEMENT_BUFFER_LENGTH + i];
|
||||
|
||||
/* a = (right + up) * size, b = (right - up) * size*/
|
||||
@ -179,20 +180,20 @@ void finally_draw_billboard_batch(struct MeshBatch const *batch,
|
||||
.buffer = buffer
|
||||
};
|
||||
|
||||
command.textured = true;
|
||||
command.texture_key = texture_key;
|
||||
command.textured = true;
|
||||
|
||||
command.element_buffer = get_quad_element_buffer();
|
||||
command.element_count = 6 * (uint32_t)primitives_len;
|
||||
command.range_end = 6 * (uint32_t)primitives_len;
|
||||
command.element_count = 6 * (uint32_t)processing;
|
||||
command.range_end = 6 * (uint32_t)processing;
|
||||
|
||||
/* TODO: support alpha blended case, with distance sort */
|
||||
TextureMode mode = textures_get_mode(&ctx.texture_cache, texture_key);
|
||||
if (mode == TEXTURE_MODE_GHOSTLY)
|
||||
mode = TEXTURE_MODE_SEETHROUGH;
|
||||
|
||||
command.texture_mode = mode;
|
||||
command.pipeline = PIPELINE_SPACE;
|
||||
command.texture_mode = mode;
|
||||
|
||||
command.depth_range_high = depth_range_high;
|
||||
command.depth_range_low = depth_range_low;
|
||||
|
@ -4,6 +4,7 @@
|
||||
#include "twn_types.h"
|
||||
#include "twn_gpu_texture_c.h"
|
||||
#include "twn_textures_c.h"
|
||||
#include "twn_types_c.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
@ -49,6 +50,11 @@ typedef struct {
|
||||
uint32_t element_count;
|
||||
uint32_t range_start, range_end;
|
||||
|
||||
enum {
|
||||
DEFERRED_COMMAND_DRAW_GEOMETRY_MODE_TRIANGLES = 0,
|
||||
DEFERRED_COMMAND_DRAW_GEOMETRY_MODE_LINES = 1,
|
||||
} geometry_mode;
|
||||
|
||||
bool constant_colored;
|
||||
bool textured, texture_repeat, uses_gpu_key;
|
||||
} DeferredCommandDraw;
|
||||
|
@ -20,6 +20,7 @@ DeferredCommand *deferred_commands;
|
||||
/* TODO: with buffered render, don't we use camera of wrong frame right now ? */
|
||||
Matrix4 camera_projection_matrix;
|
||||
Matrix4 camera_look_at_matrix;
|
||||
float camera_far_z;
|
||||
|
||||
float camera_2d_rotation;
|
||||
Vec2 camera_2d_position;
|
||||
@ -36,7 +37,7 @@ static void reset_camera_2d(void) {
|
||||
|
||||
|
||||
void render_clear(void) {
|
||||
draw_camera((Vec3){0, 0, 0}, (Vec3){0, 0, 1}, (Vec3){0, 1, 0}, 1.57079632679f, 1);
|
||||
draw_camera((Vec3){0, 0, 0}, (Vec3){0, 0, 1}, (Vec3){0, 1, 0}, 1.57079632679f, 1, 100);
|
||||
reset_camera_2d();
|
||||
|
||||
text_cache_reset_arena(&ctx.text_cache);
|
||||
@ -52,6 +53,9 @@ void render_clear(void) {
|
||||
|
||||
for (size_t i = 0; i < hmlenu(ctx.billboard_batches); ++i)
|
||||
arrsetlen(ctx.billboard_batches[i].value.primitives, 0);
|
||||
|
||||
for (size_t i = 0; i < hmlenu(ctx.quad_batches); ++i)
|
||||
arrsetlen(ctx.quad_batches[i].value.primitives, 0);
|
||||
}
|
||||
|
||||
|
||||
@ -196,31 +200,6 @@ void draw_nine_slice(const char *texture, Vec2 corners, Rect rect, float border_
|
||||
}
|
||||
|
||||
|
||||
TWN_API void draw_quad(char const *texture,
|
||||
Vec3 v0, /* upper-left */
|
||||
Vec3 v1, /* bottom-left */
|
||||
Vec3 v2, /* bottom-right */
|
||||
Vec3 v3, /* upper-right */
|
||||
Rect texture_region,
|
||||
Color color)
|
||||
{
|
||||
Vec2 const uv0 = { texture_region.x, texture_region.y };
|
||||
Vec2 const uv1 = { texture_region.x, texture_region.y + texture_region.h };
|
||||
Vec2 const uv2 = { texture_region.x + texture_region.w, texture_region.y + texture_region.h };
|
||||
Vec2 const uv3 = { texture_region.x + texture_region.w, texture_region.y };
|
||||
|
||||
draw_triangle(texture,
|
||||
v0, v1, v3,
|
||||
uv0, uv1, uv3,
|
||||
color, color, color);
|
||||
|
||||
draw_triangle(texture,
|
||||
v3, v1, v2,
|
||||
uv3, uv1, uv2,
|
||||
color, color, color);
|
||||
}
|
||||
|
||||
|
||||
static void render_2d(void) {
|
||||
const size_t render_queue_len = arrlenu(ctx.render_queue_2d);
|
||||
|
||||
@ -299,7 +278,7 @@ static void render_2d(void) {
|
||||
}
|
||||
|
||||
/* TODO: batching */
|
||||
case PRIMITIVE_2D_LINE: {
|
||||
case PRIMITIVE_2D_LINES: {
|
||||
struct Render2DInvocation const invocation = {
|
||||
.primitive = current,
|
||||
.layer = layer,
|
||||
@ -348,8 +327,8 @@ static void render_2d(void) {
|
||||
case PRIMITIVE_2D_CIRCLE:
|
||||
render_circle(&invocation.primitive->circle);
|
||||
break;
|
||||
case PRIMITIVE_2D_LINE:
|
||||
render_line(&invocation.primitive->line);
|
||||
case PRIMITIVE_2D_LINES:
|
||||
render_lines(&invocation.primitive->line);
|
||||
break;
|
||||
case PRIMITIVE_2D_TEXT:
|
||||
default:
|
||||
@ -380,8 +359,8 @@ static void render_2d(void) {
|
||||
case PRIMITIVE_2D_TEXT:
|
||||
render_text(&invocation.primitive->text);
|
||||
break;
|
||||
case PRIMITIVE_2D_LINE:
|
||||
render_line(&invocation.primitive->line);
|
||||
case PRIMITIVE_2D_LINES:
|
||||
render_lines(&invocation.primitive->line);
|
||||
break;
|
||||
default:
|
||||
SDL_assert(false);
|
||||
@ -397,18 +376,16 @@ static void render_2d(void) {
|
||||
static void render_space(void) {
|
||||
finally_draw_models();
|
||||
|
||||
/* nothing to do, abort */
|
||||
/* as space pipeline isn't used we can have fewer changes and initialization costs */
|
||||
if (hmlenu(ctx.uncolored_mesh_batches) != 0 || hmlenu(ctx.billboard_batches) != 0) {
|
||||
for (size_t i = 0; i < hmlenu(ctx.uncolored_mesh_batches); ++i) {
|
||||
finally_draw_uncolored_space_traingle_batch(&ctx.uncolored_mesh_batches[i].value,
|
||||
ctx.uncolored_mesh_batches[i].key);
|
||||
}
|
||||
for (size_t i = 0; i < hmlenu(ctx.uncolored_mesh_batches); ++i)
|
||||
finally_draw_uncolored_space_traingle_batch(&ctx.uncolored_mesh_batches[i].value,
|
||||
ctx.uncolored_mesh_batches[i].key);
|
||||
|
||||
for (size_t i = 0; i < hmlenu(ctx.billboard_batches); ++i) {
|
||||
finally_draw_billboard_batch(&ctx.billboard_batches[i].value, ctx.billboard_batches[i].key);
|
||||
}
|
||||
}
|
||||
for (size_t i = 0; i < hmlenu(ctx.quad_batches); ++i)
|
||||
finally_draw_space_quads_batch(&ctx.quad_batches[i].value,
|
||||
ctx.quad_batches[i].key);
|
||||
|
||||
for (size_t i = 0; i < hmlenu(ctx.billboard_batches); ++i)
|
||||
finally_draw_billboard_batch(&ctx.billboard_batches[i].value, ctx.billboard_batches[i].key);
|
||||
|
||||
render_skybox(); /* after everything else, as to use depth buffer for early z rejection */
|
||||
}
|
||||
@ -447,7 +424,7 @@ TWN_API void draw_camera_2d(Vec2 position,
|
||||
}
|
||||
|
||||
/* TODO: check for NaNs and alike */
|
||||
void draw_camera(Vec3 position, Vec3 direction, Vec3 up, float fov, float zoom) {
|
||||
void draw_camera(Vec3 position, Vec3 direction, Vec3 up, float fov, float zoom, float draw_distance) {
|
||||
bool const orthographic = fabsf(0.0f - fov) < 0.00001f;
|
||||
if (!orthographic && fov >= (float)(M_PI))
|
||||
log_warn("Invalid fov given (%f)", (double)fov);
|
||||
@ -461,6 +438,7 @@ void draw_camera(Vec3 position, Vec3 direction, Vec3 up, float fov, float zoom)
|
||||
(Vec2){ 1/-zoom, 1/zoom },
|
||||
(Vec2){ 1/zoom, 1/-zoom }
|
||||
},
|
||||
.far_z = draw_distance
|
||||
};
|
||||
|
||||
if (!orthographic)
|
||||
@ -468,6 +446,8 @@ void draw_camera(Vec3 position, Vec3 direction, Vec3 up, float fov, float zoom)
|
||||
else
|
||||
camera_projection_matrix = camera_orthographic(&camera);
|
||||
|
||||
camera_far_z = draw_distance;
|
||||
|
||||
camera_look_at_matrix = camera_look_at(&camera);
|
||||
}
|
||||
|
||||
@ -479,7 +459,8 @@ DrawCameraFromPrincipalAxesResult draw_camera_from_principal_axes(Vec3 position,
|
||||
float pitch,
|
||||
float yaw,
|
||||
float fov,
|
||||
float zoom)
|
||||
float zoom,
|
||||
float draw_distance)
|
||||
{
|
||||
bool const orthographic = fabsf(0.0f - fov) < 0.00001f;
|
||||
if (!orthographic && fov >= (float)(M_PI))
|
||||
@ -500,7 +481,7 @@ DrawCameraFromPrincipalAxesResult draw_camera_from_principal_axes(Vec3 position,
|
||||
|
||||
Vec3 const up = (Vec3){0, 1, 0};
|
||||
|
||||
draw_camera(position, direction, up, fov, zoom);
|
||||
draw_camera(position, direction, up, fov, zoom, draw_distance);
|
||||
|
||||
return (DrawCameraFromPrincipalAxesResult) {
|
||||
.direction = direction,
|
||||
@ -557,31 +538,6 @@ void issue_deferred_draw_commands(void) {
|
||||
}
|
||||
|
||||
|
||||
/* TODO: Support thickness */
|
||||
void draw_line(Vec2 start,
|
||||
Vec2 finish,
|
||||
float thickness,
|
||||
Color color)
|
||||
{
|
||||
if (fabsf(1.0f - thickness) >= 0.00001f)
|
||||
log_warn("Thickness isn't yet implemented for line drawing (got %f)", (double)thickness);
|
||||
|
||||
LinePrimitive line = {
|
||||
.start = start,
|
||||
.finish = finish,
|
||||
.thickness = thickness,
|
||||
.color = color,
|
||||
};
|
||||
|
||||
Primitive2D primitive = {
|
||||
.type = PRIMITIVE_2D_LINE,
|
||||
.line = line,
|
||||
};
|
||||
|
||||
arrput(ctx.render_queue_2d, primitive);
|
||||
}
|
||||
|
||||
|
||||
void draw_box(Rect rect,
|
||||
float thickness,
|
||||
Color color)
|
||||
|
@ -16,6 +16,7 @@
|
||||
|
||||
extern Matrix4 camera_projection_matrix;
|
||||
extern Matrix4 camera_look_at_matrix;
|
||||
extern float camera_far_z;
|
||||
|
||||
extern float camera_2d_rotation;
|
||||
extern Vec2 camera_2d_position;
|
||||
@ -23,7 +24,7 @@ extern float camera_2d_zoom;
|
||||
|
||||
extern double depth_range_low, depth_range_high;
|
||||
|
||||
#define QUAD_ELEMENT_BUFFER_LENGTH (65536 / 6)
|
||||
#define QUAD_ELEMENT_BUFFER_LENGTH ((65536 * 8) / 6)
|
||||
#define CIRCLE_VERTICES_MAX 2048
|
||||
|
||||
/* TODO: limit to only most necessary */
|
||||
@ -37,6 +38,8 @@ enum {
|
||||
TWN_UNSIGNED_BYTE,
|
||||
};
|
||||
|
||||
/* note: they're separate as on some targets they are not interchangeable like in opengl */
|
||||
/* emscripten legacy gl emulation, for example, assumes first bind to be decisive in its future usage */
|
||||
|
||||
typedef uint32_t VertexBuffer;
|
||||
typedef uint32_t IndexBuffer;
|
||||
@ -66,9 +69,12 @@ typedef struct SpritePrimitive {
|
||||
bool repeat;
|
||||
} SpritePrimitive;
|
||||
|
||||
/* batched in place */
|
||||
typedef struct LinePrimitive {
|
||||
Vec2 start;
|
||||
Vec2 finish;
|
||||
struct LineVertex {
|
||||
Vec3 position;
|
||||
Color color;
|
||||
} *vertices;
|
||||
float thickness;
|
||||
Color color;
|
||||
} LinePrimitive;
|
||||
@ -94,7 +100,7 @@ typedef struct TextPrimitive {
|
||||
|
||||
typedef enum Primitive2DType {
|
||||
PRIMITIVE_2D_SPRITE,
|
||||
PRIMITIVE_2D_LINE,
|
||||
PRIMITIVE_2D_LINES,
|
||||
PRIMITIVE_2D_RECT,
|
||||
PRIMITIVE_2D_CIRCLE,
|
||||
PRIMITIVE_2D_TEXT,
|
||||
@ -215,6 +221,7 @@ typedef struct ElementIndexedQuadWithoutColorWithoutTexture {
|
||||
Vec2 v3;
|
||||
} ElementIndexedQuadWithoutColorWithoutTexture;
|
||||
|
||||
/* TODO: rename to space quad */
|
||||
/* TODO: no color variant */
|
||||
typedef struct ElementIndexedBillboard {
|
||||
/* upper-left */
|
||||
@ -315,7 +322,7 @@ IndexBuffer get_circle_element_buffer(void);
|
||||
|
||||
void render_circle(const CirclePrimitive *circle);
|
||||
|
||||
void render_line(const LinePrimitive *line);
|
||||
void render_lines(LinePrimitive *line);
|
||||
|
||||
void render_rectangle(const RectPrimitive *rectangle);
|
||||
|
||||
@ -332,6 +339,9 @@ void push_quad_payload_to_vertex_buffer_builder(struct QuadBatch batch,
|
||||
Vec2 uv0, Vec2 uv1, Vec2 uv2, Vec2 uv3,
|
||||
Color color);
|
||||
|
||||
void finally_draw_space_quads_batch(const MeshBatch *batch,
|
||||
const TextureKey texture_key);
|
||||
|
||||
void finally_draw_text(FontData const *font_data,
|
||||
size_t len,
|
||||
Color color,
|
||||
|
@ -14,6 +14,10 @@
|
||||
#endif
|
||||
#include <stb_ds.h>
|
||||
|
||||
/* note: care must be taken to always have interleaved VBOs with all data, */
|
||||
/* as it is optimized in emscripten legacy gl emulation. */
|
||||
/* constant color isn't supported there, so care must be given to provide alternative path with VBO as well. */
|
||||
|
||||
|
||||
static TextureMode texture_mode_last_used = TEXTURE_MODE_UNKNOWN;
|
||||
static Pipeline pipeline_last_used = PIPELINE_NO;
|
||||
@ -104,9 +108,8 @@ static void finally_use_space_pipeline(void) {
|
||||
if (!list) {
|
||||
list = glGenLists(1);
|
||||
|
||||
glNewList(list, GL_COMPILE);
|
||||
glNewList(list, GL_COMPILE); {
|
||||
#endif
|
||||
{
|
||||
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
|
||||
glShadeModel(GL_FLAT);
|
||||
|
||||
@ -128,10 +131,8 @@ static void finally_use_space_pipeline(void) {
|
||||
glColor4ub(255, 255, 255, 255);
|
||||
#ifndef __EMSCRIPTEN__
|
||||
} glEndList();
|
||||
|
||||
glCallList(list);
|
||||
} glCallList(list);
|
||||
#endif
|
||||
}
|
||||
|
||||
glMatrixMode(GL_PROJECTION);
|
||||
glLoadMatrixf(&camera_projection_matrix.row[0].x);
|
||||
@ -174,7 +175,6 @@ static void finally_use_2d_pipeline(void) {
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
#ifndef __EMSCRIPTEN__
|
||||
} glEndList();
|
||||
|
||||
} glCallList(list);
|
||||
#endif
|
||||
|
||||
@ -212,8 +212,7 @@ static void setup_ghostly_texture_mode(void) {
|
||||
glDisable(GL_ALPHA_TEST);
|
||||
#ifndef __EMSCRIPTEN__
|
||||
glEndList();
|
||||
}
|
||||
glCallList(list);
|
||||
} glCallList(list);
|
||||
#endif
|
||||
}
|
||||
|
||||
@ -232,8 +231,7 @@ static void setup_seethrough_texture_mode(void) {
|
||||
glAlphaFunc(GL_EQUAL, 1.0f);
|
||||
#ifndef __EMSCRIPTEN__
|
||||
glEndList();
|
||||
}
|
||||
glCallList(list);
|
||||
} glCallList(list);
|
||||
#endif
|
||||
}
|
||||
|
||||
@ -251,8 +249,7 @@ static void setup_opaque_texture_mode(void) {
|
||||
glDisable(GL_ALPHA_TEST);
|
||||
#ifndef __EMSCRIPTEN__
|
||||
glEndList();
|
||||
}
|
||||
glCallList(list);
|
||||
} glCallList(list);
|
||||
#endif
|
||||
}
|
||||
|
||||
@ -343,7 +340,7 @@ void finish_index_builder(IndexBufferBuilder *builder) {
|
||||
|
||||
|
||||
static void load_cubemap_side(const char *path, GLenum target) {
|
||||
SDL_Surface *surface = textures_load_surface(path);
|
||||
SDL_Surface *surface = textures_load_surface(path, false);
|
||||
/* TODO: sanity check whether all of them have same dimensions? */
|
||||
glTexImage2D(target,
|
||||
0,
|
||||
@ -353,6 +350,7 @@ static void load_cubemap_side(const char *path, GLenum target) {
|
||||
surface->format->BytesPerPixel == 4 ? GL_RGBA : GL_RGB,
|
||||
GL_UNSIGNED_BYTE,
|
||||
surface->pixels);
|
||||
|
||||
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
@ -431,10 +429,8 @@ void finally_render_skybox(DeferredCommandDrawSkybox command) {
|
||||
static GLuint list = 0;
|
||||
if (!list) {
|
||||
list = glGenLists(1);
|
||||
|
||||
glNewList(list, GL_COMPILE);
|
||||
glNewList(list, GL_COMPILE); {
|
||||
#endif
|
||||
{
|
||||
/* note: assumes that space pipeline is applied already */
|
||||
glDisable(GL_ALPHA_TEST);
|
||||
glDepthMask(GL_FALSE);
|
||||
@ -445,66 +441,68 @@ void finally_render_skybox(DeferredCommandDrawSkybox command) {
|
||||
glEnable(GL_DEPTH_CLAMP);
|
||||
#endif
|
||||
|
||||
float const d = camera_far_z / sqrtf(3);
|
||||
|
||||
glBegin(GL_QUADS); {
|
||||
/* up */
|
||||
glTexCoord3f(50.f, 50.f, 50.f);
|
||||
glVertex3f(50.f, 50.f, 50.f);
|
||||
glTexCoord3f(-50.f, 50.f, 50.f);
|
||||
glVertex3f(-50.f, 50.f, 50.f);
|
||||
glTexCoord3f(-50.f, 50.f, -50.f);
|
||||
glVertex3f(-50.f, 50.f, -50.f);
|
||||
glTexCoord3f(50.f, 50.f, -50.f);
|
||||
glVertex3f(50.f, 50.f, -50.f);
|
||||
glTexCoord3f(d, d, d);
|
||||
glVertex3f(d, d, d);
|
||||
glTexCoord3f(-d, d, d);
|
||||
glVertex3f(-d, d, d);
|
||||
glTexCoord3f(-d, d, -d);
|
||||
glVertex3f(-d, d, -d);
|
||||
glTexCoord3f(d, d, -d);
|
||||
glVertex3f(d, d, -d);
|
||||
|
||||
/* down */
|
||||
glTexCoord3f(50.f, -50.f, 50.f);
|
||||
glVertex3f(50.f, -50.f, 50.f);
|
||||
glTexCoord3f(50.f, -50.f, -50.f);
|
||||
glVertex3f(50.f, -50.f, -50.f);
|
||||
glTexCoord3f(-50.f, -50.f, -50.f);
|
||||
glVertex3f(-50.f, -50.f, -50.f);
|
||||
glTexCoord3f(-50.f, -50.f, 50.f);
|
||||
glVertex3f(-50.f, -50.f, 50.f);
|
||||
glTexCoord3f(d, -d, d);
|
||||
glVertex3f(d, -d, d);
|
||||
glTexCoord3f(d, -d, -d);
|
||||
glVertex3f(d, -d, -d);
|
||||
glTexCoord3f(-d, -d, -d);
|
||||
glVertex3f(-d, -d, -d);
|
||||
glTexCoord3f(-d, -d, d);
|
||||
glVertex3f(-d, -d, d);
|
||||
|
||||
/* east */
|
||||
glTexCoord3f(50.f, -50.f, 50.f);
|
||||
glVertex3f(50.f, -50.f, 50.f);
|
||||
glTexCoord3f(50.f, 50.f, 50.f);
|
||||
glVertex3f(50.f, 50.f, 50.f);
|
||||
glTexCoord3f(50.f, 50.f, -50.f);
|
||||
glVertex3f(50.f, 50.f, -50.f);
|
||||
glTexCoord3f(50.f, -50.f, -50.f);
|
||||
glVertex3f(50.f, -50.f, -50.f);
|
||||
glTexCoord3f(d, -d, d);
|
||||
glVertex3f(d, -d, d);
|
||||
glTexCoord3f(d, d, d);
|
||||
glVertex3f(d, d, d);
|
||||
glTexCoord3f(d, d, -d);
|
||||
glVertex3f(d, d, -d);
|
||||
glTexCoord3f(d, -d, -d);
|
||||
glVertex3f(d, -d, -d);
|
||||
|
||||
/* west */
|
||||
glTexCoord3f(-50.f, -50.f, 50.f);
|
||||
glVertex3f(-50.f, -50.f, 50.f);
|
||||
glTexCoord3f(-50.f, -50.f, -50.f);
|
||||
glVertex3f(-50.f, -50.f, -50.f);
|
||||
glTexCoord3f(-50.f, 50.f, -50.f);
|
||||
glVertex3f(-50.f, 50.f, -50.f);
|
||||
glTexCoord3f(-50.f, 50.f, 50.f);
|
||||
glVertex3f(-50.f, 50.f, 50.f);
|
||||
glTexCoord3f(-d, -d, d);
|
||||
glVertex3f(-d, -d, d);
|
||||
glTexCoord3f(-d, -d, -d);
|
||||
glVertex3f(-d, -d, -d);
|
||||
glTexCoord3f(-d, d, -d);
|
||||
glVertex3f(-d, d, -d);
|
||||
glTexCoord3f(-d, d, d);
|
||||
glVertex3f(-d, d, d);
|
||||
|
||||
/* north */
|
||||
glTexCoord3f(-50.f, -50.f, 50.f);
|
||||
glVertex3f(-50.f, -50.f, 50.f);
|
||||
glTexCoord3f(-50.f, 50.f, 50.f);
|
||||
glVertex3f(-50.f, 50.f, 50.f);
|
||||
glTexCoord3f(50.f, 50.f, 50.f);
|
||||
glVertex3f(50.f, 50.f, 50.f);
|
||||
glTexCoord3f(50.f, -50.f, 50.f);
|
||||
glVertex3f(50.f, -50.f, 50.f);
|
||||
glTexCoord3f(-d, -d, d);
|
||||
glVertex3f(-d, -d, d);
|
||||
glTexCoord3f(-d, d, d);
|
||||
glVertex3f(-d, d, d);
|
||||
glTexCoord3f(d, d, d);
|
||||
glVertex3f(d, d, d);
|
||||
glTexCoord3f(d, -d, d);
|
||||
glVertex3f(d, -d, d);
|
||||
|
||||
/* south */
|
||||
glTexCoord3f(-50.f, -50.f, -50.f);
|
||||
glVertex3f(-50.f, -50.f, -50.f);
|
||||
glTexCoord3f(50.f, -50.f, -50.f);
|
||||
glVertex3f(50.f, -50.f, -50.f);
|
||||
glTexCoord3f(50.f, 50.f, -50.f);
|
||||
glVertex3f(50.f, 50.f, -50.f);
|
||||
glTexCoord3f(-50.f, 50.f, -50.f);
|
||||
glVertex3f(-50.f, 50.f, -50.f);
|
||||
glTexCoord3f(-d, -d, -d);
|
||||
glVertex3f(-d, -d, -d);
|
||||
glTexCoord3f(d, -d, -d);
|
||||
glVertex3f(d, -d, -d);
|
||||
glTexCoord3f(d, d, -d);
|
||||
glVertex3f(d, d, -d);
|
||||
glTexCoord3f(-d, d, -d);
|
||||
glVertex3f(-d, d, -d);
|
||||
} glEnd();
|
||||
|
||||
#ifndef __EMSCRIPTEN__
|
||||
@ -517,9 +515,8 @@ void finally_render_skybox(DeferredCommandDrawSkybox command) {
|
||||
|
||||
#ifndef __EMSCRIPTEN__
|
||||
} glEndList();
|
||||
glCallList(list);
|
||||
} glCallList(list);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -589,8 +586,8 @@ void finally_draw_command(DeferredCommandDraw command) {
|
||||
|
||||
finally_use_texture_mode(command.texture_mode);
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, command.vertices.buffer);
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, command.element_buffer);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, command.vertices.buffer);
|
||||
|
||||
glEnableClientState(GL_VERTEX_ARRAY);
|
||||
glVertexPointer(command.vertices.arity,
|
||||
@ -633,20 +630,32 @@ void finally_draw_command(DeferredCommandDraw command) {
|
||||
textures_bind(&ctx.texture_cache, command.texture_key);
|
||||
}
|
||||
|
||||
GLenum geometry_mode;
|
||||
switch (command.geometry_mode) {
|
||||
case DEFERRED_COMMAND_DRAW_GEOMETRY_MODE_TRIANGLES:
|
||||
geometry_mode = GL_TRIANGLES;
|
||||
break;
|
||||
case DEFERRED_COMMAND_DRAW_GEOMETRY_MODE_LINES:
|
||||
geometry_mode = GL_LINES;
|
||||
break;
|
||||
default:
|
||||
SDL_assert(false);
|
||||
}
|
||||
|
||||
if (command.element_buffer) {
|
||||
SDL_assert(command.element_count != 0);
|
||||
if (command.range_start == command.range_end)
|
||||
glDrawElements(GL_TRIANGLES, command.element_count, GL_UNSIGNED_SHORT, NULL);
|
||||
glDrawElements(geometry_mode, command.element_count, GL_UNSIGNED_SHORT, NULL);
|
||||
else
|
||||
glDrawRangeElements(GL_TRIANGLES,
|
||||
glDrawRangeElements(geometry_mode,
|
||||
command.range_start,
|
||||
command.range_end,
|
||||
command.element_count,
|
||||
GL_UNSIGNED_SHORT,
|
||||
GL_UNSIGNED_INT,
|
||||
NULL);
|
||||
} else {
|
||||
SDL_assert(command.primitive_count != 0);
|
||||
glDrawArrays(GL_TRIANGLES, 0, command.primitive_count);
|
||||
glDrawArrays(geometry_mode, 0, command.primitive_count);
|
||||
}
|
||||
|
||||
/* state clearing */
|
||||
@ -665,13 +674,3 @@ void finally_draw_command(DeferredCommandDraw command) {
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
|
||||
}
|
||||
|
||||
void render_line(const LinePrimitive *line) {
|
||||
finally_use_2d_pipeline();
|
||||
glBegin(GL_LINES);
|
||||
glColor4ub(line->color.r, line->color.g, line->color.b, line->color.a);
|
||||
glVertex2f(line->start.x, line->start.y);
|
||||
glColor4ub(line->color.r, line->color.g, line->color.b, line->color.a);
|
||||
glVertex2f(line->finish.x, line->finish.y);
|
||||
glEnd();
|
||||
}
|
||||
|
@ -62,15 +62,15 @@ IndexBuffer get_quad_element_buffer(void) {
|
||||
/* TODO: use builder interface, not direct calls (glMapBuffer isn't portable) */
|
||||
if (buffer == 0) {
|
||||
buffer = create_index_buffer();
|
||||
IndexBufferBuilder builder = build_index_buffer(buffer, sizeof (GLshort) * QUAD_ELEMENT_BUFFER_LENGTH * 6 );
|
||||
IndexBufferBuilder builder = build_index_buffer(buffer, sizeof (GLuint) * QUAD_ELEMENT_BUFFER_LENGTH * 6 );
|
||||
|
||||
for (size_t i = 0; i < QUAD_ELEMENT_BUFFER_LENGTH; ++i) {
|
||||
((GLshort *)builder.base)[i * 6 + 0] = (GLshort)(i * 4 + 0);
|
||||
((GLshort *)builder.base)[i * 6 + 1] = (GLshort)(i * 4 + 1);
|
||||
((GLshort *)builder.base)[i * 6 + 2] = (GLshort)(i * 4 + 2);
|
||||
((GLshort *)builder.base)[i * 6 + 3] = (GLshort)(i * 4 + 2);
|
||||
((GLshort *)builder.base)[i * 6 + 4] = (GLshort)(i * 4 + 3);
|
||||
((GLshort *)builder.base)[i * 6 + 5] = (GLshort)(i * 4 + 0);
|
||||
((GLuint *)builder.base)[i * 6 + 0] = (GLuint)(i * 4 + 0);
|
||||
((GLuint *)builder.base)[i * 6 + 1] = (GLuint)(i * 4 + 1);
|
||||
((GLuint *)builder.base)[i * 6 + 2] = (GLuint)(i * 4 + 2);
|
||||
((GLuint *)builder.base)[i * 6 + 3] = (GLuint)(i * 4 + 2);
|
||||
((GLuint *)builder.base)[i * 6 + 4] = (GLuint)(i * 4 + 3);
|
||||
((GLuint *)builder.base)[i * 6 + 5] = (GLuint)(i * 4 + 0);
|
||||
}
|
||||
|
||||
finish_index_builder(&builder);
|
||||
@ -87,15 +87,15 @@ IndexBuffer get_circle_element_buffer(void) {
|
||||
|
||||
if (buffer == 0) {
|
||||
buffer = create_index_buffer();
|
||||
IndexBufferBuilder builder = build_index_buffer(buffer, sizeof (GLshort) * (CIRCLE_VERTICES_MAX - 2) * 3);
|
||||
IndexBufferBuilder builder = build_index_buffer(buffer, sizeof (GLint) * (CIRCLE_VERTICES_MAX - 2) * 3);
|
||||
|
||||
for (size_t i = 1; i < CIRCLE_VERTICES_MAX - 1; ++i) {
|
||||
/* first one is center point index, always zero */
|
||||
((GLshort *)builder.base)[(i - 1) * 3 + 0] = 0;
|
||||
((GLint *)builder.base)[(i - 1) * 3 + 0] = 0;
|
||||
|
||||
/* generated point index */
|
||||
((GLshort *)builder.base)[(i - 1) * 3 + 1] = (GLshort)i;
|
||||
((GLshort *)builder.base)[(i - 1) * 3 + 2] = (GLshort)i + 1;
|
||||
((GLint *)builder.base)[(i - 1) * 3 + 1] = (GLint)i;
|
||||
((GLint *)builder.base)[(i - 1) * 3 + 2] = (GLint)i + 1;
|
||||
}
|
||||
|
||||
finish_index_builder(&builder);
|
||||
@ -147,22 +147,27 @@ GPUTexture create_gpu_texture(TextureFilter filter, bool generate_mipmaps, int c
|
||||
SDL_assert(width > 0 && height > 0);
|
||||
SDL_assert(channels > 0 && channels <= 4);
|
||||
|
||||
/* TODO: test whether emscripten emulates this */
|
||||
#ifndef __EMSCRIPTEN__
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, (GLboolean)generate_mipmaps);
|
||||
if (generate_mipmaps) {
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, (GLboolean)generate_mipmaps);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 3);
|
||||
}
|
||||
#else
|
||||
(void)generate_mipmaps;
|
||||
#endif
|
||||
|
||||
if (filter == TEXTURE_FILTER_NEAREAST) {
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, generate_mipmaps ? GL_NEAREST_MIPMAP_LINEAR : GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
} else if (filter == TEXTURE_FILTER_LINEAR) {
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, generate_mipmaps ? GL_LINEAR_MIPMAP_NEAREST : GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
}
|
||||
|
||||
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
|
||||
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
|
||||
/* it's assumed to be default gl state, so, don't bother */
|
||||
// glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
|
||||
// glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
|
||||
|
||||
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
|
||||
|
||||
|
87
src/rendering/twn_lines.c
Normal file
87
src/rendering/twn_lines.c
Normal file
@ -0,0 +1,87 @@
|
||||
#include "twn_draw_c.h"
|
||||
#include "twn_draw.h"
|
||||
#include "twn_engine_context_c.h"
|
||||
#include "twn_util.h"
|
||||
|
||||
#include <stb_ds.h>
|
||||
|
||||
/* TODO: Support thickness */
|
||||
void draw_line(Vec2 start,
|
||||
Vec2 finish,
|
||||
float thickness,
|
||||
Color color)
|
||||
{
|
||||
if (fabsf(1.0f - thickness) >= 0.00001f)
|
||||
log_warn("Thickness isn't yet implemented for line drawing (got %f)", (double)thickness);
|
||||
|
||||
struct LineVertex const v0 = { .position = (Vec3){start.x, start.y, 0}, .color = color };
|
||||
struct LineVertex const v1 = { .position = (Vec3){finish.x, finish.y, 0}, .color = color };
|
||||
|
||||
/* combine with existing position if it's compatible */
|
||||
if (arrlenu(ctx.render_queue_2d) != 0) {
|
||||
Primitive2D *const primitive = &ctx.render_queue_2d[arrlenu(ctx.render_queue_2d) - 1];
|
||||
if (primitive->type == PRIMITIVE_2D_LINES && fabsf(primitive->line.thickness - thickness) < 0.00001f &&
|
||||
primitive->line.color.a == color.a && primitive->line.color.b == color.b &&
|
||||
primitive->line.color.r == color.r && primitive->line.color.g == color.g) {
|
||||
|
||||
arrput(primitive->line.vertices, v0);
|
||||
arrput(primitive->line.vertices, v1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
LinePrimitive line = {
|
||||
.thickness = thickness,
|
||||
.color = color,
|
||||
};
|
||||
|
||||
Primitive2D primitive = {
|
||||
.type = PRIMITIVE_2D_LINES,
|
||||
.line = line,
|
||||
};
|
||||
|
||||
arrput(primitive.line.vertices, v0);
|
||||
arrput(primitive.line.vertices, v1);
|
||||
arrput(ctx.render_queue_2d, primitive);
|
||||
}
|
||||
|
||||
|
||||
void render_lines(LinePrimitive *line) {
|
||||
DeferredCommandDraw command = {0};
|
||||
|
||||
VertexBuffer buffer = get_scratch_vertex_array();
|
||||
specify_vertex_buffer(buffer, line->vertices, arrlenu(line->vertices) * sizeof (*line->vertices));
|
||||
|
||||
command.vertices = (AttributeArrayPointer) {
|
||||
.arity = 3,
|
||||
.type = TWN_FLOAT,
|
||||
.stride = sizeof (struct LineVertex),
|
||||
.offset = offsetof (struct LineVertex, position),
|
||||
.buffer = buffer
|
||||
};
|
||||
|
||||
command.colors = (AttributeArrayPointer) {
|
||||
.arity = 4,
|
||||
.type = TWN_UNSIGNED_BYTE,
|
||||
.stride = sizeof (struct LineVertex),
|
||||
.offset = offsetof (struct LineVertex, color),
|
||||
.buffer = buffer
|
||||
};
|
||||
|
||||
command.primitive_count = arrlenu(line->vertices);
|
||||
|
||||
command.geometry_mode = DEFERRED_COMMAND_DRAW_GEOMETRY_MODE_LINES;
|
||||
command.pipeline = PIPELINE_2D;
|
||||
|
||||
command.depth_range_high = depth_range_high;
|
||||
command.depth_range_low = depth_range_low;
|
||||
|
||||
DeferredCommand final_command = {
|
||||
.type = DEFERRED_COMMAND_TYPE_DRAW,
|
||||
.draw = command
|
||||
};
|
||||
|
||||
/* TODO: should it be deleted here? */
|
||||
arrfree(line->vertices);
|
||||
arrpush(deferred_commands, final_command);
|
||||
}
|
@ -1,4 +1,8 @@
|
||||
#include "twn_draw_c.h"
|
||||
#include "twn_draw.h"
|
||||
#include "twn_engine_context_c.h"
|
||||
#include "twn_util.h"
|
||||
#include "twn_util_c.h"
|
||||
|
||||
#include <stb_ds.h>
|
||||
|
||||
@ -171,3 +175,146 @@ void push_quad_payload_to_vertex_buffer_builder(struct QuadBatch batch,
|
||||
((ElementIndexedQuadWithoutColorWithoutTexture *)builder->base)[index] = payload;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void draw_quad(char const *texture,
|
||||
Vec3 v0, /* upper-left */
|
||||
Vec3 v1, /* bottom-left */
|
||||
Vec3 v2, /* bottom-right */
|
||||
Vec3 v3, /* upper-right */
|
||||
Rect texture_region,
|
||||
Color color)
|
||||
{
|
||||
Vec2 const uv0 = { texture_region.x, texture_region.y };
|
||||
Vec2 const uv1 = { texture_region.x, texture_region.y + texture_region.h };
|
||||
Vec2 const uv2 = { texture_region.x + texture_region.w, texture_region.y + texture_region.h };
|
||||
Vec2 const uv3 = { texture_region.x + texture_region.w, texture_region.y };
|
||||
|
||||
// TODO: order drawing by atlas id as well, so that texture rebinding is not as common
|
||||
const TextureKey texture_key = textures_get_key(&ctx.texture_cache, texture);
|
||||
|
||||
struct MeshBatchItem *batch_p = hmgetp_null(ctx.quad_batches, texture_key);
|
||||
if (!batch_p) {
|
||||
struct MeshBatch item = {0};
|
||||
hmput(ctx.quad_batches, texture_key, item);
|
||||
batch_p = &ctx.quad_batches[hmlenu(ctx.quad_batches) - 1]; /* TODO: can last index be used? */
|
||||
}
|
||||
|
||||
/* TODO: rename from billboards */
|
||||
struct ElementIndexedBillboard billboard = {
|
||||
.v0 = v0,
|
||||
.v1 = v1,
|
||||
.v2 = v2,
|
||||
.v3 = v3,
|
||||
|
||||
/* will be adjusted later when atlas is baked */
|
||||
.uv0 = uv0,
|
||||
.uv1 = uv1,
|
||||
.uv2 = uv2,
|
||||
.uv3 = uv3,
|
||||
|
||||
/* flat shading is assumed, so we can skip setting the duplicates */
|
||||
.c0 = color,
|
||||
// .c1 = color,
|
||||
.c2 = color,
|
||||
// .c3 = color,
|
||||
};
|
||||
|
||||
struct ElementIndexedBillboard *billboards = (struct ElementIndexedBillboard *)(void *)batch_p->value.primitives;
|
||||
|
||||
arrpush(billboards, billboard);
|
||||
batch_p->value.primitives = (uint8_t *)billboards;
|
||||
}
|
||||
|
||||
|
||||
void finally_draw_space_quads_batch(const MeshBatch *batch,
|
||||
const TextureKey texture_key)
|
||||
{
|
||||
const size_t primitives_len = arrlenu(batch->primitives);
|
||||
|
||||
/* nothing to do */
|
||||
if (primitives_len == 0)
|
||||
return;
|
||||
|
||||
const Rect srcrect = textures_get_srcrect(&ctx.texture_cache, texture_key);
|
||||
const Rect dims = textures_get_dims(&ctx.texture_cache, texture_key);
|
||||
|
||||
const float wr = srcrect.w / dims.w;
|
||||
const float hr = srcrect.h / dims.h;
|
||||
const float xr = srcrect.x / dims.w;
|
||||
const float yr = srcrect.y / dims.h;
|
||||
|
||||
/* update pixel-based uvs to correspond with texture atlases */
|
||||
for (size_t i = 0; i < primitives_len; ++i) {
|
||||
ElementIndexedBillboard *payload =
|
||||
&((ElementIndexedBillboard *)(void *)batch->primitives)[i];
|
||||
|
||||
payload->uv0.x = xr + ((float)payload->uv0.x / srcrect.w) * wr;
|
||||
payload->uv0.y = yr + ((float)payload->uv0.y / srcrect.h) * hr;
|
||||
payload->uv1.x = xr + ((float)payload->uv1.x / srcrect.w) * wr;
|
||||
payload->uv1.y = yr + ((float)payload->uv1.y / srcrect.h) * hr;
|
||||
payload->uv2.x = xr + ((float)payload->uv2.x / srcrect.w) * wr;
|
||||
payload->uv2.y = yr + ((float)payload->uv2.y / srcrect.h) * hr;
|
||||
payload->uv3.x = xr + ((float)payload->uv3.x / srcrect.w) * wr;
|
||||
payload->uv3.y = yr + ((float)payload->uv3.y / srcrect.h) * hr;
|
||||
}
|
||||
|
||||
for (size_t batch_n = 0; batch_n <= (primitives_len - 1) / QUAD_ELEMENT_BUFFER_LENGTH; batch_n++) {
|
||||
size_t const processing = MIN(primitives_len - batch_n * QUAD_ELEMENT_BUFFER_LENGTH, QUAD_ELEMENT_BUFFER_LENGTH);
|
||||
|
||||
VertexBuffer const buffer = get_scratch_vertex_array();
|
||||
|
||||
specify_vertex_buffer(buffer, &batch->primitives[batch_n * QUAD_ELEMENT_BUFFER_LENGTH], sizeof (ElementIndexedBillboard) * processing);
|
||||
|
||||
DeferredCommandDraw command = {0};
|
||||
|
||||
command.vertices = (AttributeArrayPointer) {
|
||||
.arity = 3,
|
||||
.type = TWN_FLOAT,
|
||||
.stride = offsetof(ElementIndexedBillboard, v1),
|
||||
.offset = offsetof(ElementIndexedBillboard, v0),
|
||||
.buffer = buffer
|
||||
};
|
||||
|
||||
command.texcoords = (AttributeArrayPointer) {
|
||||
.arity = 2,
|
||||
.type = TWN_FLOAT,
|
||||
.stride = offsetof(ElementIndexedBillboard, v1),
|
||||
.offset = offsetof(ElementIndexedBillboard, uv0),
|
||||
.buffer = buffer
|
||||
};
|
||||
|
||||
command.colors = (AttributeArrayPointer) {
|
||||
.arity = 4,
|
||||
.type = TWN_UNSIGNED_BYTE,
|
||||
.stride = offsetof(ElementIndexedBillboard, v1),
|
||||
.offset = offsetof(ElementIndexedBillboard, c0),
|
||||
.buffer = buffer
|
||||
};
|
||||
|
||||
command.texture_key = texture_key;
|
||||
command.textured = true;
|
||||
|
||||
command.element_buffer = get_quad_element_buffer();
|
||||
command.element_count = 6 * (uint32_t)processing;
|
||||
command.range_end = 6 * (uint32_t)processing;
|
||||
|
||||
/* TODO: support alpha blended case, with distance sort */
|
||||
TextureMode mode = textures_get_mode(&ctx.texture_cache, texture_key);
|
||||
if (mode == TEXTURE_MODE_GHOSTLY)
|
||||
mode = TEXTURE_MODE_SEETHROUGH;
|
||||
|
||||
command.pipeline = PIPELINE_SPACE;
|
||||
command.texture_mode = mode;
|
||||
|
||||
command.depth_range_high = depth_range_high;
|
||||
command.depth_range_low = depth_range_low;
|
||||
|
||||
DeferredCommand final_command = {
|
||||
.type = DEFERRED_COMMAND_TYPE_DRAW,
|
||||
.draw = command
|
||||
};
|
||||
|
||||
arrpush(deferred_commands, final_command);
|
||||
}
|
||||
}
|
||||
|
@ -23,3 +23,4 @@
|
||||
#include "rendering/twn_triangles.c"
|
||||
#include "rendering/twn_billboards.c"
|
||||
#include "rendering/twn_models.c"
|
||||
#include "rendering/twn_lines.c"
|
||||
|
@ -6,7 +6,7 @@
|
||||
|
||||
|
||||
#define CAMERA_NEAR_Z 0.1f
|
||||
#define CAMERA_FAR_Z 100.0f
|
||||
// #define CAMERA_FAR_Z 400.0f
|
||||
|
||||
|
||||
Matrix4 camera_look_at(const Camera *const camera) {
|
||||
@ -42,13 +42,13 @@ Matrix4 camera_perspective(const Camera *const camera) {
|
||||
const float aspect = ((float)ctx.base_render_width / (float)ctx.base_render_height);
|
||||
|
||||
const float f = 1.0f / tanf(camera->fov * 0.5f);
|
||||
const float fn = 1.0f / (CAMERA_NEAR_Z - CAMERA_FAR_Z);
|
||||
const float fn = 1.0f / (CAMERA_NEAR_Z - camera->far_z);
|
||||
|
||||
result.row[0].x = f / aspect;
|
||||
result.row[1].y = f;
|
||||
result.row[2].z = (CAMERA_NEAR_Z + CAMERA_FAR_Z) * fn;
|
||||
result.row[2].z = (CAMERA_NEAR_Z + camera->far_z) * fn;
|
||||
result.row[2].w = -1.0f;
|
||||
result.row[3].z = 2.0f * CAMERA_NEAR_Z * CAMERA_FAR_Z * fn;
|
||||
result.row[3].z = 2.0f * CAMERA_NEAR_Z * camera->far_z * fn;
|
||||
|
||||
return result;
|
||||
}
|
||||
@ -61,14 +61,14 @@ Matrix4 camera_orthographic(const Camera *const camera) {
|
||||
|
||||
const float rl = 1.0f / (camera->viewbox[0].y - camera->viewbox[0].x);
|
||||
const float tb = 1.0f / (camera->viewbox[1].x - camera->viewbox[1].y);
|
||||
const float fn = -1.0f / (CAMERA_FAR_Z - -CAMERA_FAR_Z);
|
||||
const float fn = -1.0f / (camera->far_z - -camera->far_z);
|
||||
|
||||
result.row[0].x = 2.0f * rl;
|
||||
result.row[1].y = 2.0f * tb;
|
||||
result.row[2].z = 2.0f * fn;
|
||||
result.row[3].x = -(camera->viewbox[0].y + camera->viewbox[0].x) * rl;
|
||||
result.row[3].y = -(camera->viewbox[1].x + camera->viewbox[1].y) * tb;
|
||||
result.row[3].z = (CAMERA_FAR_Z + -CAMERA_FAR_Z) * fn;
|
||||
result.row[3].z = (camera->far_z + -camera->far_z) * fn;
|
||||
result.row[3].w = 1.0f;
|
||||
|
||||
return result;
|
||||
|
@ -12,8 +12,9 @@ typedef struct Camera {
|
||||
Vec3 pos; /* eye position */
|
||||
Vec3 target; /* normalized target vector */
|
||||
Vec3 up; /* normalized up vector */
|
||||
float fov; /* field of view, in radians */
|
||||
Vec2 viewbox[2]; /* othrographic aabb, ((left, right), (top, bottom)) */
|
||||
float fov; /* field of view, in radians */
|
||||
float far_z;
|
||||
} Camera;
|
||||
|
||||
Matrix4 camera_look_at(const Camera *camera);
|
||||
|
@ -5,7 +5,7 @@
|
||||
#include "twn_textures_c.h"
|
||||
#include "twn_audio_c.h"
|
||||
#include "twn_input_c.h"
|
||||
#include "twn_engine_api.h"
|
||||
#include "twn_api.h"
|
||||
#include "rendering/twn_draw_c.h"
|
||||
|
||||
#include <SDL2/SDL.h>
|
||||
@ -49,6 +49,7 @@ typedef struct EngineContext {
|
||||
Primitive2D *render_queue_2d;
|
||||
MeshBatchItem *uncolored_mesh_batches;
|
||||
MeshBatchItem *billboard_batches;
|
||||
MeshBatchItem *quad_batches;
|
||||
TextCache text_cache;
|
||||
TextureCache texture_cache;
|
||||
|
||||
|
@ -631,7 +631,7 @@ static bool initialize(void) {
|
||||
|
||||
input_state_init(&ctx.input);
|
||||
|
||||
ctx.render_double_buffered = true;
|
||||
ctx.render_double_buffered = false;
|
||||
ctx.window_mouse_resident = true;
|
||||
|
||||
ctx.game.fog_color = (Color){ 255, 255, 255, 255 }; /* TODO: pick some grey? */
|
||||
|
@ -1,7 +1,7 @@
|
||||
#ifndef TWN_LOOP_H
|
||||
#define TWN_LOOP_H
|
||||
|
||||
#include "twn_engine_api.h"
|
||||
#include "twn_api.h"
|
||||
|
||||
|
||||
TWN_API int enter_loop(int argc, char **argv);
|
||||
|
@ -25,4 +25,6 @@
|
||||
#include <stb_truetype.h>
|
||||
|
||||
#define STB_IMAGE_IMPLEMENTATION
|
||||
#define STBI_NO_STDIO
|
||||
#define STBI_NO_HDR
|
||||
#include <stb_image.h>
|
||||
|
@ -95,7 +95,7 @@ static SDL_Surface *gen_missing_texture_surface(void) {
|
||||
}
|
||||
|
||||
|
||||
SDL_Surface *textures_load_surface(const char *path) {
|
||||
SDL_Surface *textures_load_surface(const char *path, bool apply_border) {
|
||||
if (SDL_strncmp(path, "!", 1) == 0)
|
||||
goto GET_MISSING_TEXTURE;
|
||||
|
||||
@ -141,11 +141,59 @@ SDL_Surface *textures_load_surface(const char *path) {
|
||||
if (surface == NULL)
|
||||
goto ERR_CANNOT_CREATE_SURFACE;
|
||||
|
||||
/* TODO: investigate possibility of growing 1px border on stbi side, reducing the overhead (right now texture is held 2 times in memory) */
|
||||
/* use in atlases introduces seams on filtering, add 1px padding, growing the resulting */
|
||||
if (apply_border && (width < 2048 && height < 2048)) {
|
||||
SDL_Surface* border = SDL_CreateRGBSurface(0,
|
||||
width + TEXTURE_BORDER_REPEAT_SIZE * 2,
|
||||
height + TEXTURE_BORDER_REPEAT_SIZE * 2,
|
||||
channels * 8,
|
||||
rmask, gmask, bmask, amask);
|
||||
if (surface == NULL)
|
||||
goto ERR_CANNOT_CREATE_BORDER;
|
||||
|
||||
/* main portion */
|
||||
SDL_SoftStretch(surface,
|
||||
&(SDL_Rect){ .x = 0, .y = 0, .w = width, .h = height },
|
||||
border,
|
||||
&(SDL_Rect){ .x = TEXTURE_BORDER_REPEAT_SIZE, .y = TEXTURE_BORDER_REPEAT_SIZE, .w = width, .h = height });
|
||||
|
||||
/* left border */
|
||||
SDL_SoftStretch(surface,
|
||||
&(SDL_Rect){ .w = 1, .h = height },
|
||||
border,
|
||||
&(SDL_Rect){ .y = TEXTURE_BORDER_REPEAT_SIZE, .w = TEXTURE_BORDER_REPEAT_SIZE, .h = height });
|
||||
|
||||
/* right border */
|
||||
SDL_SoftStretch(surface,
|
||||
&(SDL_Rect){ .x = width - 1, .w = 1, .h = height },
|
||||
border,
|
||||
&(SDL_Rect){ .y = TEXTURE_BORDER_REPEAT_SIZE, .x = width + TEXTURE_BORDER_REPEAT_SIZE, .w = TEXTURE_BORDER_REPEAT_SIZE, .h = height });
|
||||
|
||||
/* up border */
|
||||
SDL_SoftStretch(surface,
|
||||
&(SDL_Rect){ .w = width, .h = 1 },
|
||||
border,
|
||||
&(SDL_Rect){ .x = TEXTURE_BORDER_REPEAT_SIZE, .w = width, .h = TEXTURE_BORDER_REPEAT_SIZE });
|
||||
|
||||
/* bottom border */
|
||||
SDL_SoftStretch(surface,
|
||||
&(SDL_Rect){ .y = height - 1, .w = width, .h = 1 },
|
||||
border,
|
||||
&(SDL_Rect){ .x = TEXTURE_BORDER_REPEAT_SIZE, .y = height + TEXTURE_BORDER_REPEAT_SIZE, .w = width, .h = TEXTURE_BORDER_REPEAT_SIZE });
|
||||
|
||||
stbi_image_free(image_mem);
|
||||
SDL_FreeSurface(surface);
|
||||
surface = border;
|
||||
}
|
||||
|
||||
SDL_SetSurfaceBlendMode(surface, SDL_BLENDMODE_NONE);
|
||||
SDL_SetSurfaceRLE(surface, true);
|
||||
|
||||
return surface;
|
||||
|
||||
ERR_CANNOT_CREATE_BORDER:
|
||||
SDL_FreeSurface(surface);
|
||||
ERR_CANNOT_CREATE_SURFACE:
|
||||
stbi_image_free(image_mem);
|
||||
|
||||
@ -194,7 +242,7 @@ static void add_new_atlas(TextureCache *cache) {
|
||||
/* TODO: create a PBO surface if possible, reducing duplication */
|
||||
SDL_Surface *new_atlas = create_surface((int)ctx.texture_atlas_size, (int)ctx.texture_atlas_size);
|
||||
arrput(cache->atlas_surfaces, new_atlas);
|
||||
arrput(cache->atlas_textures, create_gpu_texture(TEXTURE_FILTER_NEAREAST, true, 4, (int)ctx.texture_atlas_size, (int)ctx.texture_atlas_size));
|
||||
arrput(cache->atlas_textures, create_gpu_texture(TEXTURE_FILTER_NEAREAST, false, 4, (int)ctx.texture_atlas_size, (int)ctx.texture_atlas_size));
|
||||
}
|
||||
|
||||
|
||||
@ -361,7 +409,10 @@ void textures_cache_deinit(TextureCache *cache) {
|
||||
|
||||
/* free cache hashes */
|
||||
for (size_t i = 0; i < shlenu(cache->hash); ++i) {
|
||||
if (missing_texture_surface == NULL || cache->hash[i].value.data->pixels != missing_texture_surface->pixels)
|
||||
/* TODO: better to have field that stores the source of memory directly, ugh */
|
||||
if (cache->hash[i].value.srcrect.w < 2048 && cache->hash[i].value.srcrect.h < 2048)
|
||||
(void)0; /* do nothing, memory owned by surface */
|
||||
else if (missing_texture_surface == NULL || cache->hash[i].value.data->pixels != missing_texture_surface->pixels)
|
||||
stbi_image_free(cache->hash[i].value.data->pixels);
|
||||
else
|
||||
SDL_free(cache->hash[i].value.data->pixels);
|
||||
@ -427,7 +478,7 @@ bool textures_load_workers_thread(void) {
|
||||
|
||||
SDL_assert(texture_id != -1 && queue_index != -1);
|
||||
|
||||
SDL_Surface *const surface = textures_load_surface(path);
|
||||
SDL_Surface *const surface = textures_load_surface(path, true);
|
||||
SDL_free(path);
|
||||
|
||||
Texture const response = {
|
||||
@ -594,7 +645,15 @@ int32_t textures_get_atlas_id(const TextureCache *cache, TextureKey key) {
|
||||
|
||||
Rect textures_get_srcrect(const TextureCache *cache, TextureKey key) {
|
||||
if (m_texture_key_is_valid(key)) {
|
||||
return cache->hash[key.id].value.srcrect;
|
||||
Rect const srcrect = cache->hash[key.id].value.srcrect;
|
||||
if (srcrect.w >= 2048 || srcrect.h >= 2048)
|
||||
return srcrect;
|
||||
else
|
||||
/* offset to not include border*/
|
||||
return (Rect){ .x = srcrect.x + TEXTURE_BORDER_REPEAT_SIZE,
|
||||
.y = srcrect.y + TEXTURE_BORDER_REPEAT_SIZE,
|
||||
.w = srcrect.w - TEXTURE_BORDER_REPEAT_SIZE * 2,
|
||||
.h = srcrect.h - TEXTURE_BORDER_REPEAT_SIZE * 2 };
|
||||
} else {
|
||||
CRY("Texture lookup failed.",
|
||||
"Tried to get texture that isn't loaded.");
|
||||
|
@ -13,7 +13,7 @@
|
||||
#define TEXTURE_ATLAS_SIZE_DEFAULT 2048
|
||||
#define TEXTURE_ATLAS_BIT_DEPTH 32
|
||||
#define TEXTURE_ATLAS_FORMAT SDL_PIXELFORMAT_RGBA32
|
||||
|
||||
#define TEXTURE_BORDER_REPEAT_SIZE 8
|
||||
|
||||
/* alpha channel information */
|
||||
typedef enum TextureMode {
|
||||
@ -98,7 +98,7 @@ void textures_reset_state(void);
|
||||
|
||||
/* uncached low-level loading */
|
||||
/* warn: surface->pixels must be freed along side the surface itself */
|
||||
SDL_Surface *textures_load_surface(const char *path);
|
||||
SDL_Surface *textures_load_surface(const char *path, bool apply_border);
|
||||
|
||||
/* note: will only take an effect after `textures_update_atlas` */
|
||||
bool textures_load_workers_thread(void);
|
||||
|
Loading…
Reference in New Issue
Block a user