summaryrefslogtreecommitdiff
path: root/examples/corridor.c
blob: e0bb81eda3912246479c4f6578a772d8f0e90f33 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
/*
 * corridor: a small first person level, walls from a tilemap, sprites that face you, and collision on the same map
 *
 * build:  ./build.sh -d -m tile,font obj/corridor
 *         ./build.sh -d -m tile,font,assets,image obj/corridor   (also loads sprites/marker.png from the assets if there is one)
 * run:    obj/corridor
 *
 * click to grab the mouse and look around with it, tab lets go. w and s walk, a and d strafe, the arrows turn too
 * the walls are quads made from the tilemap every frame, the markers are billboards drawn with depth so walls hide them
 * the markers are plain placeholders, put your own sprite in assets/sprites/marker.png to see it in their place
 */

#include <stdio.h>
#include <math.h>

#include "konprogram.h"
#include "kontile.h"

#if !defined(KON_MODULE_TILE) || !defined(KON_MODULE_FONT)
#error "build with: ./build.sh -d -m tile,font obj/corridor"
#endif

#define COLUMNS 16
#define ROWS 16
#define TILE 32 /* map pixels in one world unit, the collision works in those */
#define WALL_HEIGHT 1.2f
#define MARKERS 5
#define LOOK_SPEED 0.003f

#define COL_SKY 0xFF1B1826u
#define COL_DIM 0xFF9A93B3u

static const char *level[ROWS] = {
	"################",
	"#..............#",
	"#.####....####.#",
	"#.#..........#.#",
	"#.#..........#.#",
	"#.#...####...#.#",
	"#.....#..#.....#",
	"#.....#..#.....#",
	"#.#...#..#...#.#",
	"#.#...####...#.#",
	"#.#..........#.#",
	"#.#..........#.#",
	"#.####....####.#",
	"#..............#",
	"#..............#",
	"################",
};

typedef struct state {
	kon_tilemap_t map;
	uint16_t tiles[COLUMNS * ROWS];
	uint8_t solid[2];

	kon_image *brick, *floor, *marker;
	int ownsMarker;
	kon_box2_t player; /* map pixels */
	float yaw, pitch;
	int forward, strafe, turn;
	double time;
	kon_vec2_t markers[MARKERS]; /* world units */
} state_t;

/*** images made in code ***/

static kon_image *makeBrick(void)
{
	kon_image *image = kon_createFramebuffer(32, 32);
	int row, i;

	if (!image) return NULL;
	kon_clearFramebuffer(image, 0xFF6B5F92);
	for (row = 0; row < 4; row++) {
		kon_fillRectangle(image, 0, row * 8, 32, 1, 0xFF3A3558);
		for (i = 0; i < 2; i++) kon_fillRectangle(image, (row % 2) * 8 + i * 16, row * 8, 1, 8, 0xFF3A3558);
	}
	return image;
}

static kon_image *makeFloor(void)
{
	kon_image *image = kon_createFramebuffer(32, 32);

	if (!image) return NULL;
	kon_clearFramebuffer(image, 0xFF2A2640);
	kon_fillRectangle(image, 0, 0, 16, 16, 0xFF332E4E);
	kon_fillRectangle(image, 16, 16, 16, 16, 0xFF332E4E);
	return image;
}

static kon_image *makeMarker(void)
{
	kon_image *image = kon_createFramebuffer(32, 40);
	int i;

	if (!image) return NULL;
	kon_clearFramebuffer(image, 0xFF6B5F92);
	kon_fillRectangle(image, 2, 2, 28, 36, 0xFF332E4E);
	for (i = 0; i < 28; i++) {
		kon_fillRectangle(image, 2 + i, 2 + i * 36 / 28, 1, 1, 0xFF6B5F92);
		kon_fillRectangle(image, 2 + i, 37 - i * 36 / 28, 1, 1, 0xFF6B5F92);
	}
	return image;
}

/*** drawing ***/

static void quad(kon_framebuffer_t *fb, kon_mat4_t vp, const kon_material_t *material, const kon_light_t *light,
		kon_vec3_t a, kon_vec3_t b, kon_vec3_t c, kon_vec3_t d, kon_vec3_t normal, kon_vec2_t uvA, kon_vec2_t uvC)
{
	kon_vertex_t va = { a, normal, uvA }, vb = { b, normal, KON_VEC2(uvC.x, uvA.y) };
	kon_vertex_t vc = { c, normal, uvC }, vd = { d, normal, KON_VEC2(uvA.x, uvC.y) };

	kon_drawTriangle3D(fb, vp, va, vb, vc, material, light, KON_RENDER_DEFAULT);
	kon_drawTriangle3D(fb, vp, va, vc, vd, material, light, KON_RENDER_DEFAULT);
}

