initial CPC version (with some Azur refactoring)
|
@ -0,0 +1,13 @@
|
|||
# Build files
|
||||
/build-fx
|
||||
/build-cg
|
||||
/*.g1a
|
||||
/*.g3a
|
||||
|
||||
# Python bytecode
|
||||
__pycache__/
|
||||
|
||||
# Common IDE files
|
||||
*.sublime-project
|
||||
*.sublime-workspace
|
||||
.vscode
|
|
@ -0,0 +1,49 @@
|
|||
cmake_minimum_required(VERSION 3.15)
|
||||
project(Afterburner VERSION 1.0 LANGUAGES CXX C ASM)
|
||||
|
||||
include(GenerateG3A)
|
||||
include(Fxconv)
|
||||
|
||||
find_package(Azur 0.1 REQUIRED)
|
||||
find_package(Gint 2.8 REQUIRED)
|
||||
find_package(LibProf 2.1 REQUIRED)
|
||||
|
||||
set(SOURCES
|
||||
src/camera.cpp
|
||||
src/image.cpp
|
||||
src/main.cpp
|
||||
src/horizon.cpp
|
||||
src/horizon.s
|
||||
src/world.cpp
|
||||
)
|
||||
set(ASSETS
|
||||
assets-cg/plane.png
|
||||
assets-cg/square.png
|
||||
assets-cg/dot.png
|
||||
|
||||
assets-cg/tree_1.png
|
||||
assets-cg/tree_2.png
|
||||
assets-cg/tree_3.png
|
||||
assets-cg/mountain_1.png
|
||||
assets-cg/rock_1.png
|
||||
assets-cg/rock_2.png
|
||||
assets-cg/base_1.png
|
||||
assets-cg/base_2.png
|
||||
assets-cg/base_3.png
|
||||
assets-cg/minimap.png
|
||||
assets-cg/expl_1.png
|
||||
assets-cg/expl_2.png
|
||||
assets-cg/expl_3.png
|
||||
assets-cg/expl_4.png
|
||||
assets-cg/font.png
|
||||
)
|
||||
|
||||
fxconv_declare_assets(${ASSETS} WITH_METADATA)
|
||||
|
||||
add_executable(afterburner ${SOURCES} ${ASSETS})
|
||||
target_compile_options(afterburner PRIVATE -Wall -Wextra -Os -std=c++20)
|
||||
target_link_options(afterburner PRIVATE -Wl,-Map=map -Wl,--print-memory-usage)
|
||||
target_link_libraries(afterburner Azur::Azur -lnum LibProf::LibProf Gint::Gint)
|
||||
|
||||
generate_g3a(TARGET afterburner OUTPUT "AfterBur.g3a"
|
||||
NAME "Afterburner" ICONS assets-cg/icon-uns.png assets-cg/icon-sel.png)
|
|
@ -0,0 +1,7 @@
|
|||
TODO:
|
||||
* Title screen
|
||||
* Multiple games
|
||||
* Limit number of bombs
|
||||
* Fix map boundary tests
|
||||
* More interesting map generation
|
||||
* Expand gameplay: missions, enemies, etc
|
After Width: | Height: | Size: 949 B |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 589 B |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 19 KiB |
|
@ -0,0 +1,7 @@
|
|||
*.png:
|
||||
type: bopti-image
|
||||
name_regex: (.*)\.png img_\1
|
||||
profile: p8_rgb565a
|
||||
|
||||
minimap.png:
|
||||
profile: rgb565a
|
After Width: | Height: | Size: 19 KiB |
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 827 B |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 2.9 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 917 B |
After Width: | Height: | Size: 952 B |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 8.4 KiB |
|
@ -0,0 +1,188 @@
|
|||
#ifndef AFTERBURNER_H
|
||||
#define AFTERBURNER_H
|
||||
|
||||
#include <gint/display.h>
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
#include <num/num.h>
|
||||
#include <num/vec.h>
|
||||
#include <libprof.h>
|
||||
using namespace libnum;
|
||||
|
||||
//---
|
||||
// Horizon shader
|
||||
//---
|
||||
|
||||
/* Render the horizon line with a palette. The horizon line passes through
|
||||
(x,y) and has the specified angle. This is an Azur shader, the function only
|
||||
queues a command and delays the rendering to azrp_update(). */
|
||||
void ab_horizon(int x, int y, float alpha, uint16_t *palette);
|
||||
/* Configuration call, to be performed at every scale update */
|
||||
void ab_horizon_configure(void);
|
||||
|
||||
//---
|
||||
// Image utilities
|
||||
//---
|
||||
|
||||
/* Allocate a new image from a rotation + upscaling of a square image img.
|
||||
The source image must be RGB565A, P8_RGB565A or P4_RGB565A. */
|
||||
bopti_image_t *image_rotate_scale_square
|
||||
(bopti_image_t const *img, float alpha, num gamma);
|
||||
|
||||
/* Same but optimized for P8_RGB565A. *anchor_x and *anchor_y should be set to
|
||||
the coordinates of the "anchor" within the source image, and are updated to
|
||||
reflect the coordinates of that same spot in the rotated image. */
|
||||
bopti_image_t *image_rotate_scale_square_p8_rgb565a
|
||||
(bopti_image_t const *img,
|
||||
float alpha, num gamma,
|
||||
int *anchor_x, int *anchor_y);
|
||||
|
||||
/* An image with all upscaled variations from 0.125x to 2x. */
|
||||
class DynamicImage
|
||||
{
|
||||
public:
|
||||
DynamicImage(bopti_image_t const *source, float m_alpha, prof_t *prof_ctx,
|
||||
int anchor_x, int anchor_y);
|
||||
~DynamicImage();
|
||||
|
||||
/* Get the closest scaled version, generating it on demand. */
|
||||
bopti_image_t *getAtScale(num scale, int *anchor_x, int *anchor_y);
|
||||
|
||||
private:
|
||||
bopti_image_t *m_images[16];
|
||||
int m_anchor_x, m_anchor_y;
|
||||
int8_t m_ax[16], m_ay[16];
|
||||
|
||||
bopti_image_t const *m_source;
|
||||
float m_alpha;
|
||||
prof_t *m_prof;
|
||||
};
|
||||
|
||||
//---
|
||||
// 3D transforms and projections
|
||||
//---
|
||||
|
||||
struct plane
|
||||
{
|
||||
plane() {
|
||||
memset(this, 0, sizeof *this);
|
||||
}
|
||||
|
||||
/* Position in space */
|
||||
vec3 pos;
|
||||
/* Euler angles with aviation terminology */
|
||||
float roll, pitch, yaw;
|
||||
/* Air speed */
|
||||
num air_speed;
|
||||
|
||||
// The following are automatically set by plane_update()
|
||||
|
||||
/* Sine and cosine of every angle */
|
||||
num cos_r, sin_r, cos_p, sin_p, cos_y, sin_y;
|
||||
/* Vector looking forward and to the right */
|
||||
vec3 forward, right;
|
||||
/* Vector looking forward but parallel to the ground */
|
||||
vec3 forward_ground;
|
||||
};
|
||||
|
||||
struct mat3
|
||||
{
|
||||
num x11, x12, x13;
|
||||
num x21, x22, x23;
|
||||
num x31, x32, x33;
|
||||
};
|
||||
|
||||
mat3 operator * (mat3 const &A, mat3 const &B);
|
||||
vec3 operator * (mat3 const &M, vec3 const &u);
|
||||
num distance2(vec3 u, vec3 v);
|
||||
num64 distance2_64(vec3 u, vec3 v);
|
||||
|
||||
/* Update computationally-derived parameters of a plane */
|
||||
void plane_update(struct plane *plane);
|
||||
|
||||
/* Pixels per world unit (primitive control for FOV) */
|
||||
#define SCREEN_SCALING 200
|
||||
/* Minimum and maximum distance of visible objects */
|
||||
#define NEAR_CLIP_PLANE 8
|
||||
#define FAR_CLIP_PLANE 50
|
||||
#define SIDE_CLIP_PLANE 25
|
||||
/* Margin around the screen where a center point is considered visible */
|
||||
#define SCREEN_MARGIN 20
|
||||
|
||||
/* A 3D point with a handle to some sprite/information/etc. */
|
||||
struct ref_point
|
||||
{
|
||||
/* Position of the point in 3D space (evolves during transformations) */
|
||||
vec3 pos;
|
||||
/* Unique identifier (used to identify point properties in world map) */
|
||||
int id;
|
||||
};
|
||||
|
||||
void transform_world2camera(struct plane const *plane, vec3 camera_pos,
|
||||
struct ref_point *points, int size);
|
||||
|
||||
void transform_camera2screen(struct ref_point *points, int size);
|
||||
|
||||
//---
|
||||
// World map
|
||||
//---
|
||||
|
||||
/* Needs to be larger than FAR_CLIP_PLANE for culling optimisations to work */
|
||||
#define CHUNK_SIZE 64
|
||||
|
||||
struct chunk
|
||||
{
|
||||
struct ref_point *points;
|
||||
int count;
|
||||
};
|
||||
|
||||
struct world
|
||||
{
|
||||
int w, h;
|
||||
int chunks_x, chunks_y;
|
||||
struct chunk *chunks;
|
||||
};
|
||||
|
||||
/* Make a world structure using the specified set of points. The points are
|
||||
split into chunks, which allows faster lookup later on. The points are all
|
||||
copied, to the original array is safe to free after this call. */
|
||||
struct world *world_make(int w, int h, struct ref_point *points, int count);
|
||||
|
||||
/* Destroy a world structure and its copied points. */
|
||||
void world_destroy(struct world *w);
|
||||
|
||||
/* Select the points in the world that are "close" to the specified anchor.
|
||||
These are points from the anchor's chunk and up to 8 neighboring chunks. The
|
||||
size of the array is returned in *size. free() the array after use.
|
||||
|
||||
This function rotates chunks, so that for instance if position.x is 0.5 and
|
||||
forward.x < 0 (ie. we look at a region of space with x<0), chunks from the
|
||||
other side of the map are rotated in to provide a wrap-around effect.
|
||||
|
||||
extra_size entries are left uninitialized at the end of the array so that
|
||||
any dynamic sprites can be added. *size does *not* count extra_size. */
|
||||
struct ref_point *world_select(struct world const *w, vec3 position,
|
||||
vec3 forward, int *size, int extra_size);
|
||||
|
||||
//---
|
||||
// Game mechanics
|
||||
//---
|
||||
|
||||
/* Distance forward where bombs drop */
|
||||
#define BOMB_DISTANCE 15
|
||||
|
||||
struct objective
|
||||
{
|
||||
vec3 pos;
|
||||
bool destroyed;
|
||||
int type;
|
||||
};
|
||||
|
||||
struct explosion
|
||||
{
|
||||
vec3 pos;
|
||||
num time; /* if negative, no explosion */
|
||||
};
|
||||
|
||||
#endif /* AFTERBURNER_H */
|
|
@ -0,0 +1,112 @@
|
|||
#define __BSD_VISIBLE 1
|
||||
#include "afterburner.h"
|
||||
#include <azur/gint/render.h>
|
||||
#include <math.h>
|
||||
|
||||
mat3 operator *(mat3 const &A, mat3 const &B)
|
||||
{
|
||||
mat3 C;
|
||||
|
||||
C.x11 = A.x11 * B.x11 + A.x12 * B.x21 + A.x13 * B.x31;
|
||||
C.x12 = A.x11 * B.x12 + A.x12 * B.x22 + A.x13 * B.x32;
|
||||
C.x13 = A.x11 * B.x13 + A.x12 * B.x23 + A.x13 * B.x33;
|
||||
|
||||
C.x21 = A.x21 * B.x11 + A.x22 * B.x21 + A.x23 * B.x31;
|
||||
C.x22 = A.x21 * B.x12 + A.x22 * B.x22 + A.x23 * B.x32;
|
||||
C.x23 = A.x21 * B.x13 + A.x22 * B.x23 + A.x23 * B.x33;
|
||||
|
||||
C.x31 = A.x31 * B.x11 + A.x32 * B.x21 + A.x33 * B.x31;
|
||||
C.x32 = A.x31 * B.x12 + A.x32 * B.x22 + A.x33 * B.x32;
|
||||
C.x33 = A.x31 * B.x13 + A.x32 * B.x23 + A.x33 * B.x33;
|
||||
|
||||
return C;
|
||||
}
|
||||
|
||||
vec3 operator * (mat3 const &M, vec3 const &u)
|
||||
{
|
||||
vec3 v;
|
||||
|
||||
v.x = M.x11 * u.x + M.x12 * u.y + M.x13 * u.z;
|
||||
v.y = M.x21 * u.x + M.x22 * u.y + M.x23 * u.z;
|
||||
v.z = M.x31 * u.x + M.x32 * u.y + M.x33 * u.z;
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
num distance2(vec3 u, vec3 v)
|
||||
{
|
||||
u -= v;
|
||||
return u.x * u.x + u.y * u.y + u.z * u.z;
|
||||
}
|
||||
|
||||
num64 distance2_64(vec3 u, vec3 v)
|
||||
{
|
||||
u -= v;
|
||||
return num32::dmul(u.x,u.x) + num32::dmul(u.y,u.y) + num32::dmul(u.z,u.z);
|
||||
}
|
||||
|
||||
void plane_update(struct plane *plane)
|
||||
{
|
||||
float c, s;
|
||||
|
||||
sincosf(plane->roll, &s, &c);
|
||||
plane->sin_r = s;
|
||||
plane->cos_r = c;
|
||||
|
||||
sincosf(plane->pitch, &s, &c);
|
||||
plane->sin_p = s;
|
||||
plane->cos_p = c;
|
||||
|
||||
sincosf(plane->yaw, &s, &c);
|
||||
plane->sin_y = s;
|
||||
plane->cos_y = c;
|
||||
|
||||
plane->forward = vec3(
|
||||
-plane->sin_y * plane->cos_p,
|
||||
plane->cos_y * plane->cos_p,
|
||||
plane->sin_p);
|
||||
plane->forward_ground = vec3(-plane->sin_y, plane->cos_y, 0);
|
||||
plane->right = vec3(plane->cos_y, plane->sin_y, 0);
|
||||
}
|
||||
|
||||
void transform_world2camera(struct plane const *plane, vec3 camera_pos,
|
||||
struct ref_point *points, int size)
|
||||
{
|
||||
num cos_r = plane->cos_r;
|
||||
num sin_r = plane->sin_r;
|
||||
num cos_p = plane->cos_p;
|
||||
num sin_p = plane->sin_p;
|
||||
num cos_y = plane->cos_y;
|
||||
num sin_y = plane->sin_y;
|
||||
|
||||
mat3 m_roll = {
|
||||
cos_r, 0, sin_r,
|
||||
0, 1, 0,
|
||||
-sin_r, 0, cos_r,
|
||||
};
|
||||
mat3 m_pitch = {
|
||||
1, 0, 0,
|
||||
0, cos_p, +sin_p,
|
||||
0, -sin_p, cos_p,
|
||||
};
|
||||
mat3 m_yaw = {
|
||||
cos_y, +sin_y, 0,
|
||||
-sin_y, cos_y, 0,
|
||||
0, 0, 1,
|
||||
};
|
||||
|
||||
mat3 M = m_roll * (m_pitch * m_yaw);
|
||||
|
||||
for(int i = 0; i < size; i++)
|
||||
points[i].pos = M * (points[i].pos - camera_pos);
|
||||
}
|
||||
|
||||
void transform_camera2screen(struct ref_point *points, int size)
|
||||
{
|
||||
for(int i = 0; i < size; i++) {
|
||||
vec3 r = points[i].pos;
|
||||
r.x = num(azrp_width / 2) + r.x * SCREEN_SCALING / r.y;
|
||||
r.z = num(azrp_height / 2) - r.z * SCREEN_SCALING / r.y;
|
||||
points[i].pos = r;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
#include "afterburner.h"
|
||||
#include <azur/gint/render.h>
|
||||
#include <num/num.h>
|
||||
#include <math.h>
|
||||
using namespace libnum;
|
||||
|
||||
uint8_t AB_HORIZON_SHADER_ID = -1;
|
||||
|
||||
struct ab_horizon_cmd
|
||||
{
|
||||
/* Shader ID for Azur */
|
||||
uint8_t shader_id;
|
||||
uint8_t _[3];
|
||||
|
||||
/* Variation in value for a single move of +1x */
|
||||
num dx;
|
||||
/* Variation in value for a row move of -198x and +1y */
|
||||
num drow;
|
||||
/* Palette, supporting indexes 0...255 */
|
||||
uint16_t *palette;
|
||||
/* Current horizon value */
|
||||
num current;
|
||||
};
|
||||
|
||||
extern "C" {
|
||||
extern azrp_shader_t ab_horizon_shader;
|
||||
}
|
||||
|
||||
GCONSTRUCTOR
|
||||
static void register_shader(void)
|
||||
{
|
||||
AB_HORIZON_SHADER_ID = azrp_register_shader(ab_horizon_shader);
|
||||
}
|
||||
|
||||
void ab_horizon(int cx, int cy, float alpha, uint16_t *palette)
|
||||
{
|
||||
prof_enter(azrp_perf_cmdgen);
|
||||
|
||||
num sin_alpha = sinf(alpha);
|
||||
num cos_alpha = cosf(alpha);
|
||||
|
||||
num dx = -sin_alpha;
|
||||
num dy = -cos_alpha;
|
||||
|
||||
struct ab_horizon_cmd cmd;
|
||||
cmd.shader_id = AB_HORIZON_SHADER_ID;
|
||||
cmd.current = dx * -cx + dy *-cy;
|
||||
cmd.dx = dx;
|
||||
cmd.drow = -198*dx + dy;
|
||||
cmd.palette = palette;
|
||||
|
||||
azrp_queue_command(&cmd, sizeof cmd, 0, azrp_frag_count);
|
||||
prof_leave(azrp_perf_cmdgen);
|
||||
}
|
||||
|
||||
void ab_horizon_configure(void)
|
||||
{
|
||||
uint32_t value = (azrp_width << 16) | azrp_frag_height;
|
||||
azrp_set_uniforms(AB_HORIZON_SHADER_ID, (void *)value);
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
/* Horizon shader
|
||||
|
||||
r0: (temporary)
|
||||
r1: cmd.dx
|
||||
r2: cmd.drow
|
||||
r3: palette
|
||||
r4: azrp_width
|
||||
r5: cmd
|
||||
r6: azrp_frag
|
||||
r7: azrp_frag_height
|
||||
r8: cmd.current
|
||||
r9: a constant
|
||||
r10: mask for color index access
|
||||
*/
|
||||
|
||||
.global _ab_horizon_shader
|
||||
.balign 4
|
||||
|
||||
_ab_horizon_shader:
|
||||
/* r4: azrp_width (top 16 bits) | azrp_frag_height (bottom 16 bits)
|
||||
r5: struct ab_horizon_cmd *cmd
|
||||
r6: uint16_t *fragment */
|
||||
|
||||
mov.l r8, @-r15
|
||||
add #4, r5
|
||||
|
||||
mov.l r9, @-r15
|
||||
extu.w r4, r7
|
||||
|
||||
mov.l @r5+, r1 /* cmd.dx */
|
||||
shlr16 r4
|
||||
|
||||
mov.l @r5+, r2 /* cmd.drow */
|
||||
add #-2, r6
|
||||
|
||||
mov.l @r5+, r3 /* cmd.palette */
|
||||
nop
|
||||
|
||||
mov.l @r5, r8 /* num.current */
|
||||
nop
|
||||
|
||||
mov.l r10, @-r15 /* r10 = 0x1ff (511 colors) */
|
||||
mov #-1, r10
|
||||
|
||||
ldrs 1f
|
||||
mov #-23, r0
|
||||
|
||||
ldre 2f
|
||||
shld r0, r10
|
||||
|
||||
mov.l .round, r9
|
||||
nop
|
||||
|
||||
.row:
|
||||
ldrc r4
|
||||
nop
|
||||
|
||||
1: add #2, r6
|
||||
mov r8, r0
|
||||
|
||||
add r9, r0
|
||||
nop
|
||||
|
||||
shlr16 r0
|
||||
nop
|
||||
|
||||
and r10, r0
|
||||
nop
|
||||
|
||||
shll r0
|
||||
mov.w @(r0, r3), r0
|
||||
|
||||
mov.w r0, @r6
|
||||
2: add r1, r8
|
||||
|
||||
dt r7
|
||||
nop
|
||||
|
||||
bf.s .row
|
||||
add r2, r8
|
||||
|
||||
/* Update cmd.current for the next fragment */
|
||||
mov.l r8, @r5
|
||||
nop
|
||||
|
||||
mov.l @r15+, r10
|
||||
mov.l @r15+, r9
|
||||
rts
|
||||
mov.l @r15+, r8
|
||||
|
||||
.balign 4
|
||||
.round:
|
||||
/* Add 256 and also round up the next unit */
|
||||
.long 0x01008000
|
|
@ -0,0 +1,56 @@
|
|||
#include "afterburner.h"
|
||||
#include <gint/display.h>
|
||||
#include <gint/image.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
|
||||
//---
|
||||
// Dynamic images
|
||||
//---
|
||||
|
||||
DynamicImage::DynamicImage(bopti_image_t const *source, float alpha,
|
||||
prof_t *prof_ctx, int anchor_x, int anchor_y):
|
||||
m_anchor_x {anchor_x}, m_anchor_y {anchor_y}, m_source {source},
|
||||
m_alpha {alpha}, m_prof {prof_ctx}
|
||||
{
|
||||
for(int i = 0; i < 16; i++) {
|
||||
m_images[i] = NULL;
|
||||
m_ax[i] = -1;
|
||||
m_ay[i] = -1;
|
||||
}
|
||||
}
|
||||
|
||||
bopti_image_t *DynamicImage::getAtScale(num scale, int *ax, int *ay)
|
||||
{
|
||||
if(m_prof)
|
||||
prof_enter(*m_prof);
|
||||
|
||||
/* Round to the closest multiple of 0.125 */
|
||||
int i = (int)(8 * scale + num(0.5));
|
||||
if(i < 0) i = 0;
|
||||
if(i > 15) i = 15;
|
||||
|
||||
if(!m_images[i]) {
|
||||
int ax_i=m_anchor_x, ay_i=m_anchor_y;
|
||||
struct image_linear_map map;
|
||||
image_rotate_around_scale(m_source, m_alpha, scale.v, false, &ax_i,
|
||||
&ay_i, &map);
|
||||
m_images[i] = image_linear_alloc(m_source, &map);
|
||||
m_ax[i] = ax_i;
|
||||
m_ay[i] = ay_i;
|
||||
}
|
||||
|
||||
if(m_prof)
|
||||
prof_leave(*m_prof);
|
||||
|
||||
*ax = m_ax[i];
|
||||
*ay = m_ay[i];
|
||||
return m_images[i];
|
||||
}
|
||||
|
||||
DynamicImage::~DynamicImage()
|
||||
{
|
||||
for(int i = 0; i < 16; i++)
|
||||
image_free(m_images[i]);
|
||||
}
|
|
@ -0,0 +1,722 @@
|
|||
#include "afterburner.h"
|
||||
#include <azur/azur.h>
|
||||
#include <azur/gint/render.h>
|
||||
#include <gint/drivers/r61524.h>
|
||||
#include <gint/keyboard.h>
|
||||
#include <gint/usb.h>
|
||||
#include <gint/usb-ff-bulk.h>
|
||||
#include <gint/cpu.h>
|
||||
#include <gint/timer.h>
|
||||
#include <gint/kmalloc.h>
|
||||
#include <gint/rtc.h>
|
||||
#include <libprof.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
#include <fxlibc/printf.h>
|
||||
|
||||
static char logbuf[512];
|
||||
|
||||
GUNUSED static void logger(char const *fmt, ...)
|
||||
{
|
||||
if(!usb_is_open())
|
||||
return;
|
||||
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
|
||||
vsnprintf(logbuf, sizeof logbuf, fmt, args);
|
||||
va_end(args);
|
||||
|
||||
usb_fxlink_text(logbuf, 0);
|
||||
}
|
||||
|
||||
#define RGB24(hex) \
|
||||
(((hex & 0xf80000) >> 8) | \
|
||||
((hex & 0x00fc00) >> 5) | \
|
||||
((hex & 0x0000f8) >> 3))
|
||||
|
||||
/* Generate the gradient used for the horizon. This is an array of 512 colors,
|
||||
256 of which represent the ground, while the other 256 represent the sky. */
|
||||
static void generate_horizon_gradient(uint16_t *horizon_colors)
|
||||
{
|
||||
int hc = 0;
|
||||
|
||||
for(int i = 0; i < 256; i++) {
|
||||
horizon_colors[hc++] = RGB24(0x438747);
|
||||
}
|
||||
for(int i = 0; i < 32; i++) {
|
||||
int r = 0x68 + (0x3f - 0x68) * i / 32;
|
||||
int g = 0x8c + (0x66 - 0x8c) * i / 32;
|
||||
int b = 0xd6 + (0xb3 - 0xd6) * i / 32;
|
||||
horizon_colors[hc++] = RGB24(((r << 16) | (g << 8) | b));
|
||||
}
|
||||
for(int i = 0; i < 64; i++) {
|
||||
horizon_colors[hc++] = RGB24(0x3f66b3);
|
||||
};
|
||||
for(int i = 0; i < 64; i++) {
|
||||
int r = 0x3f + (0x0c - 0x3f) * i / 64;
|
||||
int g = 0x66 + (0x29 - 0x66) * i / 64;
|
||||
int b = 0xb3 + (0x61 - 0xb3) * i / 64;
|
||||
horizon_colors[hc++] = RGB24(((r << 16) | (g << 8) | b));
|
||||
}
|
||||
for(int i = 0; i < 96; i++) {
|
||||
horizon_colors[hc++] = RGB24(0x0c2961);
|
||||
}
|
||||
}
|
||||
|
||||
/* A plane controller that individually controls each direction.
|
||||
* Up/Down: move forward/backward (including pitch)
|
||||
* Left/Right: move left/right (ignoring roll; parallel to the ground)
|
||||
* SHIFT+Up/Down: pitch down/up
|
||||
* SHIFT+Left/Right: turn left/right (yaw)
|
||||
* ALPHA+Up/Down: move forward/backward (ignoring pitch)
|
||||
* ALPHA+Left/Right: roll */
|
||||
GUNUSED static void controller_debug(struct plane *plane, num dt)
|
||||
{
|
||||
int sh = keydown(KEY_SHIFT);
|
||||
int al = keydown(KEY_ALPHA);
|
||||
|
||||
float dt_f = (float)dt;
|
||||
|
||||
/* Movement speed for movement controlled here */
|
||||
num debug_speed = 6.0;
|
||||
|
||||
if(keydown(KEY_UP) && !sh && !al)
|
||||
plane->pos += plane->forward * debug_speed * dt;
|
||||
if(keydown(KEY_DOWN) && !sh && !al)
|
||||
plane->pos -= plane->forward * debug_speed * dt;
|
||||
if(keydown(KEY_RIGHT) && !sh && !al)
|
||||
plane->pos += plane->right * debug_speed * dt;
|
||||
if(keydown(KEY_LEFT) && !sh && !al)
|
||||
plane->pos -= plane->right * debug_speed * dt;
|
||||
|
||||
if(keydown(KEY_UP) && sh && !al)
|
||||
plane->pitch -= 0.3 * dt_f;
|
||||
if(keydown(KEY_DOWN) && sh && !al)
|
||||
plane->pitch += 0.3 * dt_f;
|
||||
if(keydown(KEY_RIGHT) && sh && !al)
|
||||
plane->yaw -= 0.5 * dt_f;
|
||||
if(keydown(KEY_LEFT) && sh && !al)
|
||||
plane->yaw += 0.5 * dt_f;
|
||||
|
||||
if(keydown(KEY_UP) && !sh && al)
|
||||
plane->pos += plane->forward_ground * debug_speed * dt;
|
||||
if(keydown(KEY_DOWN) && !sh && al)
|
||||
plane->pos -= plane->forward_ground * debug_speed * dt;
|
||||
if(keydown(KEY_LEFT) && !sh && al)
|
||||
plane->roll += 0.45 * dt_f;
|
||||
if(keydown(KEY_RIGHT) && !sh && al)
|
||||
plane->roll -= 0.45 * dt_f;
|
||||
|
||||
if(keydown(KEY_MINUS))
|
||||
plane->pos.z -= 2.0 * dt;
|
||||
if(keydown(KEY_PLUS))
|
||||
plane->pos.z += 2.0 * dt;
|
||||
}
|
||||
|
||||
/* A simple plane controller for players.
|
||||
* Up/Down: pitch down/up
|
||||
* Left/Right: turn left/right (yaw)
|
||||
* SHIFT/ALPHA: accelerate/decelerate
|
||||
TODO: Automatic roll effect when turning */
|
||||
static void controller_simple(struct plane *plane, num dt)
|
||||
{
|
||||
float dt_f = (float)dt;
|
||||
|
||||
if(keydown(KEY_UP))
|
||||
plane->pitch -= 0.3 * dt_f;
|
||||
if(keydown(KEY_DOWN))
|
||||
plane->pitch += 0.3 * dt_f;
|
||||
|
||||
if(keydown(KEY_RIGHT))
|
||||
plane->yaw -= 0.5 * dt_f;
|
||||
if(keydown(KEY_LEFT))
|
||||
plane->yaw += 0.5 * dt_f;
|
||||
|
||||
if(keydown(KEY_SHIFT))
|
||||
plane->air_speed += num(4.0) * dt;
|
||||
if(keydown(KEY_ALPHA))
|
||||
plane->air_speed -= num(4.0) * dt;
|
||||
|
||||
/* Lock roll to player turning, for more immersion and visual effects
|
||||
while keeping very simple controls */
|
||||
float target_roll = 0;
|
||||
if(keydown(KEY_LEFT))
|
||||
target_roll = 0.5;
|
||||
if(keydown(KEY_RIGHT))
|
||||
target_roll = -0.5;
|
||||
|
||||
if(plane->roll < target_roll) {
|
||||
plane->roll += 0.3 * dt_f;
|
||||
if(plane->roll > target_roll)
|
||||
plane->roll = target_roll;
|
||||
}
|
||||
if(plane->roll > target_roll) {
|
||||
plane->roll -= 0.3 * dt_f;
|
||||
if(plane->roll < target_roll)
|
||||
plane->roll = target_roll;
|
||||
}
|
||||
}
|
||||
|
||||
/* Sort to have closer objects last */
|
||||
static int compare_depth(void const *p1, void const *p2)
|
||||
{
|
||||
struct ref_point const *u = (struct ref_point const *)p1;
|
||||
struct ref_point const *v = (struct ref_point const *)p2;
|
||||
/* Return v < u */
|
||||
return v->pos.y.v - u->pos.y.v;
|
||||
}
|
||||
|
||||
#define remap(point, _x, _y) { \
|
||||
*(_x) = wx + (int)((point).x) * ww / world->w; \
|
||||
*(_y) = wy + wh-1 - (int)((point).y) * wh / world->h; \
|
||||
}
|
||||
|
||||
/* Generate the minimap as a bopti image (Azur can't easily draw like in the
|
||||
VRAM, and I don't have time to write a full shader for that) */
|
||||
static bopti_image_t *generate_minimap(struct world const *world,
|
||||
struct plane const *plane, struct objective *objectives,
|
||||
int objective_count)
|
||||
{
|
||||
extern bopti_image_t img_minimap;
|
||||
|
||||
/* Duplicate the empty minimap (which is full RGB565) */
|
||||
image_t *img = image_copy_alloc(&img_minimap, IMAGE_RGB565A);
|
||||
|
||||
/* Location of world map within image */
|
||||
int wx=9, wy=9, ww=30, wh=30, x, y;
|
||||
|
||||
for(int i = 0; i < objective_count; i++) {
|
||||
remap(objectives[i].pos, &x, &y);
|
||||
int color = objectives[i].destroyed ? C_RGB(15,3,31) : C_RGB(31,2,5);
|
||||
image_set_pixel(img, x, y, color);
|
||||
}
|
||||
|
||||
for(int i = 0; i < 32; i++) {
|
||||
remap(plane->pos + num(i) * plane->forward, &x, &y);
|
||||
image_set_pixel(img, x, y, C_RGB(0, 18, 4));
|
||||
}
|
||||
|
||||
remap(plane->pos, &x, &y);
|
||||
image_set_pixel(img, x, y, C_RGB(0, 31, 10));
|
||||
|
||||
return img;
|
||||
}
|
||||
|
||||
/* Draw player's plane at (x,y) */
|
||||
static void draw_player(int x, int y, struct plane *plane,
|
||||
bool orient_with_angles, bool orient_with_keyboard)
|
||||
{
|
||||
/* Rows: 25 (anchor 17), 27 (anchor 20), 26 (anchor 21)
|
||||
Columns: all 48 (anchor 23) */
|
||||
extern bopti_image_t img_plane;
|
||||
|
||||
int row = 1, col = 1;
|
||||
if(orient_with_angles) {
|
||||
if(plane->pitch > 0.08)
|
||||
row = 0;
|
||||
if(plane->pitch < -0.08)
|
||||
row = 2;
|
||||
if(plane->roll > 0.1)
|
||||
col = 0;
|
||||
if(plane->roll < -0.1)
|
||||
col = 2;
|
||||
}
|
||||
if(orient_with_keyboard) {
|
||||
if(keydown(KEY_DOWN))
|
||||
row = 0;
|
||||
if(keydown(KEY_UP))
|
||||
row = 2;
|
||||
if(keydown(KEY_LEFT))
|
||||
col = 0;
|
||||
if(keydown(KEY_RIGHT))
|
||||
col = 2;
|
||||
}
|
||||
|
||||
int top=0, height=0, ay=0;
|
||||
if(row == 0) top = 0, height = 25, ay = 17;
|
||||
if(row == 1) top = 25, height = 27, ay = 20;
|
||||
if(row == 2) top = 52, height = 26, ay = 21;
|
||||
|
||||
azrp_subimage(x-23, y-ay, &img_plane, 48 * col, top, 48, height,
|
||||
DIMAGE_NONE);
|
||||
}
|
||||
|
||||
/* Distance between two points, with wrap-around */
|
||||
num64 distance2_wrap_64(struct world const *world, vec3 u, vec3 v)
|
||||
{
|
||||
num dx = u.x - v.x;
|
||||
num dy = u.y - v.y;
|
||||
num dz = u.z - v.z;
|
||||
|
||||
/* Choose the closest x distance possible */
|
||||
if(dx < num(-world->w))
|
||||
dx += num(world->w);
|
||||
if(dx > num(world->w))
|
||||
dx -= num(world->w);
|
||||
/* Choose the closest y distance possible */
|
||||
if(dy < num(-world->h))
|
||||
dy += num(world->h);
|
||||
if(dy > num(world->h))
|
||||
dy -= num(world->h);
|
||||
|
||||
return num32::dmul(dx, dx) + num32::dmul(dy, dy) + num32::dmul(dz, dz);
|
||||
}
|
||||
|
||||
/* Render text with Azur images - quite bad, but I don't have time lol. */
|
||||
static void draw_text(int x, int y, char const *fmt, ...)
|
||||
{
|
||||
char str[128];
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
vsnprintf(str, 128, fmt, args);
|
||||
va_end(args);
|
||||
|
||||
extern bopti_image_t img_font;
|
||||
|
||||
for(int i = 0; str[i]; i++) {
|
||||
if(str[i] < 32 || str[i] >= 0x7f) continue;
|
||||
|
||||
int row = (str[i] - 32) >> 4;
|
||||
int col = (str[i] - 32) & 15;
|
||||
azrp_subimage(x + 5 * i, y, &img_font, 7 * col + 1, 9 * row + 1, 6, 8,
|
||||
DIMAGE_NONE);
|
||||
}
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
azrp_config_scale(2);
|
||||
azrp_shader_clear_configure();
|
||||
azrp_shader_image_rgb16_configure();
|
||||
azrp_shader_image_p8_configure();
|
||||
azrp_shader_image_p4_configure();
|
||||
ab_horizon_configure();
|
||||
|
||||
__printf_enable_fp();
|
||||
|
||||
srand(rtc_ticks());
|
||||
|
||||
prof_init();
|
||||
|
||||
usb_interface_t const *intf[] = { &usb_ff_bulk, NULL };
|
||||
usb_open(intf, GINT_CALL_NULL);
|
||||
|
||||
int volatile timer_flag = 1;
|
||||
int timer = timer_configure(TIMER_ANY, 33000, GINT_CALL_SET(&timer_flag));
|
||||
timer_start(timer);
|
||||
|
||||
bool lost = false;
|
||||
|
||||
//---
|
||||
// Horizon colors
|
||||
//---
|
||||
|
||||
uint16_t horizon_colors[512];
|
||||
generate_horizon_gradient(horizon_colors);
|
||||
|
||||
//---
|
||||
// World map
|
||||
//---
|
||||
|
||||
/* Stuff starts to blow up at the density of 1/16 sprite/unit² (4096
|
||||
sprites for a 256x256 world): even with chunk-based selection, we end up
|
||||
with > 2000 points to consider each frame, which makes the 3D transform
|
||||
blow up to 16-20 ms per frame. That's the obvious next optimization. */
|
||||
int point_count = 2048;
|
||||
struct ref_point *points = (struct ref_point *)
|
||||
malloc(point_count * sizeof *points);
|
||||
|
||||
/* We give 12 bases to destroy */
|
||||
struct objective objectives[12];
|
||||
int objective_count = 12;
|
||||
|
||||
/* We allow 3 explosions at the same time */
|
||||
struct explosion explosions[3];
|
||||
int explosion_count = 3;
|
||||
|
||||
for(int i = 0; i < explosion_count; i++) {
|
||||
explosions[i].pos = vec3();
|
||||
explosions[i].time = num(-1);
|
||||
}
|
||||
|
||||
for(int i = 0; i < point_count; i++) {
|
||||
num x, y;
|
||||
x.v = rand() % num(256).v;
|
||||
y.v = rand() % num(256).v;
|
||||
points[i].pos = vec3(x, y, 0);
|
||||
|
||||
if(i < objective_count) {
|
||||
objectives[i].pos = points[i].pos;
|
||||
objectives[i].destroyed = false;
|
||||
objectives[i].type = rand() % 3;
|
||||
points[i].id = 100 + i;
|
||||
}
|
||||
else {
|
||||
points[i].id = rand() % 6;
|
||||
}
|
||||
}
|
||||
|
||||
struct world *world = world_make(256, 256, points, point_count);
|
||||
if(!world) return 0;
|
||||
|
||||
//---
|
||||
// Cached explosion images
|
||||
//---
|
||||
|
||||
prof_t perf_scaling;
|
||||
|
||||
/* Unlike normal sprites, we don't rotate exlosions, so we can keep them
|
||||
cached for the whole execution */
|
||||
extern bopti_image_t img_expl_1, img_expl_2, img_expl_3, img_expl_4;
|
||||
DynamicImage dyn_expl_1(&img_expl_1, 0.0, &perf_scaling, 24, 24);
|
||||
DynamicImage dyn_expl_2(&img_expl_2, 0.0, &perf_scaling, 24, 24);
|
||||
DynamicImage dyn_expl_3(&img_expl_3, 0.0, &perf_scaling, 24, 24);
|
||||
DynamicImage dyn_expl_4(&img_expl_4, 0.0, &perf_scaling, 31, 31);
|
||||
|
||||
DynamicImage *dyn_expl[4] = {
|
||||
&dyn_expl_1, &dyn_expl_2, &dyn_expl_3, &dyn_expl_4,
|
||||
};
|
||||
|
||||
//---
|
||||
// HUD and messages
|
||||
//---
|
||||
|
||||
char const *message = NULL;
|
||||
num message_time = num(0.0);
|
||||
|
||||
//---
|
||||
// Camera
|
||||
//---
|
||||
|
||||
struct plane plane;
|
||||
plane.pos = vec3(world->w / 2, world->h / 2, 5);
|
||||
plane.roll = 0.0f;
|
||||
plane.pitch = 0.0f;
|
||||
plane.yaw = 0.0f;
|
||||
plane.air_speed = 12.0;
|
||||
|
||||
int plane_screen_x = 198/2;
|
||||
int plane_screen_y = 112/2;
|
||||
|
||||
while(1) {
|
||||
/* Wait until a new frame to cap at the desired FPS */
|
||||
while(!timer_flag) sleep();
|
||||
|
||||
//--- Total frame time
|
||||
prof_t perf_frame = prof_make();
|
||||
prof_enter(perf_frame);
|
||||
|
||||
/* Time difference for this frame (assumes consistant FPS) */
|
||||
num dt = 33e-3;
|
||||
|
||||
//--- Physics/modeling time (split in two parts)
|
||||
prof_t perf_physics = prof_make();
|
||||
prof_enter(perf_physics);
|
||||
|
||||
plane_update(&plane);
|
||||
vec3 camera_pos = plane.pos - num(10) * plane.forward;
|
||||
|
||||
/* Compute horizon reference */
|
||||
struct ref_point far_forward;
|
||||
far_forward.pos = plane.pos + plane.forward * num(FAR_CLIP_PLANE);
|
||||
far_forward.pos.z = 0;
|
||||
transform_world2camera(&plane, camera_pos, &far_forward, 1);
|
||||
transform_camera2screen(&far_forward, 1);
|
||||
|
||||
int visible_explosions = 0;
|
||||
for(int i = 0; i < explosion_count; i++)
|
||||
visible_explosions += (explosions[i].time >= num(0));
|
||||
|
||||
/* Gather world points in chunks close to the plane */
|
||||
int sprite_count;
|
||||
struct ref_point *sprites = world_select(world, plane.pos,
|
||||
plane.forward, &sprite_count, 1 + visible_explosions);
|
||||
|
||||
/* Extra sprite for the target under the plane */
|
||||
sprites[sprite_count].pos =
|
||||
plane.pos + num(BOMB_DISTANCE) * plane.forward;
|
||||
sprites[sprite_count].pos.z = 0;
|
||||
sprites[sprite_count].id = -1;
|
||||
sprite_count++;
|
||||
|
||||
/* Extra sprites for visible explosions */
|
||||
for(int i = 0; i < explosion_count; i++) {
|
||||
if(explosions[i].time < num(0))
|
||||
continue;
|
||||
|
||||
int n = (int)(explosions[i].time * 6) % 4;
|
||||
sprites[sprite_count].pos = explosions[i].pos;
|
||||
sprites[sprite_count].id = 9 + n;
|
||||
sprite_count++;
|
||||
}
|
||||
|
||||
transform_world2camera(&plane, camera_pos, sprites, sprite_count);
|
||||
|
||||
/* Prune sprites that are definitely not visible */
|
||||
int close_point_count = 0;
|
||||
for(int i = 0; i < sprite_count; i++) {
|
||||
if(sprites[i].pos.y < num(NEAR_CLIP_PLANE) ||
|
||||
sprites[i].pos.y >= num(FAR_CLIP_PLANE) ||
|
||||
abs((int)sprites[i].pos.x) >= SIDE_CLIP_PLANE)
|
||||
continue;
|
||||
sprites[close_point_count++] = sprites[i];
|
||||
}
|
||||
|
||||
transform_camera2screen(sprites, close_point_count);
|
||||
|
||||
/* Prune points that are also too far awar from the screen */
|
||||
int visible_point_count = 0;
|
||||
for(int i = 0; i < close_point_count; i++) {
|
||||
int x = (int)sprites[i].pos.x;
|
||||
int z = (int)sprites[i].pos.z;
|
||||
if(x < -SCREEN_MARGIN || x >= azrp_width + SCREEN_MARGIN
|
||||
|| z < -SCREEN_MARGIN || z >= azrp_height + SCREEN_MARGIN)
|
||||
continue;
|
||||
sprites[visible_point_count++] = sprites[i];
|
||||
}
|
||||
|
||||
/* Sort sprites furthest to closest */
|
||||
qsort(sprites, visible_point_count, sizeof *sprites, compare_depth);
|
||||
|
||||
prof_leave(perf_physics);
|
||||
|
||||
extern bopti_image_t img_dot;
|
||||
extern bopti_image_t img_tree_1, img_tree_2, img_tree_3;
|
||||
extern bopti_image_t img_mountain_1;
|
||||
extern bopti_image_t img_rock_1, img_rock_2;
|
||||
extern bopti_image_t img_base_1, img_base_2, img_base_3;
|
||||
|
||||
/* Resize images for all visible sprites */
|
||||
perf_scaling = prof_make();
|
||||
|
||||
DynamicImage dyn_tree_1(&img_tree_1,
|
||||
-plane.roll, &perf_scaling, 16, 31);
|
||||
DynamicImage dyn_tree_2(&img_tree_2,
|
||||
-plane.roll, &perf_scaling, 16, 31);
|
||||
DynamicImage dyn_tree_3(&img_tree_3,
|
||||
-plane.roll, &perf_scaling, 16, 31);
|
||||
DynamicImage dyn_mountain_1(&img_mountain_1,
|
||||
-plane.roll, &perf_scaling, 24, 34);
|
||||
DynamicImage dyn_rock_1(&img_rock_1,
|
||||
-plane.roll, &perf_scaling, 15, 20);
|
||||
DynamicImage dyn_rock_2(&img_rock_2,
|
||||
-plane.roll, &perf_scaling, 11, 15);
|
||||
DynamicImage dyn_base_1(&img_base_1,
|
||||
-plane.roll, &perf_scaling, 16, 20);
|
||||
DynamicImage dyn_base_2(&img_base_2,
|
||||
-plane.roll, &perf_scaling, 16, 20);
|
||||
DynamicImage dyn_base_3(&img_base_3,
|
||||
-plane.roll, &perf_scaling, 16, 20);
|
||||
|
||||
DynamicImage *dyn_images[9] = {
|
||||
&dyn_tree_1, &dyn_tree_2, &dyn_tree_3,
|
||||
&dyn_mountain_1,
|
||||
&dyn_rock_1, &dyn_rock_2,
|
||||
&dyn_base_1, &dyn_base_2, &dyn_base_3,
|
||||
};
|
||||
|
||||
/* Generate the minimap */
|
||||
image_t *minimap = generate_minimap(world, &plane, objectives,
|
||||
objective_count);
|
||||
|
||||
//---
|
||||
|
||||
azrp_perf_clear();
|
||||
ab_horizon((int)far_forward.pos.x, (int)far_forward.pos.z, -plane.roll,
|
||||
horizon_colors);
|
||||
|
||||
for(int i = 0; i < visible_point_count; i++) {
|
||||
int ax, ay;
|
||||
|
||||
num scale = 15.0;
|
||||
/* Make targeted buildings larger */
|
||||
if(sprites[i].id >= 6 && sprites[i].id < 9)
|
||||
scale = 25.0;
|
||||
scale = scale / sprites[i].pos.y;
|
||||
|
||||
bopti_image_t *img = NULL;
|
||||
if(sprites[i].id == -1) {
|
||||
img = &img_dot;
|
||||
ax = 2;
|
||||
ay = 2;
|
||||
}
|
||||
else if(sprites[i].id < 6) {
|
||||
img = dyn_images[sprites[i].id]->getAtScale(scale,&ax,&ay);
|
||||
}
|
||||
else if(sprites[i].id >= 9 && sprites[i].id < 13) {
|
||||
img = dyn_expl[sprites[i].id - 9]->getAtScale(scale, &ax, &ay);
|
||||
}
|
||||
else if(sprites[i].id >= 100) {
|
||||
struct objective *o = &objectives[sprites[i].id - 100];
|
||||
if(!o->destroyed)
|
||||
img = dyn_images[6+o->type]->getAtScale(scale,&ax,&ay);
|
||||
}
|
||||
|
||||
if(img) azrp_image((int)sprites[i].pos.x - ax,
|
||||
(int)sprites[i].pos.z - ay, img);
|
||||
}
|
||||
if(!lost)
|
||||
draw_player(plane_screen_x, plane_screen_y, &plane, false, !lost);
|
||||
if(message)
|
||||
draw_text(2, 2, "%s", message);
|
||||
else
|
||||
draw_text(2, 2, "Speed:%d", (int)(plane.air_speed * 40));
|
||||
azrp_image(azrp_width - minimap->width - 2, 2, minimap);
|
||||
|
||||
/* Shows a dot the forward direction on the ground */
|
||||
// azrp_image((int)far_forward.x-2, (int)far_forward.z-2, &img_dot);
|
||||
azrp_update();
|
||||
|
||||
free(sprites);
|
||||
image_free(minimap);
|
||||
|
||||
//---
|
||||
// Event handling and physics
|
||||
//---
|
||||
|
||||
bool explosion_trigger = false;
|
||||
key_event_t ev;
|
||||
while((ev = pollevent()).type != KEYEV_NONE) {
|
||||
if(ev.type == KEYEV_DOWN && ev.key == KEY_F1)
|
||||
explosion_trigger = true;
|
||||
}
|
||||
|
||||
clearevents();
|
||||
if(keydown(KEY_MENU) || keydown(KEY_EXIT))
|
||||
break;
|
||||
|
||||
prof_enter(perf_physics);
|
||||
|
||||
if(!lost)
|
||||
controller_simple(&plane, dt);
|
||||
|
||||
/* Limit pitch to reasonable ranges (extreme values cause graphical
|
||||
glitches with the horizon, and accentuate the lens problem */
|
||||
if(plane.pitch < -0.2)
|
||||
plane.pitch = -0.2;
|
||||
if(plane.pitch > 0.2)
|
||||
plane.pitch = 0.2;
|
||||
|
||||
/* Limit air speed to "reasonable" defaults */
|
||||
if(plane.air_speed < num(6.0))
|
||||
plane.air_speed = 6.0;
|
||||
if(plane.air_speed > num(24.0))
|
||||
plane.air_speed = 24.0;
|
||||
|
||||
if(!lost)
|
||||
plane.pos += plane.air_speed * dt * plane.forward;
|
||||
|
||||
/* Wrap the plane around the world map */
|
||||
if((int)plane.pos.x < 0)
|
||||
plane.pos.x += world->w;
|
||||
if((int)plane.pos.x >= world->w)
|
||||
plane.pos.x -= world->w;
|
||||
if((int)plane.pos.y < 0)
|
||||
plane.pos.y += world->h;
|
||||
if((int)plane.pos.y >= world->h)
|
||||
plane.pos.y -= world->h;
|
||||
|
||||
/* Add new explosions */
|
||||
if(!lost && explosion_trigger) {
|
||||
int id = -1;
|
||||
for(int i = 0; i < explosion_count; i++) {
|
||||
if(explosions[i].time < num(0)) {
|
||||
id = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(id >= 0) {
|
||||
explosions[id].pos =
|
||||
plane.pos + num(BOMB_DISTANCE) * plane.forward;
|
||||
explosions[id].pos.z = 0;
|
||||
explosions[id].time = 0.0;
|
||||
|
||||
bool hit_someone = false;
|
||||
|
||||
/* Destroy bases that are close! */
|
||||
for(int i = 0; i < objective_count; i++) {
|
||||
if(distance2_64(objectives[i].pos,
|
||||
explosions[id].pos) < num64(20)) {
|
||||
objectives[i].destroyed = true;
|
||||
hit_someone = true;
|
||||
}
|
||||
}
|
||||
|
||||
message = hit_someone ? "Hit!" : "Missed!";
|
||||
message_time = num(2.0);
|
||||
}
|
||||
}
|
||||
|
||||
/* Maintain the time of current explosions */
|
||||
for(int i = 0; i < explosion_count; i++) {
|
||||
if(explosions[i].time >= num(0)) {
|
||||
explosions[i].time += dt;
|
||||
if(explosions[i].time >= num(4.0/6.0))
|
||||
explosions[i].time = -1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Maintain the message time */
|
||||
if(message != NULL) {
|
||||
message_time -= dt;
|
||||
if(message_time < num(0)) {
|
||||
message = NULL;
|
||||
message_time = num(0.0);
|
||||
}
|
||||
}
|
||||
|
||||
prof_leave(perf_physics);
|
||||
prof_leave(perf_frame);
|
||||
#if 0
|
||||
logger("Frame total: %d µs\n - Rendering: update %d µs, shaders %d µs"
|
||||
"\n - Physics: %d µs\n - Sprite scaling: %d µs\n"
|
||||
"Current info:\n Position: x=%g y=%g z=%g\n Angles: roll=%g "
|
||||
"pitch=%g yaw=%g\n Air speed: %g\n"
|
||||
"Points: %d selected, %d close, %d visible\n"
|
||||
"Explosions: %d\n",
|
||||
prof_time(perf_frame),
|
||||
prof_time(azrp_perf_render),
|
||||
prof_time(azrp_perf_shaders),
|
||||
prof_time(perf_physics),
|
||||
prof_time(perf_scaling),
|
||||
(double)plane.pos.x,
|
||||
(double)plane.pos.y,
|
||||
(double)plane.pos.z,
|
||||
(double)plane.roll,
|
||||
(double)plane.pitch,
|
||||
(double)plane.yaw,
|
||||
(double)plane.air_speed,
|
||||
sprite_count,
|
||||
close_point_count,
|
||||
visible_point_count,
|
||||
visible_explosions);
|
||||
#endif
|
||||
|
||||
/* Victory condition */
|
||||
bool all_objectives_destroyed = true;
|
||||
for(int i = 0; i < objective_count; i++)
|
||||
all_objectives_destroyed &= objectives[i].destroyed;
|
||||
|
||||
if(all_objectives_destroyed) {
|
||||
message = "Mission accomplished!";
|
||||
message_time = num(32000);
|
||||
}
|
||||
|
||||
/* Death condition */
|
||||
if(!lost && plane.pos.z <= num(0.5)) {
|
||||
message = "Unfortunate.";
|
||||
message_time = num(32000);
|
||||
lost = true;
|
||||
|
||||
/* Hijack one of the explosions */
|
||||
explosions[0].pos = plane.pos;
|
||||
explosions[0].time = num(0.0);
|
||||
}
|
||||
}
|
||||
|
||||
timer_stop(timer);
|
||||
return 1;
|
||||
}
|
|
@ -0,0 +1,163 @@
|
|||
#include "afterburner.h"
|
||||
#include <stdlib.h>
|
||||
|
||||
int debug = 0;
|
||||
void *debug2 = NULL;
|
||||
|
||||
struct world *world_make(int width, int height, struct ref_point *points,
|
||||
int count)
|
||||
{
|
||||
if(!points)
|
||||
return NULL;
|
||||
|
||||
struct world *w = (struct world *)malloc(sizeof *w);
|
||||
int *js = NULL;
|
||||
size_t s;
|
||||
if(!w) goto fail;
|
||||
|
||||
w->w = width;
|
||||
w->h = height;
|
||||
w->chunks_x = (width + CHUNK_SIZE - 1) / CHUNK_SIZE;
|
||||
w->chunks_y = (height + CHUNK_SIZE - 1) / CHUNK_SIZE;
|
||||
|
||||
s = w->chunks_x * w->chunks_y * sizeof *w->chunks;
|
||||
w->chunks = (struct chunk *)malloc(s);
|
||||
if(!w->chunks) goto fail;
|
||||
for(int i = 0; i < w->chunks_x * w->chunks_y; i++) {
|
||||
w->chunks[i].points = NULL;
|
||||
w->chunks[i].count = 0;
|
||||
}
|
||||
|
||||
/* Do a first pass to count the number of points in each chunks */
|
||||
for(int i = 0; i < count; i++) {
|
||||
int cx = (int)points[i].pos.x / CHUNK_SIZE;
|
||||
int cy = (int)points[i].pos.y / CHUNK_SIZE;
|
||||
w->chunks[cy * w->chunks_x + cx].count++;
|
||||
}
|
||||
|
||||
/* Allocate all chunks' data */
|
||||
for(int i = 0; i < w->chunks_x * w->chunks_y; i++) {
|
||||
if(w->chunks[i].count == 0)
|
||||
continue;
|
||||
w->chunks[i].points = (struct ref_point *)malloc(
|
||||
w->chunks[i].count * sizeof(struct ref_point));
|
||||
if(!w->chunks[i].points) goto fail;
|
||||
}
|
||||
|
||||
/* Do a second pass to actually fill the chunks */
|
||||
s = w->chunks_x * w->chunks_y * sizeof(int);
|
||||
js = (int *)malloc(s);
|
||||
if(!js) goto fail;
|
||||
for(int i = 0; i < w->chunks_x * w->chunks_y; i++) {
|
||||
js[i] = 0;
|
||||
}
|
||||
|
||||
for(int i = 0; i < count; i++) {
|
||||
int cx = (int)points[i].pos.x / CHUNK_SIZE;
|
||||
int cy = (int)points[i].pos.y / CHUNK_SIZE;
|
||||
int j = cy * w->chunks_x + cx;
|
||||
w->chunks[j].points[js[j]++] = points[i];
|
||||
}
|
||||
|
||||
free(js);
|
||||
return w;
|
||||
|
||||
fail:
|
||||
world_destroy(w);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void world_destroy(struct world *w)
|
||||
{
|
||||
if(!w) return;
|
||||
|
||||
if(w->chunks) {
|
||||
for(int i = 0; i < w->chunks_x * w->chunks_y; i++)
|
||||
free(w->chunks[i].points);
|
||||
free(w->chunks);
|
||||
}
|
||||
|
||||
free(w);
|
||||
}
|
||||
|
||||
struct ref_point *world_select(struct world const *world, vec3 position,
|
||||
GUNUSED vec3 forward, int *size, int extra_size)
|
||||
{
|
||||
int cx1 = (int)position.x / CHUNK_SIZE;
|
||||
int cy1 = (int)position.y / CHUNK_SIZE;
|
||||
|
||||
// int fx = (int)forward.x >= 0 ? +1 : -1;
|
||||
// int fy = (int)forward.y >= 0 ? +1 : -1;
|
||||
|
||||
int chunks[9];
|
||||
int chunk_count = 0;
|
||||
|
||||
for(int dy = -1; dy <= +1; dy++)
|
||||
for(int dx = -1; dx <= +1; dx++) {
|
||||
|
||||
int cy = cy1 + dy;
|
||||
int cx = cx1 + dx;
|
||||
|
||||
if(cx < 0)
|
||||
cx = world->chunks_x - 1;
|
||||
if(cx >= world->chunks_x)
|
||||
cx = 0;
|
||||
if(cy < 0)
|
||||
cy = world->chunks_y - 1;
|
||||
if(cy >= world->chunks_y)
|
||||
cy = 0;
|
||||
|
||||
chunks[chunk_count++] = cy * world->chunks_x + cx;
|
||||
}
|
||||
|
||||
/* Count the points to be allocated */
|
||||
int total_point_count = 0;
|
||||
for(int i = 0; i < chunk_count; i++)
|
||||
total_point_count += world->chunks[chunks[i]].count;
|
||||
|
||||
/* Allocate the array */
|
||||
struct ref_point *output = (struct ref_point *)malloc(
|
||||
(total_point_count + extra_size) * sizeof *output);
|
||||
|
||||
/* Fill the points in selected chunks. While doing this, we also rotate the
|
||||
points. The player is constrained to be within the world boundary, but
|
||||
it can still be on the edge and look outside. In this situation, we
|
||||
rotate the points to give the illusion that the world wraps around. */
|
||||
num world_size_x14 = num(world->w / 4);
|
||||
num world_size_x34 = num(3 * world->w / 4);
|
||||
num world_size_y14 = num(world->h / 4);
|
||||
num world_size_y34 = num(3 * world->h / 4);
|
||||
|
||||
/* This simple test only works if the map is at least 4x as large as
|
||||
the depth of view of the plane, (here 256 > 4×50). */
|
||||
num wrap_x_minus = (position.x < world_size_x14)
|
||||
? world_size_x34 : num(world->w);
|
||||
num wrap_x_plus = (position.x > world_size_x34)
|
||||
? world_size_x14 : num(0);
|
||||
num wrap_y_minus = (position.y < world_size_y14)
|
||||
? world_size_y34 : num(world->h);
|
||||
num wrap_y_plus = (position.y > world_size_y34)
|
||||
? world_size_y14 : num(0);
|
||||
|
||||
int k = 0;
|
||||
for(int i = 0; i < chunk_count; i++) {
|
||||
struct chunk *c = &world->chunks[chunks[i]];
|
||||
for(int j = 0; j < c->count; j++) {
|
||||
struct ref_point p = c->points[j];
|
||||
|
||||
if(p.pos.x < wrap_x_plus)
|
||||
p.pos.x += num(world->w);
|
||||
else if(p.pos.x > wrap_x_minus)
|
||||
p.pos.x -= num(world->w);
|
||||
if(p.pos.y < wrap_y_plus)
|
||||
p.pos.y += num(world->h);
|
||||
else if(p.pos.y > wrap_y_minus)
|
||||
p.pos.y -= num(world->h);
|
||||
|
||||
output[k++] = p;
|
||||
}
|
||||
}
|
||||
|
||||
*size = total_point_count;
|
||||
return output;
|
||||
}
|