/* a wall face seen from its open side: bottom left and bottom right are as the viewer sees them */
static void wall(kon_framebuffer_t *fb, kon_mat4_t vp, const kon_material_t *material, const kon_light_t *light,
		kon_vec3_t left, kon_vec3_t right, kon_vec3_t normal)
{
	kon_vec3_t topRight = KON_VEC3(right.x, WALL_HEIGHT, right.z), topLeft = KON_VEC3(left.x, WALL_HEIGHT, left.z);

	quad(fb, vp, material, light, left, right, topRight, topLeft, normal, KON_VEC2(0, WALL_HEIGHT), KON_VEC2(1, 0));
}

static int open(const state_t *state, int column, int row)
{
	return !state->solid[kon_tilemapAt(&state->map, column, row)];
}

static void draw(kon_program_t *program, kon_framebuffer_t *game, void *user)
{
	state_t *state = user;
	kon_camera_t camera = kon_cameraDefault();
	kon_light_t light = kon_lightDefault();
	kon_material_t brick = kon_materialTexture(state->brick), floor = kon_materialTexture(state->floor);
	kon_vec3_t eye = KON_VEC3((state->player.x + state->player.w * 0.5f) / TILE, 0.6f, (state->player.y + state->player.h * 0.5f) / TILE);
	kon_mat4_t vp;
	int row, column, i;
	float size = (float)COLUMNS;

	camera.position = eye;
	camera.target = KON_VEC3(eye.x + sinf(state->yaw) * cosf(state->pitch), eye.y + sinf(state->pitch), eye.z - cosf(state->yaw) * cosf(state->pitch));
	camera.nearZ = 0.05f;
	camera.farZ = 40.0f;
	vp = kon_cameraViewProjection(&camera, game->width, game->height);

	kon_clearFramebuffer(game, COL_SKY);
	light.direction = KON_VEC3(-0.3f, -1.0f, -0.5f);
	light.ambient = 0.55f;

	quad(game, vp, &floor, &light, KON_VEC3(0, 0, 0), KON_VEC3(0, 0, size), KON_VEC3(size, 0, size), KON_VEC3(size, 0, 0),
		KON_VEC3(0, 1, 0), KON_VEC2(0, 0), KON_VEC2(size, size));
	quad(game, vp, &floor, &light, KON_VEC3(0, WALL_HEIGHT, 0), KON_VEC3(size, WALL_HEIGHT, 0), KON_VEC3(size, WALL_HEIGHT, size), KON_VEC3(0, WALL_HEIGHT, size),
		KON_VEC3(0, -1, 0), KON_VEC2(0, 0), KON_VEC2(size, size));

	for (row = 0; row < ROWS; row++) {
		for (column = 0; column < COLUMNS; column++) {
			float x = (float)column, z = (float)row;

			if (open(state, column, row)) continue;
			if (open(state, column, row - 1)) wall(game, vp, &brick, &light, KON_VEC3(x + 1, 0, z), KON_VEC3(x, 0, z), KON_VEC3(0, 0, -1));
			if (open(state, column, row + 1)) wall(game, vp, &brick, &light, KON_VEC3(x, 0, z + 1), KON_VEC3(x + 1, 0, z + 1), KON_VEC3(0, 0, 1));
			if (open(state, column - 1, row)) wall(game, vp, &brick, &light, KON_VEC3(x, 0, z), KON_VEC3(x, 0, z + 1), KON_VEC3(-1, 0, 0));
			if (open(state, column + 1, row)) wall(game, vp, &brick, &light, KON_VEC3(x + 1, 0, z + 1), KON_VEC3(x + 1, 0, z), KON_VEC3(1, 0, 0));
		}
	}

	for (i = 0; i < MARKERS; i++) {
		float dx = state->markers[i].x - eye.x, dz = state->markers[i].y - eye.z;
		float bob = 0.08f * sinf((float)state->time * 2.0f + (float)i);
		float brightness = 1.0f / (1.0f + sqrtf(dx * dx + dz * dz) * 0.18f);

		kon_drawBillboard(game, vp, KON_VEC3(state->markers[i].x, 0.1f + bob, state->markers[i].y), 0.6f, 0.75f, state->marker, KON_RECT(0, 0, 0, 0), brightness, KON_RENDER_DEPTH);
	}

	if (program->font) kon_drawText(game, program->font, 8, 16, "click to look with the mouse, tab lets go", COL_DIM);
}

/*** program ***/

static int event(kon_program_t *program, const kon_event_t *ev, void *user)
{
	state_t *state = user;
	int down;

	if (ev->type == KON_EVENT_MOUSE_MOVE && kon_isMouseCaptured(program->window)) {
		state->yaw += (float)ev->mouseDX * LOOK_SPEED;
		state->pitch -= (float)ev->mouseDY * LOOK_SPEED;
		if (state->pitch > 1.2f) state->pitch = 1.2f;
		if (state->pitch < -1.2f) state->pitch = -1.2f;
		return 1;
	}
	if (ev->type == KON_EVENT_MOUSE_DOWN && !kon_isMouseCaptured(program->window)) {
		kon_setMouseCapture(program->window, true);
		return 1;
	}
	if (ev->type == KON_EVENT_KEY_DOWN && ev->key == KON_KEY_TAB) {
		kon_setMouseCapture(program->window, !kon_isMouseCaptured(program->window));
		return 1;
	}
	if (ev->type != KON_EVENT_KEY_DOWN && ev->type != KON_EVENT_KEY_UP) return 0;
	down = ev->type == KON_EVENT_KEY_DOWN;

	switch (ev->key) {
	case KON_KEY_W: case KON_KEY_UP: state->forward = down ? 1 : (state->forward > 0 ? 0 : state->forward); break;
	case KON_KEY_S: case KON_KEY_DOWN: state->forward = down ? -1 : (state->forward < 0 ? 0 : state->forward); break;
	case KON_KEY_D: state->strafe = down ? 1 : (state->strafe > 0 ? 0 : state->strafe); break;
	case KON_KEY_A: state->strafe = down ? -1 : (state->strafe < 0 ? 0 : state->strafe); break;
	case KON_KEY_RIGHT: state->turn = down ? 1 : (state->turn > 0 ? 0 : state->turn); break;
	case KON_KEY_LEFT: state->turn = down ? -1 : (state->turn < 0 ? 0 : state->turn); break;
	default: return 0;
	}
	return 1;
}

static void update(kon_program_t *program, double dt, void *user)
{
	state_t *state = user;
	float speed = 2.5f * (float)dt * TILE;
	float fx = sinf(state->yaw), fz = -cosf(state->yaw);
	float dx = (fx * (float)state->forward - fz * (float)state->strafe) * speed;
	float dz = (fz * (float)state->forward + fx * (float)state->strafe) * speed;

	/* the console needs the cursor back */
	if (kon_debugConsoleOpen(program->debug) && kon_isMouseCaptured(program->window)) kon_setMouseCapture(program->window, false);

	state->time += dt;
	state->yaw += (float)state->turn * 2.0f * (float)dt;
	kon_tilemapMove(&state->map, &state->player, dx, dz, state->solid, 2);
}

static int init(kon_program_t *program, void *user)
{
	state_t *state = user;
	static const kon_vec2_t spots[MARKERS] = { { 3.5f, 6.5f }, { 12.5f, 3.5f }, { 8.0f, 13.0f }, { 5.5f, 11.5f }, { 11.5f, 7.5f } };
	int row, column, i;

	(void)program;
	state->map.columns = COLUMNS;
	state->map.rows = ROWS;
	state->map.tileWidth = TILE;
	state->map.tileHeight = TILE;
	state->map.tiles = state->tiles;
	state->solid[1] = 1;
	for (row = 0; row < ROWS; row++) {
		for (column = 0; column < COLUMNS; column++) state->tiles[row * COLUMNS + column] = level[row][column] == '#';
	}

	state->brick = makeBrick();
	state->floor = makeFloor();
#if defined(KON_MODULE_ASSETS) && defined(KON_MODULE_IMAGE)
	if (program->assets) state->marker = kon_assetImage(program->assets, "sprites/marker.png");
#endif
	if (!state->marker) {
		state->marker = makeMarker();
		state->ownsMarker = 1;
	}
	if (!state->brick || !state->floor || !state->marker) return 0;

	state->player = KON_BOX2(1.5f * TILE, 13.5f * TILE, 12, 12);
	for (i = 0; i < MARKERS; i++) state->markers[i] = spots[i];
	return 1;
}

static void shutdown(kon_program_t *program, void *user)
{
	state_t *state = user;

	(void)program;
	kon_freeFramebuffer(state->brick);
	kon_freeFramebuffer(state->floor);
	if (state->ownsMarker) kon_freeFramebuffer(state->marker);
}

int main(void)
{
	state_t state = {0};
	kon_programConfig_t config = {0};

	config.title = "corridor";
	config.gameWidth = 480;
	config.gameHeight = 270;
	config.windowWidth = 960;
	config.windowHeight = 540;
	config.user = &state;
	config.init = init;
	config.event = event;
	config.update = update;
	config.draw = draw;
	config.shutdown = shutdown;
	return kon_programRun(&config);
}