commit
09d0d54d61
@ -0,0 +1,60 @@ |
||||
# wipeout wasm in docker |
||||
FROM debian:bullseye |
||||
|
||||
LABEL Greg Lebreton <greg.lebreton@hotmail.com |
||||
|
||||
ENV ROOT_WWW_PATH /var/www/html |
||||
|
||||
RUN apt-get update && apt-get install -y \ |
||||
git \ |
||||
make \ |
||||
libx11-dev \ |
||||
libxcursor-dev \ |
||||
libxi-dev \ |
||||
libasound2-dev \ |
||||
ca-certificates \ |
||||
unzip \ |
||||
sed \ |
||||
p7zip-full \ |
||||
emscripten \ |
||||
coffeescript \ |
||||
xz-utils \ |
||||
nginx \ |
||||
wget \ |
||||
--no-install-recommends \ |
||||
&& rm -rf /var/lib/apt/lists/* |
||||
|
||||
# sinon le build failed |
||||
RUN emcc --version |
||||
|
||||
# clone repo git |
||||
RUN cd / \ |
||||
&& git clone https://github.com/phoboslab/wipeout-rewrite.git \ |
||||
&& cd wipeout-rewrite |
||||
|
||||
# copie des fichiers necessaires (wipeout-datas) |
||||
# COPY ./wipeout /wipeout-rewrite |
||||
|
||||
# wipeout datas |
||||
RUN cd /tmp \ |
||||
&& wget https://phoboslab.org/files/wipeout-data-v01.zip \ |
||||
&& unzip wipeout-data-v01.zip -d wipeout \ |
||||
&& cp -r wipeout/* /wipeout-rewrite |
||||
|
||||
# build |
||||
RUN cd wipeout-rewrite \ |
||||
&& make wasm |
||||
|
||||
# mise en place des fichiers |
||||
RUN cp /wipeout-rewrite/build/wasm/* /var/www/html \ |
||||
&& cp /wipeout-rewrite/src/wasm-index.html /var/www/html/index.html \ |
||||
&& cp -r /wipeout-rewrite/wipeout /var/www/html |
||||
|
||||
|
||||
WORKDIR ${ROOT_WWW_PATH} |
||||
|
||||
EXPOSE 80 |
||||
|
||||
COPY entrypoint.sh / |
||||
|
||||
CMD [ "sh", "/entrypoint.sh"] |
@ -0,0 +1,185 @@ |
||||
CC ?= gcc
|
||||
EMCC ?= emcc
|
||||
UNAME_S := $(shell uname -s)
|
||||
RENDERER ?= GL
|
||||
USE_GLX ?= false
|
||||
DEBUG ?= false
|
||||
|
||||
L_FLAGS ?= -lm -rdynamic
|
||||
C_FLAGS ?= -std=gnu99 -Wall -Wno-unused-variable
|
||||
|
||||
ifeq ($(DEBUG), true) |
||||
C_FLAGS := $(C_FLAGS) -g
|
||||
else |
||||
C_FLAGS := $(C_FLAGS) -O3
|
||||
endif |
||||
|
||||
|
||||
# Rendeder ---------------------------------------------------------------------
|
||||
|
||||
ifeq ($(RENDERER), GL) |
||||
RENDERER_SRC = src/render_gl.c
|
||||
C_FLAGS := $(C_FLAGS) -DRENDERER_GL
|
||||
else ifeq ($(RENDERER), SOFTWARE) |
||||
RENDERER_SRC = src/render_software.c
|
||||
C_FLAGS := $(C_FLAGS) -DRENDERER_SOFTWARE
|
||||
else |
||||
$(error Unknown RENDERER) |
||||
endif |
||||
|
||||
ifeq ($(GL_VERSION), GLES2) |
||||
C_FLAGS := $(C_FLAGS) -DUSE_GLES2
|
||||
endif |
||||
|
||||
|
||||
|
||||
# macOS ------------------------------------------------------------------------
|
||||
|
||||
ifeq ($(UNAME_S), Darwin) |
||||
C_FLAGS := $(C_FLAGS) -x objective-c -I/opt/homebrew/include -D_THREAD_SAFE -w
|
||||
L_FLAGS := $(L_FLAGS) -L/opt/homebrew/lib -framework Foundation
|
||||
|
||||
ifeq ($(RENDERER), GL)
|
||||
L_FLAGS := $(L_FLAGS) -lGLEW -GLU -framework OpenGL
|
||||
endif
|
||||
|
||||
L_FLAGS_SDL = -lSDL2
|
||||
L_FLAGS_SOKOL = -framework Cocoa -framework QuartzCore -framework AudioToolbox
|
||||
|
||||
|
||||
# Linux ------------------------------------------------------------------------
|
||||
|
||||
else ifeq ($(UNAME_S), Linux) |
||||
ifeq ($(RENDERER), GL)
|
||||
L_FLAGS := $(L_FLAGS) -lGLEW
|
||||
|
||||
# Prefer modern GLVND instead of legacy X11-only GLX
|
||||
ifeq ($(USE_GLX), true)
|
||||
L_FLAGS := $(L_FLAGS) -lGL
|
||||
else
|
||||
L_FLAGS := $(L_FLAGS) -lOpenGL
|
||||
endif
|
||||
endif
|
||||
|
||||
L_FLAGS_SDL = -lSDL2
|
||||
L_FLAGS_SOKOL = -lX11 -lXcursor -pthread -lXi -ldl -lasound
|
||||
|
||||
|
||||
# Windows ----------------------------------------------------------------------
|
||||
|
||||
else ifeq ($(OS), Windows_NT) |
||||
$(error TODO: FLAGS for windows have not been set up. Please modify this makefile and send a PR!) |
||||
|
||||
|
||||
|
||||
else |
||||
$(error Unknown environment) |
||||
endif |
||||
|
||||
|
||||
|
||||
# Source files -----------------------------------------------------------------
|
||||
|
||||
TARGET_NATIVE ?= wipegame
|
||||
BUILD_DIR = build/obj/native
|
||||
BUILD_DIR_WASM = build/obj/wasm
|
||||
|
||||
WASM_RELEASE_DIR ?= build/wasm
|
||||
TARGET_WASM ?= $(WASM_RELEASE_DIR)/wipeout.js
|
||||
TARGET_WASM_MINIMAL ?= $(WASM_RELEASE_DIR)/wipeout-minimal.js
|
||||
|
||||
COMMON_SRC = \
|
||||
src/wipeout/race.c \
|
||||
src/wipeout/camera.c \
|
||||
src/wipeout/object.c \
|
||||
src/wipeout/droid.c \
|
||||
src/wipeout/ui.c \
|
||||
src/wipeout/hud.c \
|
||||
src/wipeout/image.c \
|
||||
src/wipeout/game.c \
|
||||
src/wipeout/menu.c \
|
||||
src/wipeout/main_menu.c \
|
||||
src/wipeout/ingame_menus.c \
|
||||
src/wipeout/title.c \
|
||||
src/wipeout/intro.c \
|
||||
src/wipeout/scene.c \
|
||||
src/wipeout/ship.c \
|
||||
src/wipeout/ship_ai.c \
|
||||
src/wipeout/ship_player.c \
|
||||
src/wipeout/track.c \
|
||||
src/wipeout/weapon.c \
|
||||
src/wipeout/particle.c \
|
||||
src/wipeout/sfx.c \
|
||||
src/utils.c \
|
||||
src/types.c \
|
||||
src/system.c \
|
||||
src/mem.c \
|
||||
src/input.c \
|
||||
$(RENDERER_SRC)
|
||||
|
||||
|
||||
# Targets native ---------------------------------------------------------------
|
||||
|
||||
COMMON_OBJ = $(patsubst %.c, $(BUILD_DIR)/%.o, $(COMMON_SRC))
|
||||
COMMON_DEPS = $(patsubst %.c, $(BUILD_DIR)/%.d, $(COMMON_SRC))
|
||||
|
||||
sdl: $(BUILD_DIR)/src/platform_sdl.o |
||||
sdl: $(COMMON_OBJ) |
||||
$(CC) $^ -o $(TARGET_NATIVE) $(L_FLAGS) $(L_FLAGS_SDL)
|
||||
|
||||
sokol: $(BUILD_DIR)/src/platform_sokol.o |
||||
sokol: $(COMMON_OBJ) |
||||
$(CC) $^ -o $(TARGET_NATIVE) $(L_FLAGS) $(L_FLAGS_SOKOL)
|
||||
|
||||
$(BUILD_DIR): |
||||
mkdir -p $(BUILD_DIR)
|
||||
|
||||
$(BUILD_DIR)/%.o: %.c |
||||
mkdir -p $(dir $@)
|
||||
$(CC) $(C_FLAGS) -MMD -MP -c $< -o $@
|
||||
|
||||
-include $(COMMON_DEPS) |
||||
|
||||
|
||||
# Targets wasm -----------------------------------------------------------------
|
||||
|
||||
COMMON_OBJ_WASM = $(patsubst %.c, $(BUILD_DIR_WASM)/%.o, $(COMMON_SRC))
|
||||
COMMON_DEPS_WASM = $(patsubst %.c, $(BUILD_DIR_WASM)/%.d, $(COMMON_SRC))
|
||||
|
||||
wasm: wasm_full wasm_minimal |
||||
cp src/wasm-index.html $(WASM_RELEASE_DIR)/game.html
|
||||
|
||||
|
||||
wasm_full: $(BUILD_DIR_WASM)/src/platform_sokol.o |
||||
wasm_full: $(COMMON_OBJ_WASM) |
||||
mkdir -p $(WASM_RELEASE_DIR)
|
||||
$(EMCC) $^ -o $(TARGET_WASM) -lGLEW -lGL \
|
||||
-s ALLOW_MEMORY_GROWTH=1 \
|
||||
-s ENVIRONMENT=web \
|
||||
--preload-file wipeout
|
||||
|
||||
wasm_minimal: $(BUILD_DIR_WASM)/src/platform_sokol.o |
||||
wasm_minimal: $(COMMON_OBJ_WASM) |
||||
mkdir -p $(WASM_RELEASE_DIR)
|
||||
$(EMCC) $^ -o $(TARGET_WASM_MINIMAL) -lGLEW -lGL \
|
||||
-s ALLOW_MEMORY_GROWTH=1 \
|
||||
-s ENVIRONMENT=web \
|
||||
--preload-file wipeout \
|
||||
--exclude-file wipeout/music \
|
||||
--exclude-file wipeout/intro.mpeg
|
||||
|
||||
$(BUILD_DIR_WASM): |
||||
mkdir -p $(BUILD_DIR_WASM)
|
||||
|
||||
$(BUILD_DIR_WASM)/%.o: %.c |
||||
mkdir -p $(dir $@)
|
||||
$(EMCC) $(C_FLAGS) -MMD -MP -c $< -o $@
|
||||
|
||||
-include $(COMMON_DEPS_WASM) |
||||
|
||||
|
||||
|
||||
|
||||
.PHONY: clean |
||||
clean: |
||||
$(RM) -rf $(BUILD_DIR) $(BUILD_DIR_WASM) $(WASM_RELEASE_DIR)
|
@ -0,0 +1,299 @@ |
||||
#include <string.h> |
||||
|
||||
#include "input.h" |
||||
#include "utils.h" |
||||
|
||||
static const char *button_names[] = { |
||||
NULL, |
||||
NULL, |
||||
NULL, |
||||
NULL, |
||||
[INPUT_KEY_A] = "A", |
||||
[INPUT_KEY_B] = "B", |
||||
[INPUT_KEY_C] = "C", |
||||
[INPUT_KEY_D] = "D", |
||||
[INPUT_KEY_E] = "E", |
||||
[INPUT_KEY_F] = "F", |
||||
[INPUT_KEY_G] = "G", |
||||
[INPUT_KEY_H] = "H", |
||||
[INPUT_KEY_I] = "I", |
||||
[INPUT_KEY_J] = "J", |
||||
[INPUT_KEY_K] = "K", |
||||
[INPUT_KEY_L] = "L", |
||||
[INPUT_KEY_M] = "M", |
||||
[INPUT_KEY_N] = "N", |
||||
[INPUT_KEY_O] = "O", |
||||
[INPUT_KEY_P] = "P", |
||||
[INPUT_KEY_Q] = "Q", |
||||
[INPUT_KEY_R] = "R", |
||||
[INPUT_KEY_S] = "S", |
||||
[INPUT_KEY_T] = "T", |
||||
[INPUT_KEY_U] = "U", |
||||
[INPUT_KEY_V] = "V", |
||||
[INPUT_KEY_W] = "W", |
||||
[INPUT_KEY_X] = "X", |
||||
[INPUT_KEY_Y] = "Y", |
||||
[INPUT_KEY_Z] = "Z", |
||||
[INPUT_KEY_1] = "1", |
||||
[INPUT_KEY_2] = "2", |
||||
[INPUT_KEY_3] = "3", |
||||
[INPUT_KEY_4] = "4", |
||||
[INPUT_KEY_5] = "5", |
||||
[INPUT_KEY_6] = "6", |
||||
[INPUT_KEY_7] = "7", |
||||
[INPUT_KEY_8] = "8", |
||||
[INPUT_KEY_9] = "9", |
||||
[INPUT_KEY_0] = "0", |
||||
[INPUT_KEY_RETURN] = "RETURN", |
||||
[INPUT_KEY_ESCAPE] = "ESCAPE", |
||||
[INPUT_KEY_BACKSPACE] = "BACKSP", |
||||
[INPUT_KEY_TAB] = "TAB", |
||||
[INPUT_KEY_SPACE] = "SPACE", |
||||
[INPUT_KEY_MINUS] = "MINUS", |
||||
[INPUT_KEY_EQUALS] = "EQUALS", |
||||
[INPUT_KEY_LEFTBRACKET] = "LBRACKET", |
||||
[INPUT_KEY_RIGHTBRACKET] = "RBRACKET", |
||||
[INPUT_KEY_BACKSLASH] = "BSLASH", |
||||
[INPUT_KEY_HASH] = "HASH", |
||||
[INPUT_KEY_SEMICOLON] = "SMICOL", |
||||
[INPUT_KEY_APOSTROPHE] = "APO", |
||||
[INPUT_KEY_TILDE] = "TILDE", |
||||
[INPUT_KEY_COMMA] = "COMMA", |
||||
[INPUT_KEY_PERIOD] = "PERIOD", |
||||
[INPUT_KEY_SLASH] = "SLASH", |
||||
[INPUT_KEY_CAPSLOCK] = "CAPS", |
||||
[INPUT_KEY_F1] = "F1", |
||||
[INPUT_KEY_F2] = "F2", |
||||
[INPUT_KEY_F3] = "F3", |
||||
[INPUT_KEY_F4] = "F4", |
||||
[INPUT_KEY_F5] = "F5", |
||||
[INPUT_KEY_F6] = "F6", |
||||
[INPUT_KEY_F7] = "F7", |
||||
[INPUT_KEY_F8] = "F8", |
||||
[INPUT_KEY_F9] = "F9", |
||||
[INPUT_KEY_F10] = "F10", |
||||
[INPUT_KEY_F11] = "F11", |
||||
[INPUT_KEY_F12] = "F12", |
||||
[INPUT_KEY_PRINTSCREEN] = "PRTSC", |
||||
[INPUT_KEY_SCROLLLOCK] = "SCRLK", |
||||
[INPUT_KEY_PAUSE] = "PAUSE", |
||||
[INPUT_KEY_INSERT] = "INSERT", |
||||
[INPUT_KEY_HOME] = "HOME", |
||||
[INPUT_KEY_PAGEUP] = "PG UP", |
||||
[INPUT_KEY_DELETE] = "DELETE", |
||||
[INPUT_KEY_END] = "END", |
||||
[INPUT_KEY_PAGEDOWN] = "PG DOWN", |
||||
[INPUT_KEY_RIGHT] = "RIGHT", |
||||
[INPUT_KEY_LEFT] = "LEFT", |
||||
[INPUT_KEY_DOWN] = "DOWN", |
||||
[INPUT_KEY_UP] = "UP", |
||||
[INPUT_KEY_NUMLOCK] = "NLOCK", |
||||
[INPUT_KEY_KP_DIVIDE] = "KPDIV", |
||||
[INPUT_KEY_KP_MULTIPLY] = "KPMUL", |
||||
[INPUT_KEY_KP_MINUS] = "KPMINUS", |
||||
[INPUT_KEY_KP_PLUS] = "KPPLUS", |
||||
[INPUT_KEY_KP_ENTER] = "KPENTER", |
||||
[INPUT_KEY_KP_1] = "KP1", |
||||
[INPUT_KEY_KP_2] = "KP2", |
||||
[INPUT_KEY_KP_3] = "KP3", |
||||
[INPUT_KEY_KP_4] = "KP4", |
||||
[INPUT_KEY_KP_5] = "KP5", |
||||
[INPUT_KEY_KP_6] = "KP6", |
||||
[INPUT_KEY_KP_7] = "KP7", |
||||
[INPUT_KEY_KP_8] = "KP8", |
||||
[INPUT_KEY_KP_9] = "KP9", |
||||
[INPUT_KEY_KP_0] = "KP0", |
||||
[INPUT_KEY_KP_PERIOD] = "KPPERIOD", |
||||
|
||||
[INPUT_KEY_LCTRL] = "LCTRL", |
||||
[INPUT_KEY_LSHIFT] = "LSHIFT", |
||||
[INPUT_KEY_LALT] = "LALT", |
||||
[INPUT_KEY_LGUI] = "LGUI", |
||||
[INPUT_KEY_RCTRL] = "RCTRL", |
||||
[INPUT_KEY_RSHIFT] = "RSHIFT", |
||||
[INPUT_KEY_RALT] = "RALT", |
||||
NULL, |
||||
[INPUT_GAMEPAD_A] = "A", |
||||
[INPUT_GAMEPAD_Y] = "Y", |
||||
[INPUT_GAMEPAD_B] = "B", |
||||
[INPUT_GAMEPAD_X] = "X", |
||||
[INPUT_GAMEPAD_L_SHOULDER] = "LSHLDR", |
||||
[INPUT_GAMEPAD_R_SHOULDER] = "RSHLDR", |
||||
[INPUT_GAMEPAD_L_TRIGGER] = "LTRIG", |
||||
[INPUT_GAMEPAD_R_TRIGGER] = "RTRIG", |
||||
[INPUT_GAMEPAD_SELECT] = "SELECT", |
||||
[INPUT_GAMEPAD_START] = "START", |
||||
[INPUT_GAMEPAD_L_STICK_PRESS] = "LSTK", |
||||
[INPUT_GAMEPAD_R_STICK_PRESS] = "RSTK", |
||||
[INPUT_GAMEPAD_DPAD_UP] = "DPUP", |
||||
[INPUT_GAMEPAD_DPAD_DOWN] = "DPDOWN", |
||||
[INPUT_GAMEPAD_DPAD_LEFT] = "DPLEFT", |
||||
[INPUT_GAMEPAD_DPAD_RIGHT] = "DPRIGHT", |
||||
[INPUT_GAMEPAD_HOME] = "HOME", |
||||
[INPUT_GAMEPAD_L_STICK_UP] = "LSTKUP", |
||||
[INPUT_GAMEPAD_L_STICK_DOWN] = "LSTKDOWN", |
||||
[INPUT_GAMEPAD_L_STICK_LEFT] = "LSTKLEFT", |
||||
[INPUT_GAMEPAD_L_STICK_RIGHT] = "LSTKRIGHT", |
||||
[INPUT_GAMEPAD_R_STICK_UP] = "RSTKUP", |
||||
[INPUT_GAMEPAD_R_STICK_DOWN] = "RSTKDOWN", |
||||
[INPUT_GAMEPAD_R_STICK_LEFT] = "RSTKLEFT", |
||||
[INPUT_GAMEPAD_R_STICK_RIGHT] = "RSTKRIGHT", |
||||
NULL, |
||||
[INPUT_MOUSE_LEFT] = "MLEFT", |
||||
[INPUT_MOUSE_MIDDLE] = "MMIDDLE", |
||||
[INPUT_MOUSE_RIGHT] = "MRIGHT", |
||||
[INPUT_MOUSE_WHEEL_UP] = "MWUP", |
||||
[INPUT_MOUSE_WHEEL_DOWN] = "MWDOWN", |
||||
}; |
||||
|
||||
static float actions_state[INPUT_ACTION_MAX]; |
||||
static bool actions_pressed[INPUT_ACTION_MAX]; |
||||
static bool actions_released[INPUT_ACTION_MAX]; |
||||
|
||||
static uint8_t expected_button[INPUT_ACTION_MAX]; |
||||
static uint8_t bindings[INPUT_LAYER_MAX][INPUT_BUTTON_MAX]; |
||||
|
||||
static input_capture_callback_t capture_callback; |
||||
static void *capture_user; |
||||
|
||||
static int32_t mouse_x; |
||||
static int32_t mouse_y; |
||||
|
||||
void input_init() { |
||||
input_unbind_all(INPUT_LAYER_SYSTEM); |
||||
input_unbind_all(INPUT_LAYER_USER); |
||||
} |
||||
|
||||
void input_cleanup() { |
||||
|
||||
} |
||||
|
||||
void input_clear() { |
||||
clear(actions_pressed); |
||||
clear(actions_released); |
||||
} |
||||
|
||||
void input_set_layer_button_state(input_layer_t layer, button_t button, float state) { |
||||
error_if(layer < 0 || layer >= INPUT_LAYER_MAX, "Invalid input layer %d", layer); |
||||
|
||||
uint8_t action = bindings[layer][button]; |
||||
if (action == INPUT_ACTION_NONE) { |
||||
return; |
||||
} |
||||
|
||||
uint8_t expected = expected_button[action]; |
||||
if (!expected || expected == button) { |
||||
state = (state > INPUT_DEADZONE) ? state : 0; |
||||
|
||||
if (state && !actions_state[action]) { |
||||
actions_pressed[action] = true; |
||||
expected_button[action] = button; |
||||
} |
||||
else if (!state && actions_state[action]) { |
||||
actions_released[action] = true; |
||||
expected_button[action] = INPUT_BUTTON_NONE; |
||||
} |
||||
actions_state[action] = state; |
||||
} |
||||
} |
||||
|
||||
void input_set_button_state(button_t button, float state) { |
||||
error_if(button < 0 || button >= INPUT_BUTTON_MAX, "Invalid input button %d", button); |
||||
|
||||
input_set_layer_button_state(INPUT_LAYER_SYSTEM, button, state); |
||||
input_set_layer_button_state(INPUT_LAYER_USER, button, state); |
||||
|
||||
if (capture_callback) { |
||||
if (state > INPUT_DEADZONE_CAPTURE) { |
||||
capture_callback(capture_user, button, 0); |
||||
} |
||||
} |
||||
} |
||||
|
||||
void input_set_mouse_pos(int32_t x, int32_t y) { |
||||
mouse_x = x; |
||||
mouse_y = y; |
||||
} |
||||
|
||||
void input_capture(input_capture_callback_t cb, void *user) { |
||||
capture_callback = cb; |
||||
capture_user = user; |
||||
input_clear(); |
||||
} |
||||
|
||||
void input_textinput(int32_t ascii_char) { |
||||
if (capture_callback) { |
||||
capture_callback(capture_user, INPUT_INVALID, ascii_char); |
||||
} |
||||
} |
||||
|
||||
void input_bind(input_layer_t layer, button_t button, uint8_t action) { |
||||
error_if(button < 0 || button >= INPUT_BUTTON_MAX, "Invalid input button %d", button); |
||||
error_if(action < 0 || action >= INPUT_ACTION_MAX, "Invalid input action %d", action); |
||||
error_if(layer < 0 || layer >= INPUT_LAYER_MAX, "Invalid input layer %d", layer); |
||||
|
||||
actions_state[action] = 0; |
||||
bindings[layer][button] = action; |
||||
} |
||||
|
||||
uint8_t input_bound_to_action(button_t button) { |
||||
error_if(button < 0 || button >= INPUT_BUTTON_MAX, "Invalid input button %d", button); |
||||
return bindings[INPUT_LAYER_USER][button]; |
||||
} |
||||
|
||||
void input_unbind(input_layer_t layer, button_t button) { |
||||
error_if(layer < 0 || layer >= INPUT_LAYER_MAX, "Invalid input layer %d", layer); |
||||
error_if(button < 0 || button >= INPUT_BUTTON_MAX, "Invalid input button %d", button); |
||||
|
||||
bindings[layer][button] = INPUT_ACTION_NONE; |
||||
} |
||||
|
||||
void input_unbind_all(input_layer_t layer) { |
||||
error_if(layer < 0 || layer >= INPUT_LAYER_MAX, "Invalid input layer %d", layer); |
||||
|
||||
for (uint32_t button = 0; button < INPUT_BUTTON_MAX; button++) { |
||||
input_unbind(layer, button); |
||||
} |
||||
} |
||||
|
||||
|
||||
float input_state(uint8_t action) { |
||||
error_if(action < 0 || action >= INPUT_ACTION_MAX, "Invalid input action %d", action); |
||||
return actions_state[action]; |
||||
} |
||||
|
||||
|
||||
bool input_pressed(uint8_t action) { |
||||
error_if(action < 0 || action >= INPUT_ACTION_MAX, "Invalid input action %d", action); |
||||
return actions_pressed[action]; |
||||
} |
||||
|
||||
|
||||
bool input_released(uint8_t action) { |
||||
error_if(action < 0 || action >= INPUT_ACTION_MAX, "Invalid input action %d", action); |
||||
return actions_released[action]; |
||||
} |
||||
|
||||
vec2_t input_mouse_pos() { |
||||
return vec2(mouse_x, mouse_y); |
||||
} |
||||
|
||||
|
||||
button_t input_name_to_button(const char *name) { |
||||
for (int32_t i = 0; i < INPUT_BUTTON_MAX; i++) { |
||||
if (button_names[i] && strcmp(name, button_names[i]) == 0) { |
||||
return i; |
||||
} |
||||
} |
||||
return INPUT_INVALID; |
||||
} |
||||
|
||||
const char *input_button_to_name(button_t button) { |
||||
if ( |
||||
button < 0 || button >= INPUT_BUTTON_MAX || |
||||
!button_names[button] |
||||
) { |
||||
return NULL; |
||||
} |
||||
return button_names[button]; |
||||
} |
@ -0,0 +1,191 @@ |
||||
#ifndef INPUT_H |
||||
#define INPUT_H |
||||
|
||||
#include "types.h" |
||||
|
||||
typedef enum { |
||||
INPUT_INVALID = 0, |
||||
INPUT_KEY_A = 4, |
||||
INPUT_KEY_B = 5, |
||||
INPUT_KEY_C = 6, |
||||
INPUT_KEY_D = 7, |
||||
INPUT_KEY_E = 8, |
||||
INPUT_KEY_F = 9, |
||||
INPUT_KEY_G = 10, |
||||
INPUT_KEY_H = 11, |
||||
INPUT_KEY_I = 12, |
||||
INPUT_KEY_J = 13, |
||||
INPUT_KEY_K = 14, |
||||
INPUT_KEY_L = 15, |
||||
INPUT_KEY_M = 16, |
||||
INPUT_KEY_N = 17, |
||||
INPUT_KEY_O = 18, |
||||
INPUT_KEY_P = 19, |
||||
INPUT_KEY_Q = 20, |
||||
INPUT_KEY_R = 21, |
||||
INPUT_KEY_S = 22, |
||||
INPUT_KEY_T = 23, |
||||
INPUT_KEY_U = 24, |
||||
INPUT_KEY_V = 25, |
||||
INPUT_KEY_W = 26, |
||||
INPUT_KEY_X = 27, |
||||
INPUT_KEY_Y = 28, |
||||
INPUT_KEY_Z = 29, |
||||
INPUT_KEY_1 = 30, |
||||
INPUT_KEY_2 = 31, |
||||
INPUT_KEY_3 = 32, |
||||
INPUT_KEY_4 = 33, |
||||
INPUT_KEY_5 = 34, |
||||
INPUT_KEY_6 = 35, |
||||
INPUT_KEY_7 = 36, |
||||
INPUT_KEY_8 = 37, |
||||
INPUT_KEY_9 = 38, |
||||
INPUT_KEY_0 = 39, |
||||
INPUT_KEY_RETURN = 40, |
||||
INPUT_KEY_ESCAPE = 41, |
||||
INPUT_KEY_BACKSPACE = 42, |
||||
INPUT_KEY_TAB = 43, |
||||
INPUT_KEY_SPACE = 44, |
||||
INPUT_KEY_MINUS = 45, |
||||
INPUT_KEY_EQUALS = 46, |
||||
INPUT_KEY_LEFTBRACKET = 47, |
||||
INPUT_KEY_RIGHTBRACKET = 48, |
||||
INPUT_KEY_BACKSLASH = 49, |
||||
INPUT_KEY_HASH = 50, |
||||
INPUT_KEY_SEMICOLON = 51, |
||||
INPUT_KEY_APOSTROPHE = 52, |
||||
INPUT_KEY_TILDE = 53, |
||||
INPUT_KEY_COMMA = 54, |
||||
INPUT_KEY_PERIOD = 55, |
||||
INPUT_KEY_SLASH = 56, |
||||
INPUT_KEY_CAPSLOCK = 57, |
||||
INPUT_KEY_F1 = 58, |
||||
INPUT_KEY_F2 = 59, |
||||
INPUT_KEY_F3 = 60, |
||||
INPUT_KEY_F4 = 61, |
||||
INPUT_KEY_F5 = 62, |
||||
INPUT_KEY_F6 = 63, |
||||
INPUT_KEY_F7 = 64, |
||||
INPUT_KEY_F8 = 65, |
||||
INPUT_KEY_F9 = 66, |
||||
INPUT_KEY_F10 = 67, |
||||
INPUT_KEY_F11 = 68, |
||||
INPUT_KEY_F12 = 69, |
||||
INPUT_KEY_PRINTSCREEN = 70, |
||||
INPUT_KEY_SCROLLLOCK = 71, |
||||
INPUT_KEY_PAUSE = 72, |
||||
INPUT_KEY_INSERT = 73, |
||||
INPUT_KEY_HOME = 74, |
||||
INPUT_KEY_PAGEUP = 75, |
||||
INPUT_KEY_DELETE = 76, |
||||
INPUT_KEY_END = 77, |
||||
INPUT_KEY_PAGEDOWN = 78, |
||||
INPUT_KEY_RIGHT = 79, |
||||
INPUT_KEY_LEFT = 80, |
||||
INPUT_KEY_DOWN = 81, |
||||
INPUT_KEY_UP = 82, |
||||
INPUT_KEY_NUMLOCK = 83, |
||||
INPUT_KEY_KP_DIVIDE = 84, |
||||
INPUT_KEY_KP_MULTIPLY = 85, |
||||
INPUT_KEY_KP_MINUS = 86, |
||||
INPUT_KEY_KP_PLUS = 87, |
||||
INPUT_KEY_KP_ENTER = 88, |
||||
INPUT_KEY_KP_1 = 89, |
||||
INPUT_KEY_KP_2 = 90, |
||||
INPUT_KEY_KP_3 = 91, |
||||
INPUT_KEY_KP_4 = 92, |
||||
INPUT_KEY_KP_5 = 93, |
||||
INPUT_KEY_KP_6 = 94, |
||||
INPUT_KEY_KP_7 = 95, |
||||
INPUT_KEY_KP_8 = 96, |
||||
INPUT_KEY_KP_9 = 97, |
||||
INPUT_KEY_KP_0 = 98, |
||||
INPUT_KEY_KP_PERIOD = 99, |
||||
|
||||
INPUT_KEY_LCTRL = 100, |
||||
INPUT_KEY_LSHIFT = 101, |
||||
INPUT_KEY_LALT = 102, |
||||
INPUT_KEY_LGUI = 103, |
||||
INPUT_KEY_RCTRL = 104, |
||||
INPUT_KEY_RSHIFT = 105, |
||||
INPUT_KEY_RALT = 106, |
||||
|
||||
INPUT_KEY_MAX = 107, |
||||
|
||||
INPUT_GAMEPAD_A = 108, |
||||
INPUT_GAMEPAD_Y = 109, |
||||
INPUT_GAMEPAD_B = 110, |
||||
INPUT_GAMEPAD_X = 111, |
||||
INPUT_GAMEPAD_L_SHOULDER = 112, |
||||
INPUT_GAMEPAD_R_SHOULDER = 113, |
||||
INPUT_GAMEPAD_L_TRIGGER = 114, |
||||
INPUT_GAMEPAD_R_TRIGGER = 115, |
||||
INPUT_GAMEPAD_SELECT = 116, |
||||
INPUT_GAMEPAD_START = 117, |
||||
INPUT_GAMEPAD_L_STICK_PRESS = 118, |
||||
INPUT_GAMEPAD_R_STICK_PRESS = 119, |
||||
INPUT_GAMEPAD_DPAD_UP = 120, |
||||
INPUT_GAMEPAD_DPAD_DOWN = 121, |
||||
INPUT_GAMEPAD_DPAD_LEFT = 122, |
||||
INPUT_GAMEPAD_DPAD_RIGHT = 123, |
||||
INPUT_GAMEPAD_HOME = 124, |
||||
INPUT_GAMEPAD_L_STICK_UP = 125, |
||||
INPUT_GAMEPAD_L_STICK_DOWN = 126, |
||||
INPUT_GAMEPAD_L_STICK_LEFT = 127, |
||||
INPUT_GAMEPAD_L_STICK_RIGHT = 128, |
||||
INPUT_GAMEPAD_R_STICK_UP = 129, |
||||
INPUT_GAMEPAD_R_STICK_DOWN = 130, |
||||
INPUT_GAMEPAD_R_STICK_LEFT = 131, |
||||
INPUT_GAMEPAD_R_STICK_RIGHT = 132, |
||||
|
||||
INPUT_MOUSE_LEFT = 134, |
||||
INPUT_MOUSE_MIDDLE = 135, |
||||
INPUT_MOUSE_RIGHT = 136, |
||||
INPUT_MOUSE_WHEEL_UP = 137, |
||||
INPUT_MOUSE_WHEEL_DOWN = 138, |
||||
|
||||
INPUT_BUTTON_MAX = 139 |
||||
} button_t; |
||||
|
||||
typedef enum { |
||||
INPUT_LAYER_SYSTEM = 0, |
||||
INPUT_LAYER_USER = 1, |
||||
INPUT_LAYER_MAX = 2 |
||||
} input_layer_t; |
||||
|
||||
typedef void(*input_capture_callback_t) |
||||
(void *user, button_t button, int32_t ascii_char); |
||||
|
||||
void input_init(); |
||||
void input_cleanup(); |
||||
void input_clear(); |
||||
|
||||
void input_bind(input_layer_t layer, button_t button, uint8_t action); |
||||
void input_unbind(input_layer_t layer,button_t button); |
||||
void input_unbind_all(input_layer_t layer); |
||||
|
||||
uint8_t input_bound_to_action(button_t button); |
||||
|
||||
void input_set_button_state(button_t button, float state); |
||||
void input_set_mouse_pos(int32_t x, int32_t y); |
||||
void input_textinput(int32_t ascii_char); |
||||
|
||||
void input_capture(input_capture_callback_t cb, void *user); |
||||
|
||||
float input_state(uint8_t action); |
||||
bool input_pressed(uint8_t action); |
||||
bool input_released(uint8_t action); |
||||
vec2_t input_mouse_pos(); |
||||
|
||||
button_t input_name_to_button(const char *name); |
||||
const char *input_button_to_name(button_t button); |
||||
|
||||
|
||||
#define INPUT_ACTION_COMMAND 31 |
||||
#define INPUT_ACTION_MAX 32 |
||||
#define INPUT_DEADZONE 0.1 |
||||
#define INPUT_DEADZONE_CAPTURE 0.5 |
||||
#define INPUT_ACTION_NONE 255 |
||||
#define INPUT_BUTTON_NONE 0 |
||||
|
||||
#endif |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,728 @@ |
||||
/*
|
||||
|
||||
Copyright (c) 2023, Dominic Szablewski - https://phoboslab.org
|
||||
SPDX-License-Identifier: MIT |
||||
|
||||
QOA - The "Quite OK Audio" format for fast, lossy audio compression |
||||
|
||||
|
||||
-- Data Format |
||||
|
||||
QOA encodes pulse-code modulated (PCM) audio data with up to 255 channels,
|
||||
sample rates from 1 up to 16777215 hertz and a bit depth of 16 bits. |
||||
|
||||
The compression method employed in QOA is lossy; it discards some information |
||||
from the uncompressed PCM data. For many types of audio signals this compression |
||||
is "transparent", i.e. the difference from the original file is often not |
||||
audible. |
||||
|
||||
QOA encodes 20 samples of 16 bit PCM data into slices of 64 bits. A single |
||||
sample therefore requires 3.2 bits of storage space, resulting in a 5x |
||||
compression (16 / 3.2). |
||||
|
||||
A QOA file consists of an 8 byte file header, followed by a number of frames. |
||||
Each frame contains an 8 byte frame header, the current 16 byte en-/decoder |
||||
state per channel and 256 slices per channel. Each slice is 8 bytes wide and |
||||
encodes 20 samples of audio data. |
||||
|
||||
All values, including the slices, are big endian. The file layout is as follows: |
||||
|
||||
struct { |
||||
struct { |
||||
char magic[4]; // magic bytes "qoaf"
|
||||
uint32_t samples; // samples per channel in this file
|
||||
} file_header;
|
||||
|
||||
struct { |
||||
struct { |
||||
uint8_t num_channels; // no. of channels
|
||||
uint24_t samplerate; // samplerate in hz
|
||||
uint16_t fsamples; // samples per channel in this frame
|
||||
uint16_t fsize; // frame size (includes this header)
|
||||
} frame_header;
|
||||
|
||||
struct { |
||||
int16_t history[4]; // most recent last
|
||||
int16_t weights[4]; // most recent last
|
||||
} lms_state[num_channels];
|
||||
|
||||
qoa_slice_t slices[256][num_channels]; |
||||
|
||||
} frames[ceil(samples / (256 * 20))]; |
||||
} qoa_file_t; |
||||
|
||||
Each `qoa_slice_t` contains a quantized scalefactor `sf_quant` and 20 quantized |
||||
residuals `qrNN`: |
||||
|
||||
.- QOA_SLICE -- 64 bits, 20 samples --------------------------/ /------------. |
||||
| Byte[0] | Byte[1] | Byte[2] \ \ Byte[7] | |
||||
| 7 6 5 4 3 2 1 0 | 7 6 5 4 3 2 1 0 | 7 6 5 / / 2 1 0 | |
||||
|------------+--------+--------+--------+---------+---------+-\ \--+---------| |
||||
| sf_quant | qr00 | qr01 | qr02 | qr03 | qr04 | / / | qr19 | |
||||
`-------------------------------------------------------------\ \------------` |
||||
|
||||
Each frame except the last must contain exactly 256 slices per channel. The last |
||||
frame may contain between 1 .. 256 (inclusive) slices per channel. The last |
||||
slice (for each channel) in the last frame may contain less than 20 samples; the |
||||
slice still must be 8 bytes wide, with the unused samples zeroed out. |
||||
|
||||
Channels are interleaved per slice. E.g. for 2 channel stereo:
|
||||
slice[0] = L, slice[1] = R, slice[2] = L, slice[3] = R ... |
||||
|
||||
A valid QOA file or stream must have at least one frame. Each frame must contain |
||||
at least one channel and one sample with a samplerate between 1 .. 16777215 |
||||
(inclusive). |
||||
|
||||
If the total number of samples is not known by the encoder, the samples in the |
||||
file header may be set to 0x00000000 to indicate that the encoder is
|
||||
"streaming". In a streaming context, the samplerate and number of channels may |
||||
differ from frame to frame. For static files (those with samples set to a |
||||
non-zero value), each frame must have the same number of channels and same |
||||
samplerate. |
||||
|
||||
Note that this implementation of QOA only handles files with a known total |
||||
number of samples. |
||||
|
||||
A decoder should support at least 8 channels. The channel layout for channel |
||||
counts 1 .. 8 is: |
||||
|
||||
1. Mono |
||||
2. L, R |
||||
3. L, R, C
|
||||
4. FL, FR, B/SL, B/SR
|
||||
5. FL, FR, C, B/SL, B/SR
|
||||
6. FL, FR, C, LFE, B/SL, B/SR |
||||
7. FL, FR, C, LFE, B, SL, SR
|
||||
8. FL, FR, C, LFE, BL, BR, SL, SR |
||||
|
||||
QOA predicts each audio sample based on the previously decoded ones using a |
||||
"Sign-Sign Least Mean Squares Filter" (LMS). This prediction plus the
|
||||
dequantized residual forms the final output sample. |
||||
|
||||
*/ |
||||
|
||||
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
Header - Public functions */ |
||||
|
||||
#ifndef QOA_H |
||||
#define QOA_H |
||||
|
||||
#ifdef __cplusplus |
||||
extern "C" { |
||||
#endif |
||||
|
||||
#define QOA_MIN_FILESIZE 16 |
||||
#define QOA_MAX_CHANNELS 8 |
||||
|
||||
#define QOA_SLICE_LEN 20 |
||||
#define QOA_SLICES_PER_FRAME 256 |
||||
#define QOA_FRAME_LEN (QOA_SLICES_PER_FRAME * QOA_SLICE_LEN) |
||||
#define QOA_LMS_LEN 4 |
||||
#define QOA_MAGIC 0x716f6166 /* 'qoaf' */ |
||||
|
||||
#define QOA_FRAME_SIZE(channels, slices) \ |
||||
(8 + QOA_LMS_LEN * 4 * channels + 8 * slices * channels) |
||||
|
||||
typedef struct { |
||||
int history[QOA_LMS_LEN]; |
||||
int weights[QOA_LMS_LEN]; |
||||
} qoa_lms_t; |
||||
|
||||
typedef struct { |
||||
unsigned int channels; |
||||
unsigned int samplerate; |
||||
unsigned int samples; |
||||
qoa_lms_t lms[QOA_MAX_CHANNELS]; |
||||
#ifdef QOA_RECORD_TOTAL_ERROR |
||||
double error; |
||||
#endif |
||||
} qoa_desc; |
||||
|
||||
unsigned int qoa_encode_header(qoa_desc *qoa, unsigned char *bytes); |
||||
unsigned int qoa_encode_frame(const short *sample_data, qoa_desc *qoa, unsigned int frame_len, unsigned char *bytes); |
||||
void *qoa_encode(const short *sample_data, qoa_desc *qoa, unsigned int *out_len); |
||||
|
||||
unsigned int qoa_max_frame_size(qoa_desc *qoa); |
||||
unsigned int qoa_decode_header(const unsigned char *bytes, int size, qoa_desc *qoa); |
||||
unsigned int qoa_decode_frame(const unsigned char *bytes, unsigned int size, qoa_desc *qoa, short *sample_data, unsigned int *frame_len); |
||||
short *qoa_decode(const unsigned char *bytes, int size, qoa_desc *file); |
||||
|
||||
#ifndef QOA_NO_STDIO |
||||
|
||||
int qoa_write(const char *filename, const short *sample_data, qoa_desc *qoa); |
||||
void *qoa_read(const char *filename, qoa_desc *qoa); |
||||
|
||||
#endif /* QOA_NO_STDIO */ |
||||
|
||||
|
||||
#ifdef __cplusplus |
||||
} |
||||
#endif |
||||
#endif /* QOA_H */ |
||||
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
Implementation */ |
||||
|
||||
#ifdef QOA_IMPLEMENTATION |
||||
#include <stdlib.h> |
||||
|
||||
#ifndef QOA_MALLOC |
||||
#define QOA_MALLOC(sz) malloc(sz) |
||||
#define QOA_FREE(p) free(p) |
||||
#endif |
||||
|
||||
typedef unsigned long long qoa_uint64_t; |
||||
|
||||
|
||||
/* The quant_tab provides an index into the dequant_tab for residuals in the
|
||||
range of -8 .. 8. It maps this range to just 3bits and becomes less accurate at
|
||||
the higher end. Note that the residual zero is identical to the lowest positive
|
||||
value. This is mostly fine, since the qoa_div() function always rounds away
|
||||
from zero. */ |
||||
|
||||
static const int qoa_quant_tab[17] = { |
||||
7, 7, 7, 5, 5, 3, 3, 1, /* -8..-1 */ |
||||
0, /* 0 */ |
||||
0, 2, 2, 4, 4, 6, 6, 6 /* 1.. 8 */ |
||||
}; |
||||
|
||||
|
||||
/* We have 16 different scalefactors. Like the quantized residuals these become
|
||||
less accurate at the higher end. In theory, the highest scalefactor that we |
||||
would need to encode the highest 16bit residual is (2**16)/8 = 8192. However we |
||||
rely on the LMS filter to predict samples accurately enough that a maximum
|
||||
residual of one quarter of the 16 bit range is sufficient. I.e. with the
|
||||
scalefactor 2048 times the quant range of 8 we can encode residuals up to 2**14. |
||||
|
||||
The scalefactor values are computed as: |
||||
scalefactor_tab[s] <- round(pow(s + 1, 2.75)) */ |
||||
|
||||
static const int qoa_scalefactor_tab[16] = { |
||||
1, 7, 21, 45, 84, 138, 211, 304, 421, 562, 731, 928, 1157, 1419, 1715, 2048 |
||||
}; |
||||
|
||||
|
||||
/* The reciprocal_tab maps each of the 16 scalefactors to their rounded
|
||||
reciprocals 1/scalefactor. This allows us to calculate the scaled residuals in
|
||||
the encoder with just one multiplication instead of an expensive division. We
|
||||
do this in .16 fixed point with integers, instead of floats. |
||||
|
||||
The reciprocal_tab is computed as: |
||||
reciprocal_tab[s] <- ((1<<16) + scalefactor_tab[s] - 1) / scalefactor_tab[s] */ |
||||
|
||||
static const int qoa_reciprocal_tab[16] = { |
||||
65536, 9363, 3121, 1457, 781, 475, 311, 216, 156, 117, 90, 71, 57, 47, 39, 32 |
||||
}; |
||||
|
||||
|
||||
/* The dequant_tab maps each of the scalefactors and quantized residuals to
|
||||
their unscaled & dequantized version. |
||||
|
||||
Since qoa_div rounds away from the zero, the smallest entries are mapped to 3/4 |
||||
instead of 1. The dequant_tab assumes the following dequantized values for each
|
||||
of the quant_tab indices and is computed as: |
||||
float dqt[8] = {0.75, -0.75, 2.5, -2.5, 4.5, -4.5, 7, -7}; |
||||
dequant_tab[s][q] <- round_ties_away_from_zero(scalefactor_tab[s] * dqt[q]) |
||||
|
||||
The rounding employed here is "to nearest, ties away from zero", i.e. positive |
||||
and negative values are treated symmetrically. |
||||
*/ |
||||
|
||||
static const int qoa_dequant_tab[16][8] = { |
||||
{ 1, -1, 3, -3, 5, -5, 7, -7}, |
||||
{ 5, -5, 18, -18, 32, -32, 49, -49}, |
||||
{ 16, -16, 53, -53, 95, -95, 147, -147}, |
||||
{ 34, -34, 113, -113, 203, -203, 315, -315}, |
||||
{ 63, -63, 210, -210, 378, -378, 588, -588}, |
||||
{ 104, -104, 345, -345, 621, -621, 966, -966}, |
||||
{ 158, -158, 528, -528, 950, -950, 1477, -1477}, |
||||
{ 228, -228, 760, -760, 1368, -1368, 2128, -2128}, |
||||
{ 316, -316, 1053, -1053, 1895, -1895, 2947, -2947}, |
||||
{ 422, -422, 1405, -1405, 2529, -2529, 3934, -3934}, |
||||
{ 548, -548, 1828, -1828, 3290, -3290, 5117, -5117}, |
||||
{ 696, -696, 2320, -2320, 4176, -4176, 6496, -6496}, |
||||
{ 868, -868, 2893, -2893, 5207, -5207, 8099, -8099}, |
||||
{1064, -1064, 3548, -3548, 6386, -6386, 9933, -9933}, |
||||
{1286, -1286, 4288, -4288, 7718, -7718, 12005, -12005}, |
||||
{1536, -1536, 5120, -5120, 9216, -9216, 14336, -14336}, |
||||
}; |
||||
|
||||
|
||||
/* The Least Mean Squares Filter is the heart of QOA. It predicts the next
|
||||
sample based on the previous 4 reconstructed samples. It does so by continuously |
||||
adjusting 4 weights based on the residual of the previous prediction. |
||||
|
||||
The next sample is predicted as the sum of (weight[i] * history[i]). |
||||
|
||||
The adjustment of the weights is done with a "Sign-Sign-LMS" that adds or |
||||
subtracts the residual to each weight, based on the corresponding sample from
|
||||
the history. This, surprisingly, is sufficient to get worthwhile predictions. |
||||
|
||||
This is all done with fixed point integers. Hence the right-shifts when updating |
||||
the weights and calculating the prediction. */ |
||||
|
||||
static int qoa_lms_predict(qoa_lms_t *lms) { |
||||
int prediction = 0; |
||||
for (int i = 0; i < QOA_LMS_LEN; i++) { |
||||
prediction += lms->weights[i] * lms->history[i]; |
||||
} |
||||
return prediction >> 13; |
||||
} |
||||
|
||||
static void qoa_lms_update(qoa_lms_t *lms, int sample, int residual) { |
||||
int delta = residual >> 4; |
||||
for (int i = 0; i < QOA_LMS_LEN; i++) { |
||||
lms->weights[i] += lms->history[i] < 0 ? -delta : delta; |
||||
} |
||||
|
||||
for (int i = 0; i < QOA_LMS_LEN-1; i++) { |
||||
lms->history[i] = lms->history[i+1]; |
||||
} |
||||
lms->history[QOA_LMS_LEN-1] = sample; |
||||
} |
||||
|
||||
|
||||
/* qoa_div() implements a rounding division, but avoids rounding to zero for
|
||||
small numbers. E.g. 0.1 will be rounded to 1. Note that 0 itself still
|
||||
returns as 0, which is handled in the qoa_quant_tab[]. |
||||
qoa_div() takes an index into the .16 fixed point qoa_reciprocal_tab as an |
||||
argument, so it can do the division with a cheaper integer multiplication. */ |
||||
|
||||
static inline int qoa_div(int v, int scalefactor) { |
||||
int reciprocal = qoa_reciprocal_tab[scalefactor]; |
||||
int n = (v * reciprocal + (1 << 15)) >> 16; |
||||
n = n + ((v > 0) - (v < 0)) - ((n > 0) - (n < 0)); /* round away from 0 */ |
||||
return n; |
||||
} |
||||
|
||||
static inline int qoa_clamp(int v, int min, int max) { |
||||
if (v < min) { return min; } |
||||
if (v > max) { return max; } |
||||
return v; |
||||
} |
||||
|
||||
/* This specialized clamp function for the signed 16 bit range improves decode
|
||||
performance quite a bit. The extra if() statement works nicely with the CPUs |
||||
branch prediction as this branch is rarely taken. */ |
||||
|
||||
static inline int qoa_clamp_s16(int v) { |
||||
if ((unsigned int)(v + 32768) > 65535) { |
||||
if (v < -32768) { return -32768; } |
||||
if (v > 32767) { return 32767; } |
||||
} |
||||
return v; |
||||
} |
||||
|
||||
static inline qoa_uint64_t qoa_read_u64(const unsigned char *bytes, unsigned int *p) { |
||||
bytes += *p; |
||||
*p += 8; |
||||
return
|
||||
((qoa_uint64_t)(bytes[0]) << 56) | ((qoa_uint64_t)(bytes[1]) << 48) | |
||||
((qoa_uint64_t)(bytes[2]) << 40) | ((qoa_uint64_t)(bytes[3]) << 32) | |
||||
((qoa_uint64_t)(bytes[4]) << 24) | ((qoa_uint64_t)(bytes[5]) << 16) | |
||||
((qoa_uint64_t)(bytes[6]) << 8) | ((qoa_uint64_t)(bytes[7]) << 0); |
||||
} |
||||
|
||||
static inline void qoa_write_u64(qoa_uint64_t v, unsigned char *bytes, unsigned int *p) { |
||||
bytes += *p; |
||||
*p += 8; |
||||
bytes[0] = (v >> 56) & 0xff; |
||||
bytes[1] = (v >> 48) & 0xff; |
||||
bytes[2] = (v >> 40) & 0xff; |
||||
bytes[3] = (v >> 32) & 0xff; |
||||
bytes[4] = (v >> 24) & 0xff; |
||||
bytes[5] = (v >> 16) & 0xff; |
||||
bytes[6] = (v >> 8) & 0xff; |
||||
bytes[7] = (v >> 0) & 0xff; |
||||
} |
||||
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
Encoder */ |
||||
|
||||
unsigned int qoa_encode_header(qoa_desc *qoa, unsigned char *bytes) { |
||||
unsigned int p = 0; |
||||
qoa_write_u64(((qoa_uint64_t)QOA_MAGIC << 32) | qoa->samples, bytes, &p); |
||||
return p; |
||||
} |
||||
|
||||
unsigned int qoa_encode_frame(const short *sample_data, qoa_desc *qoa, unsigned int frame_len, unsigned char *bytes) { |
||||
unsigned int channels = qoa->channels; |
||||
|
||||
unsigned int p = 0; |
||||
unsigned int slices = (frame_len + QOA_SLICE_LEN - 1) / QOA_SLICE_LEN; |
||||
unsigned int frame_size = QOA_FRAME_SIZE(channels, slices); |
||||
int prev_scalefactor[QOA_MAX_CHANNELS] = {0}; |
||||
|
||||
/* Write the frame header */ |
||||
qoa_write_u64(( |
||||
(qoa_uint64_t)qoa->channels << 56 | |
||||
(qoa_uint64_t)qoa->samplerate << 32 | |
||||
(qoa_uint64_t)frame_len << 16 | |
||||
(qoa_uint64_t)frame_size |
||||
), bytes, &p); |
||||
|
||||
|
||||
for (int c = 0; c < channels; c++) { |
||||
/* If the weights have grown too large, reset them to 0. This may happen
|
||||
with certain high-frequency sounds. This is a last resort and will
|
||||
introduce quite a bit of noise, but should at least prevent pops/clicks */ |
||||
int weights_sum =
|
||||
qoa->lms[c].weights[0] * qoa->lms[c].weights[0] +
|
||||
qoa->lms[c].weights[1] * qoa->lms[c].weights[1] +
|
||||
qoa->lms[c].weights[2] * qoa->lms[c].weights[2] +
|
||||
qoa->lms[c].weights[3] * qoa->lms[c].weights[3]; |
||||
if (weights_sum > 0x2fffffff) { |
||||
qoa->lms[c].weights[0] = 0; |
||||
qoa->lms[c].weights[1] = 0; |
||||
qoa->lms[c].weights[2] = 0; |
||||
qoa->lms[c].weights[3] = 0; |
||||
} |
||||
|
||||
/* Write the current LMS state */ |
||||
qoa_uint64_t weights = 0; |
||||
qoa_uint64_t history = 0; |
||||
for (int i = 0; i < QOA_LMS_LEN; i++) { |
||||
history = (history << 16) | (qoa->lms[c].history[i] & 0xffff); |
||||
weights = (weights << 16) | (qoa->lms[c].weights[i] & 0xffff); |
||||
} |
||||
qoa_write_u64(history, bytes, &p); |
||||
qoa_write_u64(weights, bytes, &p); |
||||
} |
||||
|
||||
/* We encode all samples with the channels interleaved on a slice level.
|
||||
E.g. for stereo: (ch-0, slice 0), (ch 1, slice 0), (ch 0, slice 1), ...*/ |
||||
for (int sample_index = 0; sample_index < frame_len; sample_index += QOA_SLICE_LEN) { |
||||
|
||||
for (int c = 0; c < channels; c++) { |
||||
int slice_len = qoa_clamp(QOA_SLICE_LEN, 0, frame_len - sample_index); |
||||
int slice_start = sample_index * channels + c; |
||||
int slice_end = (sample_index + slice_len) * channels + c;
|
||||
|
||||
/* Brute for search for the best scalefactor. Just go through all
|
||||
16 scalefactors, encode all samples for the current slice and
|
||||
meassure the total squared error. */ |
||||
qoa_uint64_t best_error = -1; |
||||
qoa_uint64_t best_slice; |
||||
qoa_lms_t best_lms; |
||||
int best_scalefactor; |
||||
|
||||
for (int sfi = 0; sfi < 16; sfi++) { |
||||
/* There is a strong correlation between the scalefactors of
|
||||
neighboring slices. As an optimization, start testing |
||||
the best scalefactor of the previous slice first. */ |
||||
int scalefactor = (sfi + prev_scalefactor[c]) % 16; |
||||
|
||||
/* We have to reset the LMS state to the last known good one
|
||||
before trying each scalefactor, as each pass updates the LMS |
||||
state when encoding. */ |
||||
qoa_lms_t lms = qoa->lms[c]; |
||||
qoa_uint64_t slice = scalefactor; |
||||
qoa_uint64_t current_error = 0; |
||||
|
||||
for (int si = slice_start; si < slice_end; si += channels) { |
||||
int sample = sample_data[si]; |
||||
int predicted = qoa_lms_predict(&lms); |
||||
|
||||
int residual = sample - predicted; |
||||
int scaled = qoa_div(residual, scalefactor); |
||||
int clamped = qoa_clamp(scaled, -8, 8); |
||||
int quantized = qoa_quant_tab[clamped + 8]; |
||||
int dequantized = qoa_dequant_tab[scalefactor][quantized]; |
||||
int reconstructed = qoa_clamp_s16(predicted + dequantized); |
||||
|
||||
long long error = (sample - reconstructed); |
||||
current_error += error * error; |
||||
if (current_error > best_error) { |
||||
break; |
||||
} |
||||
|
||||
qoa_lms_update(&lms, reconstructed, dequantized); |
||||
slice = (slice << 3) | quantized; |
||||
} |
||||
|
||||
if (current_error < best_error) { |
||||
best_error = current_error; |
||||
best_slice = slice; |
||||
best_lms = lms; |
||||
best_scalefactor = scalefactor; |
||||
} |
||||
} |
||||
|
||||
prev_scalefactor[c] = best_scalefactor; |
||||
|
||||
qoa->lms[c] = best_lms; |
||||
#ifdef QOA_RECORD_TOTAL_ERROR |
||||
qoa->error += best_error; |
||||
#endif |
||||
|
||||
/* If this slice was shorter than QOA_SLICE_LEN, we have to left-
|
||||
shift all encoded data, to ensure the rightmost bits are the empty |
||||
ones. This should only happen in the last frame of a file as all |
||||
slices are completely filled otherwise. */ |
||||
best_slice <<= (QOA_SLICE_LEN - slice_len) * 3; |
||||
qoa_write_u64(best_slice, bytes, &p); |
||||
} |
||||
} |
||||
|
||||
return p; |
||||
} |
||||
|
||||
void *qoa_encode(const short *sample_data, qoa_desc *qoa, unsigned int *out_len) { |
||||
if ( |
||||
qoa->samples == 0 ||
|
||||
qoa->samplerate == 0 || qoa->samplerate > 0xffffff || |
||||
qoa->channels == 0 || qoa->channels > QOA_MAX_CHANNELS |
||||
) { |
||||
return NULL; |
||||
} |
||||
|
||||
/* Calculate the encoded size and allocate */ |
||||
unsigned int num_frames = (qoa->samples + QOA_FRAME_LEN-1) / QOA_FRAME_LEN; |
||||
unsigned int num_slices = (qoa->samples + QOA_SLICE_LEN-1) / QOA_SLICE_LEN; |
||||
unsigned int encoded_size = 8 + /* 8 byte file header */ |
||||
num_frames * 8 + /* 8 byte frame headers */ |
||||
num_frames * QOA_LMS_LEN * 4 * qoa->channels + /* 4 * 4 bytes lms state per channel */ |
||||
num_slices * 8 * qoa->channels; /* 8 byte slices */ |
||||
|
||||
unsigned char *bytes = QOA_MALLOC(encoded_size); |
||||
|
||||
for (int c = 0; c < qoa->channels; c++) { |
||||
/* Set the initial LMS weights to {0, 0, -1, 2}. This helps with the
|
||||
prediction of the first few ms of a file. */ |
||||
qoa->lms[c].weights[0] = 0; |
||||
qoa->lms[c].weights[1] = 0; |
||||
qoa->lms[c].weights[2] = -(1<<13); |
||||
qoa->lms[c].weights[3] = (1<<14); |
||||
|
||||
/* Explicitly set the history samples to 0, as we might have some
|
||||
garbage in there. */ |
||||
for (int i = 0; i < QOA_LMS_LEN; i++) { |
||||
qoa->lms[c].history[i] = 0; |
||||
} |
||||
} |
||||
|
||||
|
||||
/* Encode the header and go through all frames */ |
||||
unsigned int p = qoa_encode_header(qoa, bytes); |
||||
#ifdef QOA_RECORD_TOTAL_ERROR |
||||
qoa->error = 0; |
||||
#endif |
||||
|
||||
int frame_len = QOA_FRAME_LEN; |
||||
for (int sample_index = 0; sample_index < qoa->samples; sample_index += frame_len) { |
||||
frame_len = qoa_clamp(QOA_FRAME_LEN, 0, qoa->samples - sample_index);
|
||||
const short *frame_samples = sample_data + sample_index * qoa->channels; |
||||
unsigned int frame_size = qoa_encode_frame(frame_samples, qoa, frame_len, bytes + p); |
||||
p += frame_size; |
||||
} |
||||
|
||||
*out_len = p; |
||||
return bytes; |
||||
} |
||||
|
||||
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
Decoder */ |
||||
|
||||
unsigned int qoa_max_frame_size(qoa_desc *qoa) { |
||||
return QOA_FRAME_SIZE(qoa->channels, QOA_SLICES_PER_FRAME); |
||||
} |
||||
|
||||
unsigned int qoa_decode_header(const unsigned char *bytes, int size, qoa_desc *qoa) { |
||||
unsigned int p = 0; |
||||
if (size < QOA_MIN_FILESIZE) { |
||||
return 0; |
||||
} |
||||
|
||||
|
||||
/* Read the file header, verify the magic number ('qoaf') and read the
|
||||
total number of samples. */ |
||||
qoa_uint64_t file_header = qoa_read_u64(bytes, &p); |
||||
|
||||
if ((file_header >> 32) != QOA_MAGIC) { |
||||
return 0; |
||||
} |
||||
|
||||
qoa->samples = file_header & 0xffffffff; |
||||
if (!qoa->samples) { |
||||
return 0; |
||||
} |
||||
|
||||
/* Peek into the first frame header to get the number of channels and
|
||||
the samplerate. */ |
||||
qoa_uint64_t frame_header = qoa_read_u64(bytes, &p); |
||||
qoa->channels = (frame_header >> 56) & 0x0000ff; |
||||
qoa->samplerate = (frame_header >> 32) & 0xffffff; |
||||
|
||||
if (qoa->channels == 0 || qoa->samples == 0 || qoa->samplerate == 0) { |
||||
return 0; |
||||
} |
||||
|
||||
return 8; |
||||
} |
||||
|
||||
unsigned int qoa_decode_frame(const unsigned char *bytes, unsigned int size, qoa_desc *qoa, short *sample_data, unsigned int *frame_len) { |
||||
unsigned int p = 0; |
||||
*frame_len = 0; |
||||
|
||||
if (size < 8 + QOA_LMS_LEN * 4 * qoa->channels) { |
||||
return 0; |
||||
} |
||||
|
||||
/* Read and verify the frame header */ |
||||
qoa_uint64_t frame_header = qoa_read_u64(bytes, &p); |
||||
int channels = (frame_header >> 56) & 0x0000ff; |
||||
int samplerate = (frame_header >> 32) & 0xffffff; |
||||
int samples = (frame_header >> 16) & 0x00ffff; |
||||
int frame_size = (frame_header ) & 0x00ffff; |
||||
|
||||
int data_size = frame_size - 8 - QOA_LMS_LEN * 4 * channels; |
||||
int num_slices = data_size / 8; |
||||
int max_total_samples = num_slices * QOA_SLICE_LEN; |
||||
|
||||
if ( |
||||
channels != qoa->channels ||
|
||||
samplerate != qoa->samplerate || |
||||
frame_size > size || |
||||
samples * channels > max_total_samples |
||||
) { |
||||
return 0; |
||||
} |
||||
|
||||
|
||||
/* Read the LMS state: 4 x 2 bytes history, 4 x 2 bytes weights per channel */ |
||||
for (int c = 0; c < channels; c++) { |
||||
qoa_uint64_t history = qoa_read_u64(bytes, &p); |
||||
qoa_uint64_t weights = qoa_read_u64(bytes, &p); |
||||
for (int i = 0; i < QOA_LMS_LEN; i++) { |
||||
qoa->lms[c].history[i] = ((signed short)(history >> 48)); |
||||
history <<= 16; |
||||
qoa->lms[c].weights[i] = ((signed short)(weights >> 48)); |
||||
weights <<= 16; |
||||
} |
||||
} |
||||
|
||||
|
||||
/* Decode all slices for all channels in this frame */ |
||||
for (int sample_index = 0; sample_index < samples; sample_index += QOA_SLICE_LEN) { |
||||
for (int c = 0; c < channels; c++) { |
||||
qoa_uint64_t slice = qoa_read_u64(bytes, &p); |
||||
|
||||
int scalefactor = (slice >> 60) & 0xf; |
||||
int slice_start = sample_index * channels + c; |
||||
int slice_end = qoa_clamp(sample_index + QOA_SLICE_LEN, 0, samples) * channels + c; |
||||
|
||||
for (int si = slice_start; si < slice_end; si += channels) { |
||||
int predicted = qoa_lms_predict(&qoa->lms[c]); |
||||
int quantized = (slice >> 57) & 0x7; |
||||
int dequantized = qoa_dequant_tab[scalefactor][quantized]; |
||||
int reconstructed = qoa_clamp_s16(predicted + dequantized); |
||||
|
||||
sample_data[si] = reconstructed; |
||||
slice <<= 3; |
||||
|
||||
qoa_lms_update(&qoa->lms[c], reconstructed, dequantized); |
||||
} |
||||
} |
||||
} |
||||
|
||||
*frame_len = samples; |
||||
return p; |
||||
} |
||||
|
||||
short *qoa_decode(const unsigned char *bytes, int size, qoa_desc *qoa) { |
||||
unsigned int p = qoa_decode_header(bytes, size, qoa); |
||||
if (!p) { |
||||
return NULL; |
||||
} |
||||
|
||||
/* Calculate the required size of the sample buffer and allocate */ |
||||
int total_samples = qoa->samples * qoa->channels; |
||||
short *sample_data = QOA_MALLOC(total_samples * sizeof(short)); |
||||
|
||||
unsigned int sample_index = 0; |
||||
unsigned int frame_len; |
||||
unsigned int frame_size; |
||||
|
||||
/* Decode all frames */ |
||||
do { |
||||
short *sample_ptr = sample_data + sample_index * qoa->channels; |
||||
frame_size = qoa_decode_frame(bytes + p, size - p, qoa, sample_ptr, &frame_len); |
||||
|
||||
p += frame_size; |
||||
sample_index += frame_len; |
||||
} while (frame_size && sample_index < qoa->samples); |
||||
|
||||
qoa->samples = sample_index; |
||||
return sample_data; |
||||
} |
||||
|
||||
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
File read/write convenience functions */ |
||||
|
||||
#ifndef QOA_NO_STDIO |
||||
#include <stdio.h> |
||||
|
||||
int qoa_write(const char *filename, const short *sample_data, qoa_desc *qoa) { |
||||
FILE *f = fopen(filename, "wb"); |
||||
unsigned int size; |
||||
void *encoded; |
||||
|
||||
if (!f) { |
||||
return 0; |
||||
} |
||||
|
||||
encoded = qoa_encode(sample_data, qoa, &size); |
||||
if (!encoded) { |
||||
fclose(f); |
||||
return 0; |
||||
} |
||||
|
||||
fwrite(encoded, 1, size, f); |
||||
fclose(f); |
||||
|
||||
QOA_FREE(encoded); |
||||
return size; |
||||
} |
||||
|
||||
void *qoa_read(const char *filename, qoa_desc *qoa) { |
||||
FILE *f = fopen(filename, "rb"); |
||||
int size, bytes_read; |
||||
void *data; |
||||
short *sample_data; |
||||
|
||||
if (!f) { |
||||
return NULL; |
||||
} |
||||
|
||||
fseek(f, 0, SEEK_END); |
||||
size = ftell(f); |
||||
if (size <= 0) { |
||||
fclose(f); |
||||
return NULL; |
||||
} |
||||
fseek(f, 0, SEEK_SET); |
||||
|
||||
data = QOA_MALLOC(size); |
||||
if (!data) { |
||||
fclose(f); |
||||
return NULL; |
||||
} |
||||
|
||||
bytes_read = fread(data, 1, size, f); |
||||
fclose(f); |
||||
|
||||
sample_data = qoa_decode(data, bytes_read, qoa); |
||||
QOA_FREE(data); |
||||
return sample_data; |
||||
} |
||||
|
||||
#endif /* QOA_NO_STDIO */ |
||||
#endif /* QOA_IMPLEMENTATION */ |
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,324 @@ |
||||
#if defined(SOKOL_IMPL) && !defined(SOKOL_TIME_IMPL) |
||||
#define SOKOL_TIME_IMPL |
||||
#endif |
||||
#ifndef SOKOL_TIME_INCLUDED |
||||
/*
|
||||
sokol_time.h -- simple cross-platform time measurement |
||||
|
||||
Project URL: https://github.com/floooh/sokol
|
||||
|
||||
Do this: |
||||
#define SOKOL_IMPL or |
||||
#define SOKOL_TIME_IMPL |
||||
before you include this file in *one* C or C++ file to create the |
||||
implementation. |
||||
|
||||
Optionally provide the following defines with your own implementations: |
||||
SOKOL_ASSERT(c) - your own assert macro (default: assert(c)) |
||||
SOKOL_TIME_API_DECL - public function declaration prefix (default: extern) |
||||
SOKOL_API_DECL - same as SOKOL_TIME_API_DECL |
||||
SOKOL_API_IMPL - public function implementation prefix (default: -) |
||||
|
||||
If sokol_time.h is compiled as a DLL, define the following before |
||||
including the declaration or implementation: |
||||
|
||||
SOKOL_DLL |
||||
|
||||
On Windows, SOKOL_DLL will define SOKOL_TIME_API_DECL as __declspec(dllexport) |
||||
or __declspec(dllimport) as needed. |
||||
|
||||
void stm_setup(); |
||||
Call once before any other functions to initialize sokol_time |
||||
(this calls for instance QueryPerformanceFrequency on Windows) |
||||
|
||||
uint64_t stm_now(); |
||||
Get current point in time in unspecified 'ticks'. The value that |
||||
is returned has no relation to the 'wall-clock' time and is |
||||
not in a specific time unit, it is only useful to compute |
||||
time differences. |
||||
|
||||
uint64_t stm_diff(uint64_t new, uint64_t old); |
||||
Computes the time difference between new and old. This will always |
||||
return a positive, non-zero value. |
||||
|
||||
uint64_t stm_since(uint64_t start); |
||||
Takes the current time, and returns the elapsed time since start |
||||
(this is a shortcut for "stm_diff(stm_now(), start)") |
||||
|
||||
uint64_t stm_laptime(uint64_t* last_time); |
||||
This is useful for measuring frame time and other recurring |
||||
events. It takes the current time, returns the time difference |
||||
to the value in last_time, and stores the current time in |
||||
last_time for the next call. If the value in last_time is 0, |
||||
the return value will be zero (this usually happens on the |
||||
very first call). |
||||
|
||||
uint64_t stm_round_to_common_refresh_rate(uint64_t duration) |
||||
This oddly named function takes a measured frame time and |
||||
returns the closest "nearby" common display refresh rate frame duration |
||||
in ticks. If the input duration isn't close to any common display |
||||
refresh rate, the input duration will be returned unchanged as a fallback. |
||||
The main purpose of this function is to remove jitter/inaccuracies from |
||||
measured frame times, and instead use the display refresh rate as |
||||
frame duration. |
||||
|
||||
Use the following functions to convert a duration in ticks into |
||||
useful time units: |
||||
|
||||
double stm_sec(uint64_t ticks); |
||||
double stm_ms(uint64_t ticks); |
||||
double stm_us(uint64_t ticks); |
||||
double stm_ns(uint64_t ticks); |
||||
Converts a tick value into seconds, milliseconds, microseconds |
||||
or nanoseconds. Note that not all platforms will have nanosecond |
||||
or even microsecond precision. |
||||
|
||||
Uses the following time measurement functions under the hood: |
||||
|
||||
Windows: QueryPerformanceFrequency() / QueryPerformanceCounter() |
||||
MacOS/iOS: mach_absolute_time() |
||||
emscripten: performance.now() |
||||
Linux+others: clock_gettime(CLOCK_MONOTONIC) |
||||
|
||||
zlib/libpng license |
||||
|
||||
Copyright (c) 2018 Andre Weissflog |
||||
|
||||
This software is provided 'as-is', without any express or implied warranty. |
||||
In no event will the authors be held liable for any damages arising from the |
||||
use of this software. |
||||
|
||||
Permission is granted to anyone to use this software for any purpose, |
||||
including commercial applications, and to alter it and redistribute it |
||||
freely, subject to the following restrictions: |
||||
|
||||
1. The origin of this software must not be misrepresented; you must not |
||||
claim that you wrote the original software. If you use this software in a |
||||
product, an acknowledgment in the product documentation would be |
||||
appreciated but is not required. |
||||
|
||||
2. Altered source versions must be plainly marked as such, and must not |
||||
be misrepresented as being the original software. |
||||
|
||||
3. This notice may not be removed or altered from any source |
||||
distribution. |
||||
*/ |
||||
#define SOKOL_TIME_INCLUDED (1) |
||||
#include <stdint.h> |
||||
|
||||
#if defined(SOKOL_API_DECL) && !defined(SOKOL_TIME_API_DECL) |
||||
#define SOKOL_TIME_API_DECL SOKOL_API_DECL |
||||
#endif |
||||
#ifndef SOKOL_TIME_API_DECL |
||||
#if defined(_WIN32) && defined(SOKOL_DLL) && defined(SOKOL_TIME_IMPL) |
||||
#define SOKOL_TIME_API_DECL __declspec(dllexport) |
||||
#elif defined(_WIN32) && defined(SOKOL_DLL) |
||||
#define SOKOL_TIME_API_DECL __declspec(dllimport) |
||||
#else |
||||
#define SOKOL_TIME_API_DECL extern |
||||
#endif |
||||
#endif |
||||
|
||||
#ifdef __cplusplus |
||||
extern "C" { |
||||
#endif |
||||
|
||||
SOKOL_TIME_API_DECL void stm_setup(void); |
||||
SOKOL_TIME_API_DECL uint64_t stm_now(void); |
||||
SOKOL_TIME_API_DECL uint64_t stm_diff(uint64_t new_ticks, uint64_t old_ticks); |
||||
SOKOL_TIME_API_DECL uint64_t stm_since(uint64_t start_ticks); |
||||
SOKOL_TIME_API_DECL uint64_t stm_laptime(uint64_t* last_time); |
||||
SOKOL_TIME_API_DECL uint64_t stm_round_to_common_refresh_rate(uint64_t frame_ticks); |
||||
SOKOL_TIME_API_DECL double stm_sec(uint64_t ticks); |
||||
SOKOL_TIME_API_DECL double stm_ms(uint64_t ticks); |
||||
SOKOL_TIME_API_DECL double stm_us(uint64_t ticks); |
||||
SOKOL_TIME_API_DECL double stm_ns(uint64_t ticks); |
||||
|
||||
#ifdef __cplusplus |
||||
} /* extern "C" */ |
||||
#endif |
||||
#endif // SOKOL_TIME_INCLUDED
|
||||
|
||||
/*-- IMPLEMENTATION ----------------------------------------------------------*/ |
||||
#ifdef SOKOL_TIME_IMPL |
||||
#define SOKOL_TIME_IMPL_INCLUDED (1) |
||||
#include <string.h> /* memset */ |
||||
|
||||
#ifndef SOKOL_API_IMPL |
||||
#define SOKOL_API_IMPL |
||||
#endif |
||||
#ifndef SOKOL_ASSERT |
||||
#include <assert.h> |
||||
#define SOKOL_ASSERT(c) assert(c) |
||||
#endif |
||||
#ifndef _SOKOL_PRIVATE |
||||
#if defined(__GNUC__) || defined(__clang__) |
||||
#define _SOKOL_PRIVATE __attribute__((unused)) static |
||||
#else |
||||
#define _SOKOL_PRIVATE static |
||||
#endif |
||||
#endif |
||||
|
||||
#if defined(_WIN32) |
||||
#ifndef WIN32_LEAN_AND_MEAN |
||||
#define WIN32_LEAN_AND_MEAN |
||||
#endif |
||||
#include <windows.h> |
||||
typedef struct { |
||||
uint32_t initialized; |
||||
LARGE_INTEGER freq; |
||||
LARGE_INTEGER start; |
||||
} _stm_state_t; |
||||
#elif defined(__APPLE__) && defined(__MACH__) |
||||
#include <mach/mach_time.h> |
||||
typedef struct { |
||||
uint32_t initialized; |
||||
mach_timebase_info_data_t timebase; |
||||
uint64_t start; |
||||
} _stm_state_t; |
||||
#elif defined(__EMSCRIPTEN__) |
||||
#include <emscripten/emscripten.h> |
||||
typedef struct { |
||||
uint32_t initialized; |
||||
double start; |
||||
} _stm_state_t; |
||||
#else /* anything else, this will need more care for non-Linux platforms */ |
||||
#ifdef ESP8266 |
||||
// On the ESP8266, clock_gettime ignores the first argument and CLOCK_MONOTONIC isn't defined
|
||||
#define CLOCK_MONOTONIC 0 |
||||
#endif |
||||
#include <time.h> |
||||
typedef struct { |
||||
uint32_t initialized; |
||||
uint64_t start; |
||||
} _stm_state_t; |
||||
#endif |
||||
static _stm_state_t _stm; |
||||
|
||||
/* prevent 64-bit overflow when computing relative timestamp
|
||||
see https://gist.github.com/jspohr/3dc4f00033d79ec5bdaf67bc46c813e3
|
||||
*/ |
||||
#if defined(_WIN32) || (defined(__APPLE__) && defined(__MACH__)) |
||||
_SOKOL_PRIVATE int64_t int64_muldiv(int64_t value, int64_t numer, int64_t denom) { |
||||
int64_t q = value / denom; |
||||
int64_t r = value % denom; |
||||
return q * numer + r * numer / denom; |
||||
} |
||||
#endif |
||||
|
||||
#if defined(__EMSCRIPTEN__) |
||||
EM_JS(double, stm_js_perfnow, (void), { |
||||
return performance.now(); |
||||
}); |
||||
#endif |
||||
|
||||
SOKOL_API_IMPL void stm_setup(void) { |
||||
memset(&_stm, 0, sizeof(_stm)); |
||||
_stm.initialized = 0xABCDABCD; |
||||
#if defined(_WIN32) |
||||
QueryPerformanceFrequency(&_stm.freq); |
||||
QueryPerformanceCounter(&_stm.start); |
||||
#elif defined(__APPLE__) && defined(__MACH__) |
||||
mach_timebase_info(&_stm.timebase); |
||||
_stm.start = mach_absolute_time(); |
||||
#elif defined(__EMSCRIPTEN__) |
||||
_stm.start = stm_js_perfnow(); |
||||
#else |
||||
struct timespec ts; |
||||
clock_gettime(CLOCK_MONOTONIC, &ts); |
||||
_stm.start = (uint64_t)ts.tv_sec*1000000000 + (uint64_t)ts.tv_nsec; |
||||
#endif |
||||
} |
||||
|
||||
SOKOL_API_IMPL uint64_t stm_now(void) { |
||||
SOKOL_ASSERT(_stm.initialized == 0xABCDABCD); |
||||
uint64_t now; |
||||
#if defined(_WIN32) |
||||
LARGE_INTEGER qpc_t; |
||||
QueryPerformanceCounter(&qpc_t); |
||||
now = (uint64_t) int64_muldiv(qpc_t.QuadPart - _stm.start.QuadPart, 1000000000, _stm.freq.QuadPart); |
||||
#elif defined(__APPLE__) && defined(__MACH__) |
||||
const uint64_t mach_now = mach_absolute_time() - _stm.start; |
||||
now = (uint64_t) int64_muldiv((int64_t)mach_now, (int64_t)_stm.timebase.numer, (int64_t)_stm.timebase.denom); |
||||
#elif defined(__EMSCRIPTEN__) |
||||
double js_now = stm_js_perfnow() - _stm.start; |
||||
SOKOL_ASSERT(js_now >= 0.0); |
||||
now = (uint64_t) (js_now * 1000000.0); |
||||
#else |
||||
struct timespec ts; |
||||
clock_gettime(CLOCK_MONOTONIC, &ts); |
||||
now = ((uint64_t)ts.tv_sec*1000000000 + (uint64_t)ts.tv_nsec) - _stm.start; |
||||
#endif |
||||
return now; |
||||
} |
||||
|
||||
SOKOL_API_IMPL uint64_t stm_diff(uint64_t new_ticks, uint64_t old_ticks) { |
||||
if (new_ticks > old_ticks) { |
||||
return new_ticks - old_ticks; |
||||
} |
||||
else { |
||||
return 1; |
||||
} |
||||
} |
||||
|
||||
SOKOL_API_IMPL uint64_t stm_since(uint64_t start_ticks) { |
||||
return stm_diff(stm_now(), start_ticks); |
||||
} |
||||
|
||||
SOKOL_API_IMPL uint64_t stm_laptime(uint64_t* last_time) { |
||||
SOKOL_ASSERT(last_time); |
||||
uint64_t dt = 0; |
||||
uint64_t now = stm_now(); |
||||
if (0 != *last_time) { |
||||
dt = stm_diff(now, *last_time); |
||||
} |
||||
*last_time = now; |
||||
return dt; |
||||
} |
||||
|
||||
// first number is frame duration in ns, second number is tolerance in ns,
|
||||
// the resulting min/max values must not overlap!
|
||||
static const uint64_t _stm_refresh_rates[][2] = { |
||||
{ 16666667, 1000000 }, // 60 Hz: 16.6667 +- 1ms
|
||||
{ 13888889, 250000 }, // 72 Hz: 13.8889 +- 0.25ms
|
||||
{ 13333333, 250000 }, // 75 Hz: 13.3333 +- 0.25ms
|
||||
{ 11764706, 250000 }, // 85 Hz: 11.7647 +- 0.25
|
||||
{ 11111111, 250000 }, // 90 Hz: 11.1111 +- 0.25ms
|
||||
{ 10000000, 500000 }, // 100 Hz: 10.0000 +- 0.5ms
|
||||
{ 8333333, 500000 }, // 120 Hz: 8.3333 +- 0.5ms
|
||||
{ 6944445, 500000 }, // 144 Hz: 6.9445 +- 0.5ms
|
||||
{ 4166667, 1000000 }, // 240 Hz: 4.1666 +- 1ms
|
||||
{ 0, 0 }, // keep the last element always at zero
|
||||
}; |
||||
|
||||
SOKOL_API_IMPL uint64_t stm_round_to_common_refresh_rate(uint64_t ticks) { |
||||
uint64_t ns; |
||||
int i = 0; |
||||
while (0 != (ns = _stm_refresh_rates[i][0])) { |
||||
uint64_t tol = _stm_refresh_rates[i][1]; |
||||
if ((ticks > (ns - tol)) && (ticks < (ns + tol))) { |
||||
return ns; |
||||
} |
||||
i++; |
||||
} |
||||
// fallthough: didn't fit into any buckets
|
||||
return ticks; |
||||
} |
||||
|
||||
SOKOL_API_IMPL double stm_sec(uint64_t ticks) { |
||||
return (double)ticks / 1000000000.0; |
||||
} |
||||
|
||||
SOKOL_API_IMPL double stm_ms(uint64_t ticks) { |
||||
return (double)ticks / 1000000.0; |
||||
} |
||||
|
||||
SOKOL_API_IMPL double stm_us(uint64_t ticks) { |
||||
return (double)ticks / 1000.0; |
||||
} |
||||
|
||||
SOKOL_API_IMPL double stm_ns(uint64_t ticks) { |
||||
return (double)ticks; |
||||
} |
||||
#endif /* SOKOL_TIME_IMPL */ |
||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,81 @@ |
||||
#include <stdlib.h> |
||||
#include <string.h> |
||||
|
||||
#include "mem.h" |
||||
#include "utils.h" |
||||
|
||||
|
||||
static uint8_t hunk[MEM_HUNK_BYTES]; |
||||
static uint32_t bump_len = 0; |
||||
static uint32_t temp_len = 0; |
||||
|
||||
static uint32_t temp_objects[MEM_TEMP_OBJECTS_MAX] = {}; |
||||
static uint32_t temp_objects_len; |
||||
|
||||
|
||||
// Bump allocator - returns bytes from the front of the hunk
|
||||
|
||||
// These allocations persist for many frames. The allocator level is reset
|
||||
// whenever we load a new race track or menu in game_set_scene()
|
||||
|
||||
void *mem_mark() { |
||||
return &hunk[bump_len]; |
||||
} |
||||
|
||||
void *mem_bump(uint32_t size) { |
||||
error_if(bump_len + temp_len + size >= MEM_HUNK_BYTES, "Failed to allocate %d bytes in hunk mem", size); |
||||
uint8_t *p = &hunk[bump_len]; |
||||
bump_len += size; |
||||
memset(p, 0, size); |
||||
return p; |
||||
} |
||||
|
||||
void mem_reset(void *p) { |
||||
uint32_t offset = (uint8_t *)p - (uint8_t *)hunk; |
||||
error_if(offset > bump_len || offset > MEM_HUNK_BYTES, "Invalid mem reset"); |
||||
bump_len = offset; |
||||
} |
||||
|
||||
|
||||
|
||||
// Temp allocator - returns bytes from the back of the hunk
|
||||
|
||||
// Temporary allocated bytes are not allowed to persist for multiple frames. You
|
||||
// need to explicitly free them when you are done. Temp allocated bytes don't
|
||||
// have be freed in reverse allocation order. I.e. you can allocate A then B,
|
||||
// and aftewards free A then B.
|
||||
|
||||
void *mem_temp_alloc(uint32_t size) { |
||||
size = ((size >> 3) + 7) << 3; // allign to 8 bytes
|
||||
|
||||
error_if(bump_len + temp_len + size >= MEM_HUNK_BYTES, "Failed to allocate %d bytes in temp mem", size); |
||||
error_if(temp_objects_len >= MEM_TEMP_OBJECTS_MAX, "MEM_TEMP_OBJECTS_MAX reached"); |
||||
|
||||
temp_len += size; |
||||
void *p = &hunk[MEM_HUNK_BYTES - temp_len]; |
||||
temp_objects[temp_objects_len++] = temp_len; |
||||
return p; |
||||
} |
||||
|
||||
void mem_temp_free(void *p) { |
||||
uint32_t offset = (uint8_t *)&hunk[MEM_HUNK_BYTES] - (uint8_t *)p; |
||||
error_if(offset > MEM_HUNK_BYTES, "Object 0x%p not in temp hunk", p); |
||||
|
||||
bool found = false; |
||||
uint32_t remaining_max = 0; |
||||
for (int i = 0; i < temp_objects_len; i++) { |
||||
if (temp_objects[i] == offset) { |
||||
temp_objects[i--] = temp_objects[--temp_objects_len]; |
||||
found = true; |
||||
} |
||||
else if (temp_objects[i] > remaining_max) { |
||||
remaining_max = temp_objects[i]; |
||||
} |
||||
} |
||||
error_if(!found, "Object 0x%p not in temp hunk", p); |
||||
temp_len = remaining_max; |
||||
} |
||||
|
||||
void mem_temp_check() { |
||||
error_if(temp_len != 0, "Temp memory not free: %d object(s)", temp_objects_len); |
||||
} |
@ -0,0 +1,17 @@ |
||||
#ifndef MEM_H |
||||
#define MEM_H |
||||
|
||||
#include "types.h" |
||||
|
||||
#define MEM_TEMP_OBJECTS_MAX 8 |
||||
#define MEM_HUNK_BYTES (4 * 1024 * 1024) |
||||
|
||||
void *mem_bump(uint32_t size); |
||||
void *mem_mark(); |
||||
void mem_reset(void *p); |
||||
|
||||
void *mem_temp_alloc(uint32_t size); |
||||
void mem_temp_free(void *p); |
||||
void mem_temp_check(); |
||||
|
||||
#endif |
@ -0,0 +1,16 @@ |
||||
#ifndef PLATFORM_H |
||||
#define PLATFORM_H |
||||
|
||||
#include "types.h" |
||||
|
||||
void platform_exit(); |
||||
vec2i_t platform_screen_size(); |
||||
double platform_now(); |
||||
void platform_set_fullscreen(bool fullscreen); |
||||
void platform_set_audio_mix_cb(void (*cb)(float *buffer, uint32_t len)); |
||||
|
||||
#if defined(RENDERER_SOFTWARE) |
||||
rgba_t *platform_get_screenbuffer(int32_t *pitch); |
||||
#endif |
||||
|
||||
#endif |
@ -0,0 +1,350 @@ |
||||
#include <SDL2/SDL.h> |
||||
|
||||
#include "platform.h" |
||||
#include "input.h" |
||||
#include "system.h" |
||||
|
||||
static uint64_t perf_freq = 0; |
||||
static bool wants_to_exit = false; |
||||
static SDL_Window *window; |
||||
static SDL_AudioDeviceID audio_device; |
||||
static SDL_GameController *gamepad; |
||||
static void (*audio_callback)(float *buffer, uint32_t len) = NULL; |
||||
|
||||
|
||||
uint8_t platform_sdl_gamepad_map[] = { |
||||
[SDL_CONTROLLER_BUTTON_A] = INPUT_GAMEPAD_A, |
||||
[SDL_CONTROLLER_BUTTON_B] = INPUT_GAMEPAD_B, |
||||
[SDL_CONTROLLER_BUTTON_X] = INPUT_GAMEPAD_X, |
||||
[SDL_CONTROLLER_BUTTON_Y] = INPUT_GAMEPAD_Y, |
||||
[SDL_CONTROLLER_BUTTON_BACK] = INPUT_GAMEPAD_SELECT, |
||||
[SDL_CONTROLLER_BUTTON_GUIDE] = INPUT_GAMEPAD_HOME, |
||||
[SDL_CONTROLLER_BUTTON_START] = INPUT_GAMEPAD_START, |
||||
[SDL_CONTROLLER_BUTTON_LEFTSTICK] = INPUT_GAMEPAD_L_STICK_PRESS, |
||||
[SDL_CONTROLLER_BUTTON_RIGHTSTICK] = INPUT_GAMEPAD_R_STICK_PRESS, |
||||
[SDL_CONTROLLER_BUTTON_LEFTSHOULDER] = INPUT_GAMEPAD_L_SHOULDER, |
||||
[SDL_CONTROLLER_BUTTON_RIGHTSHOULDER] = INPUT_GAMEPAD_R_SHOULDER, |
||||
[SDL_CONTROLLER_BUTTON_DPAD_UP] = INPUT_GAMEPAD_DPAD_UP, |
||||
[SDL_CONTROLLER_BUTTON_DPAD_DOWN] = INPUT_GAMEPAD_DPAD_DOWN, |
||||
[SDL_CONTROLLER_BUTTON_DPAD_LEFT] = INPUT_GAMEPAD_DPAD_LEFT, |
||||
[SDL_CONTROLLER_BUTTON_DPAD_RIGHT] = INPUT_GAMEPAD_DPAD_RIGHT, |
||||
[SDL_CONTROLLER_BUTTON_MAX] = INPUT_INVALID |
||||
}; |
||||
|
||||
|
||||
uint8_t platform_sdl_axis_map[] = { |
||||
[SDL_CONTROLLER_AXIS_LEFTX] = INPUT_GAMEPAD_L_STICK_LEFT, |
||||
[SDL_CONTROLLER_AXIS_LEFTY] = INPUT_GAMEPAD_L_STICK_UP, |
||||
[SDL_CONTROLLER_AXIS_RIGHTX] = INPUT_GAMEPAD_R_STICK_LEFT, |
||||
[SDL_CONTROLLER_AXIS_RIGHTY] = INPUT_GAMEPAD_R_STICK_UP, |
||||
[SDL_CONTROLLER_AXIS_TRIGGERLEFT] = INPUT_GAMEPAD_L_TRIGGER, |
||||
[SDL_CONTROLLER_AXIS_TRIGGERRIGHT] = INPUT_GAMEPAD_R_TRIGGER, |
||||
[SDL_CONTROLLER_AXIS_MAX] = INPUT_INVALID |
||||
}; |
||||
|
||||
|
||||
void platform_exit() { |
||||
wants_to_exit = true; |
||||
} |
||||
|
||||
SDL_GameController *platform_find_gamepad() { |
||||
for (int i = 0; i < SDL_NumJoysticks(); i++) { |
||||
if (SDL_IsGameController(i)) { |
||||
return SDL_GameControllerOpen(i); |
||||
} |
||||
} |
||||
|
||||
return NULL; |
||||
} |
||||
|
||||
|
||||
void platform_pump_events() { |
||||
SDL_Event ev; |
||||
while (SDL_PollEvent(&ev)) { |
||||
// Input Keyboard
|
||||
if (ev.type == SDL_KEYDOWN || ev.type == SDL_KEYUP) { |
||||
int code = ev.key.keysym.scancode; |
||||
float state = ev.type == SDL_KEYDOWN ? 1.0 : 0.0; |
||||
if (code >= SDL_SCANCODE_LCTRL && code <= SDL_SCANCODE_RALT) { |
||||
int code_internal = code - SDL_SCANCODE_LCTRL + INPUT_KEY_LCTRL; |
||||
input_set_button_state(code_internal, state); |
||||
} |
||||
else if (code > 0 && code < INPUT_KEY_MAX) { |
||||
input_set_button_state(code, state); |
||||
} |
||||
} |
||||
|
||||
else if (ev.type == SDL_TEXTINPUT) { |
||||
input_textinput(ev.text.text[0]); |
||||
} |
||||
|
||||
// Gamepads connect/disconnect
|
||||
else if (ev.type == SDL_CONTROLLERDEVICEADDED) { |
||||
gamepad = SDL_GameControllerOpen(ev.cdevice.which); |
||||
} |
||||
else if (ev.type == SDL_CONTROLLERDEVICEREMOVED) { |
||||
if (gamepad && ev.cdevice.which == SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(gamepad))) { |
||||
SDL_GameControllerClose(gamepad); |
||||
gamepad = platform_find_gamepad(); |
||||
} |
||||
} |
||||
|
||||
// Input Gamepad Buttons
|
||||
else if ( |
||||
ev.type == SDL_CONTROLLERBUTTONDOWN ||
|
||||
ev.type == SDL_CONTROLLERBUTTONUP |
||||
) { |
||||
if (ev.cbutton.button < SDL_CONTROLLER_BUTTON_MAX) { |
||||
button_t button = platform_sdl_gamepad_map[ev.cbutton.button]; |
||||
if (button != INPUT_INVALID) { |
||||
float state = ev.type == SDL_CONTROLLERBUTTONDOWN ? 1.0 : 0.0; |
||||
input_set_button_state(button, state); |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Input Gamepad Axis
|
||||
else if (ev.type == SDL_CONTROLLERAXISMOTION) { |
||||
float state = (float)ev.caxis.value / 32767.0; |
||||
|
||||
if (ev.caxis.axis < SDL_CONTROLLER_AXIS_MAX) { |
||||
int code = platform_sdl_axis_map[ev.caxis.axis]; |
||||
if ( |
||||
code == INPUT_GAMEPAD_L_TRIGGER ||
|
||||
code == INPUT_GAMEPAD_R_TRIGGER |
||||
) { |
||||
input_set_button_state(code, state); |
||||
} |
||||
else if (state > 0) { |
||||
input_set_button_state(code, 0.0); |
||||
input_set_button_state(code+1, state); |
||||
} |
||||
else { |
||||
input_set_button_state(code, -state); |
||||
input_set_button_state(code+1, 0.0); |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Mouse buttons
|
||||
else if ( |
||||
ev.type == SDL_MOUSEBUTTONDOWN || |
||||
ev.type == SDL_MOUSEBUTTONUP |
||||
) { |
||||
button_t button = INPUT_BUTTON_NONE; |
||||
switch (ev.button.button) { |
||||
case SDL_BUTTON_LEFT: button = INPUT_MOUSE_LEFT; break; |
||||
case SDL_BUTTON_MIDDLE: button = INPUT_MOUSE_MIDDLE; break; |
||||
case SDL_BUTTON_RIGHT: button = INPUT_MOUSE_RIGHT; break; |
||||
default: break; |
||||
} |
||||
if (button != INPUT_BUTTON_NONE) { |
||||
float state = ev.type == SDL_MOUSEBUTTONDOWN ? 1.0 : 0.0; |
||||
input_set_button_state(button, state); |
||||
} |
||||
} |
||||
|
||||
// Mouse wheel
|
||||
else if (ev.type == SDL_MOUSEWHEEL) { |
||||
button_t button = ev.wheel.y > 0
|
||||
? INPUT_MOUSE_WHEEL_UP |
||||
: INPUT_MOUSE_WHEEL_DOWN; |
||||
input_set_button_state(button, 1.0); |
||||
input_set_button_state(button, 0.0); |
||||
} |
||||
|
||||
// Mouse move
|
||||
else if (ev.type == SDL_MOUSEMOTION) { |
||||
input_set_mouse_pos(ev.motion.x, ev.motion.y); |
||||
} |
||||
|
||||
// Window Events
|
||||
if (ev.type == SDL_QUIT) { |
||||
wants_to_exit = true; |
||||
} |
||||
else if ( |
||||
ev.type == SDL_WINDOWEVENT && |
||||
( |
||||
ev.window.event == SDL_WINDOWEVENT_SIZE_CHANGED || |
||||
ev.window.event == SDL_WINDOWEVENT_RESIZED |
||||
) |
||||
) { |
||||
system_resize(platform_screen_size()); |
||||
} |
||||
} |
||||
} |
||||
|
||||
double platform_now() { |
||||
uint64_t perf_counter = SDL_GetPerformanceCounter(); |
||||
return (double)perf_counter / (double)perf_freq; |
||||
} |
||||
|
||||
void platform_set_fullscreen(bool fullscreen) { |
||||
if (fullscreen) { |
||||
int32_t display = SDL_GetWindowDisplayIndex(window); |
||||
|
||||
SDL_DisplayMode mode; |
||||
SDL_GetDesktopDisplayMode(display, &mode); |
||||
SDL_SetWindowDisplayMode(window, &mode); |
||||
SDL_SetWindowFullscreen(window, SDL_WINDOW_FULLSCREEN); |
||||
SDL_ShowCursor(SDL_DISABLE); |
||||
} |
||||
else { |
||||
SDL_SetWindowFullscreen(window, 0); |
||||
SDL_ShowCursor(SDL_ENABLE); |
||||
} |
||||
} |
||||
|
||||
void platform_audio_callback(void* userdata, uint8_t* stream, int len) { |
||||
if (audio_callback) { |
||||
audio_callback((float *)stream, len/sizeof(float)); |
||||
} |
||||
else { |
||||
memset(stream, 0, len); |
||||
} |
||||
} |
||||
|
||||
void platform_set_audio_mix_cb(void (*cb)(float *buffer, uint32_t len)) { |
||||
audio_callback = cb; |
||||
SDL_PauseAudioDevice(audio_device, 0); |
||||
} |
||||
|
||||
|
||||
#if defined(RENDERER_GL) // ----------------------------------------------------
|
||||
#define PLATFORM_WINDOW_FLAGS SDL_WINDOW_OPENGL |
||||
SDL_GLContext platform_gl; |
||||
|
||||
void platform_video_init() { |
||||
#if defined(USE_GLES2) |
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES); |
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2); |
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1); |
||||
#endif |
||||
|
||||
platform_gl = SDL_GL_CreateContext(window); |
||||
SDL_GL_SetSwapInterval(1); |
||||
} |
||||
|
||||
void platform_prepare_frame() { |
||||
// nothing
|
||||
} |
||||
|
||||
void platform_video_cleanup() { |
||||
SDL_GL_DeleteContext(platform_gl); |
||||
} |
||||
|
||||
void platform_end_frame() { |
||||
SDL_GL_SwapWindow(window); |
||||
} |
||||
|
||||
vec2i_t platform_screen_size() { |
||||
int width, height; |
||||
SDL_GL_GetDrawableSize(window, &width, &height); |
||||
return vec2i(width, height); |
||||
} |
||||
|
||||
|
||||
#elif defined(RENDERER_SOFTWARE) // ----------------------------------------------
|
||||
#define PLATFORM_WINDOW_FLAGS 0 |
||||
static SDL_Renderer *renderer; |
||||
static SDL_Texture *screenbuffer = NULL; |
||||
static void *screenbuffer_pixels = NULL; |
||||
static int screenbuffer_pitch; |
||||
static vec2i_t screenbuffer_size = vec2i(0, 0); |
||||
static vec2i_t screen_size = vec2i(0, 0); |
||||
|
||||
|
||||
void platform_video_init() { |
||||
renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED); |
||||
} |
||||
|
||||
void platform_video_cleanup() { |
||||
|
||||
} |
||||
|
||||
void platform_prepare_frame() { |
||||
if (screen_size.x != screenbuffer_size.x || screen_size.y != screenbuffer_size.y) { |
||||
if (screenbuffer) { |
||||
SDL_DestroyTexture(screenbuffer); |
||||
} |
||||
screenbuffer = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ABGR8888, SDL_TEXTUREACCESS_STREAMING, screen_size.x, screen_size.y); |
||||
screenbuffer_size = screen_size; |
||||
} |
||||
SDL_LockTexture(screenbuffer, NULL, &screenbuffer_pixels, &screenbuffer_pitch); |
||||
} |
||||
|
||||
void platform_end_frame() { |
||||
screenbuffer_pixels = NULL; |
||||
SDL_UnlockTexture(screenbuffer); |
||||
SDL_RenderCopy(renderer, screenbuffer, NULL, NULL); |
||||
SDL_RenderPresent(renderer); |
||||
} |
||||
|
||||
rgba_t *platform_get_screenbuffer(int32_t *pitch) { |
||||
*pitch = screenbuffer_pitch; |
||||
return screenbuffer_pixels; |
||||
} |
||||
|
||||
vec2i_t platform_screen_size() { |
||||
int width, height; |
||||
SDL_GetWindowSize(window, &width, &height); |
||||
|
||||
// float aspect = (float)width / (float)height;
|
||||
// screen_size = vec2i(240 * aspect, 240);
|
||||
screen_size = vec2i(width, height); |
||||
return screen_size; |
||||
} |
||||
|
||||
#else |
||||
#error "Unsupported renderer for platform SDL" |
||||
#endif |
||||
|
||||
|
||||
|
||||
int main(int argc, char *argv[]) { |
||||
SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER); |
||||
|
||||
int gcdb_res = SDL_GameControllerAddMappingsFromFile("gamecontrollerdb.txt"); |
||||
if (gcdb_res < 0) { |
||||
printf("Failed to load gamecontrollerdb.txt\n"); |
||||
} |
||||
else { |
||||
printf("load gamecontrollerdb.txt\n"); |
||||
} |
||||
|
||||
audio_device = SDL_OpenAudioDevice(NULL, 0, &(SDL_AudioSpec){ |
||||
.freq = 44100, |
||||
.format = AUDIO_F32, |
||||
.channels = 2, |
||||
.samples = 1024, |
||||
.callback = platform_audio_callback |
||||
}, NULL, 0); |
||||
|
||||
gamepad = platform_find_gamepad(); |
||||
|
||||
perf_freq = SDL_GetPerformanceFrequency(); |
||||
|
||||
window = SDL_CreateWindow( |
||||
SYSTEM_WINDOW_NAME, |
||||
SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, |
||||
SYSTEM_WINDOW_WIDTH, SYSTEM_WINDOW_HEIGHT, |
||||
SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE | PLATFORM_WINDOW_FLAGS | SDL_WINDOW_ALLOW_HIGHDPI |
||||
); |
||||
|
||||
platform_video_init(); |
||||
system_init(); |
||||
|
||||
while (!wants_to_exit) { |
||||
platform_pump_events(); |
||||
platform_prepare_frame(); |
||||
system_update(); |
||||
platform_end_frame(); |
||||
} |
||||
|
||||
system_cleanup(); |
||||
platform_video_cleanup(); |
||||
|
||||
SDL_CloseAudioDevice(audio_device); |
||||
SDL_Quit(); |
||||
return 0; |
||||
} |
@ -0,0 +1,256 @@ |
||||
#include "platform.h" |
||||
#include "system.h" |
||||
|
||||
#if defined(RENDERER_GL) |
||||
#ifdef __EMSCRIPTEN__ |
||||
#define SOKOL_GLES2 |
||||
#else |
||||
#define SOKOL_GLCORE33 |
||||
#endif |
||||
#else |
||||
#error "Unsupported renderer for platform SOKOL" |
||||
#endif |
||||
|
||||
#define SOKOL_IMPL |
||||
#include "libs/sokol_audio.h" |
||||
#include "libs/sokol_time.h" |
||||
#include "libs/sokol_app.h" |
||||
#include "input.h" |
||||
|
||||
static const uint8_t keyboard_map[] = { |
||||
[SAPP_KEYCODE_SPACE] = INPUT_KEY_SPACE, |
||||
[SAPP_KEYCODE_APOSTROPHE] = INPUT_KEY_APOSTROPHE, |
||||
[SAPP_KEYCODE_COMMA] = INPUT_KEY_COMMA, |
||||
[SAPP_KEYCODE_MINUS] = INPUT_KEY_MINUS, |
||||
[SAPP_KEYCODE_PERIOD] = INPUT_KEY_PERIOD, |
||||
[SAPP_KEYCODE_SLASH] = INPUT_KEY_SLASH, |
||||
[SAPP_KEYCODE_0] = INPUT_KEY_0, |
||||
[SAPP_KEYCODE_1] = INPUT_KEY_1, |
||||
[SAPP_KEYCODE_2] = INPUT_KEY_2, |
||||
[SAPP_KEYCODE_3] = INPUT_KEY_3, |
||||
[SAPP_KEYCODE_4] = INPUT_KEY_4, |
||||
[SAPP_KEYCODE_5] = INPUT_KEY_5, |
||||
[SAPP_KEYCODE_6] = INPUT_KEY_6, |
||||
[SAPP_KEYCODE_7] = INPUT_KEY_7, |
||||
[SAPP_KEYCODE_8] = INPUT_KEY_8, |
||||
[SAPP_KEYCODE_9] = INPUT_KEY_9, |
||||
[SAPP_KEYCODE_SEMICOLON] = INPUT_KEY_SEMICOLON, |
||||
[SAPP_KEYCODE_EQUAL] = INPUT_KEY_EQUALS, |
||||
[SAPP_KEYCODE_A] = INPUT_KEY_A, |
||||
[SAPP_KEYCODE_B] = INPUT_KEY_B, |
||||
[SAPP_KEYCODE_C] = INPUT_KEY_C, |
||||
[SAPP_KEYCODE_D] = INPUT_KEY_D, |
||||
[SAPP_KEYCODE_E] = INPUT_KEY_E, |
||||
[SAPP_KEYCODE_F] = INPUT_KEY_F, |
||||
[SAPP_KEYCODE_G] = INPUT_KEY_G, |
||||
[SAPP_KEYCODE_H] = INPUT_KEY_H, |
||||
[SAPP_KEYCODE_I] = INPUT_KEY_I, |
||||
[SAPP_KEYCODE_J] = INPUT_KEY_J, |
||||
[SAPP_KEYCODE_K] = INPUT_KEY_K, |
||||
[SAPP_KEYCODE_L] = INPUT_KEY_L, |
||||
[SAPP_KEYCODE_M] = INPUT_KEY_M, |
||||
[SAPP_KEYCODE_N] = INPUT_KEY_N, |
||||
[SAPP_KEYCODE_O] = INPUT_KEY_O, |
||||
[SAPP_KEYCODE_P] = INPUT_KEY_P, |
||||
[SAPP_KEYCODE_Q] = INPUT_KEY_Q, |
||||
[SAPP_KEYCODE_R] = INPUT_KEY_R, |
||||
[SAPP_KEYCODE_S] = INPUT_KEY_S, |
||||
[SAPP_KEYCODE_T] = INPUT_KEY_T, |
||||
[SAPP_KEYCODE_U] = INPUT_KEY_U, |
||||
[SAPP_KEYCODE_V] = INPUT_KEY_V, |
||||
[SAPP_KEYCODE_W] = INPUT_KEY_W, |
||||
[SAPP_KEYCODE_X] = INPUT_KEY_X, |
||||
[SAPP_KEYCODE_Y] = INPUT_KEY_Y, |
||||
[SAPP_KEYCODE_Z] = INPUT_KEY_Z, |
||||
[SAPP_KEYCODE_LEFT_BRACKET] = INPUT_KEY_LEFTBRACKET, |
||||
[SAPP_KEYCODE_BACKSLASH] = INPUT_KEY_BACKSLASH, |
||||
[SAPP_KEYCODE_RIGHT_BRACKET] = INPUT_KEY_RIGHTBRACKET, |
||||
[SAPP_KEYCODE_GRAVE_ACCENT] = INPUT_KEY_TILDE, |
||||
[SAPP_KEYCODE_WORLD_1] = INPUT_INVALID, // not implemented
|
||||
[SAPP_KEYCODE_WORLD_2] = INPUT_INVALID, // not implemented
|
||||
[SAPP_KEYCODE_ESCAPE] = INPUT_KEY_ESCAPE, |
||||
[SAPP_KEYCODE_ENTER] = INPUT_KEY_RETURN, |
||||
[SAPP_KEYCODE_TAB] = INPUT_KEY_TAB, |
||||
[SAPP_KEYCODE_BACKSPACE] = INPUT_KEY_BACKSPACE, |
||||
[SAPP_KEYCODE_INSERT] = INPUT_KEY_INSERT, |
||||
[SAPP_KEYCODE_DELETE] = INPUT_KEY_DELETE, |
||||
[SAPP_KEYCODE_RIGHT] = INPUT_KEY_RIGHT, |
||||
[SAPP_KEYCODE_LEFT] = INPUT_KEY_LEFT, |
||||
[SAPP_KEYCODE_DOWN] = INPUT_KEY_DOWN, |
||||
[SAPP_KEYCODE_UP] = INPUT_KEY_UP, |
||||
[SAPP_KEYCODE_PAGE_UP] = INPUT_KEY_PAGEUP, |
||||
[SAPP_KEYCODE_PAGE_DOWN] = INPUT_KEY_PAGEDOWN, |
||||
[SAPP_KEYCODE_HOME] = INPUT_KEY_HOME, |
||||
[SAPP_KEYCODE_END] = INPUT_KEY_END, |
||||
[SAPP_KEYCODE_CAPS_LOCK] = INPUT_KEY_CAPSLOCK, |
||||
[SAPP_KEYCODE_SCROLL_LOCK] = INPUT_KEY_SCROLLLOCK, |
||||
[SAPP_KEYCODE_NUM_LOCK] = INPUT_KEY_NUMLOCK, |
||||
[SAPP_KEYCODE_PRINT_SCREEN] = INPUT_KEY_PRINTSCREEN, |
||||
[SAPP_KEYCODE_PAUSE] = INPUT_KEY_PAUSE, |
||||
[SAPP_KEYCODE_F1] = INPUT_KEY_F1, |
||||
[SAPP_KEYCODE_F2] = INPUT_KEY_F2, |
||||
[SAPP_KEYCODE_F3] = INPUT_KEY_F3, |
||||
[SAPP_KEYCODE_F4] = INPUT_KEY_F4, |
||||
[SAPP_KEYCODE_F5] = INPUT_KEY_F5, |
||||
[SAPP_KEYCODE_F6] = INPUT_KEY_F6, |
||||
[SAPP_KEYCODE_F7] = INPUT_KEY_F7, |
||||
[SAPP_KEYCODE_F8] = INPUT_KEY_F8, |
||||
[SAPP_KEYCODE_F9] = INPUT_KEY_F9, |
||||
[SAPP_KEYCODE_F10] = INPUT_KEY_F10, |
||||
[SAPP_KEYCODE_F11] = INPUT_KEY_F11, |
||||
[SAPP_KEYCODE_F12] = INPUT_KEY_F12, |
||||
[SAPP_KEYCODE_F13] = INPUT_INVALID, // not implemented
|
||||
[SAPP_KEYCODE_F14] = INPUT_INVALID, // not implemented
|
||||
[SAPP_KEYCODE_F15] = INPUT_INVALID, // not implemented
|
||||
[SAPP_KEYCODE_F16] = INPUT_INVALID, // not implemented
|
||||
[SAPP_KEYCODE_F17] = INPUT_INVALID, // not implemented
|
||||
[SAPP_KEYCODE_F18] = INPUT_INVALID, // not implemented
|
||||
[SAPP_KEYCODE_F19] = INPUT_INVALID, // not implemented
|
||||
[SAPP_KEYCODE_F20] = INPUT_INVALID, // not implemented
|
||||
[SAPP_KEYCODE_F21] = INPUT_INVALID, // not implemented
|
||||
[SAPP_KEYCODE_F22] = INPUT_INVALID, // not implemented
|
||||
[SAPP_KEYCODE_F23] = INPUT_INVALID, // not implemented
|
||||
[SAPP_KEYCODE_F24] = INPUT_INVALID, // not implemented
|
||||
[SAPP_KEYCODE_F25] = INPUT_INVALID, // not implemented
|
||||
[SAPP_KEYCODE_KP_0] = INPUT_KEY_KP_0, |
||||
[SAPP_KEYCODE_KP_1] = INPUT_KEY_KP_1, |
||||
[SAPP_KEYCODE_KP_2] = INPUT_KEY_KP_2, |
||||
[SAPP_KEYCODE_KP_3] = INPUT_KEY_KP_3, |
||||
[SAPP_KEYCODE_KP_4] = INPUT_KEY_KP_4, |
||||
[SAPP_KEYCODE_KP_5] = INPUT_KEY_KP_5, |
||||
[SAPP_KEYCODE_KP_6] = INPUT_KEY_KP_6, |
||||
[SAPP_KEYCODE_KP_7] = INPUT_KEY_KP_7, |
||||
[SAPP_KEYCODE_KP_8] = INPUT_KEY_KP_8, |
||||
[SAPP_KEYCODE_KP_9] = INPUT_KEY_KP_9, |
||||
[SAPP_KEYCODE_KP_DECIMAL] = INPUT_KEY_KP_PERIOD, |
||||
[SAPP_KEYCODE_KP_DIVIDE] = INPUT_KEY_KP_DIVIDE, |
||||
[SAPP_KEYCODE_KP_MULTIPLY] = INPUT_KEY_KP_MULTIPLY, |
||||
[SAPP_KEYCODE_KP_SUBTRACT] = INPUT_KEY_KP_MINUS, |
||||
[SAPP_KEYCODE_KP_ADD] = INPUT_KEY_KP_PLUS, |
||||
[SAPP_KEYCODE_KP_ENTER] = INPUT_KEY_KP_ENTER, |
||||
[SAPP_KEYCODE_KP_EQUAL] = INPUT_INVALID, // not implemented
|
||||
[SAPP_KEYCODE_LEFT_SHIFT] = INPUT_KEY_LSHIFT, |
||||
[SAPP_KEYCODE_LEFT_CONTROL] = INPUT_KEY_LCTRL, |
||||
[SAPP_KEYCODE_LEFT_ALT] = INPUT_KEY_LALT, |
||||
[SAPP_KEYCODE_LEFT_SUPER] = INPUT_INVALID, // not implemented
|
||||
[SAPP_KEYCODE_RIGHT_SHIFT] = INPUT_KEY_RSHIFT, |
||||
[SAPP_KEYCODE_RIGHT_CONTROL] = INPUT_KEY_RCTRL, |
||||
[SAPP_KEYCODE_RIGHT_ALT] = INPUT_KEY_RALT, |
||||
[SAPP_KEYCODE_RIGHT_SUPER] = INPUT_INVALID, // not implemented
|
||||
[SAPP_KEYCODE_MENU] = INPUT_INVALID, // not implemented
|
||||
}; |
||||
|
||||
|
||||
static void (*audio_callback)(float *buffer, uint32_t len) = NULL; |
||||
|
||||
void platform_exit() { |
||||
sapp_quit(); |
||||
} |
||||
|
||||
vec2i_t platform_screen_size() { |
||||
return vec2i(sapp_width(), sapp_height()); |
||||
} |
||||
|
||||
double platform_now() { |
||||
return stm_sec(stm_now()); |
||||
} |
||||
|
||||
void platform_set_fullscreen(bool fullscreen) { |
||||
if (fullscreen == sapp_is_fullscreen()) { |
||||
return; |
||||
} |
||||
|
||||
sapp_toggle_fullscreen(); |
||||
sapp_show_mouse(!fullscreen); |
||||
} |
||||
|
||||
void platform_handle_event(const sapp_event* ev) { |
||||
// Input Keyboard
|
||||
if (ev->type == SAPP_EVENTTYPE_KEY_DOWN || ev->type == SAPP_EVENTTYPE_KEY_UP) { |
||||
float state = ev->type == SAPP_EVENTTYPE_KEY_DOWN ? 1.0 : 0.0; |
||||
if (ev->key_code > 0 && ev->key_code < sizeof(keyboard_map)) { |
||||
int code = keyboard_map[ev->key_code]; |
||||
input_set_button_state(code, state); |
||||
} |
||||
} |
||||
|
||||
else if (ev->type == SAPP_EVENTTYPE_CHAR) { |
||||
input_textinput(ev->char_code); |
||||
} |
||||
|
||||
|
||||
// Input Gamepad Axis
|
||||
// TODO: not implemented by sokol_app itself
|
||||
|
||||
// Mouse buttons
|
||||
else if ( |
||||
ev->type == SAPP_EVENTTYPE_MOUSE_DOWN || |
||||
ev->type == SAPP_EVENTTYPE_MOUSE_UP |
||||
) { |
||||
button_t button = INPUT_BUTTON_NONE; |
||||
switch (ev->mouse_button) { |
||||
case SAPP_MOUSEBUTTON_LEFT: button = INPUT_MOUSE_LEFT; break; |
||||
case SAPP_MOUSEBUTTON_MIDDLE: button = INPUT_MOUSE_MIDDLE; break; |
||||
case SAPP_MOUSEBUTTON_RIGHT: button = INPUT_MOUSE_RIGHT; break; |
||||
default: break; |
||||
} |
||||
if (button != INPUT_BUTTON_NONE) { |
||||
float state = ev->type == SAPP_EVENTTYPE_MOUSE_DOWN ? 1.0 : 0.0; |
||||
input_set_button_state(button, state); |
||||
} |
||||
} |
||||
|
||||
// Mouse wheel
|
||||
else if (ev->type == SAPP_EVENTTYPE_MOUSE_SCROLL) { |
||||
button_t button = ev->scroll_y > 0
|
||||
? INPUT_MOUSE_WHEEL_UP |
||||
: INPUT_MOUSE_WHEEL_DOWN; |
||||
input_set_button_state(button, 1.0); |
||||
input_set_button_state(button, 0.0); |
||||
} |
||||
|
||||
// Mouse move
|
||||
else if (ev->type == SAPP_EVENTTYPE_MOUSE_MOVE) { |
||||
input_set_mouse_pos(ev->mouse_x, ev->mouse_y); |
||||
} |
||||
|
||||
// Window Events
|
||||
if (ev->type == SAPP_EVENTTYPE_RESIZED) { |
||||
system_resize(vec2i(ev->window_width, ev->window_height)); |
||||
} |
||||
} |
||||
|
||||
void platform_audio_callback(float* buffer, int num_frames, int num_channels) { |
||||
if (audio_callback) { |
||||
audio_callback(buffer, num_frames * num_channels); |
||||
} |
||||
else { |
||||
memset(buffer, 0, num_frames * sizeof(float)); |
||||
} |
||||
} |
||||
|
||||
void platform_set_audio_mix_cb(void (*cb)(float *buffer, uint32_t len)) { |
||||
audio_callback = cb; |
||||
} |
||||
|
||||
sapp_desc sokol_main(int argc, char* argv[]) { |
||||
stm_setup(); |
||||
|
||||
saudio_setup(&(saudio_desc){ |
||||
.sample_rate = 44100, |
||||
.buffer_frames = 1024, |
||||
.num_packets = 256, |
||||
.num_channels = 2, |
||||
.stream_cb = platform_audio_callback, |
||||
}); |
||||
|
||||
return (sapp_desc) { |
||||
.width = SYSTEM_WINDOW_WIDTH, |
||||
.height = SYSTEM_WINDOW_HEIGHT, |
||||
.init_cb = system_init, |
||||
.frame_cb = system_update, |
||||
.cleanup_cb = system_cleanup, |
||||
.event_cb = platform_handle_event, |
||||
.win32_console_attach = true |
||||
}; |
||||
} |
@ -0,0 +1,64 @@ |
||||
#ifndef RENDER_H |
||||
#define RENDER_H |
||||
|
||||
#include "types.h" |
||||
|
||||
typedef enum { |
||||
RENDER_BLEND_NORMAL, |
||||
RENDER_BLEND_LIGHTER |
||||
} render_blend_mode_t; |
||||
|
||||
typedef enum { |
||||
RENDER_RES_NATIVE, |
||||
RENDER_RES_240P, |
||||
RENDER_RES_480P, |
||||
} render_resolution_t; |
||||
|
||||
typedef enum { |
||||
RENDER_POST_NONE, |
||||
RENDER_POST_CRT, |
||||
NUM_RENDER_POST_EFFCTS, |
||||
} render_post_effect_t; |
||||
|
||||
#define RENDER_USE_MIPMAPS 1 |
||||
|
||||
#define RENDER_FADEOUT_NEAR 48000.0 |
||||
#define RENDER_FADEOUT_FAR 64000.0 |
||||
|
||||
extern uint16_t RENDER_NO_TEXTURE; |
||||
|
||||
void render_init(vec2i_t screen_size); |
||||
void render_cleanup(); |
||||
|
||||
void render_set_screen_size(vec2i_t size); |
||||
void render_set_resolution(render_resolution_t res); |
||||
void render_set_post_effect(render_post_effect_t post); |
||||
vec2i_t render_size(); |
||||
|
||||
void render_frame_prepare(); |
||||
void render_frame_end(); |
||||
|
||||
void render_set_view(vec3_t pos, vec3_t angles); |
||||
void render_set_view_2d(); |
||||
void render_set_model_mat(mat4_t *m); |
||||
void render_set_depth_write(bool enabled); |
||||
void render_set_depth_test(bool enabled); |
||||
void render_set_depth_offset(float offset); |
||||
void render_set_screen_position(vec2_t pos); |
||||
void render_set_blend_mode(render_blend_mode_t mode); |
||||
void render_set_cull_backface(bool enabled); |
||||
|
||||
vec3_t render_transform(vec3_t pos); |
||||
void render_push_tris(tris_t tris, uint16_t texture); |
||||
void render_push_sprite(vec3_t pos, vec2i_t size, rgba_t color, uint16_t texture); |
||||
void render_push_2d(vec2i_t pos, vec2i_t size, rgba_t color, uint16_t texture); |
||||
void render_push_2d_tile(vec2i_t pos, vec2i_t uv_offset, vec2i_t uv_size, vec2i_t size, rgba_t color, uint16_t texture_index); |
||||
|
||||
uint16_t render_texture_create(uint32_t width, uint32_t height, rgba_t *pixels); |
||||
vec2i_t render_texture_size(uint16_t texture_index); |
||||
void render_texture_replace_pixels(int16_t texture_index, rgba_t *pixels); |
||||
uint16_t render_textures_len(); |
||||
void render_textures_reset(uint16_t len); |
||||
void render_textures_dump(const char *path); |
||||
|
||||
#endif |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,369 @@ |
||||
#include "system.h" |
||||
#include "render.h" |
||||
#include "mem.h" |
||||
#include "utils.h" |
||||
#include "platform.h" |
||||
|
||||
#define NEAR_PLANE 16.0 |
||||
#define FAR_PLANE (RENDER_FADEOUT_FAR) |
||||
#define TEXTURES_MAX 1024 |
||||
|
||||
|
||||
typedef struct { |
||||
vec2i_t size; |
||||
rgba_t *pixels; |
||||
} render_texture_t; |
||||
|
||||
static void line(vec2i_t p0, vec2i_t p1, rgba_t color); |
||||
|
||||
static rgba_t *screen_buffer; |
||||
static int32_t screen_pitch; |
||||
static int32_t screen_ppr; |
||||
static vec2i_t screen_size; |
||||
|
||||
static mat4_t view_mat = mat4_identity(); |
||||
static mat4_t mvp_mat = mat4_identity(); |
||||
static mat4_t projection_mat = mat4_identity(); |
||||
static mat4_t sprite_mat = mat4_identity(); |
||||
|
||||
static render_texture_t textures[TEXTURES_MAX]; |
||||
static uint32_t textures_len; |
||||
|
||||
uint16_t RENDER_NO_TEXTURE; |
||||
|
||||
|
||||
void render_init(vec2i_t screen_size) { |
||||
render_set_screen_size(screen_size); |
||||
textures_len = 0; |
||||
|
||||
rgba_t white_pixels[4] = { |
||||
rgba(128,128,128,255), rgba(128,128,128,255), |
||||
rgba(128,128,128,255), rgba(128,128,128,255) |
||||
}; |
||||
RENDER_NO_TEXTURE = render_texture_create(2, 2, white_pixels); |
||||
} |
||||
|
||||
void render_cleanup() {} |
||||
|
||||
void render_set_screen_size(vec2i_t size) { |
||||
screen_size = size; |
||||
|
||||
float aspect = (float)size.x / (float)size.y; |
||||
float fov = (73.75 / 180.0) * 3.14159265358; |
||||
float f = 1.0 / tan(fov / 2); |
||||
float nf = 1.0 / (NEAR_PLANE - FAR_PLANE); |
||||
projection_mat = mat4( |
||||
f / aspect, 0, 0, 0, |
||||
0, f, 0, 0,
|
||||
0, 0, (FAR_PLANE + NEAR_PLANE) * nf, -1,
|
||||
0, 0, 2 * FAR_PLANE * NEAR_PLANE * nf, 0 |
||||
); |
||||
} |
||||
|
||||
void render_set_resolution(render_resolution_t res) {} |
||||
void render_set_post_effect(render_post_effect_t post) {} |
||||
|
||||
vec2i_t render_size() { |
||||
return screen_size; |
||||
} |
||||
|
||||
|
||||
void render_frame_prepare() { |
||||
screen_buffer = platform_get_screenbuffer(&screen_pitch); |
||||
screen_ppr = screen_pitch / sizeof(rgba_t); |
||||
|
||||
rgba_t color = rgba(0, 0, 0, 255); |
||||
for (uint32_t y = 0; y < screen_size.y; y++) { |
||||
for (uint32_t x = 0; x < screen_size.x; x++) { |
||||
screen_buffer[y * screen_ppr + x] = color; |
||||
} |
||||
} |
||||
} |
||||
|
||||
void render_frame_end() {} |
||||
|
||||
void render_set_view(vec3_t pos, vec3_t angles) { |
||||
view_mat = mat4_identity(); |
||||
mat4_set_translation(&view_mat, vec3(0, 0, 0)); |
||||
mat4_set_roll_pitch_yaw(&view_mat, vec3(angles.x, -angles.y + M_PI, angles.z + M_PI)); |
||||
mat4_translate(&view_mat, vec3_inv(pos)); |
||||
mat4_set_yaw_pitch_roll(&sprite_mat, vec3(-angles.x, angles.y - M_PI, 0)); |
||||
|
||||
render_set_model_mat(&mat4_identity()); |
||||
} |
||||
|
||||
void render_set_view_2d() { |
||||
float near = -1; |
||||
float far = 1; |
||||
float left = 0; |
||||
float right = screen_size.x; |
||||
float bottom = screen_size.y; |
||||
float top = 0; |
||||
float lr = 1 / (left - right); |
||||
float bt = 1 / (bottom - top); |
||||
float nf = 1 / (near - far); |
||||
mvp_mat = mat4( |
||||
-2 * lr, 0, 0, 0, |
||||
0, -2 * bt, 0, 0, |
||||
0, 0, 2 * nf, 0,
|
||||
(left + right) * lr, (top + bottom) * bt, (far + near) * nf, 1 |
||||
); |
||||
} |
||||
|
||||
void render_set_model_mat(mat4_t *m) { |
||||
mat4_t vm_mat; |
||||
mat4_mul(&vm_mat, &view_mat, m); |
||||
mat4_mul(&mvp_mat, &projection_mat, &vm_mat); |
||||
} |
||||
|
||||
void render_set_depth_write(bool enabled) {} |
||||
void render_set_depth_test(bool enabled) {} |
||||
void render_set_depth_offset(float offset) {} |
||||
void render_set_screen_position(vec2_t pos) {} |
||||
void render_set_blend_mode(render_blend_mode_t mode) {} |
||||
void render_set_cull_backface(bool enabled) {} |
||||
|
||||
vec3_t render_transform(vec3_t pos) { |
||||
return vec3_transform(vec3_transform(pos, &view_mat), &projection_mat); |
||||
} |
||||
|
||||
void render_push_tris(tris_t tris, uint16_t texture_index) { |
||||
float w2 = screen_size.x * 0.5; |
||||
float h2 = screen_size.y * 0.5; |
||||
|
||||
vec3_t p0 = vec3_transform(tris.vertices[0].pos, &mvp_mat); |
||||
vec3_t p1 = vec3_transform(tris.vertices[1].pos, &mvp_mat); |
||||
vec3_t p2 = vec3_transform(tris.vertices[2].pos, &mvp_mat); |
||||
if (p0.z >= 1.0 || p1.z >= 1.0 || p2.z >= 1.0) { |
||||
return; |
||||
} |
||||
|
||||
vec2i_t sc0 = vec2i(p0.x * w2 + w2, h2 - p0.y * h2); |
||||
vec2i_t sc1 = vec2i(p1.x * w2 + w2, h2 - p1.y * h2); |
||||
vec2i_t sc2 = vec2i(p2.x * w2 + w2, h2 - p2.y * h2); |
||||
|
||||
rgba_t color = tris.vertices[0].color; |
||||
color.as_rgba.r = min(color.as_rgba.r * 2, 255); |
||||
color.as_rgba.g = min(color.as_rgba.g * 2, 255); |
||||
color.as_rgba.b = min(color.as_rgba.b * 2, 255); |
||||
color.as_rgba.a = clamp(color.as_rgba.a * (1.0-p0.z) * FAR_PLANE * (2.0/255.0), 0, 255); |
||||
|
||||
line(sc0, sc1, color); |
||||
line(sc1, sc2, color); |
||||
line(sc2, sc0, color); |
||||
} |
||||
|
||||
void render_push_sprite(vec3_t pos, vec2i_t size, rgba_t color, uint16_t texture_index) { |
||||
error_if(texture_index >= textures_len, "Invalid texture %d", texture_index); |
||||
|
||||
vec3_t p0 = vec3_add(pos, vec3_transform(vec3(-size.x * 0.5, -size.y * 0.5, 0), &sprite_mat)); |
||||
vec3_t p1 = vec3_add(pos, vec3_transform(vec3( size.x * 0.5, -size.y * 0.5, 0), &sprite_mat)); |
||||
vec3_t p2 = vec3_add(pos, vec3_transform(vec3(-size.x * 0.5, size.y * 0.5, 0), &sprite_mat)); |
||||
vec3_t p3 = vec3_add(pos, vec3_transform(vec3( size.x * 0.5, size.y * 0.5, 0), &sprite_mat)); |
||||
|
||||
render_texture_t *t = &textures[texture_index]; |
||||
render_push_tris((tris_t){ |
||||
.vertices = { |
||||
{.pos = p0, .uv = {0, 0}, .color = color}, |
||||
{.pos = p1, .uv = {0 + t->size.x ,0}, .color = color}, |
||||
{.pos = p2, .uv = {0, 0 + t->size.y}, .color = color}, |
||||
} |
||||
}, texture_index); |
||||
render_push_tris((tris_t){ |
||||
.vertices = { |
||||
{.pos = p2, .uv = {0, 0 + t->size.y}, .color = color}, |
||||
{.pos = p1, .uv = {0 + t->size.x, 0}, .color = color}, |
||||
{.pos = p3, .uv = {0 + t->size.x, 0 + t->size.y}, .color = color}, |
||||
} |
||||
}, texture_index); |
||||
} |
||||
|
||||
void render_push_2d(vec2i_t pos, vec2i_t size, rgba_t color, uint16_t texture_index) { |
||||
render_push_2d_tile(pos, vec2i(0, 0), render_texture_size(texture_index), size, color, texture_index); |
||||
} |
||||
|
||||
void render_push_2d_tile(vec2i_t pos, vec2i_t uv_offset, vec2i_t uv_size, vec2i_t size, rgba_t color, uint16_t texture_index) { |
||||
error_if(texture_index >= textures_len, "Invalid texture %d", texture_index); |
||||
render_push_tris((tris_t){ |
||||
.vertices = { |
||||
{.pos = {pos.x, pos.y + size.y, 0}, .uv = {uv_offset.x , uv_offset.y + uv_size.y}, .color = color}, |
||||
{.pos = {pos.x + size.x, pos.y, 0}, .uv = {uv_offset.x + uv_size.x, uv_offset.y}, .color = color}, |
||||
{.pos = {pos.x, pos.y, 0}, .uv = {uv_offset.x , uv_offset.y}, .color = color}, |
||||
} |
||||
}, texture_index); |
||||
|
||||
render_push_tris((tris_t){ |
||||
.vertices = { |
||||
{.pos = {pos.x + size.x, pos.y + size.y, 0}, .uv = {uv_offset.x + uv_size.x, uv_offset.y + uv_size.y}, .color = color}, |
||||
{.pos = {pos.x + size.x, pos.y, 0}, .uv = {uv_offset.x + uv_size.x, uv_offset.y}, .color = color}, |
||||
{.pos = {pos.x, pos.y + size.y, 0}, .uv = {uv_offset.x , uv_offset.y + uv_size.y}, .color = color}, |
||||
} |
||||
}, texture_index); |
||||
} |
||||
|
||||
|
||||
uint16_t render_texture_create(uint32_t width, uint32_t height, rgba_t *pixels) { |
||||
error_if(textures_len >= TEXTURES_MAX, "TEXTURES_MAX reached"); |
||||
|
||||
uint32_t byte_size = width * height * sizeof(rgba_t); |
||||
uint16_t texture_index = textures_len; |
||||
|
||||
textures[texture_index] = (render_texture_t){{width, height}, NULL}; |
||||
// textures[texture_index] = (render_texture_t){{width, height}, mem_bump(byte_size)};
|
||||
// memcpy(textures[texture_index].pixels, pixels, byte_size);
|
||||
|
||||
textures_len++; |
||||
return texture_index; |
||||
} |
||||
|
||||
vec2i_t render_texture_size(uint16_t texture_index) { |
||||
error_if(texture_index >= textures_len, "Invalid texture %d", texture_index); |
||||
return textures[texture_index].size; |
||||
} |
||||
|
||||
void render_texture_replace_pixels(int16_t texture_index, rgba_t *pixels) { |
||||
error_if(texture_index >= textures_len, "Invalid texture %d", texture_index); |
||||
render_texture_t *t = &textures[texture_index]; |
||||
// memcpy(t->pixels, pixels, t->size.x * t->size.y * sizeof(rgba_t));
|
||||
} |
||||
|
||||
uint16_t render_textures_len() { |
||||
return textures_len; |
||||
} |
||||
|
||||
void render_textures_reset(uint16_t len) { |
||||
error_if(len > textures_len, "Invalid texture reset len %d >= %d", len, textures_len); |
||||
textures_len = len; |
||||
} |
||||
|
||||
void render_textures_dump(const char *path) {} |
||||
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
static inline rgba_t color_mix(rgba_t in, rgba_t out) { |
||||
return rgba( |
||||
lerp(in.as_rgba.r, out.as_rgba.r, out.as_rgba.a/255.0), |
||||
lerp(in.as_rgba.g, out.as_rgba.g, out.as_rgba.a/255.0), |
||||
lerp(in.as_rgba.b, out.as_rgba.b, out.as_rgba.a/255.0), |
||||
1 |
||||
); |
||||
} |
||||
|
||||
typedef enum { |
||||
CLIP_INSIDE = 0, |
||||
CLIP_LEFT = (1<<0), |
||||
CLIP_RIGHT = (1<<1), |
||||
CLIP_BOTTOM = (1<<2), |
||||
CLIP_TOP = (1<<3), |
||||
} clip_code_t; |
||||
|
||||
static inline clip_code_t clip_code(vec2i_t p) { |
||||
clip_code_t cc = CLIP_INSIDE; |
||||
if (p.x < 0) { |
||||
flags_add(cc, CLIP_LEFT); |
||||
} |
||||
else if (p.x >= screen_size.x) { |
||||
flags_add(cc, CLIP_RIGHT);
|
||||
} |
||||
if (p.y < 0) { |
||||
flags_add(cc, CLIP_BOTTOM); |
||||
} |
||||
else if (p.y >= screen_size.y) { |
||||
flags_add(cc, CLIP_TOP);
|
||||
} |
||||
return cc; |
||||
} |
||||
|
||||
static void line(vec2i_t p0, vec2i_t p1, rgba_t color) { |
||||
// Cohen Sutherland Line Clipping
|
||||
clip_code_t cc0 = clip_code(p0); |
||||
clip_code_t cc1 = clip_code(p1); |
||||
bool accept = false; |
||||
|
||||
vec2i_t ss = vec2i(screen_size.x-1, screen_size.y-1); |
||||
while (true) { |
||||
if (!(cc0 | cc1)) { |
||||
accept = true; |
||||
break; |
||||
} |
||||
else if (cc0 & cc1) { |
||||
break; |
||||
} |
||||
else { |
||||
vec2i_t r = p0; |
||||
clip_code_t cc_out = cc0 ? cc0 : cc1; |
||||
|
||||
if (flags_is(cc_out, CLIP_TOP)) { |
||||
r.x = p0.x + (p1.x - p0.x) * (ss.y - p0.y) / (p1.y - p0.y); |
||||
r.y = ss.y; |
||||
} |
||||
else if (flags_is(cc_out, CLIP_BOTTOM)) { |
||||
r.x = p0.x + (p1.x - p0.x) * (-p0.y) / (p1.y - p0.y); |
||||
r.y = 0; |
||||
} |
||||
else if (flags_is(cc_out, CLIP_RIGHT)) { |
||||
r.y = p0.y + (p1.y - p0.y) * (ss.x - p0.x) / (p1.x - p0.x); |
||||
r.x = ss.x; |
||||
} |
||||
else if (flags_is(cc_out, CLIP_LEFT)) { |
||||
r.y = p0.y + (p1.y - p0.y) * (-p0.x) / (p1.x - p0.x); |
||||
r.x = 0; |
||||
} |
||||
|
||||
if (cc_out == cc0) { |
||||
p0.x = r.x; |
||||
p0.y = r.y; |
||||
cc0 = clip_code(p0); |
||||
} |
||||
else { |
||||
p1.x = r.x; |
||||
p1.y = r.y; |
||||
cc1 = clip_code(p1); |
||||
} |
||||
} |
||||
} |
||||
if (!accept) { |
||||
return; |
||||
} |
||||
|
||||
// Bresenham's line algorithm
|
||||
bool steep = false;
|
||||
if (abs(p0.x - p1.x) < abs(p0.y - p1.y)) { |
||||
swap(p0.x, p0.y);
|
||||
swap(p1.x, p1.y);
|
||||
steep = true; |
||||
}
|
||||
if (p0.x > p1.x) {
|
||||
swap(p0.x, p1.x);
|
||||
swap(p0.y, p1.y);
|
||||
}
|
||||
int32_t dx = p1.x - p0.x;
|
||||
int32_t dy = p1.y - p0.y;
|
||||
int32_t derror2 = abs(dy) * 2;
|
||||
int32_t error2 = 0;
|
||||
int32_t y = p0.y; |
||||
int32_t ydir = (p1.y > p0.y ? 1 : -1); |
||||
|
||||
if (steep) { |
||||
for (int32_t x = p0.x; x <= p1.x; x++) { |
||||
screen_buffer[x * screen_ppr + y] = color_mix(screen_buffer[x * screen_ppr + y], color); |
||||
error2 += derror2;
|
||||
if (error2 > dx) {
|
||||
y += ydir; |
||||
error2 -= dx * 2;
|
||||
}
|
||||
} |
||||
} |
||||
else { |
||||
for (int32_t x = p0.x; x <= p1.x; x++) { |
||||
screen_buffer[y * screen_ppr + x] = color_mix(screen_buffer[y * screen_ppr + x], color); |
||||
error2 += derror2;
|
||||
if (error2 > dx) {
|
||||
y += ydir; |
||||
error2 -= dx * 2;
|
||||
}
|
||||
} |
||||
} |
||||
} |
@ -0,0 +1,81 @@ |
||||
#include "system.h" |
||||
#include "input.h" |
||||
#include "render.h" |
||||
#include "platform.h" |
||||
#include "mem.h" |
||||
#include "utils.h" |
||||
|
||||
#include "wipeout/game.h" |
||||
|
||||
static double time_real; |
||||
static double time_scaled; |
||||
static double time_scale = 1.0; |
||||
static double tick_last; |
||||
static double cycle_time = 0; |
||||
|
||||
void system_init() { |
||||
time_real = platform_now(); |
||||
input_init(); |
||||
render_init(platform_screen_size()); |
||||
game_init(); |
||||
} |
||||
|
||||
void system_cleanup() { |
||||
render_cleanup(); |
||||
input_cleanup(); |
||||
} |
||||
|
||||
void system_exit() { |
||||
platform_exit(); |
||||
} |
||||
|
||||
void system_update() { |
||||
double time_real_now = platform_now(); |
||||
double real_delta = time_real_now - time_real; |
||||
time_real = time_real_now; |
||||
tick_last = min(real_delta, 0.1) * time_scale; |
||||
time_scaled += tick_last; |
||||
|
||||
// FIXME: come up with a better way to wrap the cycle_time, so that it
|
||||
// doesn't lose precission, but also doesn't jump upon reset.
|
||||
cycle_time = time_scaled; |
||||
if (cycle_time > 3600 * M_PI) { |
||||
cycle_time -= 3600 * M_PI; |
||||
} |
||||
|
||||
render_frame_prepare(); |
||||
|
||||
game_update(); |
||||
|
||||
render_frame_end(); |
||||
input_clear(); |
||||
mem_temp_check(); |
||||
} |
||||
|
||||
void system_reset_cycle_time() { |
||||
cycle_time = 0; |
||||
} |
||||
|
||||
void system_resize(vec2i_t size) { |
||||
render_set_screen_size(size); |
||||
} |
||||
|
||||
double system_time_scale_get() { |
||||
return time_scale; |
||||
} |
||||
|
||||
void system_time_scale_set(double scale) { |
||||
time_scale = scale; |
||||
} |
||||
|
||||
double system_tick() { |
||||
return tick_last; |
||||
} |
||||
|
||||
double system_time() { |
||||
return time_scaled; |
||||
} |
||||
|
||||
double system_cycle_time() { |
||||
return cycle_time; |
||||
} |
@ -0,0 +1,23 @@ |
||||
#ifndef SYSTEM_H |
||||
#define SYSTEM_H |
||||
|
||||
#include "types.h" |
||||
|
||||
#define SYSTEM_WINDOW_NAME "wipEout" |
||||
#define SYSTEM_WINDOW_WIDTH 1280 |
||||
#define SYSTEM_WINDOW_HEIGHT 720 |
||||
|
||||
void system_init(); |
||||
void system_update(); |
||||
void system_cleanup(); |
||||
void system_exit(); |
||||
void system_resize(vec2i_t size); |
||||
|
||||
double system_time(); |
||||
double system_tick(); |
||||
double system_cycle_time(); |
||||
void system_reset_cycle_time(); |
||||
double system_time_scale_get(); |
||||
void system_time_scale_set(double ts); |
||||
|
||||
#endif |
@ -0,0 +1,116 @@ |
||||
#include <math.h> |
||||
#include "types.h" |
||||
#include "utils.h" |
||||
|
||||
vec3_t vec3_wrap_angle(vec3_t a) { |
||||
return vec3(wrap_angle(a.x), wrap_angle(a.y), wrap_angle(a.z)); |
||||
} |
||||
|
||||
float vec3_angle(vec3_t a, vec3_t b) { |
||||
float magnitude = sqrt( |
||||
(a.x * a.x + a.y * a.y + a.z * a.z) *
|
||||
(b.x * b.x + b.y * b.y + b.z * b.z) |
||||
); |
||||
float cosine = (magnitude == 0) |
||||
? 1 |
||||
: vec3_dot(a, b) / magnitude; |
||||
return acos(clamp(cosine, -1, 1)); |
||||
} |
||||
|
||||
vec3_t vec3_transform(vec3_t a, mat4_t *mat) { |
||||
float w = mat->m[3] * a.x + mat->m[7] * a.y + mat->m[11] * a.z + mat->m[15]; |
||||
if (w == 0) { |
||||
w = 1; |
||||
} |
||||
return vec3( |
||||
(mat->m[0] * a.x + mat->m[4] * a.y + mat->m[ 8] * a.z + mat->m[12]) / w, |
||||
(mat->m[1] * a.x + mat->m[5] * a.y + mat->m[ 9] * a.z + mat->m[13]) / w, |
||||
(mat->m[2] * a.x + mat->m[6] * a.y + mat->m[10] * a.z + mat->m[14]) / w |
||||
); |
||||
} |
||||
|
||||
vec3_t vec3_project_to_ray(vec3_t p, vec3_t r0, vec3_t r1) { |
||||
vec3_t ray = vec3_normalize(vec3_sub(r1, r0)); |
||||
float dp = vec3_dot(vec3_sub(p, r0), ray); |
||||
return vec3_add(r0, vec3_mulf(ray, dp)); |
||||
} |
||||
|
||||
float vec3_distance_to_plane(vec3_t p, vec3_t plane_pos, vec3_t plane_normal) { |
||||
float dot_product = vec3_dot(vec3_sub(plane_pos, p), plane_normal); |
||||
float norm_dot_product = vec3_dot(vec3_mulf(plane_normal, -1), plane_normal); |
||||
return dot_product / norm_dot_product; |
||||
} |
||||
|
||||
vec3_t vec3_reflect(vec3_t incidence, vec3_t normal, float f) { |
||||
return vec3_add(incidence, vec3_mulf(normal, vec3_dot(normal, vec3_mulf(incidence, -1)) * f)); |
||||
} |
||||
|
||||
void mat4_set_translation(mat4_t *mat, vec3_t pos) { |
||||
mat->cols[3][0] = pos.x; |
||||
mat->cols[3][1] = pos.y; |
||||
mat->cols[3][2] = pos.z; |
||||
} |
||||
|
||||
void mat4_set_yaw_pitch_roll(mat4_t *mat, vec3_t rot) { |
||||
float sx = sin( rot.x); |
||||
float sy = sin(-rot.y); |
||||
float sz = sin(-rot.z); |
||||
float cx = cos( rot.x); |
||||
float cy = cos(-rot.y); |
||||
float cz = cos(-rot.z); |
||||
|
||||
mat->cols[0][0] = cy * cz + sx * sy * sz; |
||||
mat->cols[1][0] = cz * sx * sy - cy * sz; |
||||
mat->cols[2][0] = cx * sy; |
||||
mat->cols[0][1] = cx * sz; |
||||
mat->cols[1][1] = cx * cz; |
||||
mat->cols[2][1] = -sx; |
||||
mat->cols[0][2] = -cz * sy + cy * sx * sz; |
||||
mat->cols[1][2] = cy * cz * sx + sy * sz; |
||||
mat->cols[2][2] = cx * cy; |
||||
} |
||||
|
||||
void mat4_set_roll_pitch_yaw(mat4_t *mat, vec3_t rot) { |
||||
float sx = sin( rot.x); |
||||
float sy = sin(-rot.y); |
||||
float sz = sin(-rot.z); |
||||
float cx = cos( rot.x); |
||||
float cy = cos(-rot.y); |
||||
float cz = cos(-rot.z); |
||||
|
||||
mat->cols[0][0] = cy * cz - sx * sy * sz; |
||||
mat->cols[1][0] = -cx * sz; |
||||
mat->cols[2][0] = cz * sy + cy * sx * sz; |
||||
mat->cols[0][1] = cz * sx * sy + cy * sz; |
||||
mat->cols[1][1] = cx *cz; |
||||
mat->cols[2][1] = -cy * cz * sx + sy * sz; |
||||
mat->cols[0][2] = -cx * sy; |
||||
mat->cols[1][2] = sx; |
||||
mat->cols[2][2] = cx * cy; |
||||
} |
||||
|
||||
void mat4_translate(mat4_t *mat, vec3_t translation) { |
||||
mat->m[12] = mat->m[0] * translation.x + mat->m[4] * translation.y + mat->m[8] * translation.z + mat->m[12]; |
||||
mat->m[13] = mat->m[1] * translation.x + mat->m[5] * translation.y + mat->m[9] * translation.z + mat->m[13]; |
||||
mat->m[14] = mat->m[2] * translation.x + mat->m[6] * translation.y + mat->m[10] * translation.z + mat->m[14]; |
||||
mat->m[15] = mat->m[3] * translation.x + mat->m[7] * translation.y + mat->m[11] * translation.z + mat->m[15]; |
||||
} |
||||
|
||||
void mat4_mul(mat4_t *res, mat4_t *a, mat4_t *b) { |
||||
res->m[ 0] = b->m[ 0] * a->m[0] + b->m[ 1] * a->m[4] + b->m[ 2] * a->m[ 8] + b->m[ 3] * a->m[12]; |
||||
res->m[ 1] = b->m[ 0] * a->m[1] + b->m[ 1] * a->m[5] + b->m[ 2] * a->m[ 9] + b->m[ 3] * a->m[13]; |
||||
res->m[ 2] = b->m[ 0] * a->m[2] + b->m[ 1] * a->m[6] + b->m[ 2] * a->m[10] + b->m[ 3] * a->m[14]; |
||||
res->m[ 3] = b->m[ 0] * a->m[3] + b->m[ 1] * a->m[7] + b->m[ 2] * a->m[11] + b->m[ 3] * a->m[15]; |
||||
res->m[ 4] = b->m[ 4] * a->m[0] + b->m[ 5] * a->m[4] + b->m[ 6] * a->m[ 8] + b->m[ 7] * a->m[12]; |
||||
res->m[ 5] = b->m[ 4] * a->m[1] + b->m[ 5] * a->m[5] + b->m[ 6] * a->m[ 9] + b->m[ 7] * a->m[13]; |
||||
res->m[ 6] = b->m[ 4] * a->m[2] + b->m[ 5] * a->m[6] + b->m[ 6] * a->m[10] + b->m[ 7] * a->m[14]; |
||||
res->m[ 7] = b->m[ 4] * a->m[3] + b->m[ 5] * a->m[7] + b->m[ 6] * a->m[11] + b->m[ 7] * a->m[15]; |
||||
res->m[ 8] = b->m[ 8] * a->m[0] + b->m[ 9] * a->m[4] + b->m[10] * a->m[ 8] + b->m[11] * a->m[12]; |
||||
res->m[ 9] = b->m[ 8] * a->m[1] + b->m[ 9] * a->m[5] + b->m[10] * a->m[ 9] + b->m[11] * a->m[13]; |
||||
res->m[10] = b->m[ 8] * a->m[2] + b->m[ 9] * a->m[6] + b->m[10] * a->m[10] + b->m[11] * a->m[14]; |
||||
res->m[11] = b->m[ 8] * a->m[3] + b->m[ 9] * a->m[7] + b->m[10] * a->m[11] + b->m[11] * a->m[15]; |
||||
res->m[12] = b->m[12] * a->m[0] + b->m[13] * a->m[4] + b->m[14] * a->m[ 8] + b->m[15] * a->m[12]; |
||||
res->m[13] = b->m[12] * a->m[1] + b->m[13] * a->m[5] + b->m[14] * a->m[ 9] + b->m[15] * a->m[13]; |
||||
res->m[14] = b->m[12] * a->m[2] + b->m[13] * a->m[6] + b->m[14] * a->m[10] + b->m[15] * a->m[14]; |
||||
res->m[15] = b->m[12] * a->m[3] + b->m[13] * a->m[7] + b->m[14] * a->m[11] + b->m[15] * a->m[15]; |
||||
} |
@ -0,0 +1,184 @@ |
||||
#ifndef TYPES_H |
||||
#define TYPES_H |
||||
|
||||
#include <stdint.h> |
||||
#include <stdbool.h> |
||||
#include <stdio.h> |
||||
#include <stdlib.h> |
||||
#include <math.h> |
||||
|
||||
typedef union rgba_t {
|
||||
struct {
|
||||
uint8_t r, g, b, a; |
||||
} as_rgba; |
||||
uint8_t as_components[4]; |
||||
uint32_t as_uint32; |
||||
} rgba_t; |
||||
|
||||
typedef struct { |
||||
float x, y; |
||||
} vec2_t; |
||||
|
||||
|
||||
typedef struct { |
||||
int32_t x, y; |
||||
} vec2i_t; |
||||
|
||||
|
||||
typedef struct { |
||||
float x, y, z; |
||||
} vec3_t; |
||||
|
||||
typedef union { |
||||
float m[16]; |
||||
float cols[4][4]; |
||||
} mat4_t; |
||||
|
||||
typedef struct { |
||||
vec3_t pos; |
||||
vec2_t uv; |
||||
rgba_t color; |
||||
} vertex_t; |
||||
|
||||
typedef struct { |
||||
vertex_t vertices[3]; |
||||
} tris_t; |
||||
|
||||
|
||||
#define rgba(R, G, B, A) ((rgba_t){.as_rgba = {.r = R, .g = G, .b = B, .a = A}}) |
||||
#define vec2(X, Y) ((vec2_t){.x = X, .y = Y}) |
||||
#define vec3(X, Y, Z) ((vec3_t){.x = X, .y = Y, .z = Z}) |
||||
#define vec2i(X, Y) ((vec2i_t){.x = X, .y = Y}) |
||||
|
||||
#define mat4(m0,m1,m2,m3,m4,m5,m6,m7,m8,m9,m10,m11,m12,m13,m14,m15) \ |
||||
(mat4_t){.m = { \
|
||||
m0, m1, m2, m3, \
|
||||
m4, m5, m6, m7, \
|
||||
m8, m9, m10, m11, \
|
||||
m12, m13, m14, m15 \
|
||||
}} |
||||
|
||||
#define mat4_identity() mat4( \ |
||||
1, 0, 0, 0, \
|
||||
0, 1, 0, 0, \
|
||||
0, 0, 1, 0, \
|
||||
0, 0, 0, 1 \
|
||||
) |
||||
|
||||
static inline vec2_t vec2_mulf(vec2_t a, float f) { |
||||
return vec2( |
||||
a.x * f, |
||||
a.y * f |
||||
); |
||||
} |
||||
|
||||
static inline vec2i_t vec2i_mulf(vec2i_t a, float f) { |
||||
return vec2i( |
||||
a.x * f, |
||||
a.y * f |
||||
); |
||||
} |
||||
|
||||
|
||||
static inline vec3_t vec3_add(vec3_t a, vec3_t b) { |
||||
return vec3( |
||||
a.x + b.x, |
||||
a.y + b.y, |
||||
a.z + b.z |
||||
); |
||||
} |
||||
|
||||
static inline vec3_t vec3_sub(vec3_t a, vec3_t b) { |
||||
return vec3( |
||||
a.x - b.x, |
||||
a.y - b.y, |
||||
a.z - b.z |
||||
); |
||||
} |
||||
|
||||
static inline vec3_t vec3_mul(vec3_t a, vec3_t b) { |
||||
return vec3( |
||||
a.x * b.x, |
||||
a.y * b.y, |
||||
a.z * b.z |
||||
); |
||||
} |
||||
|
||||
static inline vec3_t vec3_mulf(vec3_t a, float f) { |
||||
return vec3( |
||||
a.x * f, |
||||
a.y * f, |
||||
a.z * f |
||||
); |
||||
} |
||||
|
||||
static inline vec3_t vec3_inv(vec3_t a) { |
||||
return vec3(-a.x, -a.y, -a.z); |
||||
} |
||||
|
||||
static inline vec3_t vec3_divf(vec3_t a, float f) { |
||||
return vec3( |
||||
a.x / f, |
||||
a.y / f, |
||||
a.z / f |
||||
); |
||||
} |
||||
|
||||
static inline float vec3_len(vec3_t a) { |
||||
return sqrt(a.x * a.x + a.y * a.y + a.z * a.z); |
||||
} |
||||
|
||||
static inline vec3_t vec3_cross(vec3_t a, vec3_t b) { |
||||
return vec3( |
||||
a.y * b.z - a.z * b.y, |
||||
a.z * b.x - a.x * b.z, |
||||
a.x * b.y - a.y * b.x |
||||
); |
||||
} |
||||
|
||||
static inline float vec3_dot(vec3_t a, vec3_t b) { |
||||
return a.x * b.x + a.y * b.y + a.z * b.z; |
||||
} |
||||
|
||||
static inline vec3_t vec3_lerp(vec3_t a, vec3_t b, float t) { |
||||
return vec3( |
||||
a.x + t * (b.x - a.x), |
||||
a.y + t * (b.y - a.y), |
||||
a.z + t * (b.z - a.z) |
||||
); |
||||
} |
||||
|
||||
static inline vec3_t vec3_normalize(vec3_t a) { |
||||
float length = vec3_len(a); |
||||
return vec3( |
||||
a.x / length, |
||||
a.y / length, |
||||
a.z / length |
||||
); |
||||
} |
||||
|
||||
static inline float wrap_angle(float a) { |
||||
a = fmod(a + M_PI, M_PI * 2); |
||||
if (a < 0) { |
||||
a += M_PI * 2; |
||||
} |
||||
return a - M_PI; |
||||
} |
||||
|
||||
float vec3_angle(vec3_t a, vec3_t b); |
||||
vec3_t vec3_wrap_angle(vec3_t a); |
||||
vec3_t vec3_normalize(vec3_t a); |
||||
vec3_t vec3_project_to_ray(vec3_t p, vec3_t r0, vec3_t r1); |
||||
float vec3_distance_to_plane(vec3_t p, vec3_t plane_pos, vec3_t plane_normal); |
||||
vec3_t vec3_reflect(vec3_t incidence, vec3_t normal, float f); |
||||
|
||||
float wrap_angle(float a); |
||||
|
||||
vec3_t vec3_transform(vec3_t a, mat4_t *mat); |
||||
void mat4_set_translation(mat4_t *mat, vec3_t pos); |
||||
void mat4_set_yaw_pitch_roll(mat4_t *m, vec3_t rot); |
||||
void mat4_set_roll_pitch_yaw(mat4_t *mat, vec3_t rot); |
||||
void mat4_translate(mat4_t *mat, vec3_t translation); |
||||
void mat4_mul(mat4_t *res, mat4_t *a, mat4_t *b); |
||||
|
||||
#endif |
@ -0,0 +1,68 @@ |
||||
#include <stdint.h> |
||||
#include <string.h> |
||||
#include <stdlib.h> |
||||
#include <sys/stat.h> |
||||
#include "utils.h" |
||||
#include "mem.h" |
||||
|
||||
char temp_path[64]; |
||||
char *get_path(const char *dir, const char *file) { |
||||
strcpy(temp_path, dir); |
||||
strcpy(temp_path + strlen(dir), file); |
||||
return temp_path; |
||||
} |
||||
|
||||
|
||||
bool file_exists(char *path) { |
||||
struct stat s; |
||||
return (stat(path, &s) == 0); |
||||
} |
||||
|
||||
uint8_t *file_load(char *path, uint32_t *bytes_read) { |
||||
FILE *f = fopen(path, "rb"); |
||||
error_if(!f, "Could not open file for reading: %s", path); |
||||
|
||||
fseek(f, 0, SEEK_END); |
||||
int32_t size = ftell(f); |
||||
if (size <= 0) { |
||||
fclose(f); |
||||
return NULL; |
||||
} |
||||
fseek(f, 0, SEEK_SET); |
||||
|
||||
uint8_t *bytes = mem_temp_alloc(size); |
||||
if (!bytes) { |
||||
fclose(f); |
||||
return NULL; |
||||
} |
||||
|
||||
*bytes_read = fread(bytes, 1, size, f); |
||||
fclose(f); |
||||
|
||||
error_if(*bytes_read != size, "Could not read file: %s", path); |
||||
return bytes; |
||||
} |
||||
|
||||
uint32_t file_store(char *path, void *bytes, int32_t len) { |
||||
FILE *f = fopen(path, "wb"); |
||||
error_if(!f, "Could not open file for writing: %s", path); |
||||
|
||||
if (fwrite(bytes, 1, len, f) != len) { |
||||
die("Could not write file file %s", path); |
||||
} |
||||
|
||||
fclose(f); |
||||
return len; |
||||
} |
||||
|
||||
bool str_starts_with(const char *haystack, const char *needle) { |
||||
return (strncmp(haystack, needle, strlen(needle)) == 0); |
||||
} |
||||
|
||||
float rand_float(float min, float max) { |
||||
return min + ((float)rand() / (float)RAND_MAX) * (max - min); |
||||
} |
||||
|
||||
int32_t rand_int(int32_t min, int32_t max) { |
||||
return min + rand() % (max - min); |
||||
} |
@ -0,0 +1,139 @@ |
||||
#ifndef UTILS_H |
||||
#define UTILS_H |
||||
|
||||
#include <string.h> |
||||
#include "types.h" |
||||
|
||||
|
||||
#if !defined(offsetof) |
||||
#define offsetof(TYPE, ELEMENT) ((size_t)&(((TYPE *)0)->ELEMENT)) |
||||
#endif |
||||
#define member_size(type, member) sizeof(((type *)0)->member) |
||||
|
||||
#define max(a,b) ({ \ |
||||
__typeof__ (a) _a = (a); \
|
||||
__typeof__ (b) _b = (b); \
|
||||
_a > _b ? _a : _b; \
|
||||
}) |
||||
|
||||
#define min(a,b) ({ \ |
||||
__typeof__ (a) _a = (a); \
|
||||
__typeof__ (b) _b = (b); \
|
||||
_a < _b ? _a : _b; \
|
||||
}) |
||||
|
||||
#define swap(a, b) ({ \ |
||||
__typeof__(a) tmp = a; a = b; b = tmp; \
|
||||
}) |
||||
|
||||
#define clamp(v, min, max) ({ \ |
||||
__typeof__(v) _v = v, _min = min, _max = max; \
|
||||
_v > _max ? _max : _v < _min ? _min : _v; \
|
||||
}) |
||||
#define scale(v, in_min, in_max, out_min, out_max) ({ \ |
||||
__typeof__(v) _in_min = in_min, _out_min = out_min; \
|
||||
_out_min + ((out_max) - _out_min) * (((v) - _in_min) / ((in_max) - _in_min)); \
|
||||
}) |
||||
#define lerp(a, b, t) ({ \ |
||||
__typeof__(a) _a = a; \
|
||||
_a + ((b) - _a) * (t); \
|
||||
}) |
||||
|
||||
#define len(A) (sizeof(A) / sizeof(A[0])) |
||||
#define clear(A) memset(A, 0, sizeof(A)) |
||||
|
||||
|
||||
#define STRINGIFY(x) #x |
||||
#define TOSTRING(x) STRINGIFY(x) |
||||
#define die(...) \ |
||||
printf("Abort at " TOSTRING(__FILE__) " line " TOSTRING(__LINE__) ": " __VA_ARGS__); \
|
||||
printf("\n"); \
|
||||
exit(1) |
||||
|
||||
#define error_if(TEST, ...) \ |
||||
if (TEST) { \
|
||||
die(__VA_ARGS__); \
|
||||
} |
||||
|
||||
|
||||
#define flags_add(FLAGS, F) (FLAGS |= (F)) |
||||
#define flags_rm(FLAGS, F) (FLAGS &= ~(F)) |
||||
#define flags_is(FLAGS, F) ((FLAGS & (F)) == (F)) |
||||
#define flags_any(FLAGS, F) (FLAGS & (F)) |
||||
#define flags_not(FLAGS, F) ((FLAGS & (F)) != (F)) |
||||
#define flags_none(FLAGS, F) ((FLAGS & (F)) == 0) |
||||
#define flags_set(FLAGS, F) (FLAGS = (F)) |
||||
|
||||
|
||||
|
||||
char *get_path(const char *dir, const char *file); |
||||
bool str_starts_with(const char *haystack, const char *needle); |
||||
float rand_float(float min, float max); |
||||
int32_t rand_int(int32_t min, int32_t max);
|
||||
|
||||
bool file_exists(char *path); |
||||
uint8_t *file_load(char *path, uint32_t *bytes_read); |
||||
uint32_t file_store(char *path, void *bytes, int32_t len); |
||||
|
||||
|
||||
#define sort(LIST, LEN, COMPARE_FUNC) \ |
||||
for (uint32_t sort_i = 1, sort_j; sort_i < (LEN); sort_i++) { \
|
||||
sort_j = sort_i; \
|
||||
__typeof__((LIST)[0]) sort_temp = (LIST)[sort_j]; \
|
||||
while (sort_j > 0 && COMPARE_FUNC(&(LIST)[sort_j-1], &sort_temp)) { \
|
||||
(LIST)[sort_j] = (LIST)[sort_j-1]; \
|
||||
sort_j--; \
|
||||
} \
|
||||
(LIST)[sort_j] = sort_temp; \
|
||||
} |
||||
|
||||
#define shuffle(LIST, LEN) \ |
||||
for (int i = (LEN) - 1; i > 0; i--) { \
|
||||
int j = rand_int(0, i+1); \
|
||||
swap((LIST)[i], (LIST)[j]); \
|
||||
} |
||||
|
||||
|
||||
static inline uint8_t get_u8(uint8_t *bytes, uint32_t *p) { |
||||
return bytes[(*p)++]; |
||||
} |
||||
|
||||
static inline uint16_t get_u16(uint8_t *bytes, uint32_t *p) { |
||||
uint16_t v = 0; |
||||
v |= bytes[(*p)++] << 8; |
||||
v |= bytes[(*p)++] << 0; |
||||
return v; |
||||
} |
||||
|
||||
static inline uint32_t get_u32(uint8_t *bytes, uint32_t *p) { |
||||
uint32_t v = 0; |
||||
v |= bytes[(*p)++] << 24; |
||||
v |= bytes[(*p)++] << 16; |
||||
v |= bytes[(*p)++] << 8; |
||||
v |= bytes[(*p)++] << 0; |
||||
return v; |
||||
} |
||||
|
||||
static inline uint16_t get_u16_le(uint8_t *bytes, uint32_t *p) { |
||||
uint16_t v = 0; |
||||
v |= bytes[(*p)++] << 0; |
||||
v |= bytes[(*p)++] << 8; |
||||
return v; |
||||
} |
||||
|
||||
static inline uint32_t get_u32_le(uint8_t *bytes, uint32_t *p) { |
||||
uint32_t v = 0; |
||||
v |= bytes[(*p)++] << 0; |
||||
v |= bytes[(*p)++] << 8; |
||||
v |= bytes[(*p)++] << 16; |
||||
v |= bytes[(*p)++] << 24; |
||||
return v; |
||||
} |
||||
|
||||
#define get_i8(BYTES, P) ((int8_t)get_u8(BYTES, P)) |
||||
#define get_i16(BYTES, P) ((int16_t)get_u16(BYTES, P)) |
||||
#define get_i16_le(BYTES, P) ((int16_t)get_u16_le(BYTES, P)) |
||||
#define get_i32(BYTES, P) ((int32_t)get_u32(BYTES, P)) |
||||
#define get_i32_le(BYTES, P) ((int32_t)get_u32_le(BYTES, P)) |
||||
|
||||
#endif |
@ -0,0 +1,238 @@ |
||||
<!doctype html> |
||||
<html lang="en-us"> |
||||
<head> |
||||
<meta charset="utf-8"> |
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"> |
||||
<title>wipEout</title> |
||||
<style> |
||||
html, body { |
||||
color: #ccc; |
||||
font-family: Monospace; |
||||
font-size: 13px; |
||||
background-color: #111; |
||||
margin: 0px; |
||||
} |
||||
a { |
||||
color: #ffc603; |
||||
text-decoration: none; |
||||
} |
||||
a:hover { |
||||
text-decoration: underline; |
||||
} |
||||
/* the canvas *must not* have any border or padding, or mouse coords will be wrong */ |
||||
canvas { |
||||
border: 0px none; |
||||
background-color: black; |
||||
width: 100%; |
||||
height: 100%; |
||||
aspect-ratio: 16 / 9; |
||||
} |
||||
|
||||
#head { |
||||
left: 0; |
||||
right: 0; |
||||
top: 0; |
||||
padding: 4px 16px 6px 16px; |
||||
xheight: 24px; |
||||
background-color: #222; |
||||
display: flex; |
||||
justify-content: space-between; |
||||
} |
||||
@keyframes page-loader { |
||||
0% {transform: rotate(0deg); } |
||||
100% { transform: rotate(360deg);} |
||||
} |
||||
.container { |
||||
position: relative; |
||||
} |
||||
.info-overlay { |
||||
text-align: center; |
||||
position: absolute; |
||||
top: 0; |
||||
bottom: 0; |
||||
left: 0; |
||||
right: 0; |
||||
margin: auto; |
||||
width: 200px; |
||||
height: 200px; |
||||
display: none; |
||||
} |
||||
.fullscreen { |
||||
margin: 0 8px; |
||||
} |
||||
#spinner { |
||||
content: ""; |
||||
border-radius: 50%; |
||||
width: 48px; |
||||
height: 48px; |
||||
position: absolute; |
||||
margin: auto; |
||||
top: 0; |
||||
bottom: 0; |
||||
left: 0; |
||||
right: 0; |
||||
border-top: 2px solid #222; |
||||
border-right: 2px solid #222; |
||||
border-bottom: 2px solid #222; |
||||
border-left: 2px solid #ffc603; |
||||
transform: translateZ(0); |
||||
animation: page-loader 1.1s infinite linear; |
||||
} |
||||
h1 { |
||||
color: #fff; |
||||
display: inline-block; |
||||
margin: 0; |
||||
padding: 0; |
||||
font-size: 12px; |
||||
} |
||||
.key { |
||||
margin-right: 16px; |
||||
} |
||||
kbd { |
||||
background-color: #eee; |
||||
border-radius: 3px; |
||||
border: 1px solid #b4b4b4; |
||||
box-shadow: |
||||
0 1px 1px rgba(0, 0, 0, 0.2), |
||||
0 2px 0 0 rgba(255, 255, 255, 0.7) inset; |
||||
color: #333; |
||||
display: inline-block; |
||||
font-size: 0.85em; |
||||
font-weight: 700; |
||||
line-height: 1; |
||||
padding: 1px 2px; |
||||
white-space: nowrap; |
||||
} |
||||
</style> |
||||
</head> |
||||
<body> |
||||
<div id="head"> |
||||
<div> |
||||
<h1>wipEout</h1> |
||||
<a href="#" id="fullscreen">fullscreen</a> |
||||
<span class="key"><kbd>◀</kbd><kbd>▲</kbd><kbd>▼</kbd><kbd>▶</kbd> steering</span> |
||||
<span class="key"><kbd>X</kbd> thrust</span> |
||||
<span class="key"><kbd>Z</kbd> shoot</span> |
||||
<span class="key"><kbd>C</kbd> brake left</span> |
||||
<span class="key"><kbd>V</kbd> brake right</span> |
||||
<span class="key"><kbd>A</kbd> view</span> |
||||
</div> |
||||
<div> |
||||
Read: |
||||
<a href="https://phoboslab.org/log/2023/08/rewriting-wipeout"> |
||||
Rewriting wipEout |
||||
</a> |
||||
</div> |
||||
</div> |
||||
<div id="game" class="container"> |
||||
<canvas id="canvas" oncontextmenu="event.preventDefault()" tabindex=-1></canvas> |
||||
<div class="info-overlay" id="loading"> |
||||
<div id="spinner"></div> |
||||
<div id="status">Downloading...</div> |
||||
<div> |
||||
<progress value="0" max="100" id="progress" hidden=1></progress> |
||||
</div> |
||||
</div> |
||||
<div class="info-overlay" id="select-version"> |
||||
<p> |
||||
<a href="#" id="load-full-version">FULL VERSION</a><br/> |
||||
the complete game ~144mb |
||||
</p> |
||||
<p> |
||||
<a href="#" id="load-minimal-version">MINIMAL VERSION</a><br/> |
||||
no intro, no music ~11mb |
||||
</p> |
||||
</div> |
||||
</div> |
||||
|
||||
</div> |
||||
<script type='text/javascript'> |
||||
var loadScript = (ev, src) => { |
||||
ev.preventDefault(); |
||||
|
||||
// Hide select-version, show loader |
||||
document.getElementById('select-version').style.display = 'none'; |
||||
document.getElementById('loading').style.display = 'block'; |
||||
|
||||
// Load the requested script |
||||
var s = document.createElement('script'); |
||||
s.setAttribute('src', src); |
||||
document.head.appendChild(s); |
||||
|
||||
// Attemp to unlock Audio :( |
||||
var audioCtx = new AudioContext(); |
||||
audioCtx.resume(); |
||||
return false; |
||||
}; |
||||
var requestFullscreen = (ev) => { |
||||
ev.preventDefault(); |
||||
document.getElementById('game').requestFullscreen(); |
||||
}; |
||||
document.getElementById('select-version').style.display = 'block'; |
||||
document.getElementById('fullscreen').addEventListener('click', (ev) => requestFullscreen(ev, 'wipeout.js')) |
||||
document.getElementById('load-full-version').addEventListener('click', (ev) => loadScript(ev, 'wipeout.js')); |
||||
document.getElementById('load-minimal-version').addEventListener('click', (ev) => loadScript(ev, 'wipeout-minimal.js')); |
||||
|
||||
var statusElement = document.getElementById('status'); |
||||
var progressElement = document.getElementById('progress'); |
||||
var spinnerElement = document.getElementById('spinner'); |
||||
|
||||
var Module = { |
||||
preRun: [], |
||||
postRun: [], |
||||
print: function(text) { |
||||
if (arguments.length > 1) text = Array.prototype.slice.call(arguments).join(' '); |
||||
console.log(text); |
||||
}, |
||||
canvas: document.getElementById('canvas'), |
||||
setStatus: (text) => { |
||||
if (!Module.setStatus.last) { |
||||
Module.setStatus.last = { time: Date.now(), text: '' }; |
||||
} |
||||
if (text === Module.setStatus.last.text) { |
||||
return; |
||||
} |
||||
var m = text.match(/([^(]+)\((\d+(\.\d+)?)\/(\d+)\)/); |
||||
var now = Date.now(); |
||||
if (m && now - Module.setStatus.last.time < 30) { |
||||
return; // if this is a progress update, skip it if too soon |
||||
} |
||||
Module.setStatus.last.time = now; |
||||
Module.setStatus.last.text = text; |
||||
if (m) { |
||||
text = m[1]; |
||||
progressElement.value = parseInt(m[2])*100; |
||||
progressElement.max = parseInt(m[4])*100; |
||||
progressElement.hidden = false; |
||||
spinnerElement.hidden = false; |
||||
} else { |
||||
progressElement.value = null; |
||||
progressElement.max = null; |
||||
progressElement.hidden = true; |
||||
if (!text) { |
||||
spinnerElement.hidden = true; |
||||
document.getElementById('loading').style.display = 'none'; |
||||
} |
||||
} |
||||
statusElement.innerHTML = text; |
||||
}, |
||||
totalDependencies: 0, |
||||
monitorRunDependencies: (left) => { |
||||
Module.totalDependencies = Math.max(Module.totalDependencies, left); |
||||
Module.setStatus(left ? 'preparing... (' + (Module.totalDependencies-left) + '/' + Module.totalDependencies + ')' : 'all downloads complete.'); |
||||
} |
||||
}; |
||||
Module.setStatus('downloading...'); |
||||
window.onerror = () => { |
||||
Module.setStatus('Exception thrown, see JavaScript console'); |
||||
document.getElementById('loading').style.display = 'block'; |
||||
spinnerElement.style.display = 'none'; |
||||
Module.setStatus = (text) => { |
||||
if (text) { |
||||
console.error('[post-exception status] ' + text); |
||||
} |
||||
}; |
||||
}; |
||||
</script> |
||||
</body> |
||||
</html> |
@ -0,0 +1,167 @@ |
||||
#include "../mem.h" |
||||
#include "../utils.h" |
||||
#include "../types.h" |
||||
#include "../render.h" |
||||
#include "../system.h" |
||||
|
||||
#include "object.h" |
||||
#include "track.h" |
||||
#include "ship.h" |
||||
#include "weapon.h" |
||||
#include "droid.h" |
||||
#include "camera.h" |
||||
|
||||
void camera_init(camera_t *camera, section_t *section) { |
||||
camera->section = section; |
||||
for (int i = 0; i < 10; i++) { |
||||
camera->section = camera->section->next; |
||||
} |
||||
|
||||
camera->position = camera->section->center; |
||||
camera->velocity = vec3(0, 0, 0); |
||||
camera->angle = vec3(0, 0, 0); |
||||
camera->angular_velocity = vec3(0, 0, 0); |
||||
camera->mat = mat4_identity(); |
||||
camera->has_initial_section = false; |
||||
} |
||||
|
||||
void camera_update(camera_t *camera, ship_t *ship, droid_t *droid) { |
||||
camera->last_position = camera->position; |
||||
(camera->update_func)(camera, ship, droid); |
||||
camera->real_velocity = vec3_mulf(vec3_sub(camera->position, camera->last_position), 1.0/system_tick()); |
||||
} |
||||
|
||||
void camera_update_race_external(camera_t *camera, ship_t *ship, droid_t *droid) { |
||||
vec3_t pos = vec3_sub(ship->position, vec3_mulf(ship->dir_forward, 1024)); |
||||
pos.y -= 200; |
||||
camera->section = track_nearest_section(pos, camera->section, NULL); |
||||
section_t *next = camera->section->next; |
||||
|
||||
vec3_t target = vec3_project_to_ray(pos, next->center, camera->section->center); |
||||
|
||||
vec3_t diff_from_center = vec3_sub(pos, target); |
||||
vec3_t acc = diff_from_center; |
||||
acc.y += vec3_len(diff_from_center) * 0.5; |
||||
|
||||
camera->velocity = vec3_sub(camera->velocity, vec3_mulf(acc, 0.015625 * 30 * system_tick())); |
||||
camera->velocity = vec3_sub(camera->velocity, vec3_mulf(camera->velocity, 0.125 * 30 * system_tick())); |
||||
pos = vec3_add(pos, camera->velocity); |
||||
|
||||
camera->position = pos; |
||||
camera->angle = vec3(ship->angle.x, ship->angle.y, 0); |
||||
} |
||||
|
||||
void camera_update_race_internal(camera_t *camera, ship_t *ship, droid_t *droid) { |
||||
camera->section = ship->section; |
||||
camera->position = ship_cockpit(ship); |
||||
camera->angle = vec3(ship->angle.x, ship->angle.y, ship->angle.z); |
||||
} |
||||
|
||||
void camera_update_race_intro(camera_t *camera, ship_t *ship, droid_t *droid) { |
||||
// Set to final position
|
||||
vec3_t pos = vec3_sub(ship->position, vec3_mulf(ship->dir_forward, 0.25 * 4096)); |
||||
|
||||
pos.x += sin(( (ship->update_timer - UPDATE_TIME_RACE_VIEW) * 30 * 3.0 * M_PI * 2) / 4096.0) * 4096; |
||||
pos.y -= (2 * (ship->update_timer - UPDATE_TIME_RACE_VIEW) * 30) + 200; |
||||
pos.z += sin(( (ship->update_timer - UPDATE_TIME_RACE_VIEW) * 30 * 3.0 * M_PI * 2) / 4096.0) * 4096; |
||||
|
||||
if (!camera->has_initial_section) { |
||||
camera->section = ship->section; |
||||
camera->has_initial_section = true; |
||||
} |
||||
else { |
||||
camera->section = track_nearest_section(pos, camera->section, NULL); |
||||
} |
||||
|
||||
camera->position = pos; |
||||
camera->angle.z = 0; |
||||
camera->angle.x = ship->angle.x * 0.5; |
||||
vec3_t target = vec3_sub(ship->position, pos); |
||||
|
||||
camera->angle.y = -atan2(target.x, target.z); |
||||
|
||||
if (ship->update_timer <= UPDATE_TIME_RACE_VIEW) { |
||||
flags_add(ship->flags, SHIP_VIEW_INTERNAL); |
||||
camera->update_func = camera_update_race_internal; |
||||
} |
||||
} |
||||
|
||||
void camera_update_attract_circle(camera_t *camera, ship_t *ship, droid_t *droid) { |
||||
camera->update_timer -= system_tick(); |
||||
if (camera->update_timer <= 0) { |
||||
camera->update_func = camera_update_attract_random; |
||||
} |
||||
// FIXME: not exactly sure what I'm doing here. The PSX version behaves
|
||||
// differently.
|
||||
camera->section = ship->section; |
||||
|
||||
camera->position.x = ship->position.x + sin(ship->angle.y) * 512; |
||||
camera->position.y = ship->position.y + ((ship->angle.x * 512 / (M_PI * 2)) - 200); |
||||
camera->position.z = ship->position.z - cos(ship->angle.y) * 512; |
||||
|
||||
camera->position.x += sin(camera->update_timer * 0.25) * 512; |
||||
camera->position.y -= 400; |
||||
camera->position.z += cos(camera->update_timer * 0.25) * 512; |
||||
camera->position = vec3_sub(camera->position, vec3_mulf(ship->dir_up, 256)); |
||||
|
||||
vec3_t target = vec3_sub(ship->position, camera->position); |
||||
float height = sqrt(target.x * target.x + target.z * target.z); |
||||
camera->angle.x = -atan2(target.y, height); |
||||
camera->angle.y = -atan2(target.x, target.z); |
||||
} |
||||
|
||||
void camera_update_rescue(camera_t *camera, ship_t *ship, droid_t *droid) { |
||||
camera->position = vec3_add(camera->section->center, vec3(300, -1500, 300)); |
||||
|
||||
vec3_t target = vec3_sub(droid->position, camera->position); |
||||
float height = sqrt(target.x * target.x + target.z * target.z); |
||||
camera->angle.x = -atan2(target.y, height); |
||||
camera->angle.y = -atan2(target.x, target.z); |
||||
} |
||||
|
||||
|
||||
void camera_update_attract_internal(camera_t *camera, ship_t *ship, droid_t *droid) { |
||||
camera->update_timer -= system_tick(); |
||||
if (camera->update_timer <= 0) { |
||||
camera->update_func = camera_update_attract_random; |
||||
} |
||||
|
||||
camera->section = ship->section; |
||||
camera->position = ship_cockpit(ship); |
||||
camera->angle = vec3(ship->angle.x, ship->angle.y, 0); // No roll
|
||||
} |
||||
|
||||
void camera_update_static_follow(camera_t *camera, ship_t *ship, droid_t *droid) { |
||||
camera->update_timer -= system_tick(); |
||||
if (camera->update_timer <= 0) { |
||||
camera->update_func = camera_update_attract_random; |
||||
} |
||||
|
||||
vec3_t target = vec3_sub(ship->position, camera->position); |
||||
float height = sqrt(target.x * target.x + target.z * target.z); |
||||
camera->angle.x = -atan2(target.y, height); |
||||
camera->angle.y = -atan2(target.x, target.z); |
||||
} |
||||
|
||||
void camera_update_attract_random(camera_t *camera, ship_t *ship, droid_t *droid) { |
||||
flags_rm(ship->flags, SHIP_VIEW_INTERNAL); |
||||
|
||||
if (rand() % 2) { |
||||
camera->update_func = camera_update_attract_circle; |
||||
camera->update_timer = 5; |
||||
} |
||||
else { |
||||
camera->update_func = camera_update_static_follow; |
||||
camera->update_timer = 5; |
||||
section_t *section = ship->section->next; |
||||
for (int i = 0; i < 10; i++) { |
||||
section = section->next; |
||||
} |
||||
|
||||
camera->section = section; |
||||
camera->position = section->center; |
||||
camera->position.y -= 500; |
||||
} |
||||
|
||||
(camera->update_func)(camera, ship, droid); |
||||
} |
@ -0,0 +1,32 @@ |
||||
#ifndef CAMERA_H |
||||
#define CAMERA_H |
||||
|
||||
#include "../types.h" |
||||
#include "droid.h" |
||||
|
||||
typedef struct camera_t { |
||||
vec3_t position; |
||||
vec3_t velocity; |
||||
vec3_t angle; |
||||
vec3_t angular_velocity; |
||||
vec3_t last_position; |
||||
vec3_t real_velocity; |
||||
mat4_t mat; |
||||
section_t *section; |
||||
bool has_initial_section; |
||||
float update_timer; |
||||
void (*update_func)(struct camera_t *, ship_t *, droid_t *); |
||||
} camera_t; |
||||
|
||||
void camera_init(camera_t *camera, section_t *section); |
||||
void camera_update(camera_t *camera, ship_t *ship, droid_t *droid); |
||||
void camera_update_race_external(camera_t *, ship_t *camShip, droid_t *); |
||||
void camera_update_race_internal(camera_t *, ship_t *camShip, droid_t *); |
||||
void camera_update_race_intro(camera_t *, ship_t *camShip, droid_t *); |
||||
void camera_update_attract_circle(camera_t *, ship_t *camShip, droid_t *); |
||||
void camera_update_attract_internal(camera_t *, ship_t *camShip, droid_t *); |
||||
void camera_update_static_follow(camera_t *, ship_t *camShip, droid_t *); |
||||
void camera_update_attract_random(camera_t *, ship_t *camShip, droid_t *); |
||||
void camera_update_rescue(camera_t *, ship_t *camShip, droid_t *); |
||||
|
||||
#endif |
@ -0,0 +1,260 @@ |
||||
#include "../types.h" |
||||
#include "../mem.h" |
||||
#include "../system.h" |
||||
#include "../utils.h" |
||||
|
||||
#include "object.h" |
||||
#include "track.h" |
||||
#include "ship.h" |
||||
#include "weapon.h" |
||||
#include "hud.h" |
||||
#include "droid.h" |
||||
#include "camera.h" |
||||
#include "image.h" |
||||
#include "scene.h" |
||||
#include "object.h" |
||||
#include "game.h" |
||||
|
||||
static Object *droid_model; |
||||
|
||||
void droid_load() { |
||||
texture_list_t droid_textures = image_get_compressed_textures("wipeout/common/rescu.cmp"); |
||||
droid_model = objects_load("wipeout/common/rescu.prm", droid_textures); |
||||
} |
||||
|
||||
void droid_init(droid_t *droid, ship_t *ship) { |
||||
droid->section = g.track.sections; |
||||
|
||||
while (flags_not(droid->section->flags, SECTION_JUMP)) { |
||||
droid->section = droid->section->next; |
||||
} |
||||
|
||||
droid->position = vec3_add(ship->position, vec3(0, -200, 0)); |
||||
droid->velocity = vec3(0, 0, 0); |
||||
droid->acceleration = vec3(0, 0, 0); |
||||
droid->angle = vec3(0, 0, 0); |
||||
droid->angular_velocity = vec3(0, 0, 0); |
||||
droid->update_timer = DROID_UPDATE_TIME_INITIAL; |
||||
droid->mat = mat4_identity(); |
||||
|
||||
droid->cycle_timer = 0; |
||||
droid->update_func = droid_update_intro; |
||||
|
||||
droid->sfx_tractor = sfx_reserve_loop(SFX_TRACTOR); |
||||
flags_rm(droid->sfx_tractor->flags, SFX_PLAY); |
||||
} |
||||
|
||||
void droid_draw(droid_t *droid) { |
||||
droid->cycle_timer += system_tick() * M_PI * 2; |
||||
|
||||
Prm prm = {.primitive = droid_model->primitives}; |
||||
int rf = sin(droid->cycle_timer) * 127 + 128; |
||||
int gf = sin(droid->cycle_timer + 0.2) * 127 + 128; |
||||
int bf = sin(droid->cycle_timer * 0.5 + 0.1) * 127 + 128; |
||||
|
||||
int r, g, b; |
||||
|
||||
for (int i = 0; i < 11; i++) { |
||||
if (i < 2) { |
||||
r = 40; |
||||
g = gf; |
||||
b = 40; |
||||
} |
||||
else if (i < 6) { |
||||
r = bf >> 1; |
||||
b = bf; |
||||
g = bf >> 1; |
||||
} |
||||
else { |
||||
r = rf; |
||||
b = 40; |
||||
g = 40; |
||||
} |
||||
|
||||
switch (prm.f3->type) { |
||||
case PRM_TYPE_GT3: |
||||
prm.gt3->colour[0].as_rgba.r = r; |
||||
prm.gt3->colour[0].as_rgba.g = g; |
||||
prm.gt3->colour[0].as_rgba.b = b; |
||||
|
||||
prm.gt3->colour[1].as_rgba.r = r; |
||||
prm.gt3->colour[1].as_rgba.g = g; |
||||
prm.gt3->colour[1].as_rgba.b = b; |
||||
|
||||
prm.gt3->colour[2].as_rgba.r = r; |
||||
prm.gt3->colour[2].as_rgba.g = g; |
||||
prm.gt3->colour[2].as_rgba.b = b; |
||||
prm.gt3++; |
||||
break; |
||||
|
||||
case PRM_TYPE_GT4: |
||||
prm.gt4->colour[0].as_rgba.r = r; |
||||
prm.gt4->colour[0].as_rgba.g = g; |
||||
prm.gt4->colour[0].as_rgba.b = b; |
||||
|
||||
prm.gt4->colour[1].as_rgba.r = r; |
||||
prm.gt4->colour[1].as_rgba.g = g; |
||||
prm.gt4->colour[1].as_rgba.b = b; |
||||
|
||||
prm.gt4->colour[2].as_rgba.r = r; |
||||
prm.gt4->colour[2].as_rgba.g = g; |
||||
prm.gt4->colour[2].as_rgba.b = b; |
||||
|
||||
prm.gt4->colour[3].as_rgba.r = 40; |
||||
prm.gt4->colour[3].as_rgba.g = 40; |
||||
prm.gt4->colour[3].as_rgba.b = 40; |
||||
prm.gt4++; |
||||
break; |
||||
} |
||||
} |
||||
|
||||
mat4_set_translation(&droid->mat, droid->position); |
||||
mat4_set_yaw_pitch_roll(&droid->mat, droid->angle); |
||||
object_draw(droid_model, &droid->mat); |
||||
} |
||||
|
||||
void droid_update(droid_t *droid, ship_t *ship) { |
||||
(droid->update_func)(droid, ship); |
||||
|
||||
droid->velocity = vec3_add(droid->velocity, vec3_mulf(droid->acceleration, 30 * system_tick())); |
||||
droid->velocity = vec3_sub(droid->velocity, vec3_mulf(droid->velocity, 0.125 * 30 * system_tick())); |
||||
droid->position = vec3_add(droid->position, vec3_mulf(droid->velocity, 0.015625 * 30 * system_tick())); |
||||
droid->angle = vec3_add(droid->angle, vec3_mulf(droid->angular_velocity, system_tick())); |
||||
droid->angle = vec3_wrap_angle(droid->angle); |
||||
|
||||
if (flags_is(droid->sfx_tractor->flags, SFX_PLAY)) { |
||||
sfx_set_position(droid->sfx_tractor, droid->position, droid->velocity, 0.5); |
||||
} |
||||
} |
||||
|
||||
void droid_update_intro(droid_t *droid, ship_t *ship) { |
||||
droid->update_timer -= system_tick(); |
||||
|
||||
if (droid->update_timer < DROID_UPDATE_TIME_INTRO_3) { |
||||
droid->acceleration.x = (-sin(droid->angle.y) * cos(droid->angle.x)) * 0.25 * 4096.0; |
||||
droid->acceleration.y = 0; |
||||
droid->acceleration.z = (cos(droid->angle.y) * cos(droid->angle.x)) * 0.25 * 4096.0; |
||||
droid->angular_velocity.y = 0; |
||||
} |
||||
|
||||
else if (droid->update_timer < DROID_UPDATE_TIME_INTRO_2) { |
||||
droid->acceleration.x = (-sin(droid->angle.y) * cos(droid->angle.x)) * 0.125 * 4096.0; |
||||
droid->acceleration.y = -140; |
||||
droid->acceleration.z = (cos(droid->angle.y) * cos(droid->angle.x)) * 0.125 * 4096.0; |
||||
droid->angular_velocity.y = (-8.0 / 4096.0) * M_PI * 2 * 30; |
||||
} |
||||
|
||||
else if (droid->update_timer < DROID_UPDATE_TIME_INTRO_1) { |
||||
droid->acceleration.y -= 90 * system_tick(); |
||||
droid->angular_velocity.y = (8.0 / 4096.0) * M_PI * 2 * 30; |
||||
} |
||||
|
||||
if (droid->update_timer <= 0) { |
||||
droid->update_timer = DROID_UPDATE_TIME_INITIAL; |
||||
droid->update_func = droid_update_idle; |
||||
droid->position.x = droid->section->center.x; |
||||
droid->position.y = -3000; |
||||
droid->position.z = droid->section->center.z; |
||||
} |
||||
} |
||||
|
||||
void droid_update_idle(droid_t *droid, ship_t *ship) { |
||||
section_t *next = droid->section->next; |
||||
|
||||
vec3_t target = vec3( |
||||
(droid->section->center.x + next->center.x) * 0.5, |
||||
droid->section->center.y - 3000, |
||||
(droid->section->center.z + next->center.z) * 0.5 |
||||
); |
||||
|
||||
vec3_t target_vector = vec3_sub(target, droid->position); |
||||
|
||||
float target_heading = -atan2(target_vector.x, target_vector.z); |
||||
float quickest_turn = target_heading - droid->angle.y; |
||||
float turn; |
||||
if (droid->angle.y < 0) { |
||||
turn = target_heading - (droid->angle.y + M_PI*2); |
||||
} |
||||
else { |
||||
turn = target_heading - (droid->angle.y - M_PI*2); |
||||
} |
||||
|
||||
if (fabsf(turn) < fabsf(quickest_turn)) { |
||||
droid->angular_velocity.y = turn * 30 / 64.0; |
||||
} |
||||
else { |
||||
droid->angular_velocity.y = quickest_turn * 30.0 / 64.0; |
||||
} |
||||
|
||||
droid->acceleration.x = (-sin(droid->angle.y) * cos(droid->angle.x)) * 0.125 * 4096; |
||||
droid->acceleration.y = target_vector.y / 64.0; |
||||
droid->acceleration.z = (cos(droid->angle.y) * cos(droid->angle.x)) * 0.125 * 4096; |
||||
|
||||
if (flags_is(ship->flags, SHIP_IN_RESCUE)) { |
||||
flags_add(droid->sfx_tractor->flags, SFX_PLAY); |
||||
|
||||
droid->update_func = droid_update_rescue; |
||||
droid->update_timer = DROID_UPDATE_TIME_INITIAL; |
||||
|
||||
g.camera.update_func = camera_update_rescue; |
||||
flags_add(ship->flags, SHIP_VIEW_REMOTE); |
||||
if (flags_is(ship->section->flags, SECTION_JUMP)) { |
||||
g.camera.section = ship->section->next; |
||||
} |
||||
else { |
||||
g.camera.section = ship->section; |
||||
} |
||||
|
||||
// If droid is not nearby the rescue position teleport it in!
|
||||
if (droid->section != ship->section && droid->section != ship->section->prev) { |
||||
droid->section = ship->section; |
||||
section_t *next = droid->section->next; |
||||
|
||||
droid->position.x = (droid->section->center.x + next->center.x) * 0.5; |
||||
droid->position.y = droid->section->center.y - 3000; |
||||
droid->position.z = (droid->section->center.z + next->center.z) * 0.5; |
||||
} |
||||
flags_rm(ship->flags, SHIP_IN_TOW); |
||||
droid->velocity = vec3(0,0,0); |
||||
droid->acceleration = vec3(0,0,0); |
||||
} |
||||
|
||||
// AdjustDirectionalNote(START_SIREN, 0, 0, (VECTOR){droid->position.x, droid->position.y, droid->position.z});
|
||||
} |
||||
|
||||
void droid_update_rescue(droid_t *droid, ship_t *ship) { |
||||
droid->angular_velocity.y = 0; |
||||
droid->angle.y = ship->angle.y; |
||||
|
||||
vec3_t target = vec3(ship->position.x, ship->position.y - 350, ship->position.z); |
||||
vec3_t distance = vec3_sub(target, droid->position); |
||||
|
||||
|
||||
if (flags_is(ship->flags, SHIP_IN_TOW)) { |
||||
droid->velocity = vec3(0,0,0); |
||||
droid->acceleration = vec3(0,0,0); |
||||
droid->position = target; |
||||
} |
||||
else if (vec3_len(distance) < 8) { |
||||
flags_add(ship->flags, SHIP_IN_TOW); |
||||
droid->velocity = vec3(0,0,0); |
||||
droid->acceleration = vec3(0,0,0); |
||||
droid->position = target; |
||||
} |
||||
else { |
||||
droid->velocity = vec3_mulf(distance, 16);
|
||||
} |
||||
|
||||
|
||||
// Are we done rescuing?
|
||||
if (flags_not(ship->flags, SHIP_IN_RESCUE)) { |
||||
flags_rm(droid->sfx_tractor->flags, SFX_PLAY); |
||||
droid->siren_started = false; |
||||
droid->update_func = droid_update_idle; |
||||
droid->update_timer = DROID_UPDATE_TIME_INITIAL; |
||||
|
||||
while (flags_not(droid->section->flags, SECTION_JUMP)) { |
||||
droid->section = droid->section->prev; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,39 @@ |
||||
#ifndef DROID_H |
||||
#define DROID_H |
||||
|
||||
#include "../types.h" |
||||
#include "track.h" |
||||
#include "ship.h" |
||||
#include "sfx.h" |
||||
|
||||
#define DROID_UPDATE_TIME_INITIAL (800 * (1.0/30.0)) |
||||
#define DROID_UPDATE_TIME_INTRO_1 (770 * (1.0/30.0)) |
||||
#define DROID_UPDATE_TIME_INTRO_2 (710 * (1.0/30.0)) |
||||
#define DROID_UPDATE_TIME_INTRO_3 (400 * (1.0/30.0)) |
||||
|
||||
typedef struct droid_t { |
||||
section_t *section; |
||||
vec3_t position; |
||||
vec3_t velocity; |
||||
vec3_t acceleration; |
||||
vec3_t angle; |
||||
vec3_t angular_velocity; |
||||
bool siren_started; |
||||
float cycle_timer; |
||||
float update_timer; |
||||
void (*update_func)(struct droid_t *, ship_t *); |
||||
mat4_t mat; |
||||
Object *model; |
||||
sfx_t *sfx_tractor; |
||||
} droid_t; |
||||
|
||||
void droid_draw(droid_t *droid); |
||||
|
||||
void droid_load(); |
||||
void droid_init(droid_t *droid, ship_t *ship); |
||||
void droid_update(droid_t *droid, ship_t *ship); |
||||
void droid_update_intro(droid_t *droid, ship_t *ship); |
||||
void droid_update_idle(droid_t *droid, ship_t *ship); |
||||
void droid_update_rescue(droid_t *droid, ship_t *ship); |
||||
|
||||
#endif |
@ -0,0 +1,637 @@ |
||||
#include <string.h> |
||||
|
||||
#include "../mem.h" |
||||
#include "../utils.h" |
||||
#include "../system.h" |
||||
#include "../platform.h" |
||||
#include "../input.h" |
||||
|
||||
#include "game.h" |
||||
#include "ship.h" |
||||
#include "weapon.h" |
||||
#include "droid.h" |
||||
#include "object.h" |
||||
#include "hud.h" |
||||
#include "game.h" |
||||
#include "sfx.h" |
||||
#include "ui.h" |
||||
#include "particle.h" |
||||
#include "race.h" |
||||
#include "main_menu.h" |
||||
#include "title.h" |
||||
#include "intro.h" |
||||
|
||||
#define TURN_ACCEL(V) NTSC_ACCELERATION(ANGLE_NORM_TO_RADIAN(FIXED_TO_FLOAT(YAW_VELOCITY(V)))) |
||||
#define TURN_VEL(V) NTSC_VELOCITY(ANGLE_NORM_TO_RADIAN(FIXED_TO_FLOAT(YAW_VELOCITY(V)))) |
||||
|
||||
const game_def_t def = { |
||||
.race_classes = { |
||||
[RACE_CLASS_VENOM] = {.name = "VENOM CLASS"}, |
||||
[RACE_CLASS_RAPIER] = {.name = "RAPIER CLASS"}, |
||||
}, |
||||
|
||||
.race_types = { |
||||
[RACE_TYPE_CHAMPIONSHIP] = {.name = "CHAMPIONSHIP RACE"}, |
||||
[RACE_TYPE_SINGLE] = {.name = "SINGLE RACE"}, |
||||
[RACE_TYPE_TIME_TRIAL] = {.name = "TIME TRIAL"}, |
||||
}, |
||||
|
||||
.pilots = { |
||||
[PILOT_JOHN_DEKKA] = {.name = "JOHN DEKKA", .portrait = "wipeout/textures/dekka.cmp", .team = 0, .logo_model = 0}, |
||||
[PILOT_DANIEL_CHANG] = {.name = "DANIEL CHANG", .portrait = "wipeout/textures/chang.cmp", .team = 0, .logo_model = 4}, |
||||
[PILOT_ARIAL_TETSUO] = {.name = "ARIAL TETSUO", .portrait = "wipeout/textures/arial.cmp", .team = 1, .logo_model = 6}, |
||||
[PILOT_ANASTASIA_CHEROVOSKI] = {.name = "ANASTASIA CHEROVOSKI", .portrait = "wipeout/textures/anast.cmp", .team = 1, .logo_model = 7}, |
||||
[PILOT_KEL_SOLAAR] = {.name = "KEL SOLAAR", .portrait = "wipeout/textures/solar.cmp", .team = 2, .logo_model = 2}, |
||||
[PILOT_ARIAN_TETSUO] = {.name = "ARIAN TETSUO", .portrait = "wipeout/textures/arian.cmp", .team = 2, .logo_model = 5}, |
||||
[PILOT_SOFIA_DE_LA_RENTE] = {.name = "SOFIA DE LA RENTE", .portrait = "wipeout/textures/sophi.cmp", .team = 3, .logo_model = 1}, |
||||
[PILOT_PAUL_JACKSON] = {.name = "PAUL JACKSON", .portrait = "wipeout/textures/paul.cmp", .team = 3, .logo_model = 3}, |
||||
}, |
||||
|
||||
.ship_model_to_pilot = {6, 4, 7, 1, 5, 2, 3, 0}, |
||||
.race_points_for_rank = {9, 7, 5, 3, 2, 1, 0, 0}, |
||||
|
||||
// SHIP ATTRIBUTES
|
||||
// TEAM 1 TEAM 2 TEAM 3 TEAM 4
|
||||
// Acceleration: *** ***** ** ****
|
||||
// Top Speed: **** ** **** ***
|
||||
// Armour: ***** *** **** **
|
||||
// Turn Rate: ** **** *** *****
|
||||
|
||||
.teams = { |
||||
[TEAM_AG_SYSTEMS] = { |
||||
.name = "AG SYSTEMS", |
||||
.logo_model = 2, |
||||
.pilots = {0, 1}, |
||||
.attributes = { |
||||
[RACE_CLASS_VENOM] = {.mass = 150, .thrust_max = 790, .resistance = 140, .turn_rate = TURN_ACCEL(160), .turn_rate_max = TURN_VEL(2560), .skid = 12}, |
||||
[RACE_CLASS_RAPIER] = {.mass = 150, .thrust_max = 1200, .resistance = 140, .turn_rate = TURN_ACCEL(160), .turn_rate_max = TURN_VEL(2560), .skid = 10}, |
||||
}, |
||||
}, |
||||
[TEAM_AURICOM] = { |
||||
.name = "AURICOM", |
||||
.logo_model = 3, |
||||
.pilots = {2, 3}, |
||||
.attributes = { |
||||
[RACE_CLASS_VENOM] = {.mass = 150, .thrust_max = 850, .resistance = 134, .turn_rate = TURN_ACCEL(140), .turn_rate_max = TURN_VEL(1920), .skid = 20}, |
||||
[RACE_CLASS_RAPIER] = {.mass = 150, .thrust_max = 1400, .resistance = 140, .turn_rate = TURN_ACCEL(120), .turn_rate_max = TURN_VEL(1920), .skid = 14}, |
||||
}, |
||||
}, |
||||
[TEAM_QIREX] = { |
||||
.name = "QIREX", |
||||
.logo_model = 1, |
||||
.pilots = {4, 5}, |
||||
.attributes = { |
||||
[RACE_CLASS_VENOM] = {.mass = 150, .thrust_max = 850, .resistance = 140, .turn_rate = TURN_ACCEL(120), .turn_rate_max = TURN_VEL(1920), .skid = 24}, |
||||
[RACE_CLASS_RAPIER] = {.mass = 150, .thrust_max = 1400, .resistance = 130, .turn_rate = TURN_ACCEL(140), .turn_rate_max = TURN_VEL(1920), .skid = 16}, |
||||
}, |
||||
}, |
||||
[TEAM_FEISAR] = { |
||||
.name = "FEISAR", |
||||
.logo_model = 0, |
||||
.pilots = {6, 7}, |
||||
.attributes = { |
||||
[RACE_CLASS_VENOM] = {.mass = 150, .thrust_max = 790, .resistance = 134, .turn_rate = TURN_ACCEL(180), .turn_rate_max = TURN_VEL(2560), .skid = 12}, |
||||
[RACE_CLASS_RAPIER] = {.mass = 150, .thrust_max = 1200, .resistance = 130, .turn_rate = TURN_ACCEL(180), .turn_rate_max = TURN_VEL(2560), .skid = 8}, |
||||
}, |
||||
}, |
||||
}, |
||||
|
||||
.ai_settings = { |
||||
[RACE_CLASS_VENOM] = { |
||||
{.thrust_max = 2550, .thrust_magnitude = 44, .fight_back = 1}, |
||||
{.thrust_max = 2600, .thrust_magnitude = 45, .fight_back = 1}, |
||||
{.thrust_max = 2630, .thrust_magnitude = 45, .fight_back = 1}, |
||||
{.thrust_max = 2660, .thrust_magnitude = 46, .fight_back = 1}, |
||||
{.thrust_max = 2700, .thrust_magnitude = 47, .fight_back = 1}, |
||||
{.thrust_max = 2720, .thrust_magnitude = 48, .fight_back = 1}, |
||||
{.thrust_max = 2750, .thrust_magnitude = 49, .fight_back = 1}, |
||||
}, |
||||
[RACE_CLASS_RAPIER] = { |
||||
{.thrust_max = 3750, .thrust_magnitude = 50, .fight_back = 1}, |
||||
{.thrust_max = 3780, .thrust_magnitude = 53, .fight_back = 1}, |
||||
{.thrust_max = 3800, .thrust_magnitude = 55, .fight_back = 1}, |
||||
{.thrust_max = 3850, .thrust_magnitude = 57, .fight_back = 1}, |
||||
{.thrust_max = 3900, .thrust_magnitude = 60, .fight_back = 1}, |
||||
{.thrust_max = 3950, .thrust_magnitude = 62, .fight_back = 1}, |
||||
{.thrust_max = 4000, .thrust_magnitude = 65, .fight_back = 1}, |
||||
}, |
||||
}, |
||||
|
||||
.circuts = { |
||||
[CIRCUT_ALTIMA_VII] = { |
||||
.name = "ALTIMA VII", |
||||
.is_bonus_circut = false, |
||||
.settings = { |
||||
[RACE_CLASS_VENOM] = {.path = "wipeout/track02/", .start_line_pos = 27, .behind_speed = 300, .spread_base = 80, .spread_factor = 20, .sky_y_offset = -2520}, |
||||
[RACE_CLASS_RAPIER] = {.path = "wipeout/track03/", .start_line_pos = 27, .behind_speed = 500, .spread_base = 80, .spread_factor = 11, .sky_y_offset = -1930}, |
||||
} |
||||
}, |
||||
[CIRCUT_KARBONIS_V] = { |
||||
.name = "KARBONIS V", |
||||
.is_bonus_circut = false, |
||||
.settings = { |
||||
[RACE_CLASS_VENOM] = {.path = "wipeout/track04/", .start_line_pos = 16, .behind_speed = 200, .spread_base = 10, .spread_factor = 8, .sky_y_offset = -5000}, |
||||
[RACE_CLASS_RAPIER] = {.path = "wipeout/track05/", .start_line_pos = 16, .behind_speed = 500, .spread_base = 10, .spread_factor = 8, .sky_y_offset = -5000}, |
||||
} |
||||
}, |
||||
[CIRCUT_TERRAMAX] = { |
||||
.name = "TERRAMAX", |
||||
.is_bonus_circut = false, |
||||
.settings = { |
||||
[RACE_CLASS_VENOM] = {.path = "wipeout/track01/", .start_line_pos = 27, .behind_speed = 350, .spread_base = 60, .spread_factor = 11, .sky_y_offset = -820}, |
||||
[RACE_CLASS_RAPIER] = {.path = "wipeout/track06/", .start_line_pos = 27, .behind_speed = 500, .spread_base = 10, .spread_factor = 8, .sky_y_offset = 0}, |
||||
} |
||||
}, |
||||
[CIRCUT_KORODERA] = { |
||||
.name = "KORODERA", |
||||
.is_bonus_circut = false, |
||||
.settings = { |
||||
[RACE_CLASS_VENOM] = {.path = "wipeout/track12/", .start_line_pos = 16, .behind_speed = 450, .spread_base = 40, .spread_factor = 11, .sky_y_offset = -2120}, |
||||
[RACE_CLASS_RAPIER] = {.path = "wipeout/track07/", .start_line_pos = 16, .behind_speed = 500, .spread_base = 30, .spread_factor = 11, .sky_y_offset = -2260}, |
||||
} |
||||
}, |
||||
[CIRCUT_ARRIDOS_IV] = { |
||||
.name = "ARRIDOS IV", |
||||
.is_bonus_circut = false, |
||||
.settings = { |
||||
[RACE_CLASS_VENOM] = {.path = "wipeout/track08/", .start_line_pos = 16, .behind_speed = 350, .spread_base = 80, .spread_factor = 15, .sky_y_offset = -40}, |
||||
[RACE_CLASS_RAPIER] = {.path = "wipeout/track11/", .start_line_pos = 16, .behind_speed = 450, .spread_base = 30, .spread_factor = 11, .sky_y_offset = -240}, |
||||
} |
||||
}, |
||||
[CIRCUT_SILVERSTREAM] = { |
||||
.name = "SILVERSTREAM", |
||||
.is_bonus_circut = false, |
||||
.settings = { |
||||
[RACE_CLASS_VENOM] = {.path = "wipeout/track09/", .start_line_pos = 16, .behind_speed = 150, .spread_base = 10, .spread_factor = 8, .sky_y_offset = -2700}, |
||||
[RACE_CLASS_RAPIER] = {.path = "wipeout/track13/", .start_line_pos = 16, .behind_speed = 150, .spread_base = 10, .spread_factor = 8, .sky_y_offset = -2700}, |
||||
} |
||||
}, |
||||
[CIRCUT_FIRESTAR] = { |
||||
.name = "FIRESTAR", |
||||
.is_bonus_circut = true, |
||||
.settings = { |
||||
[RACE_CLASS_VENOM] = {.path = "wipeout/track10/", .start_line_pos = 27, .behind_speed = 200, .spread_base = 40, .spread_factor = 11, .sky_y_offset = 0}, |
||||
[RACE_CLASS_RAPIER] = {.path = "wipeout/track14/", .start_line_pos = 27, .behind_speed = 500, .spread_base = 40, .spread_factor = 11, .sky_y_offset = 0}, |
||||
} |
||||
}, |
||||
}, |
||||
.music = { |
||||
{.path = "wipeout/music/track01.qoa", .name = "CAIRODROME"}, |
||||
{.path = "wipeout/music/track02.qoa", .name = "CARDINAL DANCER"}, |
||||
{.path = "wipeout/music/track03.qoa", .name = "COLD COMFORT"}, |
||||
{.path = "wipeout/music/track04.qoa", .name = "DOH T"}, |
||||
{.path = "wipeout/music/track05.qoa", .name = "MESSIJ"}, |
||||
{.path = "wipeout/music/track06.qoa", .name = "OPERATIQUE"}, |
||||
{.path = "wipeout/music/track07.qoa", .name = "TENTATIVE"}, |
||||
{.path = "wipeout/music/track08.qoa", .name = "TRANCEVAAL"}, |
||||
{.path = "wipeout/music/track09.qoa", .name = "AFRO RIDE"}, |
||||
{.path = "wipeout/music/track10.qoa", .name = "CHEMICAL BEATS"}, |
||||
{.path = "wipeout/music/track11.qoa", .name = "WIPEOUT"}, |
||||
}, |
||||
.credits = { |
||||
"#MANAGING DIRECTORS", |
||||
"IAN HETHERINGTON", |
||||
"JONATHAN ELLIS", |
||||
"#DIRECTOR OF DEVELOPMENT", |
||||
"JOHN WHITE", |
||||
"#PRODUCERS", |
||||
"DOMINIC MALLINSON", |
||||
"ANDY YELLAND", |
||||
"#PRODUCT MANAGER", |
||||
"SUE CAMPBELL", |
||||
"#GAME DESIGNER", |
||||
"NICK BURCOMBE", |
||||
"", |
||||
"", |
||||
"#PLAYSTATION VERSION", |
||||
"#PROGRAMMERS", |
||||
"DAVE ROSE", |
||||
"ROB SMITH", |
||||
"JASON DENTON", |
||||
"STEWART SOCKETT", |
||||
"#ORIGINAL ARTISTS", |
||||
"NICKY CARUS WESTCOTT", |
||||
"LAURA GRIEVE", |
||||
"LOUISE SMITH", |
||||
"DARREN DOUGLAS", |
||||
"POL SIGERSON", |
||||
"#INTRO SEQUENCE", |
||||
"LEE CARUS WESTCOTT", |
||||
"#CONCEPTUAL ARTIST", |
||||
"JIM BOWERS", |
||||
"#ADDITIONAL GRAPHIC DESIGN", |
||||
"THE DESIGNERS REPUBLIC", |
||||
"#MUSIC", |
||||
"ORBITAL", |
||||
"CHEMICAL BROTHERS", |
||||
"LEFTFIELD", |
||||
"COLD STORAGE", |
||||
"#SOUND EFFECTS", |
||||
"TIM WRIGHT", |
||||
"#MANUAL WRITTEN BY", |
||||
"DAMON FAIRCLOUGH", |
||||
"NICK BURCOMBE", |
||||
"#PACKAGING DESIGN", |
||||
"THE DESIGNERS REPUBLIC", |
||||
"KEITH HOPWOOD", |
||||
"", |
||||
"", |
||||
"#PC VERSION", |
||||
"#PROGRAMMERS", |
||||
"ANDY YELLAND", |
||||
"ANDY SATTERTHWAITE", |
||||
"DAVE SMITH", |
||||
"MARK KELLY", |
||||
"JED ADAMS", |
||||
"STEVE WARD", |
||||
"CHRIS EDEN", |
||||
"SALIM SIWANI", |
||||
"#SOUND PROGRAMMING", |
||||
"ANDY CROWLEY", |
||||
"#MOVIE PROGRAMMING", |
||||
"MIKE ANTHONY", |
||||
"#CONVERSION ARTISTS", |
||||
"JOHN DWYER", |
||||
"GARY BURLEY", |
||||
"", |
||||
"", |
||||
"#ATI 3D RAGE VERSION", |
||||
"#PRODUCER", |
||||
"BILL ALLEN", |
||||
"#DEVELOPED BY", |
||||
"#BROADSWORD INTERACTIVE LTD", |
||||
"STEPHEN ROSE", |
||||
"JOHN JONES STEELE", |
||||
"", |
||||
"", |
||||
"#2023 REWRITE", |
||||
"PHOBOSLAB", |
||||
"DOMINIC SZABLEWSKI", |
||||
"", |
||||
"", |
||||
"#DEVELOPMENT SECRETARY", |
||||
"JENNIFER REES", |
||||
"", |
||||
"", |
||||
"#QUALITY ASSURANCE", |
||||
"STUART ALLEN", |
||||
"CHRIS GRAHAM", |
||||
"THOMAS REES", |
||||
"BRIAN WALSH", |
||||
"CARL BERRY", |
||||
"MARK INMAN", |
||||
"PAUL TWEEDLE", |
||||
"ANTHONY CROSS", |
||||
"EDWARD HAY", |
||||
"ROB WOLFE", |
||||
"", |
||||
"", |
||||
"#SPECIAL THANKS TO", |
||||
"THE HACKERS TEAM MGM", |
||||
"SOFTIMAGE", |
||||
"SGI", |
||||
"GLEN OCONNELL", |
||||
"JOANNE GALVIN", |
||||
"ALL AT PSYGNOSIS", |
||||
}, |
||||
.congratulations = { |
||||
.venom = { |
||||
"#WELL DONE", |
||||
"", |
||||
"VENOM CLASS", |
||||
"", |
||||
"COMPETENCE ACHIEVED", |
||||
"", |
||||
"YOU HAVE NOW QUALIFIED", |
||||
"", |
||||
"FOR THE ULTRA FAST", |
||||
"", |
||||
"RAPIER CLASS", |
||||
"", |
||||
"WE RECOMMEND YOU", |
||||
"", |
||||
"SAVE YOUR CURRENT GAME", |
||||
}, |
||||
.venom_all_circuts = { |
||||
"#AMAZING", |
||||
"", |
||||
"YOU HAVE COMPLETED THE FULL", |
||||
"", |
||||
"VENOM CLASS CHAMPIONSHIP", |
||||
"", |
||||
"", |
||||
"WELL DONE", |
||||
"", |
||||
"YOU ARE A GREAT PILOT", |
||||
"", |
||||
"", |
||||
"", |
||||
"NOW TAKE ON THE FULL", |
||||
"", |
||||
"RAPIER CLASS CHAMPIONSHIP", |
||||
"", |
||||
"", |
||||
"#KEEP GOING", |
||||
}, |
||||
.rapier = { |
||||
"#CONGRATULATIONS", |
||||
"", |
||||
"RAPIER CLASS", |
||||
"", |
||||
"COMPETENCE ACHIEVED", |
||||
"", |
||||
"YOU NOW HAVE ACCESS TO THE", |
||||
"", |
||||
"FULL VENOM AND RAPIER", |
||||
"", |
||||
"CHAMPIONSHIPS WITH THE ", |
||||
"", |
||||
"NEWLY CONSTRUCTED CIRCUIT", |
||||
"", |
||||
"FIRESTAR", |
||||
"", |
||||
"", |
||||
"", |
||||
"WE RECOMMEND YOU", |
||||
"", |
||||
"SAVE", |
||||
"", |
||||
"YOUR CURRENT GAME", |
||||
"", |
||||
"", |
||||
"#GOOD LUCK", |
||||
}, |
||||
.rapier_all_circuts = { |
||||
"#AWESOME", |
||||
"", |
||||
"YOU HAVE BEATEN", |
||||
"#WIPEOUT", |
||||
"", |
||||
"YOU ARE A TRULY", |
||||
"", |
||||
"AMAZING PILOT", |
||||
"", |
||||
"", |
||||
"", |
||||
"#CONGRATULATIONS", |
||||
"", |
||||
"", |
||||
"", |
||||
"", |
||||
"#A BIG THANKS", |
||||
"", |
||||
"FROM ALL OF US ON THE TEAM", |
||||
"", |
||||
"LOOK OUT FOR", |
||||
"#WIPEOUT II", |
||||
"", |
||||
"COMING SOON", |
||||
}, |
||||
} |
||||
}; |
||||
|
||||
save_t save = { |
||||
.magic = SAVE_DATA_MAGIC, |
||||
.is_dirty = true, |
||||
|
||||
.sfx_volume = 0.6, |
||||
.music_volume = 0.5, |
||||
.ui_scale = 0, |
||||
.show_fps = false, |
||||
.fullscreen = false, |
||||
.screen_res = 0, |
||||
.post_effect = 0, |
||||
|
||||
.has_rapier_class = true, // for testing; should be false in prod
|
||||
.has_bonus_circuts = true, // for testing; should be false in prod
|
||||
|
||||
.buttons = { |
||||
[A_UP] = {INPUT_KEY_UP, INPUT_GAMEPAD_DPAD_UP}, |
||||
[A_DOWN] = {INPUT_KEY_DOWN, INPUT_GAMEPAD_DPAD_DOWN}, |
||||
[A_LEFT] = {INPUT_KEY_LEFT, INPUT_GAMEPAD_DPAD_LEFT}, |
||||
[A_RIGHT] = {INPUT_KEY_RIGHT, INPUT_GAMEPAD_DPAD_RIGHT}, |
||||
[A_BRAKE_LEFT] = {INPUT_KEY_C, INPUT_GAMEPAD_L_SHOULDER}, |
||||
[A_BRAKE_RIGHT] = {INPUT_KEY_V, INPUT_GAMEPAD_R_SHOULDER}, |
||||
[A_THRUST] = {INPUT_KEY_X, INPUT_GAMEPAD_A}, |
||||
[A_FIRE] = {INPUT_KEY_Z, INPUT_GAMEPAD_X}, |
||||
[A_CHANGE_VIEW] = {INPUT_KEY_A, INPUT_GAMEPAD_Y}, |
||||
}, |
||||
|
||||
.highscores_name = {0,0,0,0}, |
||||
.highscores = { |
||||
[RACE_CLASS_VENOM] = { |
||||
{ |
||||
[HIGHSCORE_TAB_RACE] = {.lap_record = 85.83, .entries = {{"WIP", 254.50},{"EOU", 271.17},{"TPC", 289.50},{"NOT", 294.50},{"PSX", 314.50}}}, |
||||
[HIGHSCORE_TAB_TIME_TRIAL] = {.lap_record = 85.83, .entries = {{"MVE", 254.50},{"ALM", 271.17},{"POL", 289.50},{"NIK", 294.50},{"DAR", 314.50}}}, |
||||
}, |
||||
{ |
||||
[HIGHSCORE_TAB_RACE] = {.lap_record = 55.33, .entries = {{"AJY", 159.33},{"AJS", 172.67},{"DLS", 191.00},{"MAK", 207.67},{"JED", 219.33}}}, |
||||
[HIGHSCORE_TAB_TIME_TRIAL] = {.lap_record = 55.33, .entries = {{"DAR", 159.33},{"STU", 172.67},{"MOC", 191.00},{"DOM", 207.67},{"NIK", 219.33}}}, |
||||
}, |
||||
{ |
||||
[HIGHSCORE_TAB_RACE] = {.lap_record = 57.5, .entries = {{ "JD", 171.00},{"AJC", 189.33},{"MSA", 202.67},{ "SD", 219.33},{"TIM", 232.67}}}, |
||||
[HIGHSCORE_TAB_TIME_TRIAL] = {.lap_record = 57.5, .entries = {{"PHO", 171.00},{"ENI", 189.33},{ "XR", 202.67},{"ISI", 219.33},{ "NG", 232.67}}}, |
||||
}, |
||||
{ |
||||
[HIGHSCORE_TAB_RACE] = {.lap_record = 85.17, .entries = {{"POL", 251.33},{"DAR", 263.00},{"JAS", 283.00},{"ROB", 294.67},{"DJR", 314.82}}}, |
||||
[HIGHSCORE_TAB_TIME_TRIAL] = {.lap_record = 85.17, .entries = {{"DOM", 251.33},{"DJR", 263.00},{"MPI", 283.00},{"GOC", 294.67},{"SUE", 314.82}}}, |
||||
}, |
||||
{ |
||||
[HIGHSCORE_TAB_RACE] = {.lap_record = 80.17, .entries = {{"NIK", 236.17},{"SAL", 253.17},{"DOM", 262.33},{ "LG", 282.67},{"LNK", 298.17}}}, |
||||
[HIGHSCORE_TAB_TIME_TRIAL] = {.lap_record = 80.17, .entries = {{"NIK", 236.17},{"ROB", 253.17},{ "AM", 262.33},{"JAS", 282.67},{"DAR", 298.17}}}, |
||||
}, |
||||
{ |
||||
[HIGHSCORE_TAB_RACE] = {.lap_record = 61.67, .entries = {{"HAN", 182.33},{"PER", 196.33},{"FEC", 214.83},{"TPI", 228.83},{"ZZA", 244.33}}}, |
||||
[HIGHSCORE_TAB_TIME_TRIAL] = {.lap_record = 61.67, .entries = {{ "FC", 182.33},{"SUE", 196.33},{"ROB", 214.83},{"JEN", 228.83},{ "NT", 244.33}}}, |
||||
}, |
||||
{ |
||||
[HIGHSCORE_TAB_RACE] = {.lap_record = 63.83, .entries = {{"CAN", 195.40},{"WEH", 209.23},{"AVE", 227.90},{"ABO", 239.90},{"NUS", 240.73}}}, |
||||
[HIGHSCORE_TAB_TIME_TRIAL] = {.lap_record = 63.83, .entries = {{"DJR", 195.40},{"NIK", 209.23},{"JAS", 227.90},{"NCW", 239.90},{"LOU", 240.73}}}, |
||||
}, |
||||
}, |
||||
[RACE_CLASS_RAPIER] = { |
||||
{ |
||||
[HIGHSCORE_TAB_RACE] = {.lap_record = 69.50, .entries = {{"AJY", 200.67},{"DLS", 213.50},{"AJS", 228.67},{"MAK", 247.67},{"JED", 263.00}}}, |
||||
[HIGHSCORE_TAB_TIME_TRIAL] = {.lap_record = 69.50, .entries = {{"NCW", 200.67},{"LEE", 213.50},{"STU", 228.67},{"JAS", 247.67},{"ROB", 263.00}}}, |
||||
}, |
||||
{ |
||||
[HIGHSCORE_TAB_RACE] = {.lap_record = 47.33, .entries = {{"BOR", 134.58},{"ING", 147.00},{"HIS", 162.25},{"COR", 183.08},{ "ES", 198.25}}}, |
||||
[HIGHSCORE_TAB_TIME_TRIAL] = {.lap_record = 47.33, .entries = {{"NIK", 134.58},{"POL", 147.00},{"DAR", 162.25},{"STU", 183.08},{"ROB", 198.25}}}, |
||||
}, |
||||
{ |
||||
[HIGHSCORE_TAB_RACE] = {.lap_record = 47.83, .entries = {{"AJS", 142.08},{"DLS", 159.42},{"MAK", 178.08},{"JED", 190.25},{"AJY", 206.58}}}, |
||||
[HIGHSCORE_TAB_TIME_TRIAL] = {.lap_record = 47.83, .entries = {{"POL", 142.08},{"JIM", 159.42},{"TIM", 178.08},{"MOC", 190.25},{ "PC", 206.58}}}, |
||||
}, |
||||
{ |
||||
[HIGHSCORE_TAB_RACE] = {.lap_record = 76.75, .entries = {{"DLS", 224.17},{"DJR", 237.00},{"LEE", 257.50},{"MOC", 272.83},{"MPI", 285.17}}}, |
||||
[HIGHSCORE_TAB_TIME_TRIAL] = {.lap_record = 76.75, .entries = {{"TIM", 224.17},{"JIM", 237.00},{"NIK", 257.50},{"JAS", 272.83},{ "LG", 285.17}}}, |
||||
}, |
||||
{ |
||||
[HIGHSCORE_TAB_RACE] = {.lap_record = 65.75, .entries = {{"MAK", 191.00},{"STU", 203.67},{"JAS", 221.83},{"ROB", 239.00},{"DOM", 254.50}}}, |
||||
[HIGHSCORE_TAB_TIME_TRIAL] = {.lap_record = 65.75, .entries = {{ "LG", 191.00},{"LOU", 203.67},{"JIM", 221.83},{"HAN", 239.00},{ "NT", 254.50}}}, |
||||
}, |
||||
{ |
||||
[HIGHSCORE_TAB_RACE] = {.lap_record = 59.23, .entries = {{"JED", 156.67},{"NCW", 170.33},{"LOU", 188.83},{"DAR", 201.00},{"POL", 221.50}}}, |
||||
[HIGHSCORE_TAB_TIME_TRIAL] = {.lap_record = 59.23, .entries = {{"STU", 156.67},{"DAV", 170.33},{"DOM", 188.83},{"MOR", 201.00},{"GAN", 221.50}}}, |
||||
}, |
||||
{ |
||||
[HIGHSCORE_TAB_RACE] = {.lap_record = 55.00, .entries = {{ "PC", 162.42},{"POL", 179.58},{"DAR", 194.75},{"DAR", 208.92},{"MSC", 224.58}}}, |
||||
[HIGHSCORE_TAB_TIME_TRIAL] = {.lap_record = 55.00, .entries = {{"THA", 162.42},{"NKS", 179.58},{"FOR", 194.75},{"PLA", 208.92},{"YIN", 224.58}}}, |
||||
} |
||||
} |
||||
} |
||||
}; |
||||
|
||||
game_t g = {0}; |
||||
|
||||
|
||||
|
||||
struct { |
||||
void (*init)(); |
||||
void (*update)(); |
||||
} game_scenes[] = { |
||||
[GAME_SCENE_INTRO] = {intro_init, intro_update}, |
||||
[GAME_SCENE_TITLE] = {title_init, title_update}, |
||||
[GAME_SCENE_MAIN_MENU] = {main_menu_init, main_menu_update}, |
||||
[GAME_SCENE_RACE] = {race_init, race_update}, |
||||
}; |
||||
|
||||
static game_scene_t scene_current = GAME_SCENE_NONE; |
||||
static game_scene_t scene_next = GAME_SCENE_NONE; |
||||
static int global_textures_len = 0; |
||||
static void *global_mem_mark = 0; |
||||
|
||||
void game_init() { |
||||
if (file_exists("save.dat")) { |
||||
uint32_t size; |
||||
save_t *save_file = (save_t *)file_load("save.dat", &size); |
||||
if (size == sizeof(save_t) && save_file->magic == SAVE_DATA_MAGIC) { |
||||
printf("load save data success\n"); |
||||
memcpy(&save, save_file, sizeof(save_t)); |
||||
} |
||||
mem_temp_free(save_file); |
||||
} |
||||
|
||||
platform_set_fullscreen(save.fullscreen); |
||||
render_set_resolution(save.screen_res); |
||||
render_set_post_effect(save.post_effect); |
||||
|
||||
srand((int)(platform_now() * 100)); |
||||
|
||||
ui_load(); |
||||
sfx_load(); |
||||
hud_load(); |
||||
ships_load(); |
||||
droid_load(); |
||||
particles_load(); |
||||
weapons_load(); |
||||
|
||||
global_textures_len = render_textures_len(); |
||||
global_mem_mark = mem_mark(); |
||||
|
||||
sfx_music_mode(SFX_MUSIC_PAUSED); |
||||
sfx_music_play(rand_int(0, len(def.music))); |
||||
|
||||
|
||||
// System binds; always fixed
|
||||
// Keyboard
|
||||
input_bind(INPUT_LAYER_SYSTEM, INPUT_KEY_UP, A_MENU_UP); |
||||
input_bind(INPUT_LAYER_SYSTEM, INPUT_KEY_DOWN, A_MENU_DOWN); |
||||
input_bind(INPUT_LAYER_SYSTEM, INPUT_KEY_LEFT, A_MENU_LEFT); |
||||
input_bind(INPUT_LAYER_SYSTEM, INPUT_KEY_RIGHT, A_MENU_RIGHT); |
||||
|
||||
input_bind(INPUT_LAYER_SYSTEM, INPUT_KEY_BACKSPACE, A_MENU_BACK); |
||||
input_bind(INPUT_LAYER_SYSTEM, INPUT_KEY_C, A_MENU_BACK); |
||||
input_bind(INPUT_LAYER_SYSTEM, INPUT_KEY_V, A_MENU_BACK); |
||||
|
||||
input_bind(INPUT_LAYER_SYSTEM, INPUT_KEY_X, A_MENU_SELECT); |
||||
input_bind(INPUT_LAYER_SYSTEM, INPUT_KEY_RETURN, A_MENU_START); |
||||
input_bind(INPUT_LAYER_SYSTEM, INPUT_KEY_ESCAPE, A_MENU_QUIT); |
||||
|
||||
// Gamepad
|
||||
input_bind(INPUT_LAYER_SYSTEM, INPUT_GAMEPAD_DPAD_UP, A_MENU_UP); |
||||
input_bind(INPUT_LAYER_SYSTEM, INPUT_GAMEPAD_DPAD_DOWN, A_MENU_DOWN); |
||||
input_bind(INPUT_LAYER_SYSTEM, INPUT_GAMEPAD_DPAD_LEFT, A_MENU_LEFT); |
||||
input_bind(INPUT_LAYER_SYSTEM, INPUT_GAMEPAD_DPAD_RIGHT, A_MENU_RIGHT); |
||||
|
||||
input_bind(INPUT_LAYER_SYSTEM, INPUT_GAMEPAD_L_STICK_UP, A_MENU_UP); |
||||
input_bind(INPUT_LAYER_SYSTEM, INPUT_GAMEPAD_L_STICK_DOWN, A_MENU_DOWN); |
||||
input_bind(INPUT_LAYER_SYSTEM, INPUT_GAMEPAD_L_STICK_LEFT, A_MENU_LEFT); |
||||
input_bind(INPUT_LAYER_SYSTEM, INPUT_GAMEPAD_L_STICK_RIGHT, A_MENU_RIGHT); |
||||
|
||||
input_bind(INPUT_LAYER_SYSTEM, INPUT_GAMEPAD_X, A_MENU_BACK); |
||||
input_bind(INPUT_LAYER_SYSTEM, INPUT_GAMEPAD_B, A_MENU_BACK); |
||||
|
||||
input_bind(INPUT_LAYER_SYSTEM, INPUT_GAMEPAD_A, A_MENU_SELECT); |
||||
input_bind(INPUT_LAYER_SYSTEM, INPUT_GAMEPAD_START, A_MENU_START); |
||||
|
||||
|
||||
// User defined, loaded from the save struct
|
||||
for (int action = 0; action < len(save.buttons); action++) { |
||||
if (save.buttons[action][0] != INPUT_INVALID) { |
||||
input_bind(INPUT_LAYER_USER, save.buttons[action][0], action); |
||||
} |
||||
if (save.buttons[action][1] != INPUT_INVALID) { |
||||
input_bind(INPUT_LAYER_USER, save.buttons[action][1], action); |
||||
} |
||||
} |
||||
|
||||
|
||||
game_set_scene(GAME_SCENE_INTRO); |
||||
} |
||||
|
||||
void game_set_scene(game_scene_t scene) { |
||||
sfx_reset(); |
||||
scene_next = scene; |
||||
} |
||||
|
||||
void game_reset_championship() { |
||||
for (int i = 0; i < len(g.championship_ranks); i++) { |
||||
g.championship_ranks[i].points = 0; |
||||
g.championship_ranks[i].pilot = i; |
||||
} |
||||
g.lives = NUM_LIVES; |
||||
} |
||||
|
||||
void game_update() { |
||||
double frame_start_time = platform_now(); |
||||
|
||||
int sh = render_size().y; |
||||
int scale = max(1, sh >= 720 ? sh / 360 : sh / 240); |
||||
if (save.ui_scale && save.ui_scale < scale) { |
||||
scale = save.ui_scale; |
||||
} |
||||
ui_set_scale(scale); |
||||
|
||||
|
||||
if (scene_next != GAME_SCENE_NONE) { |
||||
scene_current = scene_next; |
||||
scene_next = GAME_SCENE_NONE; |
||||
render_textures_reset(global_textures_len); |
||||
mem_reset(global_mem_mark); |
||||
system_reset_cycle_time(); |
||||
|
||||
if (scene_current != GAME_SCENE_NONE) { |
||||
game_scenes[scene_current].init(); |
||||
} |
||||
} |
||||
|
||||
if (scene_current != GAME_SCENE_NONE) { |
||||
game_scenes[scene_current].update(); |
||||
} |
||||
|
||||
if (save.is_dirty) { |
||||
// FIXME: use a text based format?
|
||||
// FIXME: this should probably run async somewhere
|
||||
save.is_dirty = false; |
||||
file_store("save.dat", &save, sizeof(save_t));
|
||||
printf("wrote save.dat\n"); |
||||
} |
||||
|
||||
double now = platform_now(); |
||||
g.frame_time = now - frame_start_time; |
||||
if (g.frame_time > 0) { |
||||
g.frame_rate = ((double)g.frame_rate * 0.95) + (1.0/g.frame_time) * 0.05; |
||||
} |
||||
} |
||||
|
@ -0,0 +1,270 @@ |
||||
#ifndef GAME_H |
||||
#define GAME_H |
||||
|
||||
#include "../types.h" |
||||
|
||||
#include "droid.h" |
||||
#include "ship.h" |
||||
#include "camera.h" |
||||
#include "track.h" |
||||
|
||||
#define NUM_AI_OPPONENTS 7 |
||||
#define NUM_PILOTS_PER_TEAM 2 |
||||
#define NUM_NON_BONUS_CIRCUTS 6 |
||||
#define NUM_MUSIC_TRACKS 11 |
||||
#define NUM_HIGHSCORES 5 |
||||
|
||||
#define NUM_LAPS 3 |
||||
#define NUM_LIVES 3 |
||||
#define QUALIFYING_RANK 3 |
||||
#define SAVE_DATA_MAGIC 0x64736f77 |
||||
|
||||
typedef enum { |
||||
A_UP, |
||||
A_DOWN, |
||||
A_LEFT, |
||||
A_RIGHT, |
||||
A_BRAKE_LEFT, |
||||
A_BRAKE_RIGHT, |
||||
A_THRUST, |
||||
A_FIRE, |
||||
A_CHANGE_VIEW, |
||||
NUM_GAME_ACTIONS, |
||||
|
||||
A_MENU_UP, |
||||
A_MENU_DOWN, |
||||
A_MENU_LEFT, |
||||
A_MENU_RIGHT, |
||||
A_MENU_BACK, |
||||
A_MENU_SELECT, |
||||
A_MENU_START, |
||||
A_MENU_QUIT, |
||||
} action_t; |
||||
|
||||
|
||||
typedef enum { |
||||
GAME_SCENE_INTRO, |
||||
GAME_SCENE_TITLE, |
||||
GAME_SCENE_MAIN_MENU, |
||||
GAME_SCENE_HIGHSCORES, |
||||
GAME_SCENE_RACE, |
||||
GAME_SCENE_NONE, |
||||
NUM_GAME_SCENES |
||||
} game_scene_t; |
||||
|
||||
enum race_class { |
||||
RACE_CLASS_VENOM, |
||||
RACE_CLASS_RAPIER, |
||||
NUM_RACE_CLASSES |
||||
}; |
||||
|
||||
enum race_type { |
||||
RACE_TYPE_CHAMPIONSHIP, |
||||
RACE_TYPE_SINGLE, |
||||
RACE_TYPE_TIME_TRIAL, |
||||
NUM_RACE_TYPES, |
||||
}; |
||||
|
||||
enum highscore_tab { |
||||
HIGHSCORE_TAB_TIME_TRIAL, |
||||
HIGHSCORE_TAB_RACE, |
||||
NUM_HIGHSCORE_TABS |
||||
}; |
||||
|
||||
enum pilot { |
||||
PILOT_JOHN_DEKKA, |
||||
PILOT_DANIEL_CHANG, |
||||
PILOT_ARIAL_TETSUO, |
||||
PILOT_ANASTASIA_CHEROVOSKI, |
||||
PILOT_KEL_SOLAAR, |
||||
PILOT_ARIAN_TETSUO, |
||||
PILOT_SOFIA_DE_LA_RENTE, |
||||
PILOT_PAUL_JACKSON, |
||||
NUM_PILOTS |
||||
}; |
||||
|
||||
enum team { |
||||
TEAM_AG_SYSTEMS, |
||||
TEAM_AURICOM, |
||||
TEAM_QIREX, |
||||
TEAM_FEISAR, |
||||
NUM_TEAMS |
||||
}; |
||||
|
||||
enum circut { |
||||
CIRCUT_ALTIMA_VII, |
||||
CIRCUT_KARBONIS_V, |
||||
CIRCUT_TERRAMAX, |
||||
CIRCUT_KORODERA, |
||||
CIRCUT_ARRIDOS_IV, |
||||
CIRCUT_SILVERSTREAM, |
||||
CIRCUT_FIRESTAR, |
||||
NUM_CIRCUTS |
||||
}; |
||||
|
||||
|
||||
// Game definitions
|
||||
|
||||
typedef struct { |
||||
char *name; |
||||
} race_class_t; |
||||
|
||||
typedef struct { |
||||
char *name; |
||||
} race_type_t; |
||||
|
||||
typedef struct { |
||||
char *name; |
||||
char *portrait; |
||||
int logo_model; |
||||
int team; |
||||
} pilot_t; |
||||
|
||||
typedef struct { |
||||
float thrust_max; |
||||
float thrust_magnitude; |
||||
bool fight_back; |
||||
} ai_setting_t; |
||||
|
||||
typedef struct { |
||||
float mass; |
||||
float thrust_max; |
||||
float resistance; |
||||
float turn_rate; |
||||
float turn_rate_max; |
||||
float skid; |
||||
} team_attributes_t; |
||||
|
||||
typedef struct { |
||||
char *name; |
||||
int logo_model; |
||||
int pilots[NUM_PILOTS_PER_TEAM]; |
||||
team_attributes_t attributes[NUM_RACE_CLASSES]; |
||||
} team_t; |
||||
|
||||
typedef struct { |
||||
char *path; |
||||
float start_line_pos; |
||||
float behind_speed; |
||||
float spread_base; |
||||
float spread_factor; |
||||
float sky_y_offset; |
||||
} circut_settings_t; |
||||
|
||||
typedef struct { |
||||
char *name; |
||||
bool is_bonus_circut; |
||||
circut_settings_t settings[NUM_RACE_CLASSES]; |
||||
} circut_t; |
||||
|
||||
typedef struct { |
||||
char *path; |
||||
char *name; |
||||
} music_track_t; |
||||
|
||||
typedef struct { |
||||
race_class_t race_classes[NUM_RACE_CLASSES]; |
||||
race_type_t race_types[NUM_RACE_TYPES]; |
||||
pilot_t pilots[NUM_PILOTS]; |
||||
team_t teams[NUM_TEAMS]; |
||||
ai_setting_t ai_settings[NUM_RACE_CLASSES][NUM_AI_OPPONENTS]; |
||||
circut_t circuts[NUM_CIRCUTS]; |
||||
int ship_model_to_pilot[NUM_PILOTS]; |
||||
int race_points_for_rank[NUM_PILOTS]; |
||||
music_track_t music[NUM_MUSIC_TRACKS]; |
||||
char *credits[104]; |
||||
struct { |
||||
char *venom[15]; |
||||
char *venom_all_circuts[19]; |
||||
char *rapier[26]; |
||||
char *rapier_all_circuts[24]; |
||||
} congratulations; |
||||
} game_def_t; |
||||
|
||||
|
||||
|
||||
// Running game data
|
||||
|
||||
typedef struct { |
||||
uint16_t pilot; |
||||
uint16_t points; |
||||
} pilot_points_t; |
||||
|
||||
typedef struct { |
||||
float frame_time; |
||||
float frame_rate; |
||||
|
||||
int race_class; |
||||
int race_type; |
||||
int highscore_tab; |
||||
int team; |
||||
int pilot; |
||||
int circut; |
||||
bool is_attract_mode; |
||||
bool show_credits; |
||||
|
||||
bool is_new_lap_record; |
||||
bool is_new_race_record; |
||||
float best_lap; |
||||
float race_time; |
||||
int lives; |
||||
int race_position; |
||||
|
||||
float lap_times[NUM_PILOTS][NUM_LAPS]; |
||||
pilot_points_t race_ranks[NUM_PILOTS]; |
||||
pilot_points_t championship_ranks[NUM_PILOTS]; |
||||
|
||||
camera_t camera; |
||||
droid_t droid; |
||||
ship_t ships[NUM_PILOTS]; |
||||
track_t track; |
||||
} game_t; |
||||
|
||||
|
||||
|
||||
// Save Data
|
||||
|
||||
typedef struct { |
||||
char name[4]; |
||||
float time; |
||||
} highscores_entry_t; |
||||
|
||||
typedef struct { |
||||
highscores_entry_t entries[NUM_HIGHSCORES]; |
||||
float lap_record; |
||||
} highscores_t; |
||||
|
||||
typedef struct { |
||||
uint32_t magic; |
||||
bool is_dirty; |
||||
|
||||
float sfx_volume; |
||||
float music_volume; |
||||
uint8_t ui_scale; |
||||
bool show_fps; |
||||
bool fullscreen; |
||||
int screen_res; |
||||
int post_effect; |
||||
|
||||
uint32_t has_rapier_class; |
||||
uint32_t has_bonus_circuts; |
||||
|
||||
uint8_t buttons[NUM_GAME_ACTIONS][2]; |
||||
|
||||
char highscores_name[4]; |
||||
highscores_t highscores[NUM_RACE_CLASSES][NUM_CIRCUTS][NUM_HIGHSCORE_TABS]; |
||||
} save_t; |
||||
|
||||
|
||||
|
||||
|
||||
extern const game_def_t def; |
||||
extern game_t g; |
||||
extern save_t save; |
||||
|
||||
void game_init(); |
||||
void game_set_scene(game_scene_t scene); |
||||
void game_reset_championship(); |
||||
void game_update(); |
||||
|
||||
#endif |
@ -0,0 +1,244 @@ |
||||
#include "../types.h" |
||||
#include "../mem.h" |
||||
#include "../utils.h" |
||||
#include "../system.h" |
||||
|
||||
#include "object.h" |
||||
#include "track.h" |
||||
#include "ship.h" |
||||
#include "weapon.h" |
||||
#include "hud.h" |
||||
#include "droid.h" |
||||
#include "camera.h" |
||||
#include "image.h" |
||||
#include "ship_ai.h" |
||||
#include "game.h" |
||||
#include "ui.h" |
||||
|
||||
static texture_list_t weapon_icon_textures; |
||||
static uint16_t target_reticle; |
||||
|
||||
typedef struct { |
||||
vec2i_t offset; |
||||
uint16_t height; |
||||
rgba_t color; |
||||
} speedo_bar_t; |
||||
|
||||
const struct { |
||||
uint16_t width; |
||||
uint16_t skew; |
||||
speedo_bar_t bars[13]; |
||||
} speedo = { |
||||
.width = 121, |
||||
.skew = 2, |
||||
.bars = { |
||||
{{.x = 6, .y = 12}, .height = 10, .color = rgba( 66, 16, 49, 255)}, |
||||
{{.x = 13, .y = 12}, .height = 10, .color = rgba(115, 33, 90, 255)}, |
||||
{{.x = 20, .y = 12}, .height = 10, .color = rgba(132, 58, 164, 255)}, |
||||
{{.x = 27, .y = 12}, .height = 10, .color = rgba( 99, 90, 197, 255)}, |
||||
{{.x = 34, .y = 12}, .height = 10, .color = rgba( 74, 148, 181, 255)}, |
||||
{{.x = 41, .y = 12}, .height = 10, .color = rgba( 66, 173, 115, 255)}, |
||||
{{.x = 50, .y = 10}, .height = 12, .color = rgba( 99, 206, 58, 255)}, |
||||
{{.x = 59, .y = 8}, .height = 12, .color = rgba(189, 206, 41, 255)}, |
||||
{{.x = 69, .y = 5}, .height = 13, .color = rgba(247, 140, 33, 255)}, |
||||
{{.x = 81, .y = 2}, .height = 15, .color = rgba(255, 197, 49, 255)}, |
||||
{{.x = 95, .y = 1}, .height = 16, .color = rgba(255, 222, 115, 255)}, |
||||
{{.x = 110, .y = 1}, .height = 16, .color = rgba(255, 239, 181, 255)}, |
||||
{{.x = 126, .y = 1}, .height = 16, .color = rgba(255, 255, 255, 255)} |
||||
} |
||||
}; |
||||
|
||||
static uint16_t speedo_facia_texture; |
||||
|
||||
void hud_load() { |
||||
speedo_facia_texture = image_get_texture("wipeout/textures/speedo.tim"); |
||||
target_reticle = image_get_texture_semi_trans("wipeout/textures/target2.tim"); |
||||
weapon_icon_textures = image_get_compressed_textures("wipeout/common/wicons.cmp"); |
||||
} |
||||
|
||||
static void hud_draw_speedo_bar(vec2i_t *pos, const speedo_bar_t *a, const speedo_bar_t *b, float f, rgba_t color_override) { |
||||
rgba_t left_color, right_color; |
||||
if (color_override.as_uint32 > 0) { |
||||
left_color = color_override; |
||||
right_color = color_override; |
||||
} |
||||
else { |
||||
left_color = a->color; |
||||
right_color = rgba( |
||||
lerp(a->color.as_rgba.r, b->color.as_rgba.r, f), |
||||
lerp(a->color.as_rgba.g, b->color.as_rgba.g, f), |
||||
lerp(a->color.as_rgba.b, b->color.as_rgba.b, f), |
||||
lerp(a->color.as_rgba.a, b->color.as_rgba.a, f) |
||||
); |
||||
} |
||||
|
||||
float right_h = lerp(a->height, b->height, f); |
||||
vec2i_t top_left = vec2i(a->offset.x + 1, a->offset.y); |
||||
vec2i_t bottom_left = vec2i(a->offset.x + 1 - a->height / speedo.skew, a->offset.y + a->height); |
||||
vec2i_t top_right = vec2i(lerp(a->offset.x + 1, b->offset.x, f), lerp(a->offset.y, b->offset.y, f)); |
||||
vec2i_t bottom_right = vec2i(top_right.x - right_h / speedo.skew, top_right.y + right_h); |
||||
|
||||
top_left = ui_scaled(top_left); |
||||
bottom_left = ui_scaled(bottom_left); |
||||
top_right = ui_scaled(top_right); |
||||
bottom_right = ui_scaled(bottom_right); |
||||
|
||||
render_push_tris((tris_t) { |
||||
.vertices = { |
||||
{ |
||||
.pos = {pos->x + bottom_left.x, pos->y + bottom_left.y, 0}, |
||||
.uv = {0, 0}, |
||||
.color = left_color |
||||
}, |
||||
{ |
||||
.pos = {pos->x + top_right.x, pos->y + top_right.y, 0}, |
||||
.uv = {0, 0}, |
||||
.color = right_color |
||||
}, |
||||
{ |
||||
.pos = {pos->x + top_left.x, pos->y + top_left.y, 0}, |
||||
.uv = {0, 0}, |
||||
.color = left_color |
||||
}, |
||||
} |
||||
}, RENDER_NO_TEXTURE); |
||||
|
||||
render_push_tris((tris_t) { |
||||
.vertices = { |
||||
{ |
||||
.pos = {pos->x + bottom_right.x, pos->y + bottom_right.y, 0}, |
||||
.uv = {0, 0}, |
||||
.color = right_color |
||||
}, |
||||
{ |
||||
.pos = {pos->x + top_right.x, pos->y + top_right.y, 0}, |
||||
.uv = {0, 0}, |
||||
.color = right_color |
||||
}, |
||||
{ |
||||
.pos = {pos->x + bottom_left.x, pos->y + bottom_left.y, 0}, |
||||
.uv = {0, 0}, |
||||
.color = left_color |
||||
}, |
||||
} |
||||
}, RENDER_NO_TEXTURE); |
||||
} |
||||
|
||||
static void hud_draw_speedo_bars(vec2i_t *pos, float f, rgba_t color_override) { |
||||
if (f <= 0) { |
||||
return; |
||||
} |
||||
|
||||
if (f - floor(f) > 0.9) { |
||||
f = ceil(f); |
||||
} |
||||
if (f > 13) { |
||||
f = 13; |
||||
} |
||||
|
||||
int bars = f; |
||||
for (int i = 1; i < bars; i++) { |
||||
hud_draw_speedo_bar(pos, &speedo.bars[i - 1], &speedo.bars[i], 1, color_override); |
||||
} |
||||
|
||||
if (bars > 12) { |
||||
return; |
||||
} |
||||
|
||||
float last_bar_fraction = f - bars + 0.1; |
||||
if (last_bar_fraction <= 0) { |
||||
return; |
||||
} |
||||
|
||||
if (last_bar_fraction > 1) { |
||||
last_bar_fraction = 1; |
||||
} |
||||
int last_bar = bars == 0 ? 1 : bars; |
||||
hud_draw_speedo_bar(pos, &speedo.bars[last_bar - 1], &speedo.bars[last_bar], last_bar_fraction, color_override); |
||||
} |
||||
|
||||
static void hud_draw_speedo(int speed, int thrust) { |
||||
vec2i_t facia_pos = ui_scaled_pos(UI_POS_BOTTOM | UI_POS_RIGHT, vec2i(-141, -45)); |
||||
vec2i_t bar_pos = ui_scaled_pos(UI_POS_BOTTOM | UI_POS_RIGHT, vec2i(-141, -40)); |
||||
hud_draw_speedo_bars(&bar_pos, thrust / 65.0, rgba(255, 0, 0, 128)); |
||||
hud_draw_speedo_bars(&bar_pos, speed / 2166.0, rgba(0, 0, 0, 0)); |
||||
render_push_2d(facia_pos, ui_scaled(render_texture_size(speedo_facia_texture)), rgba(128, 128, 128, 255), speedo_facia_texture); |
||||
} |
||||
|
||||
static void hud_draw_target_icon(vec3_t position) { |
||||
vec2i_t screen_size = render_size(); |
||||
vec2i_t size = ui_scaled(render_texture_size(target_reticle)); |
||||
vec3_t projected = render_transform(position); |
||||
|
||||
vec2i_t pos = vec2i( |
||||
(( projected.x + 1.0) / 2.0) * screen_size.x - size.x / 2, |
||||
((-projected.y + 1.0) / 2.0) * screen_size.y - size.y / 2 |
||||
); |
||||
render_push_2d(pos, size, rgba(128, 128, 128, 128), target_reticle); |
||||
} |
||||
|
||||
void hud_draw(ship_t *ship) { |
||||
// Current lap time
|
||||
if (ship->lap >= 0) { |
||||
ui_draw_time(ship->lap_time, ui_scaled_pos(UI_POS_BOTTOM | UI_POS_LEFT, vec2i(16, -30)), UI_SIZE_16, UI_COLOR_DEFAULT); |
||||
|
||||
for (int i = 0; i < ship->lap && i < NUM_LAPS-1; i++) { |
||||
ui_draw_time(g.lap_times[ship->pilot][i], ui_scaled_pos(UI_POS_BOTTOM | UI_POS_LEFT, vec2i(16, -45 - (10 * i))), UI_SIZE_8, UI_COLOR_ACCENT); |
||||
} |
||||
} |
||||
|
||||
// Current Lap
|
||||
int display_lap = max(0, ship->lap + 1); |
||||
ui_draw_text("LAP", ui_scaled(vec2i(15, 8)), UI_SIZE_8, UI_COLOR_ACCENT);
|
||||
ui_draw_number(display_lap, ui_scaled(vec2i(10, 19)), UI_SIZE_16, UI_COLOR_DEFAULT);
|
||||
int width = ui_char_width('0' + display_lap, UI_SIZE_16); |
||||
ui_draw_text("OF", ui_scaled(vec2i((10 + width), 27)), UI_SIZE_8, UI_COLOR_ACCENT); |
||||
ui_draw_number(NUM_LAPS, ui_scaled(vec2i((32 + width), 19)), UI_SIZE_16, UI_COLOR_DEFAULT); |
||||
|
||||
// Race Position
|
||||
if (g.race_type != RACE_TYPE_TIME_TRIAL) { |
||||
ui_draw_text("POSITION", ui_scaled_pos(UI_POS_TOP | UI_POS_RIGHT, vec2i(-90, 8)), UI_SIZE_8, UI_COLOR_ACCENT); |
||||
ui_draw_number(ship->position_rank, ui_scaled_pos(UI_POS_TOP | UI_POS_RIGHT, vec2i(-60, 19)), UI_SIZE_16, UI_COLOR_DEFAULT); |
||||
} |
||||
|
||||
// Framerate
|
||||
if (save.show_fps) { |
||||
ui_draw_text("FPS", ui_scaled(vec2i(16, 78)), UI_SIZE_8, UI_COLOR_ACCENT); |
||||
ui_draw_number((int)(g.frame_rate), ui_scaled(vec2i(16, 90)), UI_SIZE_8, UI_COLOR_DEFAULT); |
||||
} |
||||
|
||||
// Lap Record
|
||||
ui_draw_text("LAP RECORD", ui_scaled(vec2i(15, 43)), UI_SIZE_8, UI_COLOR_ACCENT); |
||||
ui_draw_time(save.highscores[g.race_class][g.circut][g.highscore_tab].lap_record, ui_scaled(vec2i(15, 55)), UI_SIZE_8, UI_COLOR_DEFAULT); |
||||
|
||||
// Wrong way
|
||||
if (flags_not(ship->flags, SHIP_DIRECTION_FORWARD)) { |
||||
ui_draw_text_centered("WRONG WAY", ui_scaled_pos(UI_POS_MIDDLE | UI_POS_CENTER, vec2i(-20, 0)), UI_SIZE_16, UI_COLOR_ACCENT); |
||||
} |
||||
|
||||
// Speedo
|
||||
int speedo_speed = (g.camera.update_func == camera_update_attract_internal) |
||||
? ship->speed * 7 |
||||
: ship->speed; |
||||
hud_draw_speedo(speedo_speed, ship->thrust_mag); |
||||
|
||||
// Weapon icon
|
||||
if (ship->weapon_type != WEAPON_TYPE_NONE) { |
||||
vec2i_t pos = ui_scaled_pos(UI_POS_TOP | UI_POS_CENTER, vec2i(-16, 20)); |
||||
vec2i_t size = ui_scaled(vec2i(32, 32)); |
||||
uint16_t icon = texture_from_list(weapon_icon_textures, ship->weapon_type-1); |
||||
render_push_2d(pos, size, rgba(128,128,128,255), icon); |
||||
} |
||||
|
||||
// Lives
|
||||
if (g.race_type == RACE_TYPE_CHAMPIONSHIP) { |
||||
for (int i = 0; i < g.lives; i++) { |
||||
ui_draw_icon(UI_ICON_STAR, ui_scaled_pos(UI_POS_BOTTOM | UI_POS_RIGHT, vec2i(-26 - 13 * i, -50)), UI_COLOR_DEFAULT); |
||||
} |
||||
} |
||||
|
||||
// Weapon target reticle
|
||||
if (ship->weapon_target) { |
||||
hud_draw_target_icon(ship->weapon_target->position); |
||||
} |
||||
} |
@ -0,0 +1,9 @@ |
||||
#ifndef HUD_H |
||||
#define HUD_H |
||||
|
||||
#include "ship.h" |
||||
|
||||
void hud_load(); |
||||
void hud_draw(ship_t *ship); |
||||
|
||||
#endif |
@ -0,0 +1,320 @@ |
||||
#include "../types.h" |
||||
#include "../mem.h" |
||||
#include "../utils.h" |
||||
|
||||
#include "object.h" |
||||
#include "track.h" |
||||
#include "ship.h" |
||||
#include "weapon.h" |
||||
#include "droid.h" |
||||
#include "camera.h" |
||||
#include "object.h" |
||||
#include "scene.h" |
||||
#include "game.h" |
||||
#include "hud.h" |
||||
#include "image.h" |
||||
|
||||
|
||||
#define STB_IMAGE_WRITE_IMPLEMENTATION |
||||
#include "../libs/stb_image_write.h" |
||||
|
||||
|
||||
#define TIM_TYPE_PALETTED_4_BPP 0x08 |
||||
#define TIM_TYPE_PALETTED_8_BPP 0x09 |
||||
#define TIM_TYPE_TRUE_COLOR_16_BPP 0x02 |
||||
|
||||
static inline rgba_t tim_16bit_to_rgba(uint16_t c, bool transparent_bit) { |
||||
return rgba( |
||||
((c >> 0) & 0x1f) << 3, |
||||
((c >> 5) & 0x1f) << 3, |
||||
((c >> 10) & 0x1f) << 3, |
||||
(c == 0
|
||||
? 0x00 |
||||
: transparent_bit && (c & 0x7fff) == 0 ? 0x00 : 0xff |
||||
) |
||||
); |
||||
} |
||||
|
||||
image_t *image_alloc(uint32_t width, uint32_t height) { |
||||
image_t *image = mem_temp_alloc(sizeof(image_t) + width * height * sizeof(rgba_t)); |
||||
image->width = width; |
||||
image->height = height; |
||||
image->pixels = (rgba_t *)(((uint8_t *)image) + sizeof(image_t)); |
||||
return image; |
||||
} |
||||
|
||||
image_t *image_load_from_bytes(uint8_t *bytes, bool transparent) { |
||||
uint32_t p = 0; |
||||
|
||||
uint32_t magic = get_i32_le(bytes, &p); |
||||
uint32_t type = get_i32_le(bytes, &p); |
||||
uint16_t *palette = NULL; |
||||
|
||||
if ( |
||||
type == TIM_TYPE_PALETTED_4_BPP || |
||||
type == TIM_TYPE_PALETTED_8_BPP |
||||
) { |
||||
uint32_t header_length = get_i32_le(bytes, &p); |
||||
uint16_t palette_x = get_i16_le(bytes, &p); |
||||
uint16_t palette_y = get_i16_le(bytes, &p); |
||||
uint16_t palette_colors = get_i16_le(bytes, &p); |
||||
uint16_t palettes = get_i16_le(bytes, &p); |
||||
palette = (uint16_t *)(bytes + p); |
||||
p += palette_colors * 2; |
||||
} |
||||
|
||||
uint32_t data_size = get_i32_le(bytes, &p); |
||||
|
||||
int32_t pixels_per_16bit = 1; |
||||
if (type == TIM_TYPE_PALETTED_8_BPP) { |
||||
pixels_per_16bit = 2; |
||||
} |
||||
else if (type == TIM_TYPE_PALETTED_4_BPP) { |
||||
pixels_per_16bit = 4; |
||||
} |
||||
|
||||
uint16_t skip_x = get_i16_le(bytes, &p); |
||||
uint16_t skip_y = get_i16_le(bytes, &p); |
||||
uint16_t entries_per_row = get_i16_le(bytes, &p); |
||||
uint16_t rows = get_i16_le(bytes, &p); |
||||
|
||||
int32_t width = entries_per_row * pixels_per_16bit; |
||||
int32_t height = rows; |
||||
int32_t entries = entries_per_row * rows; |
||||
|
||||
image_t *image = image_alloc(width, height); |
||||
int32_t pixel_pos = 0; |
||||
|
||||
if (type == TIM_TYPE_TRUE_COLOR_16_BPP) { |
||||
for (int i = 0; i < entries; i++) { |
||||
image->pixels[pixel_pos++] = tim_16bit_to_rgba(get_i16_le(bytes, &p), transparent); |
||||
} |
||||
} |
||||
else if (type == TIM_TYPE_PALETTED_8_BPP) { |
||||
for (int i = 0; i < entries; i++) { |
||||
int32_t palette_pos = get_i16_le(bytes, &p); |
||||
image->pixels[pixel_pos++] = tim_16bit_to_rgba(palette[(palette_pos >> 0) & 0xff], transparent); |
||||
image->pixels[pixel_pos++] = tim_16bit_to_rgba(palette[(palette_pos >> 8) & 0xff], transparent); |
||||
} |
||||
} |
||||
else if (type == TIM_TYPE_PALETTED_4_BPP) { |
||||
for (int i = 0; i < entries; i++) { |
||||
int32_t palette_pos = get_i16_le(bytes, &p); |
||||
image->pixels[pixel_pos++] = tim_16bit_to_rgba(palette[(palette_pos >> 0) & 0xf], transparent); |
||||
image->pixels[pixel_pos++] = tim_16bit_to_rgba(palette[(palette_pos >> 4) & 0xf], transparent); |
||||
image->pixels[pixel_pos++] = tim_16bit_to_rgba(palette[(palette_pos >> 8) & 0xf], transparent); |
||||
image->pixels[pixel_pos++] = tim_16bit_to_rgba(palette[(palette_pos >> 12) & 0xf], transparent); |
||||
} |
||||
} |
||||
|
||||
return image; |
||||
} |
||||
|
||||
#define LZSS_INDEX_BIT_COUNT 13 |
||||
#define LZSS_LENGTH_BIT_COUNT 4 |
||||
#define LZSS_WINDOW_SIZE (1 << LZSS_INDEX_BIT_COUNT) |
||||
#define LZSS_BREAK_EVEN ((1 + LZSS_INDEX_BIT_COUNT + LZSS_LENGTH_BIT_COUNT) / 9) |
||||
#define LZSS_END_OF_STREAM 0 |
||||
#define LZSS_MOD_WINDOW(a) ((a) & (LZSS_WINDOW_SIZE - 1)) |
||||
|
||||
void lzss_decompress(uint8_t *in_data, uint8_t *out_data) { |
||||
int16_t i; |
||||
int16_t current_position; |
||||
uint8_t cc; |
||||
int16_t match_length; |
||||
int16_t match_position; |
||||
uint32_t mask; |
||||
uint32_t return_value; |
||||
uint8_t in_bfile_mask; |
||||
int16_t in_bfile_rack; |
||||
int16_t value; |
||||
uint8_t window[LZSS_WINDOW_SIZE]; |
||||
|
||||
in_bfile_rack = 0; |
||||
in_bfile_mask = 0x80; |
||||
|
||||
current_position = 1; |
||||
while (true) { |
||||
if (in_bfile_mask == 0x80) { |
||||
in_bfile_rack = (int16_t) * in_data++; |
||||
} |
||||
|
||||
value = in_bfile_rack & in_bfile_mask; |
||||
in_bfile_mask >>= 1; |
||||
if (in_bfile_mask == 0) { |
||||
in_bfile_mask = 0x80; |
||||
} |
||||
|
||||
if (value) { |
||||
mask = 1L << (8 - 1); |
||||
return_value = 0; |
||||
while (mask != 0) { |
||||
if (in_bfile_mask == 0x80) { |
||||
in_bfile_rack = (int16_t) * in_data++; |
||||
} |
||||
|
||||
if (in_bfile_rack & in_bfile_mask) { |
||||
return_value |= mask; |
||||
} |
||||
mask >>= 1; |
||||
in_bfile_mask >>= 1; |
||||
|
||||
if (in_bfile_mask == 0) { |
||||
in_bfile_mask = 0x80; |
||||
} |
||||
} |
||||
cc = (uint8_t) return_value; |
||||
*out_data++ = cc; |
||||
window[ current_position ] = cc; |
||||
current_position = LZSS_MOD_WINDOW(current_position + 1); |
||||
} |
||||
else { |
||||
mask = 1L << (LZSS_INDEX_BIT_COUNT - 1); |
||||
return_value = 0; |
||||
while (mask != 0) { |
||||
if (in_bfile_mask == 0x80) { |
||||
in_bfile_rack = (int16_t) * in_data++; |
||||
} |
||||
|
||||
if (in_bfile_rack & in_bfile_mask) { |
||||
return_value |= mask; |
||||
} |
||||
mask >>= 1; |
||||
in_bfile_mask >>= 1; |
||||
|
||||
if (in_bfile_mask == 0) { |
||||
in_bfile_mask = 0x80; |
||||
} |
||||
} |
||||
match_position = (int16_t) return_value; |
||||
|
||||
if (match_position == LZSS_END_OF_STREAM) { |
||||
break; |
||||
} |
||||
|
||||
mask = 1L << (LZSS_LENGTH_BIT_COUNT - 1); |
||||
return_value = 0; |
||||
while (mask != 0) { |
||||
if (in_bfile_mask == 0x80) { |
||||
in_bfile_rack = (int16_t) * in_data++; |
||||
} |
||||
|
||||
if (in_bfile_rack & in_bfile_mask) { |
||||
return_value |= mask; |
||||
} |
||||
mask >>= 1; |
||||
in_bfile_mask >>= 1; |
||||
|
||||
if (in_bfile_mask == 0) { |
||||
in_bfile_mask = 0x80; |
||||
} |
||||
} |
||||
match_length = (int16_t) return_value; |
||||
|
||||
match_length += LZSS_BREAK_EVEN; |
||||
|
||||
for (i = 0 ; i <= match_length ; i++) { |
||||
cc = window[LZSS_MOD_WINDOW(match_position + i)]; |
||||
*out_data++ = cc; |
||||
window[current_position] = cc; |
||||
current_position = LZSS_MOD_WINDOW(current_position + 1); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
cmp_t *image_load_compressed(char *name) { |
||||
printf("load cmp %s\n", name); |
||||
uint32_t compressed_size; |
||||
uint8_t *compressed_bytes = file_load(name, &compressed_size); |
||||
|
||||
uint32_t p = 0; |
||||
int32_t decompressed_size = 0; |
||||
int32_t image_count = get_i32_le(compressed_bytes, &p); |
||||
|
||||
// Calculate the total uncompressed size
|
||||
for (int i = 0; i < image_count; i++) { |
||||
decompressed_size += get_i32_le(compressed_bytes, &p); |
||||
} |
||||
|
||||
uint32_t struct_size = sizeof(cmp_t) + sizeof(uint8_t *) * image_count; |
||||
cmp_t *cmp = mem_temp_alloc(struct_size + decompressed_size); |
||||
cmp->len = image_count; |
||||
|
||||
uint8_t *decompressed_bytes = ((uint8_t *)cmp) + struct_size; |
||||
|
||||
// Rewind and load all offsets
|
||||
p = 4; |
||||
uint32_t offset = 0; |
||||
for (int i = 0; i < image_count; i++) { |
||||
cmp->entries[i] = decompressed_bytes + offset; |
||||
offset += get_i32_le(compressed_bytes, &p); |
||||
} |
||||
|
||||
lzss_decompress(compressed_bytes + p, decompressed_bytes); |
||||
mem_temp_free(compressed_bytes); |
||||
|
||||
return cmp; |
||||
} |
||||
|
||||
uint16_t image_get_texture(char *name) { |
||||
printf("load: %s\n", name); |
||||
uint32_t size; |
||||
uint8_t *bytes = file_load(name, &size); |
||||
image_t *image = image_load_from_bytes(bytes, false); |
||||
uint32_t texture_index = render_texture_create(image->width, image->height, image->pixels); |
||||
mem_temp_free(image); |
||||
mem_temp_free(bytes); |
||||
|
||||
return texture_index; |
||||
} |
||||
|
||||
uint16_t image_get_texture_semi_trans(char *name) { |
||||
printf("load: %s\n", name); |
||||
uint32_t size; |
||||
uint8_t *bytes = file_load(name, &size); |
||||
image_t *image = image_load_from_bytes(bytes, true); |
||||
uint32_t texture_index = render_texture_create(image->width, image->height, image->pixels); |
||||
mem_temp_free(image); |
||||
mem_temp_free(bytes); |
||||
|
||||
return texture_index; |
||||
} |
||||
|
||||
texture_list_t image_get_compressed_textures(char *name) { |
||||
cmp_t *cmp = image_load_compressed(name); |
||||
texture_list_t list = {.start = render_textures_len(), .len = cmp->len}; |
||||
|
||||
for (int i = 0; i < cmp->len; i++) { |
||||
int32_t width, height; |
||||
image_t *image = image_load_from_bytes(cmp->entries[i], false); |
||||
|
||||
// char png_name[1024] = {0};
|
||||
// sprintf(png_name, "%s.%d.png", name, i);
|
||||
// stbi_write_png(png_name, image->width, image->height, 4, image->pixels, 0);
|
||||
|
||||
render_texture_create(image->width, image->height, image->pixels); |
||||
mem_temp_free(image); |
||||
} |
||||
|
||||
mem_temp_free(cmp); |
||||
return list; |
||||
} |
||||
|
||||
uint16_t texture_from_list(texture_list_t tl, uint16_t index) { |
||||
error_if(index >= tl.len, "Texture %d not in list of len %d", index, tl.len); |
||||
return tl.start + index; |
||||
} |
||||
|
||||
void image_copy(image_t *src, image_t *dst, uint32_t sx, uint32_t sy, uint32_t sw, uint32_t sh, uint32_t dx, uint32_t dy) { |
||||
rgba_t *src_pixels = src->pixels + sy * src->width + sx; |
||||
rgba_t *dst_pixels = dst->pixels + dy * dst->width + dx; |
||||
for (uint32_t y = 0; y < sh; y++) { |
||||
for (uint32_t x = 0; x < sw; x++) { |
||||
*(dst_pixels++) = *(src_pixels++); |
||||
} |
||||
src_pixels += src->width - sw; |
||||
dst_pixels += dst->width - sw; |
||||
} |
||||
} |
||||
|
@ -0,0 +1,34 @@ |
||||
#ifndef INIT_H |
||||
#define INIT_H |
||||
|
||||
#include "../types.h" |
||||
|
||||
typedef struct { |
||||
uint16_t start; |
||||
uint16_t len; |
||||
} texture_list_t; |
||||
|
||||
#define texture_list_empty() ((texture_list_t){0, 0}) |
||||
|
||||
typedef struct { |
||||
uint32_t width; |
||||
uint32_t height; |
||||
rgba_t *pixels; |
||||
} image_t; |
||||
|
||||
typedef struct { |
||||
uint32_t len; |
||||
uint8_t *entries[]; |
||||
} cmp_t; |
||||
|
||||
image_t *image_alloc(uint32_t width, uint32_t height); |
||||
void image_copy(image_t *src, image_t *dst, uint32_t sx, uint32_t sy, uint32_t sw, uint32_t sh, uint32_t dx, uint32_t dy); |
||||
image_t *image_load_from_bytes(uint8_t *bytes, bool transparent); |
||||
cmp_t *image_load_compressed(char *name); |
||||
|
||||
uint16_t image_get_texture(char *name); |
||||
uint16_t image_get_texture_semi_trans(char *name); |
||||
texture_list_t image_get_compressed_textures(char *name); |
||||
uint16_t texture_from_list(texture_list_t tl, uint16_t index); |
||||
|
||||
#endif |
@ -0,0 +1,486 @@ |
||||
#include <string.h> |
||||
|
||||
#include "../input.h" |
||||
#include "../system.h" |
||||
#include "../utils.h" |
||||
#include "../mem.h" |
||||
|
||||
#include "menu.h" |
||||
#include "ingame_menus.h" |
||||
#include "game.h" |
||||
#include "image.h" |
||||
#include "ui.h" |
||||
#include "race.h" |
||||
|
||||
static void page_race_points_init(menu_t * menu); |
||||
static void page_championship_points_init(menu_t * menu); |
||||
static void page_hall_of_fame_init(menu_t * menu); |
||||
|
||||
static texture_list_t pilot_portraits; |
||||
static menu_t *ingame_menu; |
||||
|
||||
void ingame_menus_load() { |
||||
pilot_portraits = image_get_compressed_textures(def.pilots[g.pilot].portrait); |
||||
ingame_menu = mem_bump(sizeof(menu_t)); |
||||
} |
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Pause Menu
|
||||
|
||||
static void button_continue(menu_t *menu, int data) { |
||||
race_unpause(); |
||||
} |
||||
|
||||
static void button_restart_confirm(menu_t *menu, int data) { |
||||
if (data) { |
||||
race_restart(); |
||||
} |
||||
else { |
||||
menu_pop(menu); |
||||
} |
||||
} |
||||
|
||||
static void button_restart_or_quit(menu_t *menu, int data) { |
||||
if (data) { |
||||
race_restart(); |
||||
} |
||||
else { |
||||
game_set_scene(GAME_SCENE_MAIN_MENU); |
||||
} |
||||
} |
||||
|
||||
static void button_restart(menu_t *menu, int data) { |
||||
menu_confirm(menu, "ARE YOU SURE YOU", "WANT TO RESTART", "YES", "NO", button_restart_confirm); |
||||
} |
||||
|
||||
static void button_quit_confirm(menu_t *menu, int data) { |
||||
if (data) { |
||||
game_set_scene(GAME_SCENE_MAIN_MENU); |
||||
} |
||||
else { |
||||
menu_pop(menu); |
||||
} |
||||
} |
||||
|
||||
static void button_quit(menu_t *menu, int data) { |
||||
menu_confirm(menu, "ARE YOU SURE YOU", "WANT TO QUIT", "YES", "NO", button_quit_confirm); |
||||
} |
||||
|
||||
|
||||
static void button_music_track(menu_t *menu, int data) { |
||||
sfx_music_play(data); |
||||
sfx_music_mode(SFX_MUSIC_LOOP); |
||||
} |
||||
|
||||
static void button_music_random(menu_t *menu, int data) { |
||||
sfx_music_play(rand_int(0, len(def.music))); |
||||
sfx_music_mode(SFX_MUSIC_RANDOM); |
||||
} |
||||
|
||||
static void button_music(menu_t *menu, int data) { |
||||
menu_page_t *page = menu_push(menu, "MUSIC", NULL); |
||||
|
||||
for (int i = 0; i < len(def.music); i++) { |
||||
menu_page_add_button(page, i, def.music[i].name, button_music_track); |
||||
} |
||||
menu_page_add_button(page, 0, "RANDOM", button_music_random); |
||||
} |
||||
|
||||
menu_t *pause_menu_init() { |
||||
sfx_play(SFX_MENU_SELECT); |
||||
menu_reset(ingame_menu); |
||||
|
||||
menu_page_t *page = menu_push(ingame_menu, "PAUSED", NULL); |
||||
menu_page_add_button(page, 0, "CONTINUE", button_continue); |
||||
menu_page_add_button(page, 0, "RESTART", button_restart); |
||||
menu_page_add_button(page, 0, "QUIT", button_quit); |
||||
menu_page_add_button(page, 0, "MUSIC", button_music); |
||||
return ingame_menu; |
||||
} |
||||
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Game Over
|
||||
|
||||
menu_t *game_over_menu_init() { |
||||
sfx_play(SFX_MENU_SELECT); |
||||
menu_reset(ingame_menu); |
||||
|
||||
menu_page_t *page = menu_push(ingame_menu, "GAME OVER", NULL); |
||||
menu_page_add_button(page, 1, "", button_quit_confirm); |
||||
return ingame_menu; |
||||
} |
||||
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Race Stats
|
||||
|
||||
static void button_qualify_confirm(menu_t *menu, int data) { |
||||
if (data) { |
||||
race_restart(); |
||||
} |
||||
else { |
||||
game_set_scene(GAME_SCENE_MAIN_MENU); |
||||
} |
||||
} |
||||
|
||||
static void button_race_stats_continue(menu_t *menu, int data) { |
||||
if (g.race_type == RACE_TYPE_CHAMPIONSHIP) { |
||||
if (g.race_position <= QUALIFYING_RANK) { |
||||
page_race_points_init(menu); |
||||
} |
||||
else { |
||||
menu_page_t *page = menu_confirm(menu, "CONTINUE QUALIFYING OR QUIT", "", "QUALIFY", "QUIT", button_qualify_confirm); |
||||
page->index = 0; |
||||
} |
||||
} |
||||
else { |
||||
if (g.is_new_race_record) { |
||||
page_hall_of_fame_init(menu); |
||||
} |
||||
else { |
||||
menu_confirm(menu, "", "RESTART RACE", "RESTART", "QUIT", button_restart_or_quit); |
||||
} |
||||
} |
||||
} |
||||
|
||||
static void page_race_stats_draw(menu_t *menu, int data) { |
||||
menu_page_t *page = &menu->pages[menu->index]; |
||||
vec2i_t pos = page->title_pos; |
||||
pos.x -= 140; |
||||
pos.y += 32; |
||||
ui_pos_t anchor = UI_POS_MIDDLE | UI_POS_CENTER; |
||||
|
||||
// Pilot portrait and race position - only for championship or single race
|
||||
if (g.race_type != RACE_TYPE_TIME_TRIAL) { |
||||
vec2i_t image_pos = ui_scaled_pos(anchor, vec2i(pos.x + 180, pos.y)); |
||||
uint16_t image = texture_from_list(pilot_portraits, g.race_position <= QUALIFYING_RANK ? 1 : 0); |
||||
render_push_2d(image_pos, ui_scaled(render_texture_size(image)), rgba(0, 0, 0, 128), RENDER_NO_TEXTURE); |
||||
ui_draw_image(image_pos, image); |
||||
|
||||
ui_draw_text("RACE POSITION", ui_scaled_pos(anchor, pos), UI_SIZE_8, UI_COLOR_ACCENT); |
||||
ui_draw_number(g.race_position, ui_scaled_pos(anchor, vec2i(pos.x + ui_text_width("RACE POSITION", UI_SIZE_8)+8, pos.y)), UI_SIZE_8, UI_COLOR_DEFAULT); |
||||
} |
||||
|
||||
pos.y += 32; |
||||
|
||||
ui_draw_text("RACE STATISTICS", ui_scaled_pos(anchor, pos), UI_SIZE_8, UI_COLOR_ACCENT); |
||||
pos.y += 16; |
||||
|
||||
for (int i = 0; i < NUM_LAPS; i++) { |
||||
ui_draw_text("LAP", ui_scaled_pos(anchor, vec2i(pos.x + 8, pos.y)), UI_SIZE_8, UI_COLOR_ACCENT); |
||||
ui_draw_number(i+1, ui_scaled_pos(anchor, vec2i(pos.x + 50, pos.y)), UI_SIZE_8, UI_COLOR_ACCENT); |
||||
ui_draw_time(g.lap_times[g.pilot][i], ui_scaled_pos(anchor, vec2i(pos.x + 72, pos.y)), UI_SIZE_8, UI_COLOR_DEFAULT); |
||||
pos.y+= 12; |
||||
} |
||||
pos.y += 32; |
||||
|
||||
ui_draw_text("RACE TIME", ui_scaled_pos(anchor, pos), UI_SIZE_8, UI_COLOR_ACCENT); |
||||
pos.y += 12; |
||||
ui_draw_time(g.race_time, ui_scaled_pos(anchor, vec2i(pos.x + 8, pos.y)), UI_SIZE_8, UI_COLOR_DEFAULT); |
||||
pos.y += 12; |
||||
|
||||
ui_draw_text("BEST LAP", ui_scaled_pos(anchor, pos), UI_SIZE_8, UI_COLOR_ACCENT); |
||||
pos.y += 12; |
||||
ui_draw_time(g.best_lap, ui_scaled_pos(anchor, vec2i(pos.x + 8, pos.y)), UI_SIZE_8, UI_COLOR_DEFAULT); |
||||
pos.y += 12; |
||||
} |
||||
|
||||
menu_t *race_stats_menu_init() { |
||||
sfx_play(SFX_MENU_SELECT); |
||||
menu_reset(ingame_menu); |
||||
|
||||
char *title; |
||||
if (g.race_type == RACE_TYPE_TIME_TRIAL) { |
||||
title = ""; |
||||
} |
||||
else if (g.race_position <= QUALIFYING_RANK) { |
||||
title = "CONGRATULATIONS"; |
||||
} |
||||
else { |
||||
title = "FAILED TO QUALIFY"; |
||||
} |
||||
menu_page_t *page = menu_push(ingame_menu, title, page_race_stats_draw); |
||||
flags_add(page->layout_flags, MENU_FIXED); |
||||
page->title_anchor = UI_POS_MIDDLE | UI_POS_CENTER; |
||||
page->title_pos = vec2i(0, -100); |
||||
menu_page_add_button(page, 1, "", button_race_stats_continue); |
||||
return ingame_menu; |
||||
} |
||||
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Race Table
|
||||
|
||||
static void button_race_points_continue(menu_t *menu, int data) { |
||||
if (g.race_type == RACE_TYPE_CHAMPIONSHIP) { |
||||
page_championship_points_init(menu); |
||||
} |
||||
else if (g.is_new_race_record) { |
||||
page_hall_of_fame_init(menu); |
||||
} |
||||
else { |
||||
menu_confirm(menu, "", "RESTART RACE", "RESTART", "QUIT", button_restart_or_quit); |
||||
} |
||||
} |
||||
|
||||
static void page_race_points_draw(menu_t *menu, int data) { |
||||
menu_page_t *page = &menu->pages[menu->index]; |
||||
vec2i_t pos = page->title_pos; |
||||
pos.x -= 140; |
||||
pos.y += 32; |
||||
ui_pos_t anchor = UI_POS_MIDDLE | UI_POS_CENTER; |
||||
|
||||
ui_draw_text("PILOT NAME", ui_scaled_pos(anchor, pos), UI_SIZE_8, UI_COLOR_ACCENT); |
||||
ui_draw_text("POINTS", ui_scaled_pos(anchor, vec2i(pos.x + 222, pos.y)), UI_SIZE_8, UI_COLOR_ACCENT); |
||||
|
||||
pos.y += 24; |
||||
|
||||
for (int i = 0; i < len(g.race_ranks); i++) { |
||||
rgba_t color = g.race_ranks[i].pilot == g.pilot ? UI_COLOR_ACCENT : UI_COLOR_DEFAULT; |
||||
ui_draw_text(def.pilots[g.race_ranks[i].pilot].name, ui_scaled_pos(anchor, pos), UI_SIZE_8, color); |
||||
int w = ui_number_width(g.race_ranks[i].points, UI_SIZE_8); |
||||
ui_draw_number(g.race_ranks[i].points, ui_scaled_pos(anchor, vec2i(pos.x + 280 - w, pos.y)), UI_SIZE_8, color); |
||||
pos.y += 12; |
||||
} |
||||
} |
||||
|
||||
static void page_race_points_init(menu_t *menu) { |
||||
menu_page_t *page = menu_push(menu, "RACE POINTS", page_race_points_draw); |
||||
flags_add(page->layout_flags, MENU_FIXED); |
||||
page->title_anchor = UI_POS_MIDDLE | UI_POS_CENTER; |
||||
page->title_pos = vec2i(0, -100); |
||||
menu_page_add_button(page, 1, "", button_race_points_continue); |
||||
} |
||||
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Championship Table
|
||||
|
||||
static void button_championship_points_continue(menu_t *menu, int data) { |
||||
if (g.is_new_race_record) { |
||||
page_hall_of_fame_init(menu); |
||||
} |
||||
else if (g.race_type == RACE_TYPE_CHAMPIONSHIP) { |
||||
race_next(); |
||||
} |
||||
else { |
||||
menu_confirm(menu, "", "RESTART RACE", "RESTART", "QUIT", button_quit_confirm); |
||||
} |
||||
} |
||||
|
||||
static void page_championship_points_draw(menu_t *menu, int data) { |
||||
menu_page_t *page = &menu->pages[menu->index]; |
||||
vec2i_t pos = page->title_pos; |
||||
pos.x -= 140; |
||||
pos.y += 32; |
||||
ui_pos_t anchor = UI_POS_MIDDLE | UI_POS_CENTER; |
||||
|
||||
ui_draw_text("PILOT NAME", ui_scaled_pos(anchor, pos), UI_SIZE_8, UI_COLOR_ACCENT); |
||||
ui_draw_text("POINTS", ui_scaled_pos(anchor, vec2i(pos.x + 222, pos.y)), UI_SIZE_8, UI_COLOR_ACCENT); |
||||
|
||||
pos.y += 24; |
||||
|
||||
for (int i = 0; i < len(g.championship_ranks); i++) { |
||||
rgba_t color = g.championship_ranks[i].pilot == g.pilot ? UI_COLOR_ACCENT : UI_COLOR_DEFAULT; |
||||
ui_draw_text(def.pilots[g.championship_ranks[i].pilot].name, ui_scaled_pos(anchor, pos), UI_SIZE_8, color); |
||||
int w = ui_number_width(g.championship_ranks[i].points, UI_SIZE_8); |
||||
ui_draw_number(g.championship_ranks[i].points, ui_scaled_pos(anchor, vec2i(pos.x + 280 - w, pos.y)), UI_SIZE_8, color); |
||||
pos.y += 12; |
||||
} |
||||
} |
||||
|
||||
static void page_championship_points_init(menu_t *menu) { |
||||
menu_page_t *page = menu_push(menu, "CHAMPIONSHIP TABLE", page_championship_points_draw); |
||||
flags_add(page->layout_flags, MENU_FIXED); |
||||
page->title_anchor = UI_POS_MIDDLE | UI_POS_CENTER; |
||||
page->title_pos = vec2i(0, -100); |
||||
menu_page_add_button(page, 1, "", button_championship_points_continue); |
||||
} |
||||
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Hall of Fame
|
||||
|
||||
static highscores_entry_t hs_new_entry = { |
||||
.time = 0, |
||||
.name = "" |
||||
}; |
||||
static const char *hs_charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; |
||||
static int hs_char_index = 0; |
||||
static bool hs_entry_complete = false; |
||||
|
||||
static void hall_of_fame_draw_name_entry(menu_t *menu, ui_pos_t anchor, vec2i_t pos) { |
||||
int entry_len = strlen(hs_new_entry.name); |
||||
int entry_width = ui_text_width(hs_new_entry.name, UI_SIZE_16); |
||||
|
||||
vec2i_t c_pos = ui_scaled_pos(anchor, vec2i(pos.x + entry_width, pos.y)); |
||||
int c_first = 0; |
||||
int c_last = 38; |
||||
if (entry_len == 0) { |
||||
c_last = 37; |
||||
} |
||||
else if (entry_len == 3) { |
||||
c_first = 36; |
||||
} |
||||
|
||||
if (input_pressed(A_MENU_UP)) { |
||||
hs_char_index++; |
||||
} |
||||
else if (input_pressed(A_MENU_DOWN)) { |
||||
hs_char_index--; |
||||
} |
||||
|
||||
if (hs_char_index < c_first) { |
||||
hs_char_index = c_last-1; |
||||
} |
||||
if (hs_char_index >= c_last) { |
||||
hs_char_index = c_first; |
||||
} |
||||
|
||||
// DEL
|
||||
if (hs_char_index == 36) { |
||||
ui_draw_icon(UI_ICON_DEL, c_pos, UI_COLOR_ACCENT); |
||||
if (input_pressed(A_MENU_SELECT)) { |
||||
sfx_play(SFX_MENU_SELECT); |
||||
if (entry_len > 0) { |
||||
hs_new_entry.name[entry_len-1] = '\0'; |
||||
} |
||||
} |
||||
} |
||||
|
||||
// END
|
||||
else if (hs_char_index == 37) { |
||||
ui_draw_icon(UI_ICON_END, c_pos, UI_COLOR_ACCENT); |
||||
if (input_pressed(A_MENU_SELECT)) { |
||||
hs_entry_complete = true; |
||||
} |
||||
} |
||||
|
||||
// A-Z, 0-9
|
||||
else { |
||||
char selector[2] = {hs_charset[hs_char_index], '\0'}; |
||||
ui_draw_text(selector, c_pos, UI_SIZE_16, UI_COLOR_ACCENT); |
||||
|
||||
if (input_pressed(A_MENU_SELECT)) { |
||||
sfx_play(SFX_MENU_SELECT); |
||||
hs_new_entry.name[entry_len] = hs_charset[hs_char_index]; |
||||
hs_new_entry.name[entry_len+1] = '\0'; |
||||
} |
||||
} |
||||
|
||||
ui_draw_text(hs_new_entry.name, ui_scaled_pos(anchor, pos), UI_SIZE_16, UI_COLOR_ACCENT); |
||||
} |
||||
|
||||
static void page_hall_of_fame_draw(menu_t *menu, int data) { |
||||
// FIXME: doing this all in the draw() function leads to all kinds of
|
||||
// complications
|
||||
|
||||
highscores_t *hs = &save.highscores[g.race_class][g.circut][g.highscore_tab]; |
||||
|
||||
if (hs_entry_complete) { |
||||
sfx_play(SFX_MENU_SELECT); |
||||
strncpy(save.highscores_name, hs_new_entry.name, 4); |
||||
save.is_dirty = true; |
||||
|
||||
// Insert new highscore entry into the save struct
|
||||
highscores_entry_t temp_entry = hs->entries[0]; |
||||
for (int i = 0; i < NUM_HIGHSCORES; i++) { |
||||
if (hs_new_entry.time < hs->entries[i].time) { |
||||
for (int j = NUM_HIGHSCORES - 2; j >= i; j--) { |
||||
hs->entries[j+1] = hs->entries[j]; |
||||
} |
||||
hs->entries[i] = hs_new_entry; |
||||
break; |
||||
} |
||||
} |
||||
save.is_dirty = true; |
||||
|
||||
if (g.race_type == RACE_TYPE_CHAMPIONSHIP) { |
||||
race_next(); |
||||
} |
||||
else { |
||||
menu_reset(menu); // Can't go back!
|
||||
menu_confirm(menu, "", "RESTART RACE", "RESTART", "QUIT", button_restart_or_quit); |
||||
} |
||||
return; |
||||
} |
||||
|
||||
menu_page_t *page = &menu->pages[menu->index]; |
||||
vec2i_t pos = page->title_pos; |
||||
pos.x -= 120; |
||||
pos.y += 48; |
||||
ui_pos_t anchor = UI_POS_MIDDLE | UI_POS_CENTER; |
||||
|
||||
bool has_shown_new_entry = false; |
||||
for (int i = 0, j = 0; i < NUM_HIGHSCORES; i++, j++) { |
||||
if (!has_shown_new_entry && hs_new_entry.time < hs->entries[i].time) { |
||||
hall_of_fame_draw_name_entry(menu, anchor, pos); |
||||
ui_draw_time(hs_new_entry.time, ui_scaled_pos(anchor, vec2i(pos.x + 120, pos.y)), UI_SIZE_16, UI_COLOR_DEFAULT); |
||||
has_shown_new_entry = true; |
||||
j--; |
||||
} |
||||
else { |
||||
ui_draw_text(hs->entries[j].name, ui_scaled_pos(anchor, pos), UI_SIZE_16, UI_COLOR_DEFAULT); |
||||
ui_draw_time(hs->entries[j].time, ui_scaled_pos(anchor, vec2i(pos.x + 120, pos.y)), UI_SIZE_16, UI_COLOR_DEFAULT); |
||||
} |
||||
pos.y += 24; |
||||
} |
||||
} |
||||
|
||||
static void page_hall_of_fame_init(menu_t *menu) { |
||||
menu_reset(menu); // Can't go back!
|
||||
menu_page_t *page = menu_push(menu, "HALL OF FAME", page_hall_of_fame_draw); |
||||
flags_add(page->layout_flags, MENU_FIXED); |
||||
page->title_anchor = UI_POS_MIDDLE | UI_POS_CENTER; |
||||
page->title_pos = vec2i(0, -100); |
||||
|
||||
hs_new_entry.time = g.race_time; |
||||
strncpy(hs_new_entry.name, save.highscores_name, 4); |
||||
hs_char_index = 0; |
||||
hs_entry_complete = false; |
||||
} |
||||
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Text scroller
|
||||
|
||||
static char * const *text_scroll_lines; |
||||
static int text_scroll_lines_len; |
||||
static double text_scroll_start_time; |
||||
|
||||
static void text_scroll_menu_draw(menu_t *menu, int data) { |
||||
double time = system_time() - text_scroll_start_time; |
||||
int scale = ui_get_scale(); |
||||
int speed = 32; |
||||
vec2i_t screen = render_size(); |
||||
vec2i_t pos = vec2i(screen.x / 2, screen.y - time * scale * speed); |
||||
|
||||
for (int i = 0; i < text_scroll_lines_len; i++) { |
||||
const char *line = text_scroll_lines[i]; |
||||
|
||||
if (line[0] == '#') { |
||||
pos.y += 48 * scale; |
||||
ui_draw_text_centered(line + 1, pos, UI_SIZE_16, UI_COLOR_ACCENT); |
||||
pos.y += 32 * scale; |
||||
} |
||||
else { |
||||
ui_draw_text_centered(line, pos, UI_SIZE_8, UI_COLOR_DEFAULT);
|
||||
pos.y += 12 * scale; |
||||
} |
||||
} |
||||
} |
||||
|
||||
menu_t *text_scroll_menu_init(char * const *lines, int len) { |
||||
text_scroll_lines = lines; |
||||
text_scroll_lines_len = len; |
||||
text_scroll_start_time = system_time(); |
||||
|
||||
menu_reset(ingame_menu); |
||||
|
||||
menu_page_t *page = menu_push(ingame_menu, "", text_scroll_menu_draw); |
||||
menu_page_add_button(page, 1, "", button_quit_confirm); |
||||
return ingame_menu; |
||||
} |
@ -0,0 +1,13 @@ |
||||
#ifndef PAUSE_MENU_H |
||||
#define PAUSE_MENU_H |
||||
|
||||
#include "menu.h" |
||||
|
||||
void ingame_menus_load(); |
||||
|
||||
menu_t *pause_menu_init(); |
||||
menu_t *game_over_menu_init(); |
||||
menu_t *race_stats_menu_init(); |
||||
menu_t *text_scroll_menu_init(char * const *lines, int len); |
||||
|
||||
#endif |
@ -0,0 +1,105 @@ |
||||
#include "../system.h" |
||||
#include "../input.h" |
||||
#include "../utils.h" |
||||
#include "../types.h" |
||||
#include "../mem.h" |
||||
|
||||
#include "intro.h" |
||||
#include "ui.h" |
||||
#include "image.h" |
||||
#include "game.h" |
||||
|
||||
void free_dummmy(void *p) {} |
||||
void *realloc_dummmy(void *p, size_t sz) { |
||||
die("pl_mpeg needed to realloc. Not implemented. Maybe increase PLM_BUFFER_DEFAULT_SIZE"); |
||||
return NULL; |
||||
} |
||||
|
||||
#define PL_MPEG_IMPLEMENTATION |
||||
#define PLM_MALLOC mem_bump |
||||
#define PLM_FREE free_dummmy |
||||
#define PLM_REALLOC realloc_dummmy |
||||
#include "../libs/pl_mpeg.h" |
||||
|
||||
#define INTRO_AUDIO_BUFFER_LEN (64 * 1024) |
||||
|
||||
static plm_t *plm; |
||||
static rgba_t *frame_buffer; |
||||
static int16_t texture; |
||||
static float *audio_buffer; |
||||
static int audio_buffer_read_pos; |
||||
static int audio_buffer_write_pos; |
||||
|
||||
static void video_cb(plm_t *plm, plm_frame_t *frame, void *user); |
||||
static void audio_cb(plm_t *plm, plm_samples_t *samples, void *user); |
||||
static void audio_mix(float *samples, uint32_t len); |
||||
static void intro_end(); |
||||
|
||||
void intro_init() { |
||||
plm = plm_create_with_filename("wipeout/intro.mpeg"); |
||||
if (!plm) { |
||||
intro_end(); |
||||
return; |
||||
} |
||||
plm_set_video_decode_callback(plm, video_cb, NULL); |
||||
plm_set_audio_decode_callback(plm, audio_cb, NULL); |
||||
|
||||
plm_set_loop(plm, false); |
||||
plm_set_audio_enabled(plm, true); |
||||
plm_set_audio_stream(plm, 0); |
||||
|
||||
int w = plm_get_width(plm); |
||||
int h = plm_get_height(plm); |
||||
frame_buffer = mem_bump(w * h * sizeof(rgba_t)); |
||||
for (int i = 0; i < w * h; i++) { |
||||
frame_buffer[i] = rgba(0, 0, 0, 255); |
||||
} |
||||
texture = render_texture_create(w, h, frame_buffer); |
||||
|
||||
sfx_set_external_mix_cb(audio_mix); |
||||
audio_buffer = mem_bump(INTRO_AUDIO_BUFFER_LEN * sizeof(float) * 2); |
||||
audio_buffer_read_pos = 0; |
||||
audio_buffer_write_pos = 0; |
||||
} |
||||
|
||||
static void intro_end() { |
||||
sfx_set_external_mix_cb(NULL); |
||||
game_set_scene(GAME_SCENE_TITLE); |
||||
} |
||||
|
||||
void intro_update() { |
||||
if (!plm) { |
||||
return; |
||||
} |
||||
plm_decode(plm, system_tick()); |
||||
render_set_view_2d(); |
||||
render_push_2d(vec2i(0,0), render_size(), rgba(128, 128, 128, 255), texture); |
||||
if (plm_has_ended(plm) || input_pressed(A_MENU_SELECT) || input_pressed(A_MENU_START)) { |
||||
intro_end(); |
||||
} |
||||
} |
||||
|
||||
static void audio_cb(plm_t *plm, plm_samples_t *samples, void *user) { |
||||
int len = samples->count * 2; |
||||
for (int i = 0; i < len; i++) { |
||||
audio_buffer[audio_buffer_write_pos % INTRO_AUDIO_BUFFER_LEN] = samples->interleaved[i]; |
||||
audio_buffer_write_pos++; |
||||
} |
||||
} |
||||
|
||||
static void audio_mix(float *samples, uint32_t len) { |
||||
int i; |
||||
for (i = 0; i < len && audio_buffer_read_pos < audio_buffer_write_pos; i++) { |
||||
samples[i] = audio_buffer[audio_buffer_read_pos % INTRO_AUDIO_BUFFER_LEN]; |
||||
audio_buffer_read_pos++; |
||||
} |
||||
for (; i < len; i++) { |
||||
samples[i] = 0; |
||||
} |
||||
} |
||||
|
||||
static void video_cb(plm_t *plm, plm_frame_t *frame, void *user) { |
||||
plm_frame_to_rgba(frame, (uint8_t *)frame_buffer, plm_get_width(plm) * sizeof(rgba_t)); |
||||
render_texture_replace_pixels(texture, frame_buffer); |
||||
} |
||||
|
@ -0,0 +1,8 @@ |
||||
#ifndef INTRO_H |
||||
#define INTRO_H |
||||
|
||||
void intro_init(); |
||||
void intro_update(); |
||||
void intro_cleanup(); |
||||
|
||||
#endif |
@ -0,0 +1,541 @@ |
||||
#include "../utils.h" |
||||
#include "../system.h" |
||||
#include "../mem.h" |
||||
#include "../platform.h" |
||||
#include "../input.h" |
||||
|
||||
#include "menu.h" |
||||
#include "main_menu.h" |
||||
#include "game.h" |
||||
#include "image.h" |
||||
#include "ui.h" |
||||
|
||||
static void page_main_init(menu_t *menu); |
||||
static void page_options_init(menu_t *menu); |
||||
static void page_race_class_init(menu_t *menu); |
||||
static void page_race_type_init(menu_t *menu); |
||||
static void page_team_init(menu_t *menu); |
||||
static void page_pilot_init(menu_t *menu); |
||||
static void page_circut_init(menu_t *menu); |
||||
static void page_options_controls_init(menu_t *menu); |
||||
static void page_options_video_init(menu_t *menu); |
||||
static void page_options_audio_init(menu_t *menu); |
||||
|
||||
static uint16_t background; |
||||
static texture_list_t track_images; |
||||
static menu_t *main_menu; |
||||
|
||||
static struct { |
||||
Object *race_classes[2]; |
||||
Object *teams[4]; |
||||
Object *pilots[8]; |
||||
struct { Object *stopwatch, *save, *load, *headphones, *cd; } options; |
||||
struct { Object *championship, *msdos, *single_race, *options; } misc; |
||||
Object *rescue; |
||||
Object *controller; |
||||
} models; |
||||
|
||||
static void draw_model(Object *model, vec2_t offset, vec3_t pos, float rotation) { |
||||
render_set_view(vec3(0,0,0), vec3(0, -M_PI, -M_PI)); |
||||
render_set_screen_position(offset); |
||||
mat4_t mat = mat4_identity(); |
||||
mat4_set_translation(&mat, pos); |
||||
mat4_set_yaw_pitch_roll(&mat, vec3(0, rotation, M_PI)); |
||||
object_draw(model, &mat); |
||||
render_set_screen_position(vec2(0, 0)); |
||||
} |
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Main Menu
|
||||
|
||||
static void button_start_game(menu_t *menu, int data) { |
||||
page_race_class_init(menu); |
||||
} |
||||
|
||||
static void button_options(menu_t *menu, int data) { |
||||
page_options_init(menu); |
||||
} |
||||
|
||||
static void button_quit_confirm(menu_t *menu, int data) { |
||||
if (data) { |
||||
system_exit(); |
||||
} |
||||
else { |
||||
menu_pop(menu); |
||||
} |
||||
} |
||||
|
||||
static void button_quit(menu_t *menu, int data) { |
||||
menu_confirm(menu, "ARE YOU SURE YOU", "WANT TO QUIT", "YES", "NO", button_quit_confirm); |
||||
} |
||||
|
||||
static void page_main_draw(menu_t *menu, int data) { |
||||
switch (data) { |
||||
case 0: draw_model(g.ships[0].model, vec2(0, -0.1), vec3(0, 0, -700), system_cycle_time()); break; |
||||
case 1: draw_model(models.misc.options, vec2(0, -0.2), vec3(0, 0, -700), system_cycle_time()); break; |
||||
case 2: draw_model(models.misc.msdos, vec2(0, -0.2), vec3(0, 0, -700), system_cycle_time()); break; |
||||
} |
||||
} |
||||
|
||||
static void page_main_init(menu_t *menu) { |
||||
menu_page_t *page = menu_push(menu, "OPTIONS", page_main_draw); |
||||
flags_add(page->layout_flags, MENU_FIXED); |
||||
page->title_pos = vec2i(0, 30); |
||||
page->title_anchor = UI_POS_TOP | UI_POS_CENTER; |
||||
page->items_pos = vec2i(0, -110); |
||||
page->items_anchor = UI_POS_BOTTOM | UI_POS_CENTER; |
||||
|
||||
menu_page_add_button(page, 0, "START GAME", button_start_game); |
||||
menu_page_add_button(page, 1, "OPTIONS", button_options); |
||||
|
||||
#ifndef __EMSCRIPTEN__ |
||||
menu_page_add_button(page, 2, "QUIT", button_quit); |
||||
#endif |
||||
} |
||||
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Options
|
||||
|
||||
static void button_controls(menu_t *menu, int data) { |
||||
page_options_controls_init(menu); |
||||
} |
||||
|
||||
static void button_video(menu_t *menu, int data) { |
||||
page_options_video_init(menu); |
||||
} |
||||
|
||||
static void button_audio(menu_t *menu, int data) { |
||||
page_options_audio_init(menu); |
||||
} |
||||
|
||||
static void page_options_draw(menu_t *menu, int data) { |
||||
switch (data) { |
||||
case 0: draw_model(models.controller, vec2(0, -0.1), vec3(0, 0, -6000), system_cycle_time()); break; |
||||
case 1: draw_model(models.rescue, vec2(0, -0.2), vec3(0, 0, -700), system_cycle_time()); break; // TODO: needs better model
|
||||
case 2: draw_model(models.options.headphones, vec2(0, -0.2), vec3(0, 0, -300), system_cycle_time()); break; |
||||
} |
||||
} |
||||
|
||||
static void page_options_init(menu_t *menu) { |
||||
menu_page_t *page = menu_push(menu, "OPTIONS", page_options_draw); |
||||
flags_add(page->layout_flags, MENU_FIXED); |
||||
page->title_pos = vec2i(0, 30); |
||||
page->title_anchor = UI_POS_TOP | UI_POS_CENTER; |
||||
page->items_pos = vec2i(0, -110); |
||||
page->items_anchor = UI_POS_BOTTOM | UI_POS_CENTER; |
||||
menu_page_add_button(page, 0, "CONTROLS", button_controls); |
||||
menu_page_add_button(page, 1, "VIDEO", button_video); |
||||
menu_page_add_button(page, 2, "AUDIO", button_audio); |
||||
} |
||||
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Options Controls
|
||||
|
||||
static const char *button_names[NUM_GAME_ACTIONS][2] = {}; |
||||
static int control_current_action; |
||||
static float await_input_deadline; |
||||
|
||||
void button_capture(void *user, button_t button, int32_t ascii_char) { |
||||
if (button == INPUT_INVALID) { |
||||
return; |
||||
} |
||||
|
||||
menu_t *menu = (menu_t *)user; |
||||
if (button == INPUT_KEY_ESCAPE) { |
||||
input_capture(NULL, NULL); |
||||
menu_pop(menu); |
||||
return; |
||||
} |
||||
|
||||
int index = button < INPUT_KEY_MAX ? 0 : 1; // joypad or keyboard
|
||||
|
||||
// unbind this button if it's bound anywhere
|
||||
for (int i = 0; i < len(save.buttons); i++) { |
||||
if (save.buttons[i][index] == button) { |
||||
save.buttons[i][index] = INPUT_INVALID; |
||||
} |
||||
} |
||||
input_capture(NULL, NULL); |
||||
input_bind(INPUT_LAYER_USER, button, control_current_action); |
||||
save.buttons[control_current_action][index] = button; |
||||
save.is_dirty = true; |
||||
menu_pop(menu); |
||||
} |
||||
|
||||
static void page_options_control_set_draw(menu_t *menu, int data) { |
||||
float remaining = await_input_deadline - platform_now(); |
||||
|
||||
menu_page_t *page = &menu->pages[menu->index]; |
||||
char remaining_text[2] = { '0' + (uint8_t)clamp(remaining + 1, 0, 3), '\0'}; |
||||
vec2i_t pos = vec2i(page->items_pos.x, page->items_pos.y + 24); |
||||
ui_draw_text_centered(remaining_text, ui_scaled_pos(page->items_anchor, pos), UI_SIZE_16, UI_COLOR_DEFAULT); |
||||
|
||||
if (remaining <= 0) { |
||||
input_capture(NULL, NULL); |
||||
menu_pop(menu); |
||||
return; |
||||
} |
||||
} |
||||
|
||||
static void page_options_controls_set_init(menu_t *menu, int data) { |
||||
control_current_action = data; |
||||
await_input_deadline = platform_now() + 3; |
||||
|
||||
menu_page_t *page = menu_push(menu, "AWAITING INPUT", page_options_control_set_draw); |
||||
input_capture(button_capture, menu); |
||||
} |
||||
|
||||
|
||||
static void page_options_control_draw(menu_t *menu, int data) { |
||||
menu_page_t *page = &menu->pages[menu->index]; |
||||
|
||||
int left = page->items_pos.x + page->block_width - 100; |
||||
int right = page->items_pos.x + page->block_width; |
||||
int line_y = page->items_pos.y - 20; |
||||
|
||||
vec2i_t left_head_pos = vec2i(left - ui_text_width("KEYBOARD", UI_SIZE_8), line_y); |
||||
ui_draw_text("KEYBOARD", ui_scaled_pos(page->items_anchor, left_head_pos), UI_SIZE_8, UI_COLOR_DEFAULT); |
||||
|
||||
vec2i_t right_head_pos = vec2i(right - ui_text_width("JOYSTICK", UI_SIZE_8), line_y); |
||||
ui_draw_text("JOYSTICK", ui_scaled_pos(page->items_anchor, right_head_pos), UI_SIZE_8, UI_COLOR_DEFAULT); |
||||
line_y += 20; |
||||
|
||||
for (int action = 0; action < NUM_GAME_ACTIONS; action++) { |
||||
rgba_t text_color = UI_COLOR_DEFAULT; |
||||
if (action == data) { |
||||
text_color = UI_COLOR_ACCENT; |
||||
} |
||||
|
||||
if (save.buttons[action][0] != INPUT_INVALID) { |
||||
const char *name = input_button_to_name(save.buttons[action][0]); |
||||
if (!name) { |
||||
name = "UNKNWN"; |
||||
} |
||||
vec2i_t pos = vec2i(left - ui_text_width(name, UI_SIZE_8), line_y); |
||||
ui_draw_text(name, ui_scaled_pos(page->items_anchor, pos), UI_SIZE_8, text_color); |
||||
} |
||||
if (save.buttons[action][1] != INPUT_INVALID) { |
||||
const char *name = input_button_to_name(save.buttons[action][1]); |
||||
if (!name) { |
||||
name = "UNKNWN"; |
||||
} |
||||
vec2i_t pos = vec2i(right - ui_text_width(name, UI_SIZE_8), line_y); |
||||
ui_draw_text(name, ui_scaled_pos(page->items_anchor, pos), UI_SIZE_8, text_color); |
||||
} |
||||
line_y += 12; |
||||
} |
||||
} |
||||
|
||||
static void page_options_controls_init(menu_t *menu) { |
||||
menu_page_t *page = menu_push(menu, "CONTROLS", page_options_control_draw); |
||||
flags_set(page->layout_flags, MENU_VERTICAL | MENU_FIXED); |
||||
page->title_pos = vec2i(-160, -100); |
||||
page->title_anchor = UI_POS_MIDDLE | UI_POS_CENTER; |
||||
page->items_pos = vec2i(-160, -50); |
||||
page->block_width = 320; |
||||
page->items_anchor = UI_POS_MIDDLE | UI_POS_CENTER; |
||||
|
||||
// const char *thrust_name = button_name(A_THRUST);
|
||||
// printf("thrust: %s\n", thrust_name);
|
||||
menu_page_add_button(page, A_UP, "UP", page_options_controls_set_init); |
||||
menu_page_add_button(page, A_DOWN, "DOWN", page_options_controls_set_init); |
||||
menu_page_add_button(page, A_LEFT, "LEFT", page_options_controls_set_init); |
||||
menu_page_add_button(page, A_RIGHT, "RIGHT", page_options_controls_set_init); |
||||
menu_page_add_button(page, A_BRAKE_LEFT, "BRAKE L", page_options_controls_set_init); |
||||
menu_page_add_button(page, A_BRAKE_RIGHT, "BRAKE R", page_options_controls_set_init); |
||||
menu_page_add_button(page, A_THRUST, "THRUST", page_options_controls_set_init); |
||||
menu_page_add_button(page, A_FIRE, "FIRE", page_options_controls_set_init); |
||||
menu_page_add_button(page, A_CHANGE_VIEW, "VIEW", page_options_controls_set_init); |
||||
} |
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Options Video
|
||||
|
||||
static void toggle_fullscreen(menu_t *menu, int data) { |
||||
save.fullscreen = data; |
||||
save.is_dirty = true; |
||||
platform_set_fullscreen(save.fullscreen); |
||||
} |
||||
|
||||
static void toggle_show_fps(menu_t *menu, int data) { |
||||
save.show_fps = data; |
||||
save.is_dirty = true; |
||||
} |
||||
|
||||
static void toggle_ui_scale(menu_t *menu, int data) { |
||||
save.ui_scale = data; |
||||
save.is_dirty = true; |
||||
} |
||||
|
||||
static void toggle_res(menu_t *menu, int data) { |
||||
render_set_resolution(data); |
||||
save.screen_res = data; |
||||
save.is_dirty = true; |
||||
} |
||||
|
||||
static void toggle_post(menu_t *menu, int data) { |
||||
render_set_post_effect(data); |
||||
save.post_effect = data; |
||||
save.is_dirty = true; |
||||
} |
||||
|
||||
static const char *opts_off_on[] = {"OFF", "ON"}; |
||||
static const char *opts_ui_sizes[] = {"AUTO", "1X", "2X", "3X", "4X"}; |
||||
static const char *opts_res[] = {"NATIVE", "240P", "480P"}; |
||||
static const char *opts_post[] = {"NONE", "CRT EFFECT"}; |
||||
|
||||
static void page_options_video_init(menu_t *menu) { |
||||
menu_page_t *page = menu_push(menu, "VIDEO OPTIONS", NULL); |
||||
flags_set(page->layout_flags, MENU_VERTICAL | MENU_FIXED); |
||||
page->title_pos = vec2i(-160, -100); |
||||
page->title_anchor = UI_POS_MIDDLE | UI_POS_CENTER; |
||||
page->items_pos = vec2i(-160, -60); |
||||
page->block_width = 320; |
||||
page->items_anchor = UI_POS_MIDDLE | UI_POS_CENTER; |
||||
|
||||
#ifndef __EMSCRIPTEN__ |
||||
menu_page_add_toggle(page, save.fullscreen, "FULLSCREEN", opts_off_on, len(opts_off_on), toggle_fullscreen); |
||||
#endif |
||||
menu_page_add_toggle(page, save.ui_scale, "UI SCALE", opts_ui_sizes, len(opts_ui_sizes), toggle_ui_scale); |
||||
menu_page_add_toggle(page, save.show_fps, "SHOW FPS", opts_off_on, len(opts_off_on), toggle_show_fps); |
||||
menu_page_add_toggle(page, save.screen_res, "SCREEN RESOLUTION", opts_res, len(opts_res), toggle_res); |
||||
menu_page_add_toggle(page, save.post_effect, "POST PROCESSING", opts_post, len(opts_post), toggle_post); |
||||
} |
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Options Audio
|
||||
|
||||
static void toggle_music_volume(menu_t *menu, int data) { |
||||
save.music_volume = (float)data * 0.1; |
||||
save.is_dirty = true; |
||||
} |
||||
|
||||
static void toggle_sfx_volume(menu_t *menu, int data) { |
||||
save.sfx_volume = (float)data * 0.1;
|
||||
save.is_dirty = true; |
||||
} |
||||
|
||||
static const char *opts_volume[] = {"0", "10", "20", "30", "40", "50", "60", "70", "80", "90", "100"}; |
||||
|
||||
static void page_options_audio_init(menu_t *menu) { |
||||
menu_page_t *page = menu_push(menu, "AUDIO OPTIONS", NULL); |
||||
|
||||
flags_set(page->layout_flags, MENU_VERTICAL | MENU_FIXED); |
||||
page->title_pos = vec2i(-160, -100); |
||||
page->title_anchor = UI_POS_MIDDLE | UI_POS_CENTER; |
||||
page->items_pos = vec2i(-160, -80); |
||||
page->block_width = 320; |
||||
page->items_anchor = UI_POS_MIDDLE | UI_POS_CENTER; |
||||
|
||||
menu_page_add_toggle(page, save.music_volume * 10, "MUSIC VOLUME", opts_volume, len(opts_volume), toggle_music_volume); |
||||
menu_page_add_toggle(page, save.sfx_volume * 10, "SOUND EFFECTS VOLUME", opts_volume, len(opts_volume), toggle_sfx_volume); |
||||
} |
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Racing class
|
||||
|
||||
static void button_race_class_select(menu_t *menu, int data) { |
||||
if (!save.has_rapier_class && data == RACE_CLASS_RAPIER) { |
||||
return; |
||||
} |
||||
g.race_class = data; |
||||
page_race_type_init(menu); |
||||
} |
||||
|
||||
static void page_race_class_draw(menu_t *menu, int data) { |
||||
menu_page_t *page = &menu->pages[menu->index]; |
||||
flags_add(page->layout_flags, MENU_FIXED); |
||||
page->title_pos = vec2i(0, 30); |
||||
page->title_anchor = UI_POS_TOP | UI_POS_CENTER; |
||||
page->items_pos = vec2i(0, -110); |
||||
page->items_anchor = UI_POS_BOTTOM | UI_POS_CENTER; |
||||
draw_model(models.race_classes[data], vec2(0, -0.2), vec3(0, 0, -350), system_cycle_time()); |
||||
|
||||
if (!save.has_rapier_class && data == RACE_CLASS_RAPIER) { |
||||
render_set_view_2d(); |
||||
vec2i_t pos = vec2i(page->items_pos.x, page->items_pos.y + 32); |
||||
ui_draw_text_centered("NOT AVAILABLE", ui_scaled_pos(page->items_anchor, pos), UI_SIZE_12, UI_COLOR_ACCENT); |
||||
} |
||||
} |
||||
|
||||
static void page_race_class_init(menu_t *menu) { |
||||
menu_page_t *page = menu_push(menu, "SELECT RACING CLASS", page_race_class_draw); |
||||
for (int i = 0; i < len(def.race_classes); i++) { |
||||
menu_page_add_button(page, i, def.race_classes[i].name, button_race_class_select); |
||||
} |
||||
} |
||||
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Race Type
|
||||
|
||||
static void button_race_type_select(menu_t *menu, int data) { |
||||
g.race_type = data; |
||||
g.highscore_tab = g.race_type == RACE_TYPE_TIME_TRIAL ? HIGHSCORE_TAB_TIME_TRIAL : HIGHSCORE_TAB_RACE; |
||||
page_team_init(menu); |
||||
} |
||||
|
||||
static void page_race_type_draw(menu_t *menu, int data) { |
||||
switch (data) { |
||||
case 0: draw_model(models.misc.championship, vec2(0, -0.2), vec3(0, 0, -400), system_cycle_time()); break; |
||||
case 1: draw_model(models.misc.single_race, vec2(0, -0.2), vec3(0, 0, -400), system_cycle_time()); break; |
||||
case 2: draw_model(models.options.stopwatch, vec2(0, -0.2), vec3(0, 0, -400), system_cycle_time()); break; |
||||
} |
||||
} |
||||
|
||||
static void page_race_type_init(menu_t *menu) { |
||||
menu_page_t *page = menu_push(menu, "SELECT RACE TYPE", page_race_type_draw); |
||||
flags_add(page->layout_flags, MENU_FIXED); |
||||
page->title_pos = vec2i(0, 30); |
||||
page->title_anchor = UI_POS_TOP | UI_POS_CENTER; |
||||
page->items_pos = vec2i(0, -110); |
||||
page->items_anchor = UI_POS_BOTTOM | UI_POS_CENTER; |
||||
for (int i = 0; i < len(def.race_types); i++) { |
||||
menu_page_add_button(page, i, def.race_types[i].name, button_race_type_select); |
||||
} |
||||
} |
||||
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Team
|
||||
|
||||
static void button_team_select(menu_t *menu, int data) { |
||||
g.team = data; |
||||
page_pilot_init(menu); |
||||
} |
||||
|
||||
static void page_team_draw(menu_t *menu, int data) { |
||||
int team_model_index = (data + 3) % 4; // models in the prm are shifted by -1
|
||||
draw_model(models.teams[team_model_index], vec2(0, -0.2), vec3(0, 0, -10000), system_cycle_time()); |
||||
draw_model(g.ships[def.teams[data].pilots[0]].model, vec2(0, -0.3), vec3(-700, -800, -1300), system_cycle_time()*1.1); |
||||
draw_model(g.ships[def.teams[data].pilots[1]].model, vec2(0, -0.3), vec3( 700, -800, -1300), system_cycle_time()*1.2); |
||||
} |
||||
|
||||
static void page_team_init(menu_t *menu) { |
||||
menu_page_t *page = menu_push(menu, "SELECT YOUR TEAM", page_team_draw); |
||||
flags_add(page->layout_flags, MENU_FIXED); |
||||
page->title_pos = vec2i(0, 30); |
||||
page->title_anchor = UI_POS_TOP | UI_POS_CENTER; |
||||
page->items_pos = vec2i(0, -110); |
||||
page->items_anchor = UI_POS_BOTTOM | UI_POS_CENTER; |
||||
for (int i = 0; i < len(def.teams); i++) { |
||||
menu_page_add_button(page, i, def.teams[i].name, button_team_select); |
||||
} |
||||
} |
||||
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Pilot
|
||||
|
||||
static void button_pilot_select(menu_t *menu, int data) { |
||||
g.pilot = data; |
||||
if (g.race_type != RACE_TYPE_CHAMPIONSHIP) { |
||||
page_circut_init(menu); |
||||
} |
||||
else { |
||||
g.circut = 0; |
||||
game_reset_championship(); |
||||
game_set_scene(GAME_SCENE_RACE); |
||||
} |
||||
} |
||||
|
||||
static void page_pilot_draw(menu_t *menu, int data) { |
||||
draw_model(models.pilots[data], vec2(0, -0.2), vec3(0, 0, -10000), system_cycle_time()); |
||||
} |
||||
|
||||
static void page_pilot_init(menu_t *menu) { |
||||
menu_page_t *page = menu_push(menu, "CHOOSE YOUR PILOT", page_pilot_draw); |
||||
flags_add(page->layout_flags, MENU_FIXED); |
||||
page->title_pos = vec2i(0, 30); |
||||
page->title_anchor = UI_POS_TOP | UI_POS_CENTER; |
||||
page->items_pos = vec2i(0, -110); |
||||
page->items_anchor = UI_POS_BOTTOM | UI_POS_CENTER; |
||||
for (int i = 0; i < len(def.teams[g.team].pilots); i++) { |
||||
menu_page_add_button(page, def.teams[g.team].pilots[i], def.pilots[def.teams[g.team].pilots[i]].name, button_pilot_select); |
||||
} |
||||
} |
||||
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Circut
|
||||
|
||||
static void button_circut_select(menu_t *menu, int data) { |
||||
g.circut = data; |
||||
game_set_scene(GAME_SCENE_RACE); |
||||
} |
||||
|
||||
static void page_circut_draw(menu_t *menu, int data) { |
||||
vec2i_t pos = vec2i(0, -25); |
||||
vec2i_t size = vec2i(128, 74); |
||||
vec2i_t scaled_size = ui_scaled(size); |
||||
vec2i_t scaled_pos = ui_scaled_pos(UI_POS_MIDDLE | UI_POS_CENTER, vec2i(pos.x - size.x/2, pos.y - size.y/2)); |
||||
render_push_2d(scaled_pos, scaled_size, rgba(128, 128, 128, 255), texture_from_list(track_images, data)); |
||||
} |
||||
|
||||
static void page_circut_init(menu_t *menu) { |
||||
menu_page_t *page = menu_push(menu, "SELECT RACING CIRCUT", page_circut_draw); |
||||
flags_add(page->layout_flags, MENU_FIXED); |
||||
page->title_pos = vec2i(0, 30); |
||||
page->title_anchor = UI_POS_TOP | UI_POS_CENTER; |
||||
page->items_pos = vec2i(0, -100); |
||||
page->items_anchor = UI_POS_BOTTOM | UI_POS_CENTER; |
||||
for (int i = 0; i < len(def.circuts); i++) { |
||||
if (!def.circuts[i].is_bonus_circut || save.has_bonus_circuts) { |
||||
menu_page_add_button(page, i, def.circuts[i].name, button_circut_select); |
||||
} |
||||
} |
||||
} |
||||
|
||||
#define objects_unpack(DEST, SRC) \ |
||||
objects_unpack_imp((Object **)&DEST, sizeof(DEST)/sizeof(Object*), SRC) |
||||
|
||||
static void objects_unpack_imp(Object **dest_array, int len, Object *src) { |
||||
int i; |
||||
for (i = 0; src && i < len; i++) { |
||||
dest_array[i] = src; |
||||
src = src->next; |
||||
} |
||||
error_if(i != len, "expected %d models got %d", len, i) |
||||
} |
||||
|
||||
|
||||
void main_menu_init() { |
||||
g.is_attract_mode = false; |
||||
|
||||
main_menu = mem_bump(sizeof(menu_t)); |
||||
|
||||
background = image_get_texture("wipeout/textures/wipeout1.tim"); |
||||
track_images = image_get_compressed_textures("wipeout/textures/track.cmp"); |
||||
|
||||
objects_unpack(models.race_classes, objects_load("wipeout/common/leeg.prm", image_get_compressed_textures("wipeout/common/leeg.cmp"))); |
||||
objects_unpack(models.teams, objects_load("wipeout/common/teams.prm", texture_list_empty())); |
||||
objects_unpack(models.pilots, objects_load("wipeout/common/pilot.prm", image_get_compressed_textures("wipeout/common/pilot.cmp"))); |
||||
objects_unpack(models.options, objects_load("wipeout/common/alopt.prm", image_get_compressed_textures("wipeout/common/alopt.cmp"))); |
||||
objects_unpack(models.rescue, objects_load("wipeout/common/rescu.prm", image_get_compressed_textures("wipeout/common/rescu.cmp"))); |
||||
objects_unpack(models.controller, objects_load("wipeout/common/pad1.prm", image_get_compressed_textures("wipeout/common/pad1.cmp"))); |
||||
objects_unpack(models.misc, objects_load("wipeout/common/msdos.prm", image_get_compressed_textures("wipeout/common/msdos.cmp"))); |
||||
|
||||
menu_reset(main_menu); |
||||
page_main_init(main_menu); |
||||
} |
||||
|
||||
void main_menu_update() { |
||||
render_set_view_2d(); |
||||
render_push_2d(vec2i(0, 0), render_size(), rgba(128, 128, 128, 255), background); |
||||
|
||||
menu_update(main_menu); |
||||
} |
||||
|
@ -0,0 +1,7 @@ |
||||
#ifndef MAIN_MENU_H |
||||
#define MAIN_MENU_H |
||||
|
||||
void main_menu_init(); |
||||
void main_menu_update(); |
||||
|
||||
#endif |
@ -0,0 +1,245 @@ |
||||
#include "../system.h" |
||||
#include "../input.h" |
||||
#include "../utils.h" |
||||
|
||||
#include "game.h" |
||||
#include "menu.h" |
||||
#include "ui.h" |
||||
#include "sfx.h" |
||||
|
||||
bool blink() { |
||||
// blink 30 times per second
|
||||
return fmod(system_cycle_time(), 1.0/15.0) < 1.0/30.0; |
||||
} |
||||
|
||||
void menu_reset(menu_t *menu) { |
||||
menu->index = -1; |
||||
} |
||||
|
||||
menu_page_t *menu_push(menu_t *menu, char *title, void(*draw_func)(menu_t *, int)) { |
||||
error_if(menu->index >= MENU_PAGES_MAX-1, "MENU_PAGES_MAX exceeded"); |
||||
menu_page_t *page = &menu->pages[++menu->index]; |
||||
page->layout_flags = MENU_VERTICAL | MENU_ALIGN_CENTER; |
||||
page->block_width = 320; |
||||
page->title = title; |
||||
page->subtitle = NULL; |
||||
page->draw_func = draw_func; |
||||
page->entries_len = 0; |
||||
page->index = 0; |
||||
page->title_anchor = UI_POS_MIDDLE | UI_POS_CENTER; |
||||
page->items_anchor = UI_POS_MIDDLE | UI_POS_CENTER; |
||||
return page; |
||||
} |
||||
|
||||
menu_page_t *menu_confirm(menu_t *menu, char *title, char *subtitle, char *yes, char *no, void(*confirm_func)(menu_t *, int)) { |
||||
error_if(menu->index >= MENU_PAGES_MAX-1, "MENU_PAGES_MAX exceeded"); |
||||
menu_page_t *page = &menu->pages[++menu->index]; |
||||
page->layout_flags = MENU_HORIZONTAL; |
||||
page->title = title; |
||||
page->subtitle = subtitle; |
||||
page->draw_func = NULL; |
||||
page->entries_len = 0; |
||||
menu_page_add_button(page, 1, yes, confirm_func); |
||||
menu_page_add_button(page, 0, no, confirm_func); |
||||
page->index = 1; |
||||
page->title_anchor = UI_POS_MIDDLE | UI_POS_CENTER; |
||||
page->items_anchor = UI_POS_MIDDLE | UI_POS_CENTER; |
||||
return page; |
||||
} |
||||
|
||||
void menu_pop(menu_t *menu) { |
||||
if (menu->index == 0) { |
||||
return; |
||||
} |
||||
menu->index--; |
||||
} |
||||
|
||||
void menu_page_add_button(menu_page_t *page, int data, char *text, void(*select_func)(menu_t *, int)) { |
||||
error_if(page->entries_len >= MENU_ENTRIES_MAX-1, "MENU_ENTRIES_MAX exceeded"); |
||||
menu_entry_t *entry = &page->entries[page->entries_len++]; |
||||
entry->data = data; |
||||
entry->text = text; |
||||
entry->select_func = select_func; |
||||
entry->type = MENU_ENTRY_BUTTON; |
||||
} |
||||
|
||||
void menu_page_add_toggle(menu_page_t *page, int data, char *text, const char **options, int len, void(*select_func)(menu_t *, int)) { |
||||
error_if(page->entries_len >= MENU_ENTRIES_MAX-1, "MENU_ENTRIES_MAX exceeded"); |
||||
menu_entry_t *entry = &page->entries[page->entries_len++]; |
||||
entry->data = data; |
||||
entry->text = text; |
||||
entry->select_func = select_func; |
||||
entry->type = MENU_ENTRY_TOGGLE; |
||||
entry->options = options; |
||||
entry->options_len = len; |
||||
} |
||||
|
||||
|
||||
void menu_update(menu_t *menu) { |
||||
render_set_view_2d(); |
||||
|
||||
error_if(menu->index < 0, "Attempt to update menu without a page"); |
||||
menu_page_t *page = &menu->pages[menu->index]; |
||||
|
||||
// Handle menu entry selecting
|
||||
int last_index = page->index; |
||||
int selected_data = 0; |
||||
if (page->entries_len > 0) { |
||||
if (flags_is(page->layout_flags, MENU_HORIZONTAL)) { |
||||
if (input_pressed(A_MENU_LEFT)) { |
||||
page->index--; |
||||
} |
||||
else if (input_pressed(A_MENU_RIGHT)) { |
||||
page->index++; |
||||
} |
||||
} |
||||
else { |
||||
if (input_pressed(A_MENU_UP)) { |
||||
page->index--; |
||||
} |
||||
if (input_pressed(A_MENU_DOWN)) { |
||||
page->index++; |
||||
} |
||||
} |
||||
|
||||
if (page->index >= page->entries_len) { |
||||
page->index = 0; |
||||
} |
||||
if (page->index < 0) { |
||||
page->index = page->entries_len - 1; |
||||
} |
||||
|
||||
if (last_index != page->index) { |
||||
sfx_play(SFX_MENU_MOVE); |
||||
} |
||||
selected_data = page->entries[page->index].data; |
||||
} |
||||
|
||||
if (page->draw_func) { |
||||
page->draw_func(menu, selected_data); |
||||
} |
||||
|
||||
render_set_view_2d(); |
||||
|
||||
// Draw Horizontal (confirm)
|
||||
if (flags_is(page->layout_flags, MENU_HORIZONTAL)) { |
||||
vec2i_t pos = vec2i(0, -20); |
||||
ui_draw_text_centered(page->title, ui_scaled_pos(page->title_anchor, pos), UI_SIZE_8, UI_COLOR_DEFAULT); |
||||
if (page->subtitle) { |
||||
pos.y += 12; |
||||
ui_draw_text_centered(page->subtitle, ui_scaled_pos(page->title_anchor, pos), UI_SIZE_8, UI_COLOR_DEFAULT); |
||||
} |
||||
pos.y += 16; |
||||
|
||||
page = &menu->pages[menu->index]; |
||||
pos.x = -50; |
||||
for (int i = 0; i < page->entries_len; i++) { |
||||
menu_entry_t *entry = &page->entries[i]; |
||||
rgba_t text_color; |
||||
if (i == page->index && blink()) { |
||||
text_color = UI_COLOR_ACCENT; |
||||
} |
||||
else { |
||||
text_color = UI_COLOR_DEFAULT; |
||||
} |
||||
ui_draw_text_centered(entry->text, ui_scaled_pos(page->items_anchor, pos), UI_SIZE_16, text_color); |
||||
pos.x = 60; |
||||
} |
||||
} |
||||
|
||||
// Draw Vertical
|
||||
else { |
||||
vec2i_t title_pos, items_pos; |
||||
if (flags_not(page->layout_flags, MENU_FIXED)) { |
||||
int height = 20 + page->entries_len * 12; |
||||
title_pos = vec2i(0, -height/2); |
||||
items_pos = vec2i(0, -height/2 + 20); |
||||
} |
||||
else { |
||||
title_pos = page->title_pos; |
||||
items_pos = page->items_pos; |
||||
} |
||||
if (flags_is(page->layout_flags, MENU_ALIGN_CENTER)) { |
||||
ui_draw_text_centered(page->title, ui_scaled_pos(page->title_anchor, title_pos), UI_SIZE_12, UI_COLOR_ACCENT); |
||||
} |
||||
else { |
||||
ui_draw_text(page->title, ui_scaled_pos(page->title_anchor, title_pos), UI_SIZE_12, UI_COLOR_ACCENT);
|
||||
} |
||||
|
||||
page = &menu->pages[menu->index]; |
||||
for (int i = 0; i < page->entries_len; i++) { |
||||
menu_entry_t *entry = &page->entries[i]; |
||||
rgba_t text_color; |
||||
if (i == page->index && blink()) { |
||||
text_color = UI_COLOR_ACCENT; |
||||
} |
||||
else { |
||||
text_color = UI_COLOR_DEFAULT; |
||||
} |
||||
|
||||
if (flags_is(page->layout_flags, MENU_ALIGN_CENTER)) { |
||||
ui_draw_text_centered(entry->text, ui_scaled_pos(page->items_anchor, items_pos), UI_SIZE_8, text_color); |
||||
} |
||||
else { |
||||
ui_draw_text(entry->text, ui_scaled_pos(page->items_anchor, items_pos), UI_SIZE_8, text_color); |
||||
} |
||||
|
||||
if (entry->type == MENU_ENTRY_TOGGLE) { |
||||
vec2i_t toggle_pos = items_pos; |
||||
toggle_pos.x += page->block_width - ui_text_width(entry->options[entry->data], UI_SIZE_8); |
||||
ui_draw_text(entry->options[entry->data], ui_scaled_pos(page->items_anchor, toggle_pos), UI_SIZE_8, text_color); |
||||
} |
||||
items_pos.y += 12; |
||||
} |
||||
} |
||||
|
||||
// Handle back buttons
|
||||
if (input_pressed(A_MENU_BACK) || input_pressed(A_MENU_QUIT)) { |
||||
if (menu->index != 0) { |
||||
menu_pop(menu); |
||||
sfx_play(SFX_MENU_SELECT); |
||||
} |
||||
return; |
||||
} |
||||
|
||||
if (page->entries_len == 0) { |
||||
return; |
||||
} |
||||
|
||||
|
||||
// Handle toggle entries
|
||||
menu_entry_t *entry = &page->entries[page->index]; |
||||
|
||||
if (entry->type == MENU_ENTRY_TOGGLE) { |
||||
if (input_pressed(A_MENU_LEFT)) { |
||||
sfx_play(SFX_MENU_SELECT); |
||||
entry->data--; |
||||
if (entry->data < 0) { |
||||
entry->data = entry->options_len-1; |
||||
} |
||||
if (entry->select_func) { |
||||
entry->select_func(menu, entry->data); |
||||
} |
||||
} |
||||
else if (input_pressed(A_MENU_RIGHT) || input_pressed(A_MENU_SELECT) || input_pressed(A_MENU_START)) { |
||||
sfx_play(SFX_MENU_SELECT); |
||||
entry->data = (entry->data + 1) % entry->options_len; |
||||
if (entry->select_func) { |
||||
entry->select_func(menu, entry->data); |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Handle buttons
|
||||
else { |
||||
if (input_pressed(A_MENU_SELECT) || input_pressed(A_MENU_START)) { |
||||
if (entry->select_func) { |
||||
sfx_play(SFX_MENU_SELECT); |
||||
if (entry->type == MENU_ENTRY_TOGGLE) { |
||||
entry->data = (entry->data + 1) % entry->options_len; |
||||
} |
||||
entry->select_func(menu, entry->data); |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,65 @@ |
||||
#ifndef MENU_H |
||||
#define MENU_H |
||||
|
||||
#include "../types.h" |
||||
#include "ui.h" |
||||
|
||||
#define MENU_PAGES_MAX 8 |
||||
#define MENU_ENTRIES_MAX 16 |
||||
|
||||
typedef enum { |
||||
MENU_ENTRY_BUTTON, |
||||
MENU_ENTRY_TOGGLE |
||||
} menu_entry_type_t; |
||||
|
||||
typedef enum { |
||||
MENU_VERTICAL = (1<<0), |
||||
MENU_HORIZONTAL = (1<<1), |
||||
MENU_FIXED = (1<<2), |
||||
MENU_ALIGN_CENTER = (1<<3), |
||||
MENU_ALIGN_BLOCK = (1<<4) |
||||
} menu_page_layout_t; |
||||
|
||||
typedef struct menu_t menu_t; |
||||
typedef struct menu_page_t menu_page_t; |
||||
typedef struct menu_entry_t menu_entry_t; |
||||
typedef struct menu_entry_options_t menu_entry_options_t; |
||||
|
||||
struct menu_entry_t { |
||||
menu_entry_type_t type; |
||||
int data; |
||||
char *text; |
||||
void (*select_func)(menu_t *, int); |
||||
const char **options; |
||||
int options_len; |
||||
}; |
||||
|
||||
struct menu_page_t { |
||||
char *title, *subtitle; |
||||
menu_page_layout_t layout_flags; |
||||
void (*draw_func)(menu_t *, int); |
||||
menu_entry_t entries[MENU_ENTRIES_MAX]; |
||||
int entries_len; |
||||
int index; |
||||
int block_width; |
||||
vec2i_t title_pos; |
||||
ui_pos_t title_anchor; |
||||
vec2i_t items_pos; |
||||
ui_pos_t items_anchor; |
||||
}; |
||||
|
||||
struct menu_t { |
||||
menu_page_t pages[MENU_PAGES_MAX]; |
||||
int index; |
||||
}; |
||||
|
||||
|
||||
void menu_reset(menu_t *menu); |
||||
menu_page_t *menu_push(menu_t *menu, char *title, void(*draw_func)(menu_t *, int)); |
||||
menu_page_t *menu_confirm(menu_t *menu, char *title, char *subtitle, char *yes, char *no, void(*confirm_func)(menu_t *, int)); |
||||
void menu_pop(menu_t *menu); |
||||
void menu_page_add_button(menu_page_t *page, int data, char *text, void(*select_func)(menu_t *, int)); |
||||
void menu_page_add_toggle(menu_page_t *page, int data, char *text, const char **options, int len, void(*select_func)(menu_t *, int)); |
||||
void menu_update(menu_t *menu); |
||||
|
||||
#endif |
@ -0,0 +1,778 @@ |
||||
#include "../types.h" |
||||
#include "../mem.h" |
||||
#include "../render.h" |
||||
#include "../utils.h" |
||||
|
||||
#include "object.h" |
||||
#include "track.h" |
||||
#include "ship.h" |
||||
#include "weapon.h" |
||||
#include "droid.h" |
||||
#include "camera.h" |
||||
#include "object.h" |
||||
#include "scene.h" |
||||
#include "hud.h" |
||||
#include "object.h" |
||||
|
||||
|
||||
static rgba_t int32_to_rgba(uint32_t v) { |
||||
return rgba( |
||||
((v >> 24) & 0xff), |
||||
((v >> 16) & 0xff), |
||||
((v >> 8) & 0xff), |
||||
255 |
||||
); |
||||
} |
||||
|
||||
Object *objects_load(char *name, texture_list_t tl) { |
||||
uint32_t length = 0; |
||||
uint8_t *bytes = file_load(name, &length); |
||||
if (!bytes) { |
||||
die("Failed to load file %s\n", name); |
||||
} |
||||
printf("load: %s\n", name); |
||||
|
||||
Object *objectList = mem_mark(); |
||||
Object *prevObject = NULL; |
||||
uint32_t p = 0; |
||||
|
||||
while (p < length) { |
||||
Object *object = mem_bump(sizeof(Object)); |
||||
if (prevObject) { |
||||
prevObject->next = object; |
||||
} |
||||
prevObject = object; |
||||
|
||||
for (int i = 0; i < 16; i++) { |
||||
object->name[i] = get_i8(bytes, &p); |
||||
} |
||||
|
||||
object->mat = mat4_identity(); |
||||
object->vertices_len = get_i16(bytes, &p); p += 2; |
||||
object->vertices = NULL; get_i32(bytes, &p); |
||||
object->normals_len = get_i16(bytes, &p); p += 2; |
||||
object->normals = NULL; get_i32(bytes, &p); |
||||
object->primitives_len = get_i16(bytes, &p); p += 2; |
||||
object->primitives = NULL; get_i32(bytes, &p); |
||||
get_i32(bytes, &p); |
||||
get_i32(bytes, &p); |
||||
get_i32(bytes, &p); // Skeleton ref
|
||||
object->extent = get_i32(bytes, &p); |
||||
object->flags = get_i16(bytes, &p); p += 2; |
||||
object->next = NULL; get_i32(bytes, &p); |
||||
|
||||
p += 3 * 3 * 2; // relative rot matrix
|
||||
p += 2; // padding
|
||||
|
||||
object->origin.x = get_i32(bytes, &p); |
||||
object->origin.y = get_i32(bytes, &p); |
||||
object->origin.z = get_i32(bytes, &p); |
||||
|
||||
p += 3 * 3 * 2; // absolute rot matrix
|
||||
p += 2; // padding
|
||||
p += 3 * 4; // absolute translation matrix
|
||||
p += 2; // skeleton update flag
|
||||
p += 2; // padding
|
||||
p += 4; // skeleton super
|
||||
p += 4; // skeleton sub
|
||||
p += 4; // skeleton next
|
||||
|
||||
object->vertices = mem_bump(object->vertices_len * sizeof(vec3_t)); |
||||
for (int i = 0; i < object->vertices_len; i++) { |
||||
object->vertices[i].x = get_i16(bytes, &p); |
||||
object->vertices[i].y = get_i16(bytes, &p); |
||||
object->vertices[i].z = get_i16(bytes, &p); |
||||
p += 2; // padding
|
||||
} |
||||
|
||||
object->normals = mem_bump(object->normals_len * sizeof(vec3_t)); |
||||
for (int i = 0; i < object->normals_len; i++) { |
||||
object->normals[i].x = get_i16(bytes, &p); |
||||
object->normals[i].y = get_i16(bytes, &p); |
||||
object->normals[i].z = get_i16(bytes, &p); |
||||
p += 2; // padding
|
||||
} |
||||
|
||||
object->primitives = mem_mark(); |
||||
for (int i = 0; i < object->primitives_len; i++) { |
||||
Prm prm; |
||||
int16_t prm_type = get_i16(bytes, &p); |
||||
int16_t prm_flag = get_i16(bytes, &p); |
||||
|
||||
switch (prm_type) { |
||||
case PRM_TYPE_F3: |
||||
prm.ptr = mem_bump(sizeof(F3)); |
||||
prm.f3->coords[0] = get_i16(bytes, &p); |
||||
prm.f3->coords[1] = get_i16(bytes, &p); |
||||
prm.f3->coords[2] = get_i16(bytes, &p); |
||||
prm.f3->pad1 = get_i16(bytes, &p); |
||||
prm.f3->colour = int32_to_rgba(get_i32(bytes, &p)); |
||||
break; |
||||
|
||||
case PRM_TYPE_F4: |
||||
prm.ptr = mem_bump(sizeof(F4)); |
||||
prm.f4->coords[0] = get_i16(bytes, &p); |
||||
prm.f4->coords[1] = get_i16(bytes, &p); |
||||
prm.f4->coords[2] = get_i16(bytes, &p); |
||||
prm.f4->coords[3] = get_i16(bytes, &p); |
||||
prm.f4->colour = int32_to_rgba(get_i32(bytes, &p)); |
||||
break; |
||||
|
||||
case PRM_TYPE_FT3: |
||||
prm.ptr = mem_bump(sizeof(FT3)); |
||||
prm.ft3->coords[0] = get_i16(bytes, &p); |
||||
prm.ft3->coords[1] = get_i16(bytes, &p); |
||||
prm.ft3->coords[2] = get_i16(bytes, &p); |
||||
|
||||
prm.ft3->texture = texture_from_list(tl, get_i16(bytes, &p)); |
||||
prm.ft3->cba = get_i16(bytes, &p); |
||||
prm.ft3->tsb = get_i16(bytes, &p); |
||||
prm.ft3->u0 = get_i8(bytes, &p); |
||||
prm.ft3->v0 = get_i8(bytes, &p); |
||||
prm.ft3->u1 = get_i8(bytes, &p); |
||||
prm.ft3->v1 = get_i8(bytes, &p); |
||||
prm.ft3->u2 = get_i8(bytes, &p); |
||||
prm.ft3->v2 = get_i8(bytes, &p); |
||||
|
||||
prm.ft3->pad1 = get_i16(bytes, &p); |
||||
prm.ft3->colour = int32_to_rgba(get_i32(bytes, &p)); |
||||
break; |
||||
|
||||
case PRM_TYPE_FT4: |
||||
prm.ptr = mem_bump(sizeof(FT4)); |
||||
prm.ft4->coords[0] = get_i16(bytes, &p); |
||||
prm.ft4->coords[1] = get_i16(bytes, &p); |
||||
prm.ft4->coords[2] = get_i16(bytes, &p); |
||||
prm.ft4->coords[3] = get_i16(bytes, &p); |
||||
|
||||
prm.ft4->texture = texture_from_list(tl, get_i16(bytes, &p)); |
||||
prm.ft4->cba = get_i16(bytes, &p); |
||||
prm.ft4->tsb = get_i16(bytes, &p); |
||||
prm.ft4->u0 = get_i8(bytes, &p); |
||||
prm.ft4->v0 = get_i8(bytes, &p); |
||||
prm.ft4->u1 = get_i8(bytes, &p); |
||||
prm.ft4->v1 = get_i8(bytes, &p); |
||||
prm.ft4->u2 = get_i8(bytes, &p); |
||||
prm.ft4->v2 = get_i8(bytes, &p); |
||||
prm.ft4->u3 = get_i8(bytes, &p); |
||||
prm.ft4->v3 = get_i8(bytes, &p); |
||||
prm.ft4->pad1 = get_i16(bytes, &p); |
||||
prm.ft4->colour = int32_to_rgba(get_i32(bytes, &p)); |
||||
break; |
||||
|
||||
case PRM_TYPE_G3: |
||||
prm.ptr = mem_bump(sizeof(G3)); |
||||
prm.g3->coords[0] = get_i16(bytes, &p); |
||||
prm.g3->coords[1] = get_i16(bytes, &p); |
||||
prm.g3->coords[2] = get_i16(bytes, &p); |
||||
prm.g3->pad1 = get_i16(bytes, &p); |
||||
prm.g3->colour[0] = int32_to_rgba(get_i32(bytes, &p)); |
||||
prm.g3->colour[1] = int32_to_rgba(get_i32(bytes, &p)); |
||||
prm.g3->colour[2] = int32_to_rgba(get_i32(bytes, &p)); |
||||
break; |
||||
|
||||
case PRM_TYPE_G4: |
||||
prm.ptr = mem_bump(sizeof(G4)); |
||||
prm.g4->coords[0] = get_i16(bytes, &p); |
||||
prm.g4->coords[1] = get_i16(bytes, &p); |
||||
prm.g4->coords[2] = get_i16(bytes, &p); |
||||
prm.g4->coords[3] = get_i16(bytes, &p); |
||||
prm.g4->colour[0] = int32_to_rgba(get_i32(bytes, &p)); |
||||
prm.g4->colour[1] = int32_to_rgba(get_i32(bytes, &p)); |
||||
prm.g4->colour[2] = int32_to_rgba(get_i32(bytes, &p)); |
||||
prm.g4->colour[3] = int32_to_rgba(get_i32(bytes, &p)); |
||||
break; |
||||
|
||||
case PRM_TYPE_GT3: |
||||
prm.ptr = mem_bump(sizeof(GT3)); |
||||
prm.gt3->coords[0] = get_i16(bytes, &p); |
||||
prm.gt3->coords[1] = get_i16(bytes, &p); |
||||
prm.gt3->coords[2] = get_i16(bytes, &p); |
||||
|
||||
prm.gt3->texture = texture_from_list(tl, get_i16(bytes, &p)); |
||||
prm.gt3->cba = get_i16(bytes, &p); |
||||
prm.gt3->tsb = get_i16(bytes, &p); |
||||
prm.gt3->u0 = get_i8(bytes, &p); |
||||
prm.gt3->v0 = get_i8(bytes, &p); |
||||
prm.gt3->u1 = get_i8(bytes, &p); |
||||
prm.gt3->v1 = get_i8(bytes, &p); |
||||
prm.gt3->u2 = get_i8(bytes, &p); |
||||
prm.gt3->v2 = get_i8(bytes, &p); |
||||
prm.gt3->pad1 = get_i16(bytes, &p); |
||||
prm.gt3->colour[0] = int32_to_rgba(get_i32(bytes, &p)); |
||||
prm.gt3->colour[1] = int32_to_rgba(get_i32(bytes, &p)); |
||||
prm.gt3->colour[2] = int32_to_rgba(get_i32(bytes, &p)); |
||||
break; |
||||
|
||||
case PRM_TYPE_GT4: |
||||
prm.ptr = mem_bump(sizeof(GT4)); |
||||
prm.gt4->coords[0] = get_i16(bytes, &p); |
||||
prm.gt4->coords[1] = get_i16(bytes, &p); |
||||
prm.gt4->coords[2] = get_i16(bytes, &p); |
||||
prm.gt4->coords[3] = get_i16(bytes, &p); |
||||
|
||||
prm.gt4->texture = texture_from_list(tl, get_i16(bytes, &p)); |
||||
prm.gt4->cba = get_i16(bytes, &p); |
||||
prm.gt4->tsb = get_i16(bytes, &p); |
||||
prm.gt4->u0 = get_i8(bytes, &p); |
||||
prm.gt4->v0 = get_i8(bytes, &p); |
||||
prm.gt4->u1 = get_i8(bytes, &p); |
||||
prm.gt4->v1 = get_i8(bytes, &p); |
||||
prm.gt4->u2 = get_i8(bytes, &p); |
||||
prm.gt4->v2 = get_i8(bytes, &p); |
||||
prm.gt4->u3 = get_i8(bytes, &p); |
||||
prm.gt4->v3 = get_i8(bytes, &p); |
||||
prm.gt4->pad1 = get_i16(bytes, &p); |
||||
prm.gt4->colour[0] = int32_to_rgba(get_i32(bytes, &p)); |
||||
prm.gt4->colour[1] = int32_to_rgba(get_i32(bytes, &p)); |
||||
prm.gt4->colour[2] = int32_to_rgba(get_i32(bytes, &p)); |
||||
prm.gt4->colour[3] = int32_to_rgba(get_i32(bytes, &p)); |
||||
break; |
||||
|
||||
|
||||
case PRM_TYPE_LSF3: |
||||
prm.ptr = mem_bump(sizeof(LSF3)); |
||||
prm.lsf3->coords[0] = get_i16(bytes, &p); |
||||
prm.lsf3->coords[1] = get_i16(bytes, &p); |
||||
prm.lsf3->coords[2] = get_i16(bytes, &p); |
||||
prm.lsf3->normal = get_i16(bytes, &p); |
||||
prm.lsf3->colour = int32_to_rgba(get_i32(bytes, &p)); |
||||
break; |
||||
|
||||
case PRM_TYPE_LSF4: |
||||
prm.ptr = mem_bump(sizeof(LSF4)); |
||||
prm.lsf4->coords[0] = get_i16(bytes, &p); |
||||
prm.lsf4->coords[1] = get_i16(bytes, &p); |
||||
prm.lsf4->coords[2] = get_i16(bytes, &p); |
||||
prm.lsf4->coords[3] = get_i16(bytes, &p); |
||||
prm.lsf4->normal = get_i16(bytes, &p); |
||||
prm.lsf4->pad1 = get_i16(bytes, &p); |
||||
prm.lsf4->colour = int32_to_rgba(get_i32(bytes, &p)); |
||||
break; |
||||
|
||||
case PRM_TYPE_LSFT3: |
||||
prm.ptr = mem_bump(sizeof(LSFT3)); |
||||
prm.lsft3->coords[0] = get_i16(bytes, &p); |
||||
prm.lsft3->coords[1] = get_i16(bytes, &p); |
||||
prm.lsft3->coords[2] = get_i16(bytes, &p); |
||||
prm.lsft3->normal = get_i16(bytes, &p); |
||||
|
||||
prm.lsft3->texture = texture_from_list(tl, get_i16(bytes, &p)); |
||||
prm.lsft3->cba = get_i16(bytes, &p); |
||||
prm.lsft3->tsb = get_i16(bytes, &p); |
||||
prm.lsft3->u0 = get_i8(bytes, &p); |
||||
prm.lsft3->v0 = get_i8(bytes, &p); |
||||
prm.lsft3->u1 = get_i8(bytes, &p); |
||||
prm.lsft3->v1 = get_i8(bytes, &p); |
||||
prm.lsft3->u2 = get_i8(bytes, &p); |
||||
prm.lsft3->v2 = get_i8(bytes, &p); |
||||
prm.lsft3->colour = int32_to_rgba(get_i32(bytes, &p)); |
||||
break; |
||||
|
||||
case PRM_TYPE_LSFT4: |
||||
prm.ptr = mem_bump(sizeof(LSFT4)); |
||||
prm.lsft4->coords[0] = get_i16(bytes, &p); |
||||
prm.lsft4->coords[1] = get_i16(bytes, &p); |
||||
prm.lsft4->coords[2] = get_i16(bytes, &p); |
||||
prm.lsft4->coords[3] = get_i16(bytes, &p); |
||||
prm.lsft4->normal = get_i16(bytes, &p); |
||||
|
||||
prm.lsft4->texture = texture_from_list(tl, get_i16(bytes, &p)); |
||||
prm.lsft4->cba = get_i16(bytes, &p); |
||||
prm.lsft4->tsb = get_i16(bytes, &p); |
||||
prm.lsft4->u0 = get_i8(bytes, &p); |
||||
prm.lsft4->v0 = get_i8(bytes, &p); |
||||
prm.lsft4->u1 = get_i8(bytes, &p); |
||||
prm.lsft4->v1 = get_i8(bytes, &p); |
||||
prm.lsft4->u2 = get_i8(bytes, &p); |
||||
prm.lsft4->v2 = get_i8(bytes, &p); |
||||
prm.lsft4->u3 = get_i8(bytes, &p); |
||||
prm.lsft4->v3 = get_i8(bytes, &p); |
||||
prm.lsft4->colour = int32_to_rgba(get_i32(bytes, &p)); |
||||
break; |
||||
|
||||
case PRM_TYPE_LSG3: |
||||
prm.ptr = mem_bump(sizeof(LSG3)); |
||||
prm.lsg3->coords[0] = get_i16(bytes, &p); |
||||
prm.lsg3->coords[1] = get_i16(bytes, &p); |
||||
prm.lsg3->coords[2] = get_i16(bytes, &p); |
||||
prm.lsg3->normals[0] = get_i16(bytes, &p); |
||||
prm.lsg3->normals[1] = get_i16(bytes, &p); |
||||
prm.lsg3->normals[2] = get_i16(bytes, &p); |
||||
prm.lsg3->colour[0] = int32_to_rgba(get_i32(bytes, &p)); |
||||
prm.lsg3->colour[1] = int32_to_rgba(get_i32(bytes, &p)); |
||||
prm.lsg3->colour[2] = int32_to_rgba(get_i32(bytes, &p)); |
||||
break; |
||||
|
||||
case PRM_TYPE_LSG4: |
||||
prm.ptr = mem_bump(sizeof(LSG4)); |
||||
prm.lsg4->coords[0] = get_i16(bytes, &p); |
||||
prm.lsg4->coords[1] = get_i16(bytes, &p); |
||||
prm.lsg4->coords[2] = get_i16(bytes, &p); |
||||
prm.lsg4->coords[3] = get_i16(bytes, &p); |
||||
prm.lsg4->normals[0] = get_i16(bytes, &p); |
||||
prm.lsg4->normals[1] = get_i16(bytes, &p); |
||||
prm.lsg4->normals[2] = get_i16(bytes, &p); |
||||
prm.lsg4->normals[3] = get_i16(bytes, &p); |
||||
prm.lsg4->colour[0] = int32_to_rgba(get_i32(bytes, &p)); |
||||
prm.lsg4->colour[1] = int32_to_rgba(get_i32(bytes, &p)); |
||||
prm.lsg4->colour[2] = int32_to_rgba(get_i32(bytes, &p)); |
||||
prm.lsg4->colour[3] = int32_to_rgba(get_i32(bytes, &p)); |
||||
break; |
||||
|
||||
case PRM_TYPE_LSGT3: |
||||
prm.ptr = mem_bump(sizeof(LSGT3)); |
||||
prm.lsgt3->coords[0] = get_i16(bytes, &p); |
||||
prm.lsgt3->coords[1] = get_i16(bytes, &p); |
||||
prm.lsgt3->coords[2] = get_i16(bytes, &p); |
||||
prm.lsgt3->normals[0] = get_i16(bytes, &p); |
||||
prm.lsgt3->normals[1] = get_i16(bytes, &p); |
||||
prm.lsgt3->normals[2] = get_i16(bytes, &p); |
||||
|
||||
prm.lsgt3->texture = texture_from_list(tl, get_i16(bytes, &p)); |
||||
prm.lsgt3->cba = get_i16(bytes, &p); |
||||
prm.lsgt3->tsb = get_i16(bytes, &p); |
||||
prm.lsgt3->u0 = get_i8(bytes, &p); |
||||
prm.lsgt3->v0 = get_i8(bytes, &p); |
||||
prm.lsgt3->u1 = get_i8(bytes, &p); |
||||
prm.lsgt3->v1 = get_i8(bytes, &p); |
||||
prm.lsgt3->u2 = get_i8(bytes, &p); |
||||
prm.lsgt3->v2 = get_i8(bytes, &p); |
||||
prm.lsgt3->colour[0] = int32_to_rgba(get_i32(bytes, &p)); |
||||
prm.lsgt3->colour[1] = int32_to_rgba(get_i32(bytes, &p)); |
||||
prm.lsgt3->colour[2] = int32_to_rgba(get_i32(bytes, &p)); |
||||
break; |
||||
|
||||
case PRM_TYPE_LSGT4: |
||||
prm.ptr = mem_bump(sizeof(LSGT4)); |
||||
prm.lsgt4->coords[0] = get_i16(bytes, &p); |
||||
prm.lsgt4->coords[1] = get_i16(bytes, &p); |
||||
prm.lsgt4->coords[2] = get_i16(bytes, &p); |
||||
prm.lsgt4->coords[3] = get_i16(bytes, &p); |
||||
prm.lsgt4->normals[0] = get_i16(bytes, &p); |
||||
prm.lsgt4->normals[1] = get_i16(bytes, &p); |
||||
prm.lsgt4->normals[2] = get_i16(bytes, &p); |
||||
prm.lsgt4->normals[3] = get_i16(bytes, &p); |
||||
|
||||
prm.lsgt4->texture = texture_from_list(tl, get_i16(bytes, &p)); |
||||
prm.lsgt4->cba = get_i16(bytes, &p); |
||||
prm.lsgt4->tsb = get_i16(bytes, &p); |
||||
prm.lsgt4->u0 = get_i8(bytes, &p); |
||||
prm.lsgt4->v0 = get_i8(bytes, &p); |
||||
prm.lsgt4->u1 = get_i8(bytes, &p); |
||||
prm.lsgt4->v1 = get_i8(bytes, &p); |
||||
prm.lsgt4->u2 = get_i8(bytes, &p); |
||||
prm.lsgt4->v2 = get_i8(bytes, &p); |
||||
prm.lsgt4->pad1 = get_i16(bytes, &p); |
||||
prm.lsgt4->colour[0] = int32_to_rgba(get_i32(bytes, &p)); |
||||
prm.lsgt4->colour[1] = int32_to_rgba(get_i32(bytes, &p)); |
||||
prm.lsgt4->colour[2] = int32_to_rgba(get_i32(bytes, &p)); |
||||
prm.lsgt4->colour[3] = int32_to_rgba(get_i32(bytes, &p)); |
||||
break; |
||||
|
||||
|
||||
case PRM_TYPE_TSPR: |
||||
case PRM_TYPE_BSPR: |
||||
prm.ptr = mem_bump(sizeof(SPR)); |
||||
prm.spr->coord = get_i16(bytes, &p); |
||||
prm.spr->width = get_i16(bytes, &p); |
||||
prm.spr->height = get_i16(bytes, &p); |
||||
prm.spr->texture = texture_from_list(tl, get_i16(bytes, &p)); |
||||
prm.spr->colour = int32_to_rgba(get_i32(bytes, &p)); |
||||
break; |
||||
|
||||
case PRM_TYPE_SPLINE: |
||||
prm.ptr = mem_bump(sizeof(Spline)); |
||||
prm.spline->control1.x = get_i32(bytes, &p); |
||||
prm.spline->control1.y = get_i32(bytes, &p); |
||||
prm.spline->control1.z = get_i32(bytes, &p); |
||||
p += 4; // padding
|
||||
prm.spline->position.x = get_i32(bytes, &p); |
||||
prm.spline->position.y = get_i32(bytes, &p); |
||||
prm.spline->position.z = get_i32(bytes, &p); |
||||
p += 4; // padding
|
||||
prm.spline->control2.x = get_i32(bytes, &p); |
||||
prm.spline->control2.y = get_i32(bytes, &p); |
||||
prm.spline->control2.z = get_i32(bytes, &p); |
||||
p += 4; // padding
|
||||
prm.spline->colour = int32_to_rgba(get_i32(bytes, &p)); |
||||
break; |
||||
|
||||
case PRM_TYPE_POINT_LIGHT: |
||||
prm.ptr = mem_bump(sizeof(PointLight)); |
||||
prm.pointLight->position.x = get_i32(bytes, &p); |
||||
prm.pointLight->position.y = get_i32(bytes, &p); |
||||
prm.pointLight->position.z = get_i32(bytes, &p); |
||||
p += 4; // padding
|
||||
prm.pointLight->colour = int32_to_rgba(get_i32(bytes, &p)); |
||||
prm.pointLight->startFalloff = get_i16(bytes, &p); |
||||
prm.pointLight->endFalloff = get_i16(bytes, &p); |
||||
break; |
||||
|
||||
case PRM_TYPE_SPOT_LIGHT: |
||||
prm.ptr = mem_bump(sizeof(SpotLight)); |
||||
prm.spotLight->position.x = get_i32(bytes, &p); |
||||
prm.spotLight->position.y = get_i32(bytes, &p); |
||||
prm.spotLight->position.z = get_i32(bytes, &p); |
||||
p += 4; // padding
|
||||
prm.spotLight->direction.x = get_i16(bytes, &p); |
||||
prm.spotLight->direction.y = get_i16(bytes, &p); |
||||
prm.spotLight->direction.z = get_i16(bytes, &p); |
||||
p += 2; // padding
|
||||
prm.spotLight->colour = int32_to_rgba(get_i32(bytes, &p)); |
||||
prm.spotLight->startFalloff = get_i16(bytes, &p); |
||||
prm.spotLight->endFalloff = get_i16(bytes, &p); |
||||
prm.spotLight->coneAngle = get_i16(bytes, &p); |
||||
prm.spotLight->spreadAngle = get_i16(bytes, &p); |
||||
break; |
||||
|
||||
case PRM_TYPE_INFINITE_LIGHT: |
||||
prm.ptr = mem_bump(sizeof(InfiniteLight)); |
||||
prm.infiniteLight->direction.x = get_i16(bytes, &p); |
||||
prm.infiniteLight->direction.y = get_i16(bytes, &p); |
||||
prm.infiniteLight->direction.z = get_i16(bytes, &p); |
||||
p += 2; // padding
|
||||
prm.infiniteLight->colour = int32_to_rgba(get_i32(bytes, &p)); |
||||
break; |
||||
|
||||
|
||||
default: |
||||
die("bad primitive type %x \n", prm_type); |
||||
} // switch
|
||||
|
||||
prm.f3->type = prm_type; |
||||
prm.f3->flag = prm_flag; |
||||
} // each prim
|
||||
} // each object
|
||||
|
||||
mem_temp_free(bytes); |
||||
return objectList; |
||||
} |
||||
|
||||
|
||||
void object_draw(Object *object, mat4_t *mat) { |
||||
vec3_t *vertex = object->vertices; |
||||
|
||||
Prm poly = {.primitive = object->primitives}; |
||||
int primitives_len = object->primitives_len; |
||||
|
||||
render_set_model_mat(mat); |
||||
|
||||
// TODO: check for PRM_SINGLE_SIDED
|
||||
|
||||
for (int i = 0; i < primitives_len; i++) { |
||||
int coord0; |
||||
int coord1; |
||||
int coord2; |
||||
int coord3; |
||||
switch (poly.primitive->type) { |
||||
case PRM_TYPE_GT3: |
||||
coord0 = poly.gt3->coords[0]; |
||||
coord1 = poly.gt3->coords[1]; |
||||
coord2 = poly.gt3->coords[2]; |
||||
|
||||
render_push_tris((tris_t) { |
||||
.vertices = { |
||||
{ |
||||
.pos = vertex[coord2], |
||||
.uv = {poly.gt3->u2, poly.gt3->v2}, |
||||
.color = poly.gt3->colour[2] |
||||
}, |
||||
{ |
||||
.pos = vertex[coord1], |
||||
.uv = {poly.gt3->u1, poly.gt3->v1}, |
||||
.color = poly.gt3->colour[1] |
||||
}, |
||||
{ |
||||
.pos = vertex[coord0], |
||||
.uv = {poly.gt3->u0, poly.gt3->v0}, |
||||
.color = poly.gt3->colour[0] |
||||
}, |
||||
} |
||||
}, poly.gt3->texture); |
||||
|
||||
poly.gt3 += 1; |
||||
break; |
||||
|
||||
case PRM_TYPE_GT4: |
||||
coord0 = poly.gt4->coords[0]; |
||||
coord1 = poly.gt4->coords[1]; |
||||
coord2 = poly.gt4->coords[2]; |
||||
coord3 = poly.gt4->coords[3]; |
||||
|
||||
render_push_tris((tris_t) { |
||||
.vertices = { |
||||
{ |
||||
.pos = vertex[coord2], |
||||
.uv = {poly.gt4->u2, poly.gt4->v2}, |
||||
.color = poly.gt4->colour[2] |
||||
}, |
||||
{ |
||||
.pos = vertex[coord1], |
||||
.uv = {poly.gt4->u1, poly.gt4->v1}, |
||||
.color = poly.gt4->colour[1] |
||||
}, |
||||
{ |
||||
.pos = vertex[coord0], |
||||
.uv = {poly.gt4->u0, poly.gt4->v0}, |
||||
.color = poly.gt4->colour[0] |
||||
}, |
||||
} |
||||
}, poly.gt4->texture); |
||||
render_push_tris((tris_t) { |
||||
.vertices = { |
||||
{ |
||||
.pos = vertex[coord2], |
||||
.uv = {poly.gt4->u2, poly.gt4->v2}, |
||||
.color = poly.gt4->colour[2] |
||||
}, |
||||
{ |
||||
.pos = vertex[coord3], |
||||
.uv = {poly.gt4->u3, poly.gt4->v3}, |
||||
.color = poly.gt4->colour[3] |
||||
}, |
||||
{ |
||||
.pos = vertex[coord1], |
||||
.uv = {poly.gt4->u1, poly.gt4->v1}, |
||||
.color = poly.gt4->colour[1] |
||||
}, |
||||
} |
||||
}, poly.gt4->texture); |
||||
|
||||
poly.gt4 += 1; |
||||
break; |
||||
|
||||
case PRM_TYPE_FT3: |
||||
coord0 = poly.ft3->coords[0]; |
||||
coord1 = poly.ft3->coords[1]; |
||||
coord2 = poly.ft3->coords[2]; |
||||
|
||||
render_push_tris((tris_t) { |
||||
.vertices = { |
||||
{ |
||||
.pos = vertex[coord2], |
||||
.uv = {poly.ft3->u2, poly.ft3->v2}, |
||||
.color = poly.ft3->colour |
||||
}, |
||||
{ |
||||
.pos = vertex[coord1], |
||||
.uv = {poly.ft3->u1, poly.ft3->v1}, |
||||
.color = poly.ft3->colour |
||||
}, |
||||
{ |
||||
.pos = vertex[coord0], |
||||
.uv = {poly.ft3->u0, poly.ft3->v0}, |
||||
.color = poly.ft3->colour |
||||
}, |
||||
} |
||||
}, poly.ft3->texture); |
||||
|
||||
poly.ft3 += 1; |
||||
break; |
||||
|
||||
case PRM_TYPE_FT4: |
||||
coord0 = poly.ft4->coords[0]; |
||||
coord1 = poly.ft4->coords[1]; |
||||
coord2 = poly.ft4->coords[2]; |
||||
coord3 = poly.ft4->coords[3]; |
||||
|
||||
render_push_tris((tris_t) { |
||||
.vertices = { |
||||
{ |
||||
.pos = vertex[coord2], |
||||
.uv = {poly.ft4->u2, poly.ft4->v2}, |
||||
.color = poly.ft4->colour |
||||
}, |
||||
{ |
||||
.pos = vertex[coord1], |
||||
.uv = {poly.ft4->u1, poly.ft4->v1}, |
||||
.color = poly.ft4->colour |
||||
}, |
||||
{ |
||||
.pos = vertex[coord0], |
||||
.uv = {poly.ft4->u0, poly.ft4->v0}, |
||||
.color = poly.ft4->colour |
||||
}, |
||||
} |
||||
}, poly.ft4->texture); |
||||
render_push_tris((tris_t) { |
||||
.vertices = { |
||||
{ |
||||
.pos = vertex[coord2], |
||||
.uv = {poly.ft4->u2, poly.ft4->v2}, |
||||
.color = poly.ft4->colour |
||||
}, |
||||
{ |
||||
.pos = vertex[coord3], |
||||
.uv = {poly.ft4->u3, poly.ft4->v3}, |
||||
.color = poly.ft4->colour |
||||
}, |
||||
{ |
||||
.pos = vertex[coord1], |
||||
.uv = {poly.ft4->u1, poly.ft4->v1}, |
||||
.color = poly.ft4->colour |
||||
}, |
||||
} |
||||
}, poly.ft4->texture); |
||||
|
||||
poly.ft4 += 1; |
||||
break; |
||||
|
||||
case PRM_TYPE_G3: |
||||
coord0 = poly.g3->coords[0]; |
||||
coord1 = poly.g3->coords[1]; |
||||
coord2 = poly.g3->coords[2]; |
||||
|
||||
render_push_tris((tris_t) { |
||||
.vertices = { |
||||
{ |
||||
.pos = vertex[coord2], |
||||
.color = poly.g3->colour[2] |
||||
}, |
||||
{ |
||||
.pos = vertex[coord1], |
||||
.color = poly.g3->colour[1] |
||||
}, |
||||
{ |
||||
.pos = vertex[coord0], |
||||
.color = poly.g3->colour[0] |
||||
}, |
||||
} |
||||
}, RENDER_NO_TEXTURE); |
||||
|
||||
poly.g3 += 1; |
||||
break; |
||||
|
||||
case PRM_TYPE_G4: |
||||
coord0 = poly.g4->coords[0]; |
||||
coord1 = poly.g4->coords[1]; |
||||
coord2 = poly.g4->coords[2]; |
||||
coord3 = poly.g4->coords[3]; |
||||
|
||||
render_push_tris((tris_t) { |
||||
.vertices = { |
||||
{ |
||||
.pos = vertex[coord2], |
||||
.color = poly.g4->colour[2] |
||||
}, |
||||
{ |
||||
.pos = vertex[coord1], |
||||
.color = poly.g4->colour[1] |
||||
}, |
||||
{ |
||||
.pos = vertex[coord0], |
||||
.color = poly.g4->colour[0] |
||||
}, |
||||
} |
||||
}, RENDER_NO_TEXTURE); |
||||
render_push_tris((tris_t) { |
||||
.vertices = { |
||||
{ |
||||
.pos = vertex[coord2], |
||||
.color = poly.g4->colour[2] |
||||
}, |
||||
{ |
||||
.pos = vertex[coord3], |
||||
.color = poly.g4->colour[3] |
||||
}, |
||||
{ |
||||
.pos = vertex[coord1], |
||||
.color = poly.g4->colour[1] |
||||
}, |
||||
} |
||||
}, RENDER_NO_TEXTURE); |
||||
|
||||
poly.g4 += 1; |
||||
break; |
||||
|
||||
case PRM_TYPE_F3: |
||||
coord0 = poly.f3->coords[0]; |
||||
coord1 = poly.f3->coords[1]; |
||||
coord2 = poly.f3->coords[2]; |
||||
|
||||
render_push_tris((tris_t) { |
||||
.vertices = { |
||||
{ |
||||
.pos = vertex[coord2], |
||||
.color = poly.f3->colour |
||||
}, |
||||
{ |
||||
.pos = vertex[coord1], |
||||
.color = poly.f3->colour |
||||
}, |
||||
{ |
||||
.pos = vertex[coord0], |
||||
.color = poly.f3->colour |
||||
}, |
||||
} |
||||
}, RENDER_NO_TEXTURE); |
||||
|
||||
poly.f3 += 1; |
||||
break; |
||||
|
||||
case PRM_TYPE_F4: |
||||
coord0 = poly.f4->coords[0]; |
||||
coord1 = poly.f4->coords[1]; |
||||
coord2 = poly.f4->coords[2]; |
||||
coord3 = poly.f4->coords[3]; |
||||
|
||||
render_push_tris((tris_t) { |
||||
.vertices = { |
||||
{ |
||||
.pos = vertex[coord2], |
||||
.color = poly.f4->colour |
||||
}, |
||||
{ |
||||
.pos = vertex[coord1], |
||||
.color = poly.f4->colour |
||||
}, |
||||
{ |
||||
.pos = vertex[coord0], |
||||
.color = poly.f4->colour |
||||
}, |
||||
} |
||||
}, RENDER_NO_TEXTURE); |
||||
render_push_tris((tris_t) { |
||||
.vertices = { |
||||
{ |
||||
.pos = vertex[coord2], |
||||
.color = poly.f4->colour |
||||
}, |
||||
{ |
||||
.pos = vertex[coord3], |
||||
.color = poly.f4->colour |
||||
}, |
||||
{ |
||||
.pos = vertex[coord1], |
||||
.color = poly.f4->colour |
||||
}, |
||||
} |
||||
}, RENDER_NO_TEXTURE); |
||||
|
||||
poly.f4 += 1; |
||||
break; |
||||
|
||||
case PRM_TYPE_TSPR: |
||||
case PRM_TYPE_BSPR: |
||||
coord0 = poly.spr->coord; |
||||
|
||||
render_push_sprite( |
||||
vec3( |
||||
vertex[coord0].x, |
||||
vertex[coord0].y + ((poly.primitive->type == PRM_TYPE_TSPR ? poly.spr->height : -poly.spr->height) >> 1), |
||||
vertex[coord0].z |
||||
), |
||||
vec2i(poly.spr->width, poly.spr->height), |
||||
poly.spr->colour, |
||||
poly.spr->texture |
||||
); |
||||
|
||||
poly.spr += 1; |
||||
break; |
||||
|
||||
default: |
||||
break; |
||||
|
||||
} |
||||
} |
||||
} |
@ -0,0 +1,383 @@ |
||||
#ifndef OBJECT_H |
||||
#define OBJECT_H |
||||
|
||||
#include "../types.h" |
||||
#include "../render.h" |
||||
#include "../utils.h" |
||||
#include "image.h" |
||||
|
||||
// Primitive Structure Stub ( Structure varies with primitive type )
|
||||
|
||||
typedef struct Primitive { |
||||
int16_t type; // Type of Primitive
|
||||
} Primitive; |
||||
|
||||
|
||||
typedef struct F3 { |
||||
int16_t type; // Type of primitive
|
||||
int16_t flag; |
||||
int16_t coords[3]; // Indices of the coords
|
||||
int16_t pad1; |
||||
rgba_t colour; |
||||
} F3; |
||||
|
||||
typedef struct FT3 { |
||||
int16_t type; // Type of primitive
|
||||
int16_t flag; |
||||
int16_t coords[3]; // Indices of the coords
|
||||
int16_t texture; |
||||
int16_t cba; |
||||
int16_t tsb; |
||||
uint8_t u0; |
||||
uint8_t v0; |
||||
uint8_t u1; |
||||
uint8_t v1; |
||||
uint8_t u2; |
||||
uint8_t v2; |
||||
int16_t pad1; |
||||
rgba_t colour; |
||||
} FT3; |
||||
|
||||
typedef struct F4 { |
||||
int16_t type; // Type of primitive
|
||||
int16_t flag; |
||||
int16_t coords[4]; // Indices of the coords
|
||||
rgba_t colour; |
||||
} F4; |
||||
|
||||
typedef struct FT4 { |
||||
int16_t type; // Type of primitive
|
||||
int16_t flag; |
||||
int16_t coords[4]; // Indices of the coords
|
||||
int16_t texture; |
||||
int16_t cba; |
||||
int16_t tsb; |
||||
uint8_t u0; |
||||
uint8_t v0; |
||||
uint8_t u1; |
||||
uint8_t v1; |
||||
uint8_t u2; |
||||
uint8_t v2; |
||||
uint8_t u3; |
||||
uint8_t v3; |
||||
int16_t pad1; |
||||
rgba_t colour; |
||||
} FT4; |
||||
|
||||
typedef struct G3 { |
||||
int16_t type; // Type of primitive
|
||||
int16_t flag; |
||||
int16_t coords[3]; // Indices of the coords
|
||||
int16_t pad1; |
||||
rgba_t colour[3]; |
||||
} G3; |
||||
|
||||
typedef struct GT3 { |
||||
int16_t type; // Type of primitive
|
||||
int16_t flag; |
||||
int16_t coords[3]; // Indices of the coords
|
||||
int16_t texture; |
||||
int16_t cba; |
||||
int16_t tsb; |
||||
uint8_t u0; |
||||
uint8_t v0; |
||||
uint8_t u1; |
||||
uint8_t v1; |
||||
uint8_t u2; |
||||
uint8_t v2; |
||||
int16_t pad1; |
||||
rgba_t colour[3]; |
||||
} GT3; |
||||
|
||||
typedef struct G4 { |
||||
int16_t type; // Type of primitive
|
||||
int16_t flag; |
||||
int16_t coords[4]; // Indices of the coords
|
||||
rgba_t colour[4]; |
||||
} G4; |
||||
|
||||
typedef struct GT4 { |
||||
int16_t type; // Type of primitive
|
||||
int16_t flag; |
||||
int16_t coords[4]; // Indices of the coords
|
||||
int16_t texture; |
||||
int16_t cba; |
||||
int16_t tsb; |
||||
uint8_t u0; |
||||
uint8_t v0; |
||||
uint8_t u1; |
||||
uint8_t v1; |
||||
uint8_t u2; |
||||
uint8_t v2; |
||||
uint8_t u3; |
||||
uint8_t v3; |
||||
int16_t pad1; |
||||
rgba_t colour[4]; |
||||
} GT4; |
||||
|
||||
|
||||
|
||||
|
||||
/* LIGHT SOURCED POLYGONS
|
||||
*/ |
||||
|
||||
typedef struct LSF3 { |
||||
int16_t type; // Type of primitive
|
||||
int16_t flag; |
||||
int16_t coords[3]; // Indices of the coords
|
||||
int16_t normal; // Indices of the normals
|
||||
rgba_t colour; |
||||
} LSF3; |
||||
|
||||
typedef struct LSFT3 { |
||||
int16_t type; // Type of primitive
|
||||
int16_t flag; |
||||
int16_t coords[3]; // Indices of the coords
|
||||
int16_t normal; // Indices of the normals
|
||||
int16_t texture; |
||||
int16_t cba; |
||||
int16_t tsb; |
||||
uint8_t u0; |
||||
uint8_t v0; |
||||
uint8_t u1; |
||||
uint8_t v1; |
||||
uint8_t u2; |
||||
uint8_t v2; |
||||
rgba_t colour; |
||||
} LSFT3; |
||||
|
||||
typedef struct LSF4 { |
||||
int16_t type; // Type of primitive
|
||||
int16_t flag; |
||||
int16_t coords[4]; // Indices of the coords
|
||||
int16_t normal; // Indices of the normals
|
||||
int16_t pad1; |
||||
rgba_t colour; |
||||
} LSF4; |
||||
|
||||
typedef struct LSFT4 { |
||||
int16_t type; // Type of primitive
|
||||
int16_t flag; |
||||
int16_t coords[4]; // Indices of the coords
|
||||
int16_t normal; // Indices of the normals
|
||||
int16_t texture; |
||||
int16_t cba; |
||||
int16_t tsb; |
||||
uint8_t u0; |
||||
uint8_t v0; |
||||
uint8_t u1; |
||||
uint8_t v1; |
||||
uint8_t u2; |
||||
uint8_t v2; |
||||
uint8_t u3; |
||||
uint8_t v3; |
||||
rgba_t colour; |
||||
} LSFT4; |
||||
|
||||
typedef struct LSG3 { |
||||
int16_t type; // Type of primitive
|
||||
int16_t flag; |
||||
int16_t coords[3]; // Indices of the coords
|
||||
int16_t normals[3]; // Indices of the normals
|
||||
rgba_t colour[3]; |
||||
} LSG3; |
||||
|
||||
typedef struct LSGT3 { |
||||
int16_t type; // Type of primitive
|
||||
int16_t flag; |
||||
int16_t coords[3]; // Indices of the coords
|
||||
int16_t normals[3]; // Indices of the normals
|
||||
int16_t texture; |
||||
int16_t cba; |
||||
int16_t tsb; |
||||
uint8_t u0; |
||||
uint8_t v0; |
||||
uint8_t u1; |
||||
uint8_t v1; |
||||
uint8_t u2; |
||||
uint8_t v2; |
||||
rgba_t colour[3]; |
||||
} LSGT3; |
||||
|
||||
typedef struct LSG4 { |
||||
int16_t type; // Type of primitive
|
||||
int16_t flag; |
||||
int16_t coords[4]; // Indices of the coords
|
||||
int16_t normals[4]; // Indices of the normals
|
||||
rgba_t colour[4]; |
||||
} LSG4; |
||||
|
||||
typedef struct LSGT4 { |
||||
int16_t type; // Type of primitive
|
||||
int16_t flag; |
||||
int16_t coords[4]; // Indices of the coords
|
||||
int16_t normals[4]; // Indices of the normals
|
||||
int16_t texture; |
||||
int16_t cba; |
||||
int16_t tsb; |
||||
uint8_t u0; |
||||
uint8_t v0; |
||||
uint8_t u1; |
||||
uint8_t v1; |
||||
uint8_t u2; |
||||
uint8_t v2; |
||||
uint8_t u3; |
||||
uint8_t v3; |
||||
int16_t pad1; |
||||
rgba_t colour[4]; |
||||
} LSGT4; |
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* OTHER PRIMITIVE TYPES
|
||||
*/ |
||||
typedef struct SPR { |
||||
int16_t type; |
||||
int16_t flag; |
||||
int16_t coord; |
||||
int16_t width; |
||||
int16_t height; |
||||
int16_t texture; |
||||
rgba_t colour; |
||||
} SPR; |
||||
|
||||
|
||||
typedef struct Spline { |
||||
int16_t type; // Type of primitive
|
||||
int16_t flag; |
||||
vec3_t control1; |
||||
vec3_t position; |
||||
vec3_t control2; |
||||
rgba_t colour; |
||||
} Spline; |
||||
|
||||
|
||||
typedef struct PointLight { |
||||
int16_t type; |
||||
int16_t flag; |
||||
vec3_t position; |
||||
rgba_t colour; |
||||
int16_t startFalloff; |
||||
int16_t endFalloff; |
||||
} PointLight; |
||||
|
||||
|
||||
typedef struct SpotLight { |
||||
int16_t type; |
||||
int16_t flag; |
||||
vec3_t position; |
||||
vec3_t direction; |
||||
rgba_t colour; |
||||
int16_t startFalloff; |
||||
int16_t endFalloff; |
||||
int16_t coneAngle; |
||||
int16_t spreadAngle; |
||||
} SpotLight; |
||||
|
||||
|
||||
typedef struct InfiniteLight { |
||||
int16_t type; |
||||
int16_t flag; |
||||
vec3_t direction; |
||||
rgba_t colour; |
||||
} InfiniteLight; |
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// PRIMITIVE FLAGS
|
||||
|
||||
#define PRM_SINGLE_SIDED 0x0001 |
||||
#define PRM_SHIP_ENGINE 0x0002 |
||||
#define PRM_TRANSLUCENT 0x0004 |
||||
|
||||
|
||||
|
||||
#define PRM_TYPE_F3 1 |
||||
#define PRM_TYPE_FT3 2 |
||||
#define PRM_TYPE_F4 3 |
||||
#define PRM_TYPE_FT4 4 |
||||
#define PRM_TYPE_G3 5 |
||||
#define PRM_TYPE_GT3 6 |
||||
#define PRM_TYPE_G4 7 |
||||
#define PRM_TYPE_GT4 8 |
||||
|
||||
#define PRM_TYPE_LF2 9 |
||||
#define PRM_TYPE_TSPR 10 |
||||
#define PRM_TYPE_BSPR 11 |
||||
|
||||
#define PRM_TYPE_LSF3 12 |
||||
#define PRM_TYPE_LSFT3 13 |
||||
#define PRM_TYPE_LSF4 14 |
||||
#define PRM_TYPE_LSFT4 15 |
||||
#define PRM_TYPE_LSG3 16 |
||||
#define PRM_TYPE_LSGT3 17 |
||||
#define PRM_TYPE_LSG4 18 |
||||
#define PRM_TYPE_LSGT4 19 |
||||
|
||||
#define PRM_TYPE_SPLINE 20 |
||||
|
||||
#define PRM_TYPE_INFINITE_LIGHT 21 |
||||
#define PRM_TYPE_POINT_LIGHT 22 |
||||
#define PRM_TYPE_SPOT_LIGHT 23 |
||||
|
||||
|
||||
typedef struct Object { |
||||
char name[16]; |
||||
|
||||
mat4_t mat; |
||||
int16_t vertices_len; // Number of Vertices
|
||||
vec3_t *vertices; // Pointer to 3D Points
|
||||
|
||||
int16_t normals_len; // Number of Normals
|
||||
vec3_t *normals; // Pointer to 3D Normals
|
||||
|
||||
int16_t primitives_len; // Number of Primitives
|
||||
Primitive *primitives; // Pointer to Z Sort Primitives
|
||||
|
||||
vec3_t origin; |
||||
int32_t extent; // Flags for object characteristics
|
||||
int16_t flags; // Next object in list
|
||||
struct Object *next; // Next object in list
|
||||
} Object; |
||||
|
||||
typedef union Prm { |
||||
uint8_t *ptr; |
||||
int16_t *sptr; |
||||
int32_t *lptr; |
||||
Object *object; |
||||
Primitive *primitive; |
||||
|
||||
F3 *f3; |
||||
FT3 *ft3; |
||||
F4 *f4; |
||||
FT4 *ft4; |
||||
G3 *g3; |
||||
GT3 *gt3; |
||||
G4 *g4; |
||||
GT4 *gt4; |
||||
SPR *spr; |
||||
Spline *spline; |
||||
PointLight *pointLight; |
||||
SpotLight *spotLight; |
||||
InfiniteLight *infiniteLight; |
||||
|
||||
LSF3 *lsf3; |
||||
LSFT3 *lsft3; |
||||
LSF4 *lsf4; |
||||
LSFT4 *lsft4; |
||||
LSG3 *lsg3; |
||||
LSGT3 *lsgt3; |
||||
LSG4 *lsg4; |
||||
LSGT4 *lsgt4; |
||||
} Prm; |
||||
|
||||
Object *objects_load(char *name, texture_list_t tl); |
||||
void object_draw(Object *object, mat4_t *mat); |
||||
|
||||
#endif |
@ -0,0 +1,70 @@ |
||||
#include "../mem.h" |
||||
#include "../utils.h" |
||||
#include "../system.h" |
||||
#include "../render.h" |
||||
|
||||
#include "particle.h" |
||||
#include "image.h" |
||||
|
||||
static particle_t *particles; |
||||
static int particles_active = 0; |
||||
static texture_list_t particle_textures; |
||||
|
||||
void particles_load() { |
||||
particles = mem_bump(sizeof(particle_t) * PARTICLES_MAX); |
||||
particle_textures = image_get_compressed_textures("wipeout/common/effects.cmp"); |
||||
particles_init(); |
||||
} |
||||
|
||||
void particles_init() { |
||||
particles_active = 0; |
||||
} |
||||
|
||||
void particles_update() { |
||||
for (int i = 0; i < particles_active; i++) { |
||||
particle_t *p = &particles[i]; |
||||
|
||||
p->timer -= system_tick(); |
||||
p->position = vec3_add(p->position, vec3_mulf(p->velocity, system_tick())); |
||||
if (p->timer < 0) { |
||||
particles[i--] = particles[--particles_active]; |
||||
continue; |
||||
} |
||||
} |
||||
} |
||||
|
||||
void particles_draw() { |
||||
if (particles_active == 0) { |
||||
return; |
||||
} |
||||
|
||||
render_set_model_mat(&mat4_identity()); |
||||
render_set_depth_write(false); |
||||
render_set_blend_mode(RENDER_BLEND_LIGHTER); |
||||
render_set_depth_offset(-32.0); |
||||
|
||||
for (int i = 0; i < particles_active; i++) { |
||||
particle_t *p = &particles[i]; |
||||
render_push_sprite(p->position, p->size, p->color, p->texture); |
||||
} |
||||
|
||||
render_set_depth_offset(0.0); |
||||
render_set_depth_write(true); |
||||
render_set_blend_mode(RENDER_BLEND_NORMAL); |
||||
} |
||||
|
||||
void particles_spawn(vec3_t position, uint16_t type, vec3_t velocity, int size) { |
||||
if (particles_active == PARTICLES_MAX) { |
||||
return; |
||||
} |
||||
|
||||
particle_t *p = &particles[particles_active++]; |
||||
p->color = rgba(128, 128, 128, 128); |
||||
p->texture = texture_from_list(particle_textures, type); |
||||
p->position = position; |
||||
p->velocity = velocity; |
||||
p->timer = rand_float(0.75, 1.0); |
||||
p->size.x = size; |
||||
p->size.y = size; |
||||
} |
||||
|
@ -0,0 +1,32 @@ |
||||
#ifndef PARTICLE_H |
||||
#define PARTICLE_H |
||||
|
||||
#include "../types.h" |
||||
|
||||
#define PARTICLES_MAX 1024 |
||||
|
||||
#define PARTICLE_TYPE_NONE -1 |
||||
#define PARTICLE_TYPE_FIRE 0 |
||||
#define PARTICLE_TYPE_FIRE_WHITE 1 |
||||
#define PARTICLE_TYPE_SMOKE 2 |
||||
#define PARTICLE_TYPE_EBOLT 3 |
||||
#define PARTICLE_TYPE_HALO 4 |
||||
#define PARTICLE_TYPE_GREENY 5 |
||||
|
||||
typedef struct particle_t { |
||||
vec3_t position; |
||||
vec3_t velocity; |
||||
vec2i_t size; |
||||
rgba_t color; |
||||
float timer; |
||||
uint16_t type; |
||||
uint16_t texture; |
||||
} particle_t; |
||||
|
||||
void particles_load(); |
||||
void particles_init(); |
||||
void particles_spawn(vec3_t position, uint16_t type, vec3_t velocity, int size); |
||||
void particles_draw(); |
||||
void particles_update(); |
||||
|
||||
#endif |
@ -0,0 +1,279 @@ |
||||
#include "../mem.h" |
||||
#include "../input.h" |
||||
#include "../platform.h" |
||||
#include "../system.h" |
||||
#include "../utils.h" |
||||
|
||||
#include "object.h" |
||||
#include "track.h" |
||||
#include "ship.h" |
||||
#include "weapon.h" |
||||
#include "droid.h" |
||||
#include "camera.h" |
||||
#include "object.h" |
||||
#include "scene.h" |
||||
#include "game.h" |
||||
#include "hud.h" |
||||
#include "sfx.h" |
||||
#include "race.h" |
||||
#include "particle.h" |
||||
#include "menu.h" |
||||
#include "ship_ai.h" |
||||
#include "ingame_menus.h" |
||||
|
||||
#define ATTRACT_DURATION 60.0 |
||||
|
||||
static bool is_paused = false; |
||||
static bool menu_is_scroll_text = false; |
||||
static bool has_show_credits = false; |
||||
static float attract_start_time; |
||||
static menu_t *active_menu = NULL; |
||||
|
||||
void race_init() { |
||||
ingame_menus_load(); |
||||
menu_is_scroll_text = false; |
||||
|
||||
const circut_settings_t *cs = &def.circuts[g.circut].settings[g.race_class]; |
||||
track_load(cs->path); |
||||
scene_load(cs->path, cs->sky_y_offset); |
||||
|
||||
if (g.circut == CIRCUT_SILVERSTREAM && g.race_class == RACE_CLASS_RAPIER) { |
||||
scene_init_aurora_borealis();
|
||||
}
|
||||
|
||||
race_start(); |
||||
// render_textures_dump("texture_atlas.png");
|
||||
|
||||
if (g.is_attract_mode) { |
||||
attract_start_time = system_time(); |
||||
for (int i = 0; i < len(g.ships); i++) { |
||||
// FIXME: this is needed to initializes the engine sound. Should
|
||||
// maybe be done in a separate step?
|
||||
ship_ai_update_intro(&g.ships[i]);
|
||||
|
||||
g.ships[i].update_func = ship_ai_update_race; |
||||
flags_rm(g.ships[i].flags, SHIP_VIEW_INTERNAL); |
||||
flags_rm(g.ships[i].flags, SHIP_RACING); |
||||
} |
||||
g.pilot = rand_int(0, len(def.pilots)); |
||||
g.camera.update_func = camera_update_attract_random; |
||||
if (!has_show_credits || rand_int(0, 10) == 0) { |
||||
active_menu = text_scroll_menu_init(def.credits, len(def.credits)); |
||||
menu_is_scroll_text = true; |
||||
has_show_credits = true; |
||||
} |
||||
} |
||||
|
||||
is_paused = false; |
||||
} |
||||
|
||||
void race_update() { |
||||
if (is_paused) { |
||||
if (!active_menu) { |
||||
active_menu = pause_menu_init(); |
||||
} |
||||
if (input_pressed(A_MENU_QUIT)) { |
||||
race_unpause(); |
||||
} |
||||
} |
||||
else { |
||||
ships_update(); |
||||
droid_update(&g.droid, &g.ships[g.pilot]); |
||||
camera_update(&g.camera, &g.ships[g.pilot], &g.droid); |
||||
weapons_update(); |
||||
particles_update(); |
||||
scene_update(); |
||||
if (g.race_type != RACE_TYPE_TIME_TRIAL) { |
||||
track_cycle_pickups(); |
||||
} |
||||
|
||||
if (g.is_attract_mode) { |
||||
if (input_pressed(A_MENU_START) || input_pressed(A_MENU_SELECT)) { |
||||
game_set_scene(GAME_SCENE_MAIN_MENU); |
||||
} |
||||
float duration = system_time() - attract_start_time; |
||||
if ((!active_menu && duration > 30) || duration > 120) { |
||||
game_set_scene(GAME_SCENE_TITLE); |
||||
} |
||||
} |
||||
else if (active_menu == NULL && (input_pressed(A_MENU_START) || input_pressed(A_MENU_QUIT))) { |
||||
race_pause(); |
||||
} |
||||
} |
||||
|
||||
|
||||
// Draw 3D
|
||||
render_set_view(g.camera.position, g.camera.angle); |
||||
|
||||
render_set_cull_backface(false); |
||||
scene_draw(&g.camera);
|
||||
track_draw(&g.camera); |
||||
render_set_cull_backface(true); |
||||
|
||||
ships_draw(); |
||||
droid_draw(&g.droid); |
||||
weapons_draw(); |
||||
particles_draw(); |
||||
|
||||
// Draw 2d
|
||||
render_set_view_2d(); |
||||
|
||||
if (flags_is(g.ships[g.pilot].flags, SHIP_RACING)) { |
||||
hud_draw(&g.ships[g.pilot]); |
||||
} |
||||
|
||||
if (active_menu) { |
||||
if (!menu_is_scroll_text) { |
||||
vec2i_t size = render_size(); |
||||
render_push_2d(vec2i(0, 0), size, rgba(0, 0, 0, 128), RENDER_NO_TEXTURE); |
||||
} |
||||
menu_update(active_menu); |
||||
} |
||||
} |
||||
|
||||
void race_start() { |
||||
active_menu = NULL; |
||||
sfx_reset(); |
||||
scene_init(); |
||||
camera_init(&g.camera, g.track.sections); |
||||
g.camera.update_func = camera_update_race_intro; |
||||
ships_init(g.track.sections); |
||||
droid_init(&g.droid, &g.ships[g.pilot]); |
||||
particles_init(); |
||||
weapons_init(); |
||||
|
||||
for (int i = 0; i < len(g.race_ranks); i++) { |
||||
g.race_ranks[i].points = 0; |
||||
g.race_ranks[i].pilot = i; |
||||
} |
||||
for (int i = 0; i < len(g.lap_times); i++) { |
||||
for (int j = 0; j < len(g.lap_times[i]); j++) { |
||||
g.lap_times[i][j] = 0; |
||||
} |
||||
} |
||||
g.is_new_race_record = false; |
||||
g.is_new_lap_record = false; |
||||
g.best_lap = 0; |
||||
g.race_time = 0; |
||||
} |
||||
|
||||
void race_restart() { |
||||
race_unpause(); |
||||
|
||||
if (g.race_type == RACE_TYPE_CHAMPIONSHIP) { |
||||
g.lives--; |
||||
if (g.lives == 0) { |
||||
race_release_control(); |
||||
active_menu = game_over_menu_init(); |
||||
return; |
||||
} |
||||
} |
||||
|
||||
race_start(); |
||||
} |
||||
|
||||
static bool sort_points_compare(pilot_points_t *pa, pilot_points_t *pb) { |
||||
return (pa->points < pb->points); |
||||
} |
||||
|
||||
void race_end() { |
||||
race_release_control(); |
||||
|
||||
g.race_position = g.ships[g.pilot].position_rank; |
||||
|
||||
g.race_time = 0; |
||||
g.best_lap = g.lap_times[g.pilot][0]; |
||||
for (int i = 0; i < NUM_LAPS; i++) { |
||||
g.race_time += g.lap_times[g.pilot][i]; |
||||
if (g.lap_times[g.pilot][i] < g.best_lap) { |
||||
g.best_lap = g.lap_times[g.pilot][i]; |
||||
} |
||||
} |
||||
|
||||
highscores_t *hs = &save.highscores[g.race_class][g.circut][g.highscore_tab]; |
||||
if (g.best_lap < hs->lap_record) { |
||||
hs->lap_record = g.best_lap; |
||||
g.is_new_lap_record = true; |
||||
save.is_dirty = true; |
||||
} |
||||
|
||||
for (int i = 0; i < NUM_HIGHSCORES; i++) { |
||||
if (g.race_time < hs->entries[i].time) { |
||||
g.is_new_race_record = true; |
||||
break; |
||||
} |
||||
} |
||||
|
||||
if (g.race_type == RACE_TYPE_CHAMPIONSHIP) { |
||||
for (int i = 0; i < len(def.race_points_for_rank); i++) { |
||||
g.race_ranks[i].points = def.race_points_for_rank[i]; |
||||
|
||||
// Find the pilot for this race rank in the championship table
|
||||
for (int j = 0; j < len(g.championship_ranks); j++) { |
||||
if (g.race_ranks[i].pilot == g.championship_ranks[j].pilot) { |
||||
g.championship_ranks[j].points += def.race_points_for_rank[i]; |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
sort(g.championship_ranks, len(g.championship_ranks), sort_points_compare); |
||||
} |
||||
|
||||
active_menu = race_stats_menu_init(); |
||||
} |
||||
|
||||
void race_next() { |
||||
int next_circut = g.circut + 1; |
||||
|
||||
// Championship complete
|
||||
if ( |
||||
(save.has_bonus_circuts && next_circut >= NUM_CIRCUTS) || |
||||
(!save.has_bonus_circuts && next_circut >= NUM_NON_BONUS_CIRCUTS) |
||||
) { |
||||
if (g.race_class == RACE_CLASS_RAPIER) { |
||||
if (save.has_bonus_circuts) { |
||||
active_menu = text_scroll_menu_init(def.congratulations.rapier_all_circuts, len(def.congratulations.rapier_all_circuts)); |
||||
} |
||||
else { |
||||
save.has_bonus_circuts = true; |
||||
active_menu = text_scroll_menu_init(def.congratulations.rapier, len(def.congratulations.rapier)); |
||||
} |
||||
} |
||||
else { |
||||
save.has_rapier_class = true; |
||||
if (save.has_bonus_circuts) { |
||||
active_menu = text_scroll_menu_init(def.congratulations.venom_all_circuts, len(def.congratulations.venom_all_circuts)); |
||||
} |
||||
else { |
||||
active_menu = text_scroll_menu_init(def.congratulations.venom, len(def.congratulations.venom)); |
||||
} |
||||
} |
||||
save.is_dirty = true; |
||||
menu_is_scroll_text = true; |
||||
} |
||||
|
||||
// Next track
|
||||
else { |
||||
g.circut = next_circut; |
||||
game_set_scene(GAME_SCENE_RACE); |
||||
} |
||||
} |
||||
|
||||
void race_release_control() { |
||||
flags_rm(g.ships[g.pilot].flags, SHIP_RACING); |
||||
g.ships[g.pilot].remote_thrust_max = 3160; |
||||
g.ships[g.pilot].remote_thrust_mag = 32; |
||||
g.ships[g.pilot].speed = 3160; |
||||
g.camera.update_func = camera_update_attract_random; |
||||
} |
||||
|
||||
void race_pause() { |
||||
sfx_pause(); |
||||
is_paused = true; |
||||
} |
||||
|
||||
void race_unpause() { |
||||
sfx_unpause(); |
||||
is_paused = false; |
||||
active_menu = NULL; |
||||
} |
@ -0,0 +1,14 @@ |
||||
#ifndef RACE_H |
||||
#define RACE_H |
||||
|
||||
void race_init(); |
||||
void race_update(); |
||||
void race_start(); |
||||
void race_restart(); |
||||
void race_pause(); |
||||
void race_unpause(); |
||||
void race_end(); |
||||
void race_next(); |
||||
void race_release_control(); |
||||
|
||||
#endif |
@ -0,0 +1,261 @@ |
||||
#include "../mem.h" |
||||
#include "../utils.h" |
||||
#include "../system.h" |
||||
|
||||
#include "object.h" |
||||
#include "track.h" |
||||
#include "ship.h" |
||||
#include "weapon.h" |
||||
#include "scene.h" |
||||
#include "droid.h" |
||||
#include "camera.h" |
||||
#include "object.h" |
||||
#include "game.h" |
||||
|
||||
|
||||
#define SCENE_START_BOOMS_MAX 4 |
||||
#define SCENE_OIL_PUMPS_MAX 2 |
||||
#define SCENE_RED_LIGHTS_MAX 4 |
||||
#define SCENE_STANDS_MAX 20 |
||||
|
||||
static Object *scene_objects; |
||||
static Object *sky_object; |
||||
static vec3_t sky_offset; |
||||
|
||||
static Object *start_booms[SCENE_START_BOOMS_MAX]; |
||||
static int start_booms_len; |
||||
|
||||
static Object *oil_pumps[SCENE_OIL_PUMPS_MAX]; |
||||
static int oil_pumps_len; |
||||
|
||||
static Object *red_lights[SCENE_RED_LIGHTS_MAX]; |
||||
static int red_lights_len; |
||||
|
||||
typedef struct { |
||||
sfx_t *sfx; |
||||
vec3_t pos; |
||||
} scene_stand_t; |
||||
static scene_stand_t stands[SCENE_STANDS_MAX]; |
||||
static int stands_len; |
||||
|
||||
static struct { |
||||
bool enabled; |
||||
GT4 *primitives[80]; |
||||
int16_t *coords[80]; |
||||
int16_t grey_coords[80];
|
||||
} aurora_borealis; |
||||
|
||||
void scene_pulsate_red_light(Object *obj); |
||||
void scene_move_oil_pump(Object *obj); |
||||
void scene_update_aurora_borealis(); |
||||
|
||||
void scene_load(const char *base_path, float sky_y_offset) { |
||||
texture_list_t scene_textures = image_get_compressed_textures(get_path(base_path, "scene.cmp")); |
||||
scene_objects = objects_load(get_path(base_path, "scene.prm"), scene_textures); |
||||
|
||||
texture_list_t sky_textures = image_get_compressed_textures(get_path(base_path, "sky.cmp")); |
||||
sky_object = objects_load(get_path(base_path, "sky.prm") , sky_textures); |
||||
sky_offset = vec3(0, sky_y_offset, 0); |
||||
|
||||
// Collect all objects that need to be updated each frame
|
||||
start_booms_len = 0; |
||||
oil_pumps_len = 0; |
||||
red_lights_len = 0; |
||||
stands_len = 0; |
||||
|
||||
Object *obj = scene_objects; |
||||
while (obj) { |
||||
mat4_set_translation(&obj->mat, obj->origin); |
||||
|
||||
if (str_starts_with(obj->name, "start")) { |
||||
error_if(start_booms_len >= SCENE_START_BOOMS_MAX, "SCENE_START_BOOMS_MAX reached"); |
||||
start_booms[start_booms_len++] = obj; |
||||
} |
||||
else if (str_starts_with(obj->name, "redl")) { |
||||
error_if(red_lights_len >= SCENE_RED_LIGHTS_MAX, "SCENE_RED_LIGHTS_MAX reached"); |
||||
red_lights[red_lights_len++] = obj; |
||||
} |
||||
else if (str_starts_with(obj->name, "donkey")) { |
||||
error_if(oil_pumps_len >= SCENE_OIL_PUMPS_MAX, "SCENE_OIL_PUMPS_MAX reached"); |
||||
oil_pumps[oil_pumps_len++] = obj; |
||||
} |
||||
else if ( |
||||
str_starts_with(obj->name, "lostad") ||
|
||||
str_starts_with(obj->name, "stad_") || |
||||
str_starts_with(obj->name, "newstad_") |
||||
) { |
||||
error_if(stands_len >= SCENE_STANDS_MAX, "SCENE_STANDS_MAX reached"); |
||||
stands[stands_len++] = (scene_stand_t){.sfx = NULL, .pos = obj->origin}; |
||||
} |
||||
obj = obj->next; |
||||
} |
||||
|
||||
aurora_borealis.enabled = false; |
||||
} |
||||
|
||||
void scene_init() { |
||||
scene_set_start_booms(0); |
||||
for (int i = 0; i < stands_len; i++) { |
||||
stands[i].sfx = sfx_reserve_loop(SFX_CROWD); |
||||
} |
||||
} |
||||
|
||||
void scene_update() { |
||||
for (int i = 0; i < red_lights_len; i++) { |
||||
scene_pulsate_red_light(red_lights[i]); |
||||
} |
||||
for (int i = 0; i < oil_pumps_len; i++) { |
||||
scene_move_oil_pump(oil_pumps[i]); |
||||
} |
||||
for (int i = 0; i < stands_len; i++) { |
||||
sfx_set_position(stands[i].sfx, stands[i].pos, vec3(0, 0, 0), 0.4); |
||||
} |
||||
|
||||
if (aurora_borealis.enabled) { |
||||
scene_update_aurora_borealis(); |
||||
} |
||||
} |
||||
|
||||
void scene_draw(camera_t *camera) { |
||||
// Sky
|
||||
render_set_depth_write(false); |
||||
mat4_set_translation(&sky_object->mat, vec3_add(camera->position, sky_offset)); |
||||
object_draw(sky_object, &sky_object->mat); |
||||
render_set_depth_write(true); |
||||
|
||||
// Nearby objects
|
||||
vec3_t cam_pos = camera->position; |
||||
Object *object = scene_objects; |
||||
float max_dist_sq = RENDER_FADEOUT_FAR * RENDER_FADEOUT_FAR; |
||||
while (object) { |
||||
vec3_t d = vec3_sub(cam_pos, object->origin); |
||||
float dist_sq = d.x * d.x + d.y * d.y + d.z * d.z; |
||||
|
||||
if (dist_sq < max_dist_sq) { |
||||
object_draw(object, &object->mat); |
||||
} |
||||
|
||||
object = object->next; |
||||
} |
||||
|
||||
} |
||||
|
||||
void scene_set_start_booms(int light_index) { |
||||
|
||||
int lights_len = 1; |
||||
rgba_t color = rgba(0, 0, 0, 0); |
||||
|
||||
if (light_index == 0) { // reset all 3
|
||||
lights_len = 3; |
||||
color = rgba(0x20, 0x20, 0x20, 0xff); |
||||
} |
||||
else if (light_index == 1) { |
||||
color = rgba(0xff, 0x00, 0x00, 0xff); |
||||
} |
||||
else if (light_index == 2) { |
||||
color = rgba(0xff, 0x80, 0x00, 0xff); |
||||
} |
||||
else if (light_index == 3) { |
||||
color = rgba(0x00, 0xff, 0x00, 0xff); |
||||
} |
||||
|
||||
for (int i = 0; i < start_booms_len; i++) { |
||||
Prm libPoly = {.primitive = start_booms[i]->primitives}; |
||||
|
||||
for (int j = 1; j < light_index; j++) { |
||||
libPoly.gt4 += 1; |
||||
} |
||||
|
||||
for (int j = 0; j < lights_len; j++) { |
||||
for (int v = 0; v < 4; v++) { |
||||
libPoly.gt4->colour[v].as_rgba.r = color.as_rgba.r; |
||||
libPoly.gt4->colour[v].as_rgba.g = color.as_rgba.g; |
||||
libPoly.gt4->colour[v].as_rgba.b = color.as_rgba.b; |
||||
} |
||||
libPoly.gt4 += 1; |
||||
} |
||||
} |
||||
} |
||||
|
||||
|
||||
void scene_pulsate_red_light(Object *obj) { |
||||
uint8_t r = clamp(sin(system_cycle_time() * M_PI * 2) * 128 + 128, 0, 255); |
||||
Prm libPoly = {.primitive = obj->primitives}; |
||||
|
||||
for (int v = 0; v < 4; v++) { |
||||
libPoly.gt4->colour[v].as_rgba.r = r; |
||||
libPoly.gt4->colour[v].as_rgba.g = 0x00; |
||||
libPoly.gt4->colour[v].as_rgba.b = 0x00; |
||||
} |
||||
} |
||||
|
||||
void scene_move_oil_pump(Object *pump) { |
||||
mat4_set_yaw_pitch_roll(&pump->mat, vec3(sin(system_cycle_time() * 0.125 * M_PI * 2), 0, 0)); |
||||
} |
||||
|
||||
void scene_init_aurora_borealis() { |
||||
aurora_borealis.enabled = true; |
||||
clear(aurora_borealis.grey_coords); |
||||
|
||||
int count = 0; |
||||
int16_t *coords; |
||||
float y; |
||||
|
||||
Prm poly = {.primitive = sky_object->primitives}; |
||||
for (int i = 0; i < sky_object->primitives_len; i++) { |
||||
switch (poly.primitive->type) { |
||||
case PRM_TYPE_GT3: |
||||
poly.gt3 += 1; |
||||
break; |
||||
case PRM_TYPE_GT4: |
||||
coords = poly.gt4->coords; |
||||
y = sky_object->vertices[coords[0]].y; |
||||
if (y < -6000) { // -8000
|
||||
aurora_borealis.primitives[count] = poly.gt4; |
||||
if (y > -6800) { |
||||
aurora_borealis.coords[count] = poly.gt4->coords; |
||||
aurora_borealis.grey_coords[count] = -1; |
||||
} |
||||
else if (y < -11000) { |
||||
aurora_borealis.coords[count] = poly.gt4->coords; |
||||
aurora_borealis.grey_coords[count] = -2; |
||||
} |
||||
else { |
||||
aurora_borealis.coords[count] = poly.gt4->coords; |
||||
} |
||||
count++; |
||||
} |
||||
poly.gt4 += 1; |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
|
||||
void scene_update_aurora_borealis() { |
||||
float phase = system_time() / 30.0; |
||||
for (int i = 0; i < 80; i++) { |
||||
int16_t *coords = aurora_borealis.coords[i]; |
||||
|
||||
if (aurora_borealis.grey_coords[i] != -2) { |
||||
aurora_borealis.primitives[i]->colour[0].as_rgba.r = (sin(coords[0] * phase) * 64.0) + 190; |
||||
aurora_borealis.primitives[i]->colour[0].as_rgba.g = (sin(coords[0] * (phase + 0.054)) * 64.0) + 190; |
||||
aurora_borealis.primitives[i]->colour[0].as_rgba.b = (sin(coords[0] * (phase + 0.039)) * 64.0) + 190; |
||||
} |
||||
if (aurora_borealis.grey_coords[i] != -2) { |
||||
aurora_borealis.primitives[i]->colour[1].as_rgba.r = (sin(coords[1] * phase) * 64.0) + 190; |
||||
aurora_borealis.primitives[i]->colour[1].as_rgba.g = (sin(coords[1] * (phase + 0.054)) * 64.0) + 190; |
||||
aurora_borealis.primitives[i]->colour[1].as_rgba.b = (sin(coords[1] * (phase + 0.039)) * 64.0) + 190; |
||||
} |
||||
if (aurora_borealis.grey_coords[i] != -1) { |
||||
aurora_borealis.primitives[i]->colour[2].as_rgba.r = (sin(coords[2] * phase) * 64.0) + 190; |
||||
aurora_borealis.primitives[i]->colour[2].as_rgba.g = (sin(coords[2] * (phase + 0.054)) * 64.0) + 190; |
||||
aurora_borealis.primitives[i]->colour[2].as_rgba.b = (sin(coords[2] * (phase + 0.039)) * 64.0) + 190; |
||||
} |
||||
|
||||
if (aurora_borealis.grey_coords[i] != -1) { |
||||
aurora_borealis.primitives[i]->colour[3].as_rgba.r = (sin(coords[3] * phase) * 64.0) + 190; |
||||
aurora_borealis.primitives[i]->colour[3].as_rgba.g = (sin(coords[3] * (phase + 0.054)) * 64.0) + 190; |
||||
aurora_borealis.primitives[i]->colour[3].as_rgba.b = (sin(coords[3] * (phase + 0.039)) * 64.0) + 190; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,15 @@ |
||||
#ifndef SCENE_H |
||||
#define SCENE_H |
||||
|
||||
#include "image.h" |
||||
#include "camera.h" |
||||
|
||||
void scene_load(const char *path, float sky_y_offset); |
||||
void scene_draw(camera_t *camera); |
||||
void scene_init(); |
||||
void scene_set_start_booms(int num_lights); |
||||
void scene_init_aurora_borealis(); |
||||
void scene_update(); |
||||
void scene_draw(); |
||||
|
||||
#endif |
@ -0,0 +1,393 @@ |
||||
#include "../utils.h" |
||||
#include "../mem.h" |
||||
#include "../platform.h" |
||||
|
||||
#include "sfx.h" |
||||
#include "game.h" |
||||
|
||||
#define QOA_IMPLEMENTATION |
||||
#define QOA_NO_STDIO |
||||
#include "../libs/qoa.h" |
||||
|
||||
typedef struct { |
||||
int16_t *samples; |
||||
uint32_t len; |
||||
} sfx_data_t; |
||||
|
||||
typedef struct { |
||||
qoa_desc qoa; |
||||
FILE *file; |
||||
|
||||
uint32_t track_index; |
||||
uint32_t first_frame_pos; |
||||
|
||||
uint32_t buffer_len; |
||||
uint8_t *buffer; |
||||
|
||||
uint32_t sample_data_pos; |
||||
uint32_t sample_data_len; |
||||
short *sample_data; |
||||
sfx_music_mode_t mode; |
||||
} music_decoder_t; |
||||
|
||||
enum { |
||||
VAG_REGION_START = 1, |
||||
VAG_REGION = 2, |
||||
VAG_REGION_END = 4 |
||||
}; |
||||
|
||||
static const int32_t vag_tab[5][2] = { |
||||
{ 0, 0}, // { 0.0, 0.0}, << 14
|
||||
{15360, 0}, // { 60.0 / 64.0, 0.0}, << 14
|
||||
{29440, -13312}, // {115.0 / 64.0, -52.0 / 64.0}, << 14
|
||||
{25088, -14080}, // { 98.0 / 64.0, -55.0 / 64.0}, << 14
|
||||
{31232, -15360}, // {122.0 / 64.0, -60.0 / 64.0}, << 14
|
||||
}; |
||||
|
||||
static sfx_data_t *sources; |
||||
static uint32_t num_sources; |
||||
static sfx_t *nodes; |
||||
static music_decoder_t *music; |
||||
static void (*external_mix_cb)(float *, uint32_t len) = NULL; |
||||
|
||||
void sfx_load() { |
||||
// Init decode buffer for music
|
||||
uint32_t channels = 2; |
||||
music = mem_bump(sizeof(music_decoder_t)); |
||||
music->buffer = mem_bump(QOA_FRAME_SIZE(channels, QOA_SLICES_PER_FRAME)); |
||||
music->sample_data = mem_bump(channels * QOA_FRAME_LEN * sizeof(short) * 2); |
||||
music->qoa.channels = channels; |
||||
music->mode = SFX_MUSIC_RANDOM; |
||||
music->file = NULL; |
||||
music->track_index = -1; |
||||
|
||||
|
||||
// Load SFX samples
|
||||
nodes = mem_bump(SFX_MAX * sizeof(sfx_t)); |
||||
|
||||
// 16 byte blocks: 2 byte header, 14 bytes with 2x4bit samples each
|
||||
uint32_t vb_size; |
||||
uint8_t *vb = file_load("wipeout/sound/wipeout.vb", &vb_size); |
||||
uint32_t num_samples = (vb_size / 16) * 28; |
||||
|
||||
int16_t *sample_buffer = mem_bump(num_samples * sizeof(int16_t)); |
||||
sources = mem_mark(); |
||||
num_sources = 0; |
||||
|
||||
uint32_t sample_index = 0; |
||||
int32_t history[2] = {0, 0}; |
||||
for (int p = 0; p < vb_size;) { |
||||
uint8_t header = vb[p++]; |
||||
uint8_t flags = vb[p++]; |
||||
uint8_t shift = header & 0x0f; |
||||
uint8_t predictor = clamp(header >> 4, 0, 4); |
||||
|
||||
if (flags_is(flags, VAG_REGION_END)) { |
||||
mem_bump(sizeof(sfx_data_t)); |
||||
sources[num_sources].samples = &sample_buffer[sample_index]; |
||||
} |
||||
|
||||
for (uint32_t bs = 0; bs < 14; bs++) { |
||||
int32_t nibbles[2] = { |
||||
(vb[p] & 0x0f) << 12, |
||||
(vb[p] & 0xf0) << 8 |
||||
}; |
||||
p++; |
||||
|
||||
for (int ni = 0; ni < 2; ni++) { |
||||
int32_t sample = nibbles[ni]; |
||||
if (sample & 0x8000) { |
||||
sample |= 0xffff0000; |
||||
} |
||||
sample >>= shift; |
||||
sample += (history[0] * vag_tab[predictor][0] + history[1] * vag_tab[predictor][1]) >> 14; |
||||
history[1] = history[0]; |
||||
history[0] = sample; |
||||
sample_buffer[sample_index++] = clamp(sample, -32768, 32767); |
||||
} |
||||
} |
||||
|
||||
if (flags_is(flags, VAG_REGION_START)) { |
||||
error_if(sources[num_sources].samples == NULL, "VAG_REGION_START without VAG_REGION_END"); |
||||
sources[num_sources].len = &sample_buffer[sample_index] - sources[num_sources].samples; |
||||
num_sources++; |
||||
} |
||||
} |
||||
|
||||
mem_temp_free(vb); |
||||
platform_set_audio_mix_cb(sfx_stero_mix); |
||||
} |
||||
|
||||
void sfx_reset() { |
||||
for (int i = 0; i < SFX_MAX; i++) { |
||||
if (flags_is(nodes[i].flags, SFX_LOOP)) { |
||||
flags_set(nodes[i].flags, SFX_NONE); |
||||
} |
||||
} |
||||
} |
||||
|
||||
void sfx_unpause() { |
||||
for (int i = 0; i < SFX_MAX; i++) { |
||||
if (flags_is(nodes[i].flags, SFX_LOOP_PAUSE)) { |
||||
flags_rm(nodes[i].flags, SFX_LOOP_PAUSE); |
||||
flags_add(nodes[i].flags, SFX_PLAY); |
||||
} |
||||
} |
||||
} |
||||
|
||||
void sfx_pause() { |
||||
for (int i = 0; i < SFX_MAX; i++) { |
||||
if (flags_is(nodes[i].flags, SFX_PLAY | SFX_LOOP)) { |
||||
flags_rm(nodes[i].flags, SFX_PLAY); |
||||
flags_add(nodes[i].flags, SFX_LOOP_PAUSE); |
||||
} |
||||
} |
||||
} |
||||
|
||||
|
||||
|
||||
// Sound effects
|
||||
|
||||
sfx_t *sfx_get_node(sfx_source_t source_index) { |
||||
error_if(source_index < 0 || source_index > num_sources, "Invalid audio source"); |
||||
|
||||
sfx_t *sfx = NULL; |
||||
for (int i = 0; i < SFX_MAX; i++) { |
||||
if (flags_none(nodes[i].flags, SFX_PLAY | SFX_RESERVE)){ |
||||
sfx = &nodes[i]; |
||||
break; |
||||
} |
||||
} |
||||
if (!sfx) { |
||||
for (int i = 0; i < SFX_MAX; i++) { |
||||
if (flags_not(nodes[i].flags, SFX_RESERVE)) { |
||||
sfx = &nodes[i]; |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
|
||||
error_if(!sfx, "All audio nodes reserved"); |
||||
|
||||
flags_set(sfx->flags, SFX_NONE); |
||||
sfx->source = source_index; |
||||
sfx->volume = 1; |
||||
sfx->current_volume = 1; |
||||
sfx->pan = 0; |
||||
sfx->current_pan = 0; |
||||
sfx->position = 0; |
||||
|
||||
// Set default pitch. All voice samples are 44khz,
|
||||
// other effects 22khz
|
||||
sfx->pitch = source_index >= SFX_VOICE_MINES ? 1.0 : 0.5; |
||||
|
||||
return sfx; |
||||
} |
||||
|
||||
sfx_t *sfx_play(sfx_source_t source_index) { |
||||
sfx_t *sfx = sfx_get_node(source_index); |
||||
flags_set(sfx->flags, SFX_PLAY); |
||||
return sfx; |
||||
} |
||||
|
||||
sfx_t *sfx_play_at(sfx_source_t source_index, vec3_t pos, vec3_t vel, float volume) { |
||||
sfx_t *sfx = sfx_get_node(source_index); |
||||
sfx_set_position(sfx, pos, vel, volume); |
||||
if (sfx->volume > 0) { |
||||
flags_set(sfx->flags, SFX_PLAY); |
||||
} |
||||
return sfx; |
||||
} |
||||
|
||||
sfx_t *sfx_reserve_loop(sfx_source_t source_index) { |
||||
sfx_t *sfx = sfx_get_node(source_index); |
||||
flags_set(sfx->flags, SFX_RESERVE | SFX_LOOP | SFX_PLAY); |
||||
sfx->volume = 0; |
||||
sfx->current_volume = 0; |
||||
sfx->current_pan = 0; |
||||
sfx->position = rand_float(0, sources[source_index].len); |
||||
return sfx; |
||||
} |
||||
|
||||
void sfx_set_position(sfx_t *sfx, vec3_t pos, vec3_t vel, float volume) { |
||||
vec3_t relative_position = vec3_sub(g.camera.position, pos); |
||||
vec3_t relative_velocity = vec3_sub(g.camera.real_velocity, vel); |
||||
float distance = vec3_len(relative_position); |
||||
|
||||
sfx->volume = clamp(scale(distance, 512, 32768, 1, 0), 0, 1) * volume; |
||||
sfx->pan = -sin(atan2(g.camera.position.x - pos.x, g.camera.position.z - pos.z)+g.camera.angle.y); |
||||
|
||||
// Doppler effect
|
||||
float away = vec3_dot(relative_velocity, relative_position) / distance; |
||||
sfx->pitch = (262144.0 - away) / 524288.0; |
||||
} |
||||
|
||||
|
||||
|
||||
|
||||
// Music
|
||||
|
||||
uint32_t sfx_music_decode_frame() { |
||||
if (!music->file) { |
||||
return 0; |
||||
} |
||||
music->buffer_len = fread(music->buffer, 1, qoa_max_frame_size(&music->qoa), music->file); |
||||
|
||||
uint32_t frame_len; |
||||
qoa_decode_frame(music->buffer, music->buffer_len, &music->qoa, music->sample_data, &frame_len); |
||||
music->sample_data_pos = 0; |
||||
music->sample_data_len = frame_len; |
||||
return frame_len; |
||||
} |
||||
|
||||
void sfx_music_rewind() { |
||||
fseek(music->file, music->first_frame_pos, SEEK_SET); |
||||
music->sample_data_len = 0; |
||||
music->sample_data_pos = 0; |
||||
} |
||||
|
||||
void sfx_music_open(char *path) { |
||||
if (music->file) { |
||||
fclose(music->file); |
||||
music->file = NULL; |
||||
} |
||||
|
||||
FILE *file = fopen(path, "rb"); |
||||
if (!file) { |
||||
return; |
||||
} |
||||
|
||||
uint8_t header[QOA_MIN_FILESIZE]; |
||||
int read = fread(header, QOA_MIN_FILESIZE, 1, file); |
||||
if (!read) { |
||||
fclose(file); |
||||
return; |
||||
} |
||||
|
||||
qoa_desc qoa; |
||||
uint32_t first_frame_pos = qoa_decode_header(header, QOA_MIN_FILESIZE, &qoa); |
||||
if (!first_frame_pos) { |
||||
fclose(file); |
||||
return; |
||||
} |
||||
|
||||
fseek(file, first_frame_pos, SEEK_SET); |
||||
|
||||
if (qoa.channels != music->qoa.channels) { |
||||
fclose(file); |
||||
return; |
||||
} |
||||
music->qoa = qoa; |
||||
music->first_frame_pos = first_frame_pos; |
||||
music->file = file; |
||||
music->sample_data_len = 0; |
||||
music->sample_data_pos = 0; |
||||
} |
||||
|
||||
void sfx_music_play(uint32_t index) { |
||||
error_if(index >= len(def.music), "Invalid music index"); |
||||
if (index == music->track_index) { |
||||
sfx_music_rewind(); |
||||
return; |
||||
} |
||||
|
||||
printf("open music track %d\n", index); |
||||
|
||||
music->track_index = index; |
||||
sfx_music_open(def.music[index].path); |
||||
} |
||||
|
||||
void sfx_music_mode(sfx_music_mode_t mode) { |
||||
music->mode = mode; |
||||
} |
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// Mixing
|
||||
|
||||
void sfx_set_external_mix_cb(void (*cb)(float *, uint32_t len)) { |
||||
external_mix_cb = cb; |
||||
} |
||||
|
||||
void sfx_stero_mix(float *buffer, uint32_t len) { |
||||
if (external_mix_cb) { |
||||
external_mix_cb(buffer, len); |
||||
return; |
||||
} |
||||
|
||||
// Find currently active nodes: those that play and have volume > 0
|
||||
sfx_t *active_nodes[SFX_MAX_ACTIVE]; |
||||
int active_nodes_len = 0; |
||||
for (int n = 0; n < SFX_MAX; n++) { |
||||
sfx_t *sfx = &nodes[n]; |
||||
if (flags_is(sfx->flags, SFX_PLAY) && (sfx->volume > 0 || sfx->current_volume > 0.01)) { |
||||
active_nodes[active_nodes_len++] = sfx; |
||||
if (active_nodes_len >= SFX_MAX_ACTIVE) { |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
|
||||
uint32_t music_src_index = music->sample_data_pos * music->qoa.channels; |
||||
|
||||
for (int i = 0; i < len; i += 2) { |
||||
float left = 0; |
||||
float right = 0; |
||||
|
||||
// Fill buffer with all active nodes
|
||||
for (int n = 0; n < active_nodes_len; n++) { |
||||
sfx_t *sfx = active_nodes[n]; |
||||
if (flags_not(sfx->flags, SFX_PLAY)) { |
||||
continue; |
||||
} |
||||
|
||||
sfx->current_volume = sfx->current_volume * 0.999 + sfx->volume * 0.001; |
||||
sfx->current_pan = sfx->current_pan * 0.999 + sfx->pan * 0.001; |
||||
|
||||
sfx_data_t *source = &sources[sfx->source]; |
||||
float sample = (float)source->samples[(int)sfx->position] / 32768.0; |
||||
left += sample * sfx->current_volume * clamp(1.0 - sfx->current_pan, 0, 1); |
||||
right += sample * sfx->current_volume * clamp(1.0 + sfx->current_pan, 0, 1); |
||||
|
||||
sfx->position += sfx->pitch; |
||||
if (sfx->position >= source->len) { |
||||
if (flags_is(sfx->flags, SFX_LOOP)) { |
||||
sfx->position = fmod(sfx->position, source->len); |
||||
} |
||||
else { |
||||
flags_rm(sfx->flags, SFX_PLAY); |
||||
} |
||||
} |
||||
} |
||||
|
||||
left *= save.sfx_volume; |
||||
right *= save.sfx_volume; |
||||
|
||||
// Mix in music
|
||||
if (music->mode != SFX_MUSIC_PAUSED && music->file) { |
||||
if (music->sample_data_len - music->sample_data_pos == 0) { |
||||
if (!sfx_music_decode_frame()) { |
||||
if (music->mode == SFX_MUSIC_RANDOM) { |
||||
sfx_music_play(rand_int(0, len(def.music))); |
||||
} |
||||
else if (music->mode == SFX_MUSIC_SEQUENTIAL) { |
||||
sfx_music_play((music->track_index + 1) % len(def.music)); |
||||
} |
||||
else if (music->mode == SFX_MUSIC_LOOP) { |
||||
sfx_music_rewind(); |
||||
} |
||||
sfx_music_decode_frame(); |
||||
} |
||||
music_src_index = 0; |
||||
} |
||||
left += (music->sample_data[music_src_index++] / 32768.0) * save.music_volume; |
||||
right += (music->sample_data[music_src_index++] / 32768.0) * save.music_volume; |
||||
music->sample_data_pos++; |
||||
} |
||||
|
||||
buffer[i+0] = left; |
||||
buffer[i+1] = right; |
||||
} |
||||
} |
@ -0,0 +1,85 @@ |
||||
#ifndef SFX_H |
||||
#define SFX_H |
||||
|
||||
#include "../types.h" |
||||
|
||||
typedef enum { |
||||
SFX_CRUNCH, |
||||
SFX_EBOLT, |
||||
SFX_ENGINE_INTAKE, |
||||
SFX_ENGINE_RUMBLE, |
||||
SFX_ENGINE_THRUST, |
||||
SFX_EXPLOSION_1, |
||||
SFX_EXPLOSION_2, |
||||
SFX_IMPACT, |
||||
SFX_MENU_MOVE, |
||||
SFX_MENU_SELECT, |
||||
SFX_MENU_TRANSITION, |
||||
SFX_MINE_DROP, |
||||
SFX_MISSILE_FIRE, |
||||
SFX_ENGINE_REMOTE, |
||||
SFX_POWERUP, |
||||
SFX_SHIELD, |
||||
SFX_SIREN, |
||||
SFX_TRACTOR, |
||||
SFX_TURBULENCE, |
||||
SFX_CROWD, |
||||
SFX_VOICE_MINES, |
||||
SFX_VOICE_MISSILE, |
||||
SFX_VOICE_ROCKETS, |
||||
SFX_VOICE_REVCON, |
||||
SFX_VOICE_SHOCKWAVE, |
||||
SFX_VOICE_SPECIAL, |
||||
SFX_VOICE_COUNT_3, |
||||
SFX_VOICE_COUNT_2, |
||||
SFX_VOICE_COUNT_1, |
||||
SFX_VOICE_COUNT_GO, |
||||
} sfx_source_t; |
||||
|
||||
typedef enum { |
||||
SFX_NONE = 0, |
||||
SFX_PLAY = (1<<0), |
||||
SFX_RESERVE = (1<<1), |
||||
SFX_LOOP = (1<<2), |
||||
SFX_LOOP_PAUSE = (1<<3), |
||||
} sfx_flags_t; |
||||
|
||||
typedef struct { |
||||
sfx_source_t source; |
||||
sfx_flags_t flags; |
||||
float pan; |
||||
float current_pan; |
||||
float volume; |
||||
float current_volume; |
||||
float pitch; |
||||
float position; |
||||
} sfx_t; |
||||
|
||||
#define SFX_MAX 64 |
||||
#define SFX_MAX_ACTIVE 16 |
||||
|
||||
void sfx_load(); |
||||
void sfx_stero_mix(float *buffer, uint32_t len); |
||||
void sfx_set_external_mix_cb(void (*cb)(float *, uint32_t len)); |
||||
void sfx_reset(); |
||||
void sfx_pause(); |
||||
void sfx_unpause(); |
||||
|
||||
sfx_t *sfx_play(sfx_source_t source_index); |
||||
sfx_t *sfx_play_at(sfx_source_t source_index, vec3_t pos, vec3_t vel, float volume); |
||||
sfx_t *sfx_reserve_loop(sfx_source_t source_index); |
||||
void sfx_set_position(sfx_t *sfx, vec3_t pos, vec3_t vel, float volume); |
||||
|
||||
typedef enum { |
||||
SFX_MUSIC_PAUSED, |
||||
SFX_MUSIC_RANDOM, |
||||
SFX_MUSIC_SEQUENTIAL, |
||||
SFX_MUSIC_LOOP |
||||
} sfx_music_mode_t; |
||||
|
||||
void sfx_music_next(); |
||||
void sfx_music_play(uint32_t index); |
||||
void sfx_music_mode(sfx_music_mode_t); |
||||
void sfx_music_pause(); |
||||
|
||||
#endif |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,168 @@ |
||||
#ifndef SHIP_H |
||||
#define SHIP_H |
||||
|
||||
#include "../types.h" |
||||
#include "track.h" |
||||
#include "sfx.h" |
||||
|
||||
#define SHIP_IN_TOW (1<< 0) |
||||
#define SHIP_VIEW_REMOTE (1<< 1) |
||||
#define SHIP_VIEW_INTERNAL (1<< 2) |
||||
#define SHIP_DIRECTION_FORWARD (1<< 3) |
||||
#define SHIP_FLYING (1<< 4) |
||||
#define SHIP_LEFT_SIDE (1<< 5) |
||||
#define SHIP_RACING (1<< 6) |
||||
#define SHIP_COLL (1<< 7) |
||||
#define SHIP_ON_JUNCTION (1<< 8) |
||||
#define SHIP_VISIBLE (1<< 9) |
||||
#define SHIP_IN_RESCUE (1<<10) |
||||
#define SHIP_OVERTAKEN (1<<11) |
||||
#define SHIP_JUST_IN_FRONT (1<<12) |
||||
#define SHIP_JUNCTION_LEFT (1<<13) |
||||
#define SHIP_SHIELDED (1<<14) |
||||
#define SHIP_ELECTROED (1<<15) |
||||
#define SHIP_REVCONNED (1<<16) |
||||
#define SHIP_SPECIALED (1<<17) |
||||
|
||||
|
||||
// Timings
|
||||
|
||||
#define UPDATE_TIME_INITIAL (200.0 * (1.0/30.0)) |
||||
#define UPDATE_TIME_THREE (150.0 * (1.0/30.0)) |
||||
#define UPDATE_TIME_RACE_VIEW (100.0 * (1.0/30.0)) |
||||
#define UPDATE_TIME_TWO (100.0 * (1.0/30.0)) |
||||
#define UPDATE_TIME_ONE ( 50.0 * (1.0/30.0)) |
||||
#define UPDATE_TIME_GO ( 0.0 * (1.0/30.0)) |
||||
|
||||
|
||||
// Physics conversion
|
||||
|
||||
#define FIXED_TO_FLOAT(V) ((V) * (1.0/4096.0)) |
||||
#define ANGLE_NORM_TO_RADIAN(V) ((V) * M_PI * 2.0) |
||||
#define NTSC_STEP_TO_RATE_PER_SECOND(V) ((V) * 30.0) |
||||
#define NTSC_ACCELERATION(V) NTSC_STEP_TO_RATE_PER_SECOND(NTSC_STEP_TO_RATE_PER_SECOND(V)) |
||||
#define NTSC_VELOCITY(V) NTSC_STEP_TO_RATE_PER_SECOND(V) |
||||
|
||||
#define PITCH_VELOCITY(V) ((V) * (1.0/16.0)) |
||||
#define YAW_VELOCITY(V) ((V) * (1.0/64.0)) |
||||
#define ROLL_VELOCITY(V) ((V) * (1.0)) |
||||
|
||||
#define SHIP_FLYING_GRAVITY 80000.0 |
||||
#define SHIP_ON_TRACK_GRAVITY 30000.0 |
||||
#define SHIP_MIN_RESISTANCE 20 // 12
|
||||
#define SHIP_MAX_RESISTANCE 74 |
||||
#define SHIP_VELOCITY_SHIFT 6 |
||||
#define SHIP_TRACK_MAGNET 64 // 64
|
||||
#define SHIP_TRACK_FLOAT 256 |
||||
|
||||
#define SHIP_PITCH_ACCEL NTSC_ACCELERATION(ANGLE_NORM_TO_RADIAN(FIXED_TO_FLOAT(PITCH_VELOCITY(30)))) |
||||
#define SHIP_THRUST_RATE NTSC_VELOCITY(16) |
||||
#define SHIP_THRUST_FALLOFF NTSC_VELOCITY(8) |
||||
#define SHIP_BRAKE_RATE NTSC_VELOCITY(32) |
||||
|
||||
typedef struct ship_t { |
||||
int16_t pilot; |
||||
int flags; |
||||
|
||||
section_t *section, *prev_section; |
||||
|
||||
vec3_t dir_forward; |
||||
vec3_t dir_right; |
||||
vec3_t dir_up; |
||||
|
||||
vec3_t position; |
||||
vec3_t velocity; |
||||
vec3_t acceleration; |
||||
vec3_t thrust; |
||||
|
||||
vec3_t angle; |
||||
vec3_t angular_velocity; |
||||
vec3_t angular_acceleration; |
||||
|
||||
vec3_t temp_target; // used for start position and rescue target
|
||||
|
||||
float turn_rate; |
||||
float turn_rate_max; |
||||
float turn_rate_from_hit; |
||||
|
||||
float mass; |
||||
float thrust_mag; |
||||
float thrust_max; |
||||
float current_thrust_max; |
||||
float speed; |
||||
float brake_left; |
||||
float brake_right; |
||||
|
||||
float resistance; |
||||
float skid; |
||||
|
||||
float remote_thrust_mag; |
||||
float remote_thrust_max; |
||||
|
||||
// Remote Ship Attributes
|
||||
int16_t fight_back; |
||||
float start_accelerate_timer; |
||||
|
||||
// Weapon Attributes
|
||||
uint8_t weapon_type; |
||||
struct ship_t *weapon_target; |
||||
|
||||
float ebolt_timer; |
||||
float ebolt_effect_timer; |
||||
float revcon_timer; |
||||
float special_timer; |
||||
|
||||
// Race Control Attributes
|
||||
int position_rank; |
||||
int lap; |
||||
int max_lap; |
||||
float lap_time; |
||||
|
||||
int16_t section_num; |
||||
int16_t prev_section_num; |
||||
int16_t total_section_num; |
||||
|
||||
float update_timer; |
||||
float last_impact_time; |
||||
|
||||
mat4_t mat; |
||||
Object *model; |
||||
Object *collision_model; |
||||
uint16_t shadow_texture; |
||||
|
||||
struct { |
||||
vec3_t *v; |
||||
vec3_t initial; |
||||
} exhaust_plume[3]; |
||||
|
||||
// Control Routines
|
||||
vec3_t (*update_strat_func)(struct ship_t *, track_face_t *); |
||||
void (*update_func)(struct ship_t *); |
||||
|
||||
// Audio
|
||||
sfx_t *sfx_engine_thrust; |
||||
sfx_t *sfx_engine_intake; |
||||
sfx_t *sfx_turbulence; |
||||
sfx_t *sfx_shield; |
||||
} ship_t; |
||||
|
||||
void ships_load(); |
||||
void ships_init(section_t *section); |
||||
void ships_draw(); |
||||
void ships_update(); |
||||
|
||||
void ship_init(ship_t *self, section_t *section, int pilot, int position); |
||||
void ship_init_exhaust_plume(ship_t *self); |
||||
void ship_draw(ship_t *self); |
||||
void ship_draw_shadow(ship_t *self); |
||||
void ship_update(ship_t *self); |
||||
void ship_collide_with_track(ship_t *self, track_face_t *face); |
||||
void ship_collide_with_ship(ship_t *self, ship_t *other); |
||||
|
||||
vec3_t ship_cockpit(ship_t *self); |
||||
vec3_t ship_nose(ship_t *self); |
||||
vec3_t ship_wing_left(ship_t *self); |
||||
vec3_t ship_wing_right(ship_t *self); |
||||
|
||||
|
||||
#endif |
@ -0,0 +1,536 @@ |
||||
#include "../mem.h" |
||||
#include "../input.h" |
||||
#include "../system.h" |
||||
#include "../utils.h" |
||||
|
||||
#include "object.h" |
||||
#include "track.h" |
||||
#include "ship.h" |
||||
#include "weapon.h" |
||||
#include "hud.h" |
||||
#include "droid.h" |
||||
#include "camera.h" |
||||
#include "ship_ai.h" |
||||
#include "game.h" |
||||
|
||||
vec3_t ship_ai_strat_hold_center(ship_t *self, track_face_t *face); |
||||
vec3_t ship_ai_strat_hold_right(ship_t *self, track_face_t *face); |
||||
vec3_t ship_ai_strat_hold_left(ship_t *self, track_face_t *face); |
||||
vec3_t ship_ai_strat_block(ship_t *self, track_face_t *face); |
||||
vec3_t ship_ai_strat_avoid(ship_t *self, track_face_t *face); |
||||
vec3_t ship_ai_strat_avoid_other(ship_t *self, track_face_t *face); |
||||
vec3_t ship_ai_strat_zig_zag(ship_t *self, track_face_t *face); |
||||
|
||||
void ship_ai_update_intro(ship_t *self) { |
||||
self->temp_target = self->position; |
||||
self->update_func = ship_ai_update_intro_await_go; |
||||
|
||||
self->sfx_engine_thrust = sfx_reserve_loop(SFX_ENGINE_REMOTE); |
||||
sfx_set_position(self->sfx_engine_thrust, self->position, self->velocity, 0.1); |
||||
} |
||||
|
||||
void ship_ai_update_intro_await_go(ship_t *self) { |
||||
self->position.y = self->temp_target.y + sin(self->update_timer * (80.0 + self->pilot * 3.0) * 30.0 * M_PI * 2.0 / 4096.0) * 32; |
||||
|
||||
self->update_timer -= system_tick(); |
||||
if (self->update_timer <= UPDATE_TIME_GO) { |
||||
self->update_func = ship_ai_update_race; |
||||
} |
||||
} |
||||
|
||||
vec3_t ship_ai_strat_hold_left(ship_t *self, track_face_t *face) { |
||||
vec3_t fv1 = face->tris[0].vertices[1].pos; |
||||
vec3_t fv2 = face->tris[0].vertices[0].pos; |
||||
|
||||
return vec3_mulf(vec3_sub(fv1, fv2), 0.5); |
||||
} |
||||
|
||||
vec3_t ship_ai_strat_hold_right(ship_t *self, track_face_t *face) { |
||||
vec3_t fv1 = face->tris[0].vertices[0].pos; |
||||
vec3_t fv2 = face->tris[0].vertices[1].pos; |
||||
|
||||
return vec3_mulf(vec3_sub(fv1, fv2), 0.5); |
||||
} |
||||
|
||||
|
||||
vec3_t ship_ai_strat_hold_center(ship_t *self, track_face_t *face) { |
||||
return vec3(0, 0, 0); |
||||
} |
||||
|
||||
vec3_t ship_ai_strat_block(ship_t *self, track_face_t *face) { |
||||
if (flags_is(g.ships[g.pilot].flags, SHIP_LEFT_SIDE)) { |
||||
return ship_ai_strat_hold_left(self, face); |
||||
} |
||||
else { |
||||
return ship_ai_strat_hold_right(self, face); |
||||
} |
||||
|
||||
} |
||||
|
||||
vec3_t ship_ai_strat_avoid(ship_t *self, track_face_t *face) { |
||||
if (flags_is(g.ships[g.pilot].flags, SHIP_LEFT_SIDE)) { |
||||
return ship_ai_strat_hold_right(self, face); |
||||
} |
||||
else { |
||||
return ship_ai_strat_hold_left(self, face); |
||||
} |
||||
} |
||||
|
||||
|
||||
|
||||
vec3_t ship_ai_strat_avoid_other(ship_t *self, track_face_t *face) { |
||||
int min_section_num = 100; |
||||
ship_t *avoid_ship; |
||||
|
||||
for (int i = 0; i < NUM_PILOTS; i++) { |
||||
if (i != self->pilot) { |
||||
int section_diff = g.ships[i].total_section_num - self->total_section_num; |
||||
if (min_section_num < section_diff) { |
||||
min_section_num = section_diff; |
||||
avoid_ship = &g.ships[i]; |
||||
} |
||||
} |
||||
} |
||||
|
||||
if (avoid_ship && min_section_num < 10 && min_section_num > -2) { |
||||
if (flags_is(avoid_ship->flags, SHIP_LEFT_SIDE)) { |
||||
return ship_ai_strat_hold_right(self, face); |
||||
} |
||||
else { |
||||
return ship_ai_strat_hold_left(self, face); |
||||
} |
||||
} |
||||
return vec3(0, 0, 0); |
||||
} |
||||
|
||||
|
||||
|
||||
vec3_t ship_ai_strat_zig_zag(ship_t *self, track_face_t *face) { |
||||
int update_count = (self->update_timer * 30)/50; |
||||
if (update_count % 2) { |
||||
return ship_ai_strat_hold_right(self, face); |
||||
} |
||||
else { |
||||
return ship_ai_strat_hold_left(self, face); |
||||
} |
||||
} |
||||
|
||||
|
||||
void ship_ai_update_race(ship_t *self) { |
||||
vec3_t offset_vector = vec3(0, 0, 0); |
||||
|
||||
ship_t *player = &(g.ships[g.pilot]); |
||||
|
||||
if (self->ebolt_timer > 0) { |
||||
self->ebolt_timer -= system_tick(); |
||||
} |
||||
if (self->ebolt_timer <= 0) { |
||||
flags_rm(self->flags, SHIP_ELECTROED); |
||||
} |
||||
|
||||
int behind_speed = def.circuts[g.circut].settings[g.race_class].behind_speed; |
||||
|
||||
|
||||
if (flags_not(self->flags, SHIP_FLYING)) { |
||||
// Find First track base section
|
||||
track_face_t *face = track_section_get_base_face(self->section); |
||||
|
||||
int section_diff = self->total_section_num - player->total_section_num; |
||||
|
||||
flags_rm(self->flags, SHIP_JUST_IN_FRONT); |
||||
|
||||
if (self == player) { |
||||
self->update_strat_func = ship_ai_strat_avoid_other; |
||||
if (self->remote_thrust_max > self->speed) { |
||||
self->speed += self->remote_thrust_mag * 30 * system_tick(); |
||||
} |
||||
} |
||||
else { |
||||
// Make global DPA decisions , these will effect the craft in
|
||||
// relation to your race position
|
||||
|
||||
// Accelerate remote ships away at start, start_accelerate_count set in
|
||||
// InitShipData and is an exponential progression
|
||||
|
||||
if (self->start_accelerate_timer > 0) { |
||||
self->start_accelerate_timer -= system_tick(); |
||||
self->update_timer = 0; |
||||
self->update_strat_func = ship_ai_strat_avoid; |
||||
if ((self->remote_thrust_max + 1200) > self->speed) { |
||||
self->speed += (self->remote_thrust_mag + 150) * 30 * system_tick(); |
||||
} |
||||
} |
||||
|
||||
|
||||
// Ship has been left WELL BEHIND; set it to avoid
|
||||
// other ships and update its speed as normal
|
||||
|
||||
else if (section_diff < -10) { // Ship behind, AVOID
|
||||
self->update_timer = 0; |
||||
self->update_strat_func = ship_ai_strat_avoid; |
||||
|
||||
// If ship has been well passed, increase its speed to allow
|
||||
// it to make a challenge when the player fouls up
|
||||
|
||||
if (((self->remote_thrust_max + behind_speed) > self->speed)) { |
||||
self->speed += self->remote_thrust_mag * 30 * system_tick(); |
||||
} |
||||
} |
||||
|
||||
|
||||
// Ship is JUST AHEAD
|
||||
|
||||
else if ((section_diff <= 4) && (section_diff > 0)) { // Ship close by, beware does not account for lapped opponents yet
|
||||
flags_add(self->flags, SHIP_JUST_IN_FRONT); |
||||
|
||||
if (self->update_timer <= 0) { // Make New Decision
|
||||
int chance = rand_int(0, 64); // 12
|
||||
|
||||
self->update_timer = UPDATE_TIME_JUST_FRONT; |
||||
if (self->fight_back) { // Ship wants to make life difficult
|
||||
if ((chance < 40) || (self->weapon_type == WEAPON_TYPE_NONE)) { // Ship will try to block you
|
||||
self->update_strat_func = ship_ai_strat_block; |
||||
} |
||||
else if ((chance >= 40) && (chance < 52)) { // Ship will attempt to drop mines in your path
|
||||
self->update_strat_func = ship_ai_strat_block; |
||||
if (flags_not(self->flags, SHIP_SHIELDED) && flags_is(self->flags, SHIP_RACING)) { |
||||
sfx_play(SFX_VOICE_MINES); |
||||
self->weapon_type = WEAPON_TYPE_MINE; |
||||
weapons_fire_delayed(self, self->weapon_type); |
||||
} |
||||
} |
||||
else if ((chance >= 52) && (chance < 64)) { // Ship will raise its shield
|
||||
self->update_strat_func = ship_ai_strat_block; |
||||
if (flags_not(self->flags, SHIP_SHIELDED)) { |
||||
self->weapon_type = WEAPON_TYPE_SHIELD; |
||||
weapons_fire(self, self->weapon_type); |
||||
} |
||||
} |
||||
} |
||||
else { // Let the first ships be easy to pass
|
||||
self->update_strat_func = ship_ai_strat_avoid; |
||||
} |
||||
} |
||||
|
||||
self->update_timer -= system_tick(); |
||||
|
||||
if (flags_is(self->flags, SHIP_OVERTAKEN)) { |
||||
// If ship has just overtaken, slow it down to a reasonable speed
|
||||
if ((self->remote_thrust_max + behind_speed) > self->speed) { |
||||
self->speed += self->remote_thrust_mag * 30 * system_tick(); |
||||
} |
||||
} |
||||
else { |
||||
// Increase the speed of any craft just in front slightly
|
||||
if (((self->remote_thrust_max + (behind_speed >> 1)) > self->speed)) { |
||||
self->speed += self->remote_thrust_mag * 30 * system_tick(); |
||||
} |
||||
} |
||||
|
||||
} |
||||
|
||||
|
||||
// Ship is JUST BEHIND; we must decided if and how many times it 'should have a go back'
|
||||
|
||||
else if ((section_diff >= -10) && (section_diff <= 0)) { // Ship just behind, MAKE DECISION
|
||||
if (self->update_timer <= 0) { // Make New Decision
|
||||
self->update_timer = UPDATE_TIME_JUST_BEHIND; |
||||
|
||||
if (self->fight_back) { // Ship wants you to say "Outside Now!"
|
||||
if (self->weapon_type == WEAPON_TYPE_NONE) { |
||||
self->update_strat_func = ship_ai_strat_avoid; |
||||
flags_add(self->flags, SHIP_OVERTAKEN); |
||||
} |
||||
else { |
||||
int chance = rand_int(0, 64); |
||||
|
||||
if (chance < 48) { |
||||
self->update_strat_func = ship_ai_strat_block; |
||||
} |
||||
else if ((chance >= 40) && (chance < 54)) { |
||||
self->update_strat_func = ship_ai_strat_avoid; |
||||
flags_rm(self->flags, SHIP_OVERTAKEN); |
||||
if (flags_not(self->flags, SHIP_SHIELDED) && flags_is(self->flags, SHIP_RACING)) { |
||||
sfx_play(SFX_VOICE_ROCKETS); |
||||
self->weapon_type = WEAPON_TYPE_ROCKET; |
||||
weapons_fire_delayed(self, self->weapon_type); |
||||
} |
||||
} |
||||
else if ((chance >= 54) && (chance < 60)) { |
||||
self->update_strat_func = ship_ai_strat_avoid; |
||||
flags_rm(self->flags, SHIP_OVERTAKEN); |
||||
if (flags_not(self->flags, SHIP_SHIELDED) && flags_is(self->flags, SHIP_RACING)) { |
||||
sfx_play(SFX_VOICE_MISSILE); |
||||
self->weapon_type = WEAPON_TYPE_MISSILE; |
||||
self->weapon_target = &g.ships[g.pilot]; |
||||
weapons_fire_delayed(self, self->weapon_type); |
||||
} |
||||
} |
||||
else if ((chance >= 60) && (chance < 64)) { |
||||
self->update_strat_func = ship_ai_strat_avoid; |
||||
flags_rm(self->flags, SHIP_OVERTAKEN); |
||||
if (flags_not(self->flags, SHIP_SHIELDED) && flags_is(self->flags, SHIP_RACING)) { |
||||
sfx_play(SFX_VOICE_SHOCKWAVE); |
||||
self->weapon_type = WEAPON_TYPE_EBOLT; |
||||
self->weapon_target = &g.ships[g.pilot]; |
||||
weapons_fire_delayed(self, self->weapon_type); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
else { // If ship destined to be tail-ender then slow down
|
||||
self->remote_thrust_max = 2100 ; |
||||
self->remote_thrust_mag = 25; |
||||
self->speed = 2100 ; |
||||
self->update_strat_func = ship_ai_strat_avoid; |
||||
flags_rm(self->flags, SHIP_OVERTAKEN); |
||||
} |
||||
} |
||||
|
||||
for (int i = 0; i < NUM_PILOTS; i++) { // If another ship is just in front pass fight on
|
||||
if (flags_is(g.ships[i].flags, SHIP_JUST_IN_FRONT)) { |
||||
self->update_strat_func = ship_ai_strat_avoid; |
||||
flags_rm(self->flags, SHIP_OVERTAKEN); |
||||
} |
||||
} |
||||
|
||||
self->update_timer -= system_tick(); |
||||
|
||||
|
||||
if (flags_is(self->flags, SHIP_OVERTAKEN)) { |
||||
if ((self->remote_thrust_max + 700) > self->speed) { |
||||
self->speed += self->remote_thrust_mag * 2 * 30 * system_tick(); |
||||
} |
||||
} |
||||
else { |
||||
if (((self->remote_thrust_max + behind_speed) > self->speed)) { |
||||
self->speed += self->remote_thrust_mag * 30 * system_tick(); |
||||
} |
||||
} |
||||
} |
||||
|
||||
|
||||
// Ship is WELL AHEAD; we must slow the opponent to
|
||||
// give the weaker player a chance to catch up
|
||||
|
||||
else if (section_diff > (NUM_PILOTS - self->position_rank) * 15 && section_diff < 150) { |
||||
self->speed += self->remote_thrust_mag * 0.5 * 30 * system_tick(); |
||||
if (self->speed > self->remote_thrust_max * 0.5) { |
||||
self->speed = self->remote_thrust_max * 0.5; |
||||
} |
||||
|
||||
self->update_timer = 0; |
||||
self->update_strat_func = ship_ai_strat_hold_center; |
||||
} |
||||
|
||||
|
||||
// Ship is TOO FAR AHEAD
|
||||
|
||||
else if (section_diff >= 150) { // Ship too far ahead, let it continue
|
||||
self->update_timer = 0; |
||||
self->update_strat_func = ship_ai_strat_avoid; |
||||
|
||||
if ((self->remote_thrust_max > self->speed)) { |
||||
self->speed += self->remote_thrust_mag * 30 * system_tick(); |
||||
} |
||||
} |
||||
|
||||
|
||||
// Ship is IN SIGHT
|
||||
|
||||
else if ((section_diff <= 10) && (section_diff > 4)) { // Ship close by, beware does not account for lapped opponents yet
|
||||
if (self->update_timer <= 0) { // Make New Decision
|
||||
int chance = rand_int(0, 5); |
||||
|
||||
self->update_timer = UPDATE_TIME_IN_SIGHT; |
||||
switch (chance) { |
||||
case 0: self->update_strat_func = ship_ai_strat_hold_center; break; |
||||
case 1: self->update_strat_func = ship_ai_strat_hold_left; break; |
||||
case 2: self->update_strat_func = ship_ai_strat_hold_right; break; |
||||
case 3: self->update_strat_func = ship_ai_strat_block; break; |
||||
case 4: self->update_strat_func = ship_ai_strat_zig_zag; break; |
||||
} |
||||
} |
||||
|
||||
self->update_timer -= system_tick(); |
||||
|
||||
if ((self->remote_thrust_max > self->speed)) { |
||||
self->speed += self->remote_thrust_mag * 30 * system_tick(); |
||||
} |
||||
} // End of DPA control options
|
||||
|
||||
|
||||
// Ship is JUST OUT OF SIGHT
|
||||
|
||||
else { |
||||
self->update_timer = 0; |
||||
self->update_strat_func = ship_ai_strat_hold_center; |
||||
if ((self->remote_thrust_max > self->speed)) { |
||||
self->speed += self->remote_thrust_mag * 30 * system_tick(); |
||||
} |
||||
} |
||||
} |
||||
|
||||
if (!self->update_strat_func) { |
||||
self->update_strat_func = ship_ai_strat_hold_center; |
||||
} |
||||
offset_vector = (self->update_strat_func)(self, face); |
||||
|
||||
|
||||
// Make decision as to which path the craft will take at a junction
|
||||
|
||||
section_t *section = self->section->prev; |
||||
|
||||
for (int i = 0; i < 4; i++) { |
||||
section = section->next; |
||||
} |
||||
|
||||
if (section->junction) { |
||||
if (flags_is(section->junction->flags, SECTION_JUNCTION_START)) { |
||||
int chance = rand_int(0, 2); |
||||
if (chance == 0) { |
||||
flags_add(self->flags, SHIP_JUNCTION_LEFT); |
||||
} |
||||
else { |
||||
flags_rm(self->flags, SHIP_JUNCTION_LEFT); |
||||
} |
||||
} |
||||
} |
||||
|
||||
section = self->section->prev; |
||||
|
||||
for (int i = 0; i < 4; i++) { |
||||
if (section->junction) { |
||||
if (flags_is(section->junction->flags, SECTION_JUNCTION_START)) { |
||||
if (flags_is(self->flags, SHIP_JUNCTION_LEFT)) { |
||||
section = section->junction; |
||||
} |
||||
else { |
||||
section = section->next; |
||||
} |
||||
} |
||||
else { |
||||
section = section->next; |
||||
} |
||||
} |
||||
else { |
||||
section = section->next; |
||||
} |
||||
} |
||||
section_t *next = section->next; |
||||
section = self->section; |
||||
|
||||
|
||||
|
||||
|
||||
// General routines - Non decision based
|
||||
|
||||
|
||||
// Bleed off speed as orientation changes
|
||||
|
||||
self->speed -= fabsf(self->speed * self->angular_velocity.y) * 4 / (M_PI * 2) * system_tick(); // >> 14
|
||||
self->speed -= fabsf(self->speed * self->angular_velocity.x) * 4 / (M_PI * 2) * system_tick(); // >> 14
|
||||
|
||||
// If remote has gone over boost
|
||||
if (flags_is(face->flags, FACE_BOOST) && (self->update_strat_func == ship_ai_strat_hold_left || self->update_strat_func == ship_ai_strat_hold_center)) { |
||||
self->speed += 200 * 30 * system_tick(); |
||||
} |
||||
face++; |
||||
if (flags_is(face->flags, FACE_BOOST) && (self->update_strat_func == ship_ai_strat_hold_right || self->update_strat_func == ship_ai_strat_hold_center)) { |
||||
self->speed += 200 * 30 * system_tick(); |
||||
} |
||||
|
||||
vec3_t track_target; |
||||
if (flags_is(self->section->flags, SECTION_JUMP)) { // Cure for ships getting stuck on hump lip
|
||||
track_target = vec3_sub(self->section->center, self->section->prev->center); |
||||
} |
||||
else { |
||||
track_target = vec3_sub(next->center, section->center); |
||||
} |
||||
|
||||
float gap_length = vec3_len(track_target); |
||||
track_target = vec3_mulf(track_target, self->speed / gap_length); |
||||
|
||||
vec3_t path1 = vec3_add(section->center, offset_vector); |
||||
vec3_t path2 = vec3_add(next->center, offset_vector); |
||||
|
||||
vec3_t best_path = vec3_project_to_ray(self->position, path2, path1); |
||||
self->acceleration = vec3_add(track_target, vec3_mulf(vec3_sub(best_path, self->position), 0.5)); |
||||
|
||||
|
||||
vec3_t face_point = face->tris[0].vertices[0].pos; |
||||
float height = vec3_distance_to_plane(self->position, face_point, face->normal); |
||||
|
||||
if (height < 50) { |
||||
height = 50; |
||||
} |
||||
|
||||
self->acceleration = vec3_add(self->acceleration, vec3_mulf(vec3_sub( |
||||
vec3_mulf(face->normal, (SHIP_TRACK_FLOAT * SHIP_TRACK_MAGNET) / height), |
||||
vec3_mulf(face->normal, SHIP_TRACK_MAGNET) |
||||
), 16.0)); |
||||
self->velocity = vec3_add(self->velocity, vec3_mulf(self->acceleration, 30 * system_tick())); |
||||
|
||||
|
||||
float xy_dist = sqrt(track_target.x * track_target.x + track_target.z * track_target.z); |
||||
|
||||
self->angular_velocity.x = wrap_angle(-atan2(track_target.y, xy_dist) - self->angle.x) * (1.0/16.0) * 30; |
||||
self->angular_velocity.y = (wrap_angle(-atan2(track_target.x, track_target.z) - self->angle.y) * (1.0/16.0)) * 30 + self->turn_rate_from_hit; |
||||
} |
||||
|
||||
|
||||
// Ship is SHIP_FLYING
|
||||
|
||||
else { |
||||
section_t *section = self->section->next->next; |
||||
section_t *next = section->next; |
||||
|
||||
self->update_strat_func = ship_ai_strat_hold_center; |
||||
offset_vector = (self->update_strat_func)(self, NULL); |
||||
|
||||
if (self->remote_thrust_max > self->speed) { |
||||
self->speed += self->remote_thrust_mag ; |
||||
} |
||||
|
||||
self->speed -= fabsf(self->speed * self->angular_velocity.y) * (4 * M_PI * 2) * system_tick(); |
||||
vec3_t track_target = vec3_sub(next->center, section->center); |
||||
float gap_length = vec3_len(track_target); |
||||
|
||||
track_target.x = (track_target.x * self->speed) / gap_length; |
||||
track_target.z = (track_target.z * self->speed) / gap_length; |
||||
|
||||
track_target.y = 500; |
||||
|
||||
vec3_t best_path = vec3_project_to_ray(self->position, next->center, self->section->center); |
||||
self->acceleration = vec3( |
||||
(track_target.x + ((best_path.x - self->position.x) * 0.5)), |
||||
track_target.y, |
||||
(track_target.z + ((best_path.z - self->position.z) * 0.5)) |
||||
); |
||||
self->velocity = vec3_add(self->velocity, vec3_mulf(self->acceleration, 30 * system_tick())); |
||||
|
||||
self->angular_velocity.x = -0.3 - self->angle.x * 30; |
||||
self->angular_velocity.y = wrap_angle(-atan2(track_target.x, track_target.z) - self->angle.y) * (1.0/16.0) * 30; |
||||
} |
||||
|
||||
|
||||
self->angular_velocity.z += (self->angular_velocity.y * 2.0 - self->angular_velocity.z * 0.5) * 30 * system_tick(); |
||||
self->turn_rate_from_hit -= self->turn_rate_from_hit * 0.125 * 30 * system_tick(); |
||||
|
||||
self->angle = vec3_add(self->angle, vec3_mulf(self->angular_velocity, system_tick())); |
||||
self->angle.z -= self->angle.z * 0.125 * 30 * system_tick(); |
||||
self->angle = vec3_wrap_angle(self->angle); |
||||
|
||||
self->velocity = vec3_sub(self->velocity, vec3_mulf(self->velocity, 0.125 * 30 * system_tick())); |
||||
self->position = vec3_add(self->position, vec3_mulf(self->velocity, 0.015625 * 30 * system_tick())); |
||||
|
||||
if (flags_is(self->flags, SHIP_ELECTROED)) { |
||||
self->position = vec3_add(self->position, vec3(rand_float(-20, 20), rand_float(-20, 20), rand_float(-20, 20))); |
||||
|
||||
if (rand_int(0, 50) == 0) { |
||||
self->speed -= self->speed * 0.5 * 30 * system_tick(); |
||||
} |
||||
} |
||||
|
||||
sfx_set_position(self->sfx_engine_thrust, self->position, self->velocity, 0.5); |
||||
} |
@ -0,0 +1,14 @@ |
||||
#ifndef SHIP_AI_H |
||||
#define SHIP_AI_H |
||||
|
||||
#include "ship.h" |
||||
|
||||
#define UPDATE_TIME_JUST_FRONT (150.0 * (1.0/30.0)) |
||||
#define UPDATE_TIME_JUST_BEHIND (200.0 * (1.0/30.0)) |
||||
#define UPDATE_TIME_IN_SIGHT (200.0 * (1.0/30.0)) |
||||
|
||||
void ship_ai_update_race(ship_t *self); |
||||
void ship_ai_update_intro(ship_t *self); |
||||
void ship_ai_update_intro_await_go(ship_t *self); |
||||
|
||||
#endif |
@ -0,0 +1,582 @@ |
||||
#include <stdint.h> |
||||
#include <math.h> |
||||
|
||||
#include <stdio.h> |
||||
#include <stdint.h> |
||||
#include <stdlib.h> |
||||
|
||||
#include "../mem.h" |
||||
#include "object.h" |
||||
#include "track.h" |
||||
#include "ship.h" |
||||
#include "weapon.h" |
||||
#include "hud.h" |
||||
#include "droid.h" |
||||
#include "camera.h" |
||||
#include "../utils.h" |
||||
#include "scene.h" |
||||
|
||||
|
||||
#include "../input.h" |
||||
#include "../system.h" |
||||
|
||||
#include "sfx.h" |
||||
#include "ship_player.h" |
||||
#include "ship_ai.h" |
||||
#include "game.h" |
||||
#include "particle.h" |
||||
|
||||
void ship_player_update_sfx(ship_t *self) { |
||||
float speedf = self->speed * 0.000015; |
||||
self->sfx_engine_intake->volume = clamp(speedf, 0, 0.5); |
||||
self->sfx_engine_intake->pitch = 0.5 + speedf * 1.25; |
||||
|
||||
self->sfx_engine_thrust->volume = 0.05 + 0.025 * (self->thrust_mag / self->thrust_max); |
||||
self->sfx_engine_thrust->pitch = 0.2 + 0.5 * (self->thrust_mag / self->thrust_max) + speedf; |
||||
|
||||
float brake_left = self->brake_left * 0.0035; |
||||
float brake_right = self->brake_right * 0.0035; |
||||
self->sfx_turbulence->volume = (speedf * brake_left + speedf * brake_right) * 0.5; |
||||
self->sfx_turbulence->pan = (brake_right - brake_left); |
||||
|
||||
self->sfx_shield->volume = flags_is(self->flags, SHIP_SHIELDED) ? 0.5 : 0; |
||||
} |
||||
|
||||
void ship_player_update_intro(ship_t *self) { |
||||
self->temp_target = self->position; |
||||
|
||||
self->sfx_engine_thrust = sfx_reserve_loop(SFX_ENGINE_THRUST); |
||||
self->sfx_engine_intake = sfx_reserve_loop(SFX_ENGINE_INTAKE); |
||||
self->sfx_shield = sfx_reserve_loop(SFX_SHIELD); |
||||
self->sfx_turbulence = sfx_reserve_loop(SFX_TURBULENCE); |
||||
|
||||
ship_player_update_intro_general(self); |
||||
self->update_func = ship_player_update_intro_await_three; |
||||
} |
||||
|
||||
void ship_player_update_intro_await_three(ship_t *self) { |
||||
ship_player_update_intro_general(self); |
||||
|
||||
if (self->update_timer <= UPDATE_TIME_THREE) { |
||||
sfx_t *sfx = sfx_play(SFX_VOICE_COUNT_3); |
||||
self->update_func = ship_player_update_intro_await_two; |
||||
} |
||||
} |
||||
|
||||
void ship_player_update_intro_await_two(ship_t *self) { |
||||
ship_player_update_intro_general(self);
|
||||
|
||||
if (self->update_timer <= UPDATE_TIME_TWO) { |
||||
scene_set_start_booms(1); |
||||
sfx_t *sfx = sfx_play(SFX_VOICE_COUNT_2); |
||||
self->update_func = ship_player_update_intro_await_one; |
||||
} |
||||
} |
||||
|
||||
void ship_player_update_intro_await_one(ship_t *self) { |
||||
ship_player_update_intro_general(self); |
||||
|
||||
if (self->update_timer <= UPDATE_TIME_ONE) { |
||||
scene_set_start_booms(2); |
||||
sfx_t *sfx = sfx_play(SFX_VOICE_COUNT_1); |
||||
self->update_func = ship_player_update_intro_await_go; |
||||
} |
||||
} |
||||
|
||||
void ship_player_update_intro_await_go(ship_t *self) { |
||||
ship_player_update_intro_general(self); |
||||
|
||||
if (self->update_timer <= UPDATE_TIME_GO) { |
||||
scene_set_start_booms(3); |
||||
sfx_t *sfx = sfx_play(SFX_VOICE_COUNT_GO); |
||||
|
||||
if (flags_is(self->flags, SHIP_RACING)) { |
||||
// Check for stall
|
||||
if (self->thrust_mag >= 680 && self->thrust_mag <= 700) { |
||||
self->thrust_mag = 1800; |
||||
self->current_thrust_max = 1800; |
||||
} |
||||
else if (self->thrust_mag < 680) { |
||||
self->current_thrust_max = self->thrust_max; |
||||
} |
||||
else { |
||||
self->current_thrust_max = 200; |
||||
} |
||||
|
||||
self->update_timer = UPDATE_TIME_STALL; |
||||
self->update_func = ship_player_update_race; |
||||
} |
||||
else { |
||||
self->update_func = ship_ai_update_race; |
||||
} |
||||
} |
||||
} |
||||
|
||||
void ship_player_update_intro_general(ship_t *self) { |
||||
self->update_timer -= system_tick(); |
||||
self->position.y = self->temp_target.y + sin(self->update_timer * 80.0 * 30.0 * M_PI * 2.0 / 4096.0) * 32; |
||||
|
||||
// Thrust
|
||||
if (input_state(A_THRUST)) { |
||||
self->thrust_mag += input_state(A_THRUST) * SHIP_THRUST_RATE * system_tick(); |
||||
} |
||||
else { |
||||
self->thrust_mag -= SHIP_THRUST_RATE * system_tick(); |
||||
} |
||||
|
||||
self->thrust_mag = clamp(self->thrust_mag, 0, self->thrust_max); |
||||
|
||||
// View
|
||||
if (input_pressed(A_CHANGE_VIEW)) { |
||||
if (flags_not(self->flags, SHIP_VIEW_INTERNAL)) { |
||||
g.camera.update_func = camera_update_race_internal; |
||||
flags_add(self->flags, SHIP_VIEW_INTERNAL); |
||||
} |
||||
else { |
||||
g.camera.update_func = camera_update_race_external; |
||||
flags_rm(self->flags, SHIP_VIEW_INTERNAL); |
||||
} |
||||
} |
||||
|
||||
ship_player_update_sfx(self); |
||||
} |
||||
|
||||
|
||||
void ship_player_update_race(ship_t *self) { |
||||
if (flags_not(self->flags, SHIP_RACING)) { |
||||
self->update_func = ship_ai_update_race; |
||||
return; |
||||
} |
||||
|
||||
if (self->ebolt_timer > 0) { |
||||
self->ebolt_timer -= system_tick(); |
||||
} |
||||
|
||||
if (self->ebolt_timer <= 0) { |
||||
flags_rm(self->flags, SHIP_ELECTROED); |
||||
} |
||||
|
||||
if (self->revcon_timer > 0) { |
||||
self->revcon_timer -= system_tick(); |
||||
} |
||||
|
||||
if (self->revcon_timer <= 0) { |
||||
flags_rm(self->flags, SHIP_REVCONNED); |
||||
} |
||||
|
||||
if (self->special_timer > 0) { |
||||
self->special_timer -= system_tick(); |
||||
} |
||||
|
||||
if (self->special_timer <= 0) { |
||||
flags_rm(self->flags, SHIP_SPECIALED); |
||||
} |
||||
|
||||
if (flags_is(self->flags, SHIP_REVCONNED)) { |
||||
// FIXME_PL: make sure revconned is honored
|
||||
} |
||||
|
||||
self->angular_acceleration = vec3(0, 0, 0); |
||||
|
||||
if (input_state(A_LEFT)) { |
||||
if (self->angular_velocity.y >= 0) { |
||||
self->angular_acceleration.y += input_state(A_LEFT) * self->turn_rate; |
||||
} |
||||
else if (self->angular_velocity.y < 0) { |
||||
self->angular_acceleration.y += input_state(A_LEFT) * self->turn_rate * 2; |
||||
} |
||||
} |
||||
else if (input_state(A_RIGHT)) { |
||||
if (self->angular_velocity.y <= 0) { |
||||
self->angular_acceleration.y -= input_state(A_RIGHT) * self->turn_rate; |
||||
} |
||||
else if (self->angular_velocity.y > 0) { |
||||
self->angular_acceleration.y -= input_state(A_RIGHT) * self->turn_rate * 2; |
||||
} |
||||
} |
||||
|
||||
if (flags_is(self->flags, SHIP_ELECTROED)) { |
||||
self->ebolt_effect_timer += system_tick(); |
||||
// Yank the ship every 0.1 seconds
|
||||
if (self->ebolt_effect_timer > 0.1) { |
||||
if (flags_is(self->flags, SHIP_VIEW_INTERNAL)) { |
||||
// SetShake(2); // FIXME
|
||||
} |
||||
self->angular_velocity.y += rand_float(-0.5, 0.5); // FIXME: 60fps
|
||||
self->ebolt_effect_timer -= 0.1; |
||||
} |
||||
} |
||||
|
||||
self->angular_acceleration.x += input_state(A_DOWN) * SHIP_PITCH_ACCEL; |
||||
self->angular_acceleration.x -= input_state(A_UP) * SHIP_PITCH_ACCEL; |
||||
|
||||
// Handle Stall
|
||||
if (self->update_timer > 0) { |
||||
if (self->current_thrust_max < 500) { |
||||
self->current_thrust_max += rand_float(0, 165) * system_tick(); |
||||
} |
||||
self->update_timer -= system_tick(); |
||||
} |
||||
else { |
||||
// End stall / boost
|
||||
self->current_thrust_max = self->thrust_max; |
||||
} |
||||
|
||||
// Thrust
|
||||
if (input_state(A_THRUST)) { |
||||
self->thrust_mag += input_state(A_THRUST) * SHIP_THRUST_RATE * system_tick(); |
||||
} |
||||
else { |
||||
self->thrust_mag -= SHIP_THRUST_FALLOFF * system_tick(); |
||||
} |
||||
self->thrust_mag = clamp(self->thrust_mag, 0, self->current_thrust_max); |
||||
|
||||
if (flags_is(self->flags, SHIP_ELECTROED) && rand_int(0, 80) == 0) { |
||||
self->thrust_mag -= self->thrust_mag * 0.25; // FIXME: 60fps
|
||||
} |
||||
|
||||
// Brake
|
||||
if (input_state(A_BRAKE_RIGHT)) { |
||||
self->brake_right += SHIP_BRAKE_RATE * system_tick(); |
||||
} |
||||
else if (self->brake_right > 0) { |
||||
self->brake_right -= SHIP_BRAKE_RATE * system_tick(); |
||||
} |
||||
self->brake_right = clamp(self->brake_right, 0, 256); |
||||
|
||||
if (input_state(A_BRAKE_LEFT)) { |
||||
self->brake_left += SHIP_BRAKE_RATE * system_tick(); |
||||
} |
||||
else if (self->brake_left > 0) { |
||||
self->brake_left -= SHIP_BRAKE_RATE * system_tick(); |
||||
} |
||||
self->brake_left = clamp(self->brake_left, 0, 256); |
||||
|
||||
// View
|
||||
if (input_pressed(A_CHANGE_VIEW)) { |
||||
if (flags_not(self->flags, SHIP_VIEW_INTERNAL)) { |
||||
g.camera.update_func = camera_update_race_internal; |
||||
flags_add(self->flags, SHIP_VIEW_INTERNAL); |
||||
} |
||||
else { |
||||
g.camera.update_func = camera_update_race_external; |
||||
flags_rm(self->flags, SHIP_VIEW_INTERNAL); |
||||
} |
||||
} |
||||
|
||||
if (self->weapon_type == WEAPON_TYPE_MISSILE || self->weapon_type == WEAPON_TYPE_EBOLT) { |
||||
self->weapon_target = ship_player_find_target(self); |
||||
} |
||||
else { |
||||
self->weapon_target = NULL; |
||||
} |
||||
|
||||
// Fire
|
||||
// self->weapon_type = WEAPON_TYPE_MISSILE; // Test weapon
|
||||
|
||||
if (input_pressed(A_FIRE) && self->weapon_type != WEAPON_TYPE_NONE) { |
||||
if (flags_not(self->flags, SHIP_SHIELDED)) { |
||||
weapons_fire(self, self->weapon_type); |
||||
} |
||||
else { |
||||
sfx_play(SFX_MENU_MOVE); |
||||
} |
||||
} |
||||
|
||||
|
||||
// Physics
|
||||
|
||||
// Calculate thrust vector along principle axis of ship
|
||||
self->thrust = vec3_mulf(self->dir_forward, self->thrust_mag * 64); |
||||
self->speed = vec3_len(self->velocity); |
||||
vec3_t forward_velocity = vec3_mulf(self->dir_forward, self->speed); |
||||
|
||||
// SECTION_JUMP
|
||||
if (flags_is(self->section->flags, SECTION_JUMP)) { |
||||
track_face_t *face = track_section_get_base_face(self->section); |
||||
|
||||
// Project the ship's position to the track section using the face normal.
|
||||
// If the point lands on the track, the sum of the angles between the
|
||||
// point and the track vertices will be M_PI*2.
|
||||
// If it's less then M_PI*2 (minus a safety margin) we are flying!
|
||||
|
||||
vec3_t face_point = face->tris[0].vertices[0].pos; |
||||
float height = vec3_distance_to_plane(self->position, face_point, face->normal); |
||||
vec3_t plane_point = vec3_sub(self->position, vec3_mulf(face->normal, height)); |
||||
|
||||
vec3_t vec0 = vec3_sub(plane_point, face->tris[0].vertices[1].pos); |
||||
vec3_t vec1 = vec3_sub(plane_point, face->tris[0].vertices[2].pos); |
||||
face++; |
||||
vec3_t vec2 = vec3_sub(plane_point, face->tris[0].vertices[0].pos); |
||||
vec3_t vec3 = vec3_sub(plane_point, face->tris[1].vertices[0].pos); |
||||
|
||||
float angle =
|
||||
vec3_angle(vec0, vec2) + |
||||
vec3_angle(vec2, vec3) + |
||||
vec3_angle(vec3, vec1) + |
||||
vec3_angle(vec1, vec0); |
||||
if (angle < M_PI * 2 - 0.01) { |
||||
flags_add(self->flags, SHIP_FLYING); |
||||
} |
||||
} |
||||
|
||||
// Held by track
|
||||
if (flags_not(self->flags, SHIP_FLYING)) { |
||||
track_face_t *face = track_section_get_base_face(self->section); |
||||
ship_collide_with_track(self, face); |
||||
|
||||
if (flags_not(self->flags, SHIP_LEFT_SIDE)) { |
||||
face++; |
||||
} |
||||
|
||||
// Boost
|
||||
if (flags_not(self->flags, SHIP_SPECIALED) && flags_is(face->flags, FACE_BOOST)) { |
||||
vec3_t track_direction = vec3_sub(self->section->next->center, self->section->center); |
||||
self->velocity = vec3_add(self->velocity, vec3_mulf(track_direction, 30 * system_tick())); |
||||
} |
||||
|
||||
vec3_t face_point = face->tris[0].vertices[0].pos; |
||||
float height = vec3_distance_to_plane(self->position, face_point, face->normal); |
||||
|
||||
// Collision with floor
|
||||
if (height <= 0) { |
||||
if (self->last_impact_time > 0.2) { |
||||
self->last_impact_time = 0; |
||||
sfx_play_at(SFX_IMPACT, self->position, vec3(0,0,0), 1); |
||||
} |
||||
self->velocity = vec3_reflect(self->velocity, face->normal, 2); |
||||
self->velocity = vec3_sub(self->velocity, vec3_mulf(self->velocity, 0.125)); |
||||
self->velocity = vec3_sub(self->velocity, face->normal); |
||||
} |
||||
else if (height < 30) { |
||||
self->velocity = vec3_add(self->velocity, face->normal); |
||||
} |
||||
|
||||
if (height < 50) { |
||||
height = 50; |
||||
} |
||||
|
||||
// Calculate acceleration
|
||||
float brake = (self->brake_left + self->brake_right); |
||||
float resistance = (self->resistance * (SHIP_MAX_RESISTANCE - (brake * 0.125))) * 0.0078125; |
||||
|
||||
vec3_t force = vec3(0, SHIP_ON_TRACK_GRAVITY, 0); |
||||
force = vec3_add(force, vec3_mulf(vec3_mulf(face->normal, 4096), (SHIP_TRACK_MAGNET * SHIP_TRACK_FLOAT) / height)); |
||||
force = vec3_sub(force, vec3_mulf(vec3_mulf(face->normal, 4096), SHIP_TRACK_MAGNET)); |
||||
force = vec3_add(force, self->thrust); |
||||
|
||||
self->acceleration = vec3_divf(vec3_sub(forward_velocity, self->velocity), self->skid + brake * 0.25); |
||||
self->acceleration = vec3_add(self->acceleration, vec3_divf(force, self->mass)); |
||||
self->acceleration = vec3_sub(self->acceleration, vec3_divf(self->velocity, resistance)); |
||||
|
||||
// Burying the nose in the track? Move it out!
|
||||
vec3_t nose_pos = vec3_add(self->position, vec3_mulf(self->dir_forward, 128)); |
||||
float nose_height = vec3_distance_to_plane(nose_pos,face_point, face->normal); |
||||
if (nose_height < 600) { |
||||
self->angular_acceleration.x += NTSC_ACCELERATION(ANGLE_NORM_TO_RADIAN(FIXED_TO_FLOAT((height - nose_height + 5) * (1.0/16.0)))); |
||||
} |
||||
else { |
||||
self->angular_acceleration.x += NTSC_ACCELERATION(ANGLE_NORM_TO_RADIAN(FIXED_TO_FLOAT(-50.0/16.0))); |
||||
} |
||||
} |
||||
|
||||
// Flying
|
||||
else { |
||||
// Detect the need for a rescue droid
|
||||
section_t *next = self->section->next; |
||||
|
||||
vec3_t best_path = vec3_project_to_ray(self->position, next->center, self->section->center); |
||||
vec3_t distance = vec3_sub(best_path, self->position); |
||||
|
||||
if (distance.y > 0) { |
||||
distance.y = distance.y * 0.0001; |
||||
} |
||||
else { |
||||
distance = vec3_mulf(distance, 8); |
||||
} |
||||
|
||||
// Do we need to be rescued?
|
||||
if (vec3_len(distance) > 8000) { |
||||
self->update_func = ship_player_update_rescue; |
||||
self->update_timer = UPDATE_TIME_RESCUE; |
||||
flags_add(self->flags, SHIP_IN_RESCUE | SHIP_FLYING); |
||||
|
||||
section_t *landing = self->section->prev; |
||||
for (int i = 0; i < 3; i++) { |
||||
landing = landing->prev; |
||||
} |
||||
for (int i = 0; i < 10 && flags_not(landing->flags, SECTION_JUMP); i++) { |
||||
landing = landing->next; |
||||
} |
||||
self->section = landing; |
||||
self->temp_target = vec3_mulf(vec3_add(landing->center, landing->next->center), 0.5); |
||||
self->temp_target.y -= 2000; |
||||
self->velocity = vec3(0, 0, 0); |
||||
} |
||||
|
||||
|
||||
float brake = (self->brake_left + self->brake_right); |
||||
float resistance = (self->resistance * (SHIP_MAX_RESISTANCE - (brake * 0.125))) * 0.0078125; |
||||
|
||||
vec3_t force = vec3(0, SHIP_FLYING_GRAVITY, 0); |
||||
force = vec3_add(force, self->thrust); |
||||
|
||||
self->acceleration = vec3_divf(vec3_sub(forward_velocity, self->velocity), SHIP_MIN_RESISTANCE + brake * 4); |
||||
self->acceleration = vec3_add(self->acceleration, vec3_divf(force, self->mass)); |
||||
self->acceleration = vec3_sub(self->acceleration, vec3_divf(self->velocity, resistance)); |
||||
|
||||
self->angular_acceleration.x += NTSC_ACCELERATION(ANGLE_NORM_TO_RADIAN(FIXED_TO_FLOAT(-50.0/16.0))); |
||||
} |
||||
|
||||
// Position
|
||||
self->velocity = vec3_add(self->velocity, vec3_mulf(self->acceleration, 30 * system_tick())); |
||||
self->position = vec3_add(self->position, vec3_mulf(self->velocity, 0.015625 * 30 * system_tick())); |
||||
|
||||
self->angular_acceleration.x -= self->angular_velocity.x * 0.25 * 30; |
||||
self->angular_acceleration.z += (self->angular_velocity.y - 0.5 * self->angular_velocity.z) * 30; |
||||
|
||||
|
||||
// Orientation
|
||||
if (self->angular_acceleration.y == 0) { |
||||
if (self->angular_velocity.y > 0) { |
||||
self->angular_acceleration.y -= min(self->turn_rate, self->angular_velocity.y / system_tick()); |
||||
} |
||||
else if (self->angular_velocity.y < 0) { |
||||
self->angular_acceleration.y += min(self->turn_rate, -self->angular_velocity.y / system_tick()); |
||||
} |
||||
} |
||||
|
||||
self->angular_velocity = vec3_add(self->angular_velocity, vec3_mulf(self->angular_acceleration, system_tick())); |
||||
self->angular_velocity.y = clamp(self->angular_velocity.y, -self->turn_rate_max, self->turn_rate_max); |
||||
|
||||
float brake_dir = (self->brake_left - self->brake_right) * (0.125 / 4096.0); |
||||
self->angle.y += brake_dir * self->speed * 0.000030517578125 * M_PI * 2 * 30 * system_tick(); |
||||
|
||||
self->angle = vec3_add(self->angle, vec3_mulf(self->angular_velocity, system_tick())); |
||||
self->angle.z -= self->angle.z * 0.125 * 30 * system_tick(); |
||||
self->angle = vec3_wrap_angle(self->angle); |
||||
|
||||
// Prevent ship from going past the landing position of a SECTION_JUMP if going backwards.
|
||||
if (flags_not(self->flags, SHIP_DIRECTION_FORWARD) && flags_is(self->section->prev->flags, SECTION_JUMP)) { |
||||
vec3_t repulse = vec3_sub(self->section->next->center, self->section->center); |
||||
self->velocity = vec3_add(self->velocity, vec3_mulf(repulse, 2)); |
||||
} |
||||
|
||||
ship_player_update_sfx(self); |
||||
} |
||||
|
||||
|
||||
void ship_player_update_rescue(ship_t *self) { |
||||
|
||||
section_t *next = self->section->next; |
||||
|
||||
if (flags_is(self->flags, SHIP_IN_TOW)) { |
||||
self->temp_target = vec3_add(self->temp_target, vec3_mulf(vec3_sub(next->center, self->temp_target), 0.0078125)); |
||||
self->velocity = vec3_sub(self->temp_target, self->position); |
||||
vec3_t target_dir = vec3_sub(next->center, self->section->center); |
||||
|
||||
self->angular_velocity.y = wrap_angle(-atan2(target_dir.x, target_dir.z) - self->angle.y) * (1.0/16.0) * 30; |
||||
self->angle.y = wrap_angle(self->angle.y + self->angular_velocity.y * system_tick()); |
||||
} |
||||
|
||||
self->angle.x -= self->angle.x * 0.125 * 30 * system_tick(); |
||||
self->angle.z -= self->angle.z * 0.03125 * 30 * system_tick(); |
||||
|
||||
self->velocity = vec3_sub(self->velocity, vec3_mulf(self->velocity, 0.0625 * 30 * system_tick())); |
||||
self->position = vec3_add(self->position, vec3_mulf(self->velocity, 0.03125 * 30 * system_tick())); |
||||
|
||||
|
||||
// Are we done being rescued?
|
||||
float distance = vec3_len(vec3_sub(self->position, self->temp_target)); |
||||
if (flags_is(self->flags, SHIP_IN_TOW) && distance < 800) { |
||||
self->update_func = ship_player_update_race; |
||||
self->update_timer = 0; |
||||
flags_rm(self->flags, SHIP_IN_RESCUE); |
||||
flags_rm(self->flags, SHIP_VIEW_REMOTE); |
||||
|
||||
if (flags_is(self->flags, SHIP_VIEW_INTERNAL)) { |
||||
g.camera.update_func = camera_update_race_internal; |
||||
} |
||||
else { |
||||
g.camera.update_func = camera_update_race_external; |
||||
} |
||||
} |
||||
} |
||||
|
||||
|
||||
ship_t *ship_player_find_target(ship_t *self) { |
||||
int shortest_distance = 256; |
||||
ship_t *nearest_ship = NULL; |
||||
|
||||
for (int i = 0; i < len(g.ships); i++) { |
||||
ship_t *other = &g.ships[i]; |
||||
if (self == other) { |
||||
continue; |
||||
} |
||||
|
||||
// We are on a branch
|
||||
if (flags_is(self->section->flags, SECTION_JUNCTION)) { |
||||
// Other ship is on same branch
|
||||
if (other->section->flags & SECTION_JUNCTION) { |
||||
int distance = other->section->num - self->section->num; |
||||
|
||||
if (distance < shortest_distance && distance > 0) { |
||||
shortest_distance = distance; |
||||
nearest_ship = other; |
||||
} |
||||
} |
||||
|
||||
// Other ship is not on branch
|
||||
else { |
||||
section_t *section = self->section; |
||||
for (int distance = 0; distance < 10; distance++) { |
||||
section = section->next; |
||||
if (other->section == section && distance < shortest_distance && distance > 0) { |
||||
shortest_distance = distance; |
||||
nearest_ship = other; |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
// We are not on a branch
|
||||
else { |
||||
// Other ship is on a branch - check if we can reach the other ship's section
|
||||
if (flags_is(other->section->flags, SECTION_JUNCTION)) { |
||||
section_t *section = self->section; |
||||
for (int distance = 0; distance < 10; distance++) { |
||||
if (section->junction) { |
||||
section = section->junction; |
||||
} |
||||
else { |
||||
section = section->next; |
||||
} |
||||
if (other->section == section && distance < shortest_distance && distance > 0) { |
||||
shortest_distance = distance; |
||||
nearest_ship = other; |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Other ship is not on a branch
|
||||
else { |
||||
int distance = other->section->num - self->section->num; |
||||
|
||||
if (distance < shortest_distance && distance > 0) { |
||||
shortest_distance = distance; |
||||
nearest_ship = other; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
if (shortest_distance < 10) { |
||||
return nearest_ship; |
||||
} |
||||
else { |
||||
return NULL; |
||||
} |
||||
} |
||||
|
@ -0,0 +1,20 @@ |
||||
#ifndef SHIP_PLAYER_H |
||||
#define SHIP_PLAYER_H |
||||
|
||||
#include "ship.h" |
||||
|
||||
#define UPDATE_TIME_RESCUE (500.0 * (1.0/30.0)) |
||||
#define UPDATE_TIME_STALL (90.0 * (1.0/30.0)) |
||||
|
||||
void ship_player_update_intro(ship_t *self); |
||||
void ship_player_update_intro_await_three(ship_t *self); |
||||
void ship_player_update_intro_await_two(ship_t *self); |
||||
void ship_player_update_intro_await_one(ship_t *self); |
||||
void ship_player_update_intro_await_go(ship_t *self); |
||||
void ship_player_update_intro_general(ship_t *self); |
||||
void ship_player_update_race(ship_t *self); |
||||
void ship_player_update_rescue(ship_t *self); |
||||
|
||||
ship_t *ship_player_find_target(ship_t *self); |
||||
|
||||
#endif |
@ -0,0 +1,45 @@ |
||||
#include "../system.h" |
||||
#include "../input.h" |
||||
#include "../utils.h" |
||||
|
||||
#include "title.h" |
||||
#include "ui.h" |
||||
#include "image.h" |
||||
#include "game.h" |
||||
|
||||
static uint16_t title_image; |
||||
static float start_time; |
||||
static bool has_shown_attract = false; |
||||
|
||||
void title_init() { |
||||
title_image = image_get_texture("wipeout/textures/wiptitle.tim"); |
||||
start_time = system_time(); |
||||
sfx_music_mode(SFX_MUSIC_RANDOM); |
||||
} |
||||
|
||||
void title_update() { |
||||
render_set_view_2d(); |
||||
render_push_2d(vec2i(0, 0), render_size(), rgba(128, 128, 128, 255), title_image); |
||||
ui_draw_text_centered("PRESS ENTER", ui_scaled_pos(UI_POS_BOTTOM | UI_POS_CENTER, vec2i(0, -40)), UI_SIZE_8, UI_COLOR_DEFAULT); |
||||
|
||||
|
||||
if (input_pressed(A_MENU_SELECT) || input_pressed(A_MENU_START)) { |
||||
sfx_play(SFX_MENU_SELECT); |
||||
game_set_scene(GAME_SCENE_MAIN_MENU); |
||||
} |
||||
|
||||
float duration = system_time() - start_time; |
||||
if ( |
||||
(has_shown_attract && duration > 5) || |
||||
(duration > 10) |
||||
) { |
||||
sfx_music_mode(SFX_MUSIC_RANDOM); |
||||
has_shown_attract = true; |
||||
g.is_attract_mode = true; |
||||
g.pilot = rand_int(0, len(def.pilots)); |
||||
g.circut = rand_int(0, NUM_NON_BONUS_CIRCUTS); |
||||
g.race_class = rand_int(0, NUM_RACE_CLASSES); |
||||
g.race_type = RACE_TYPE_SINGLE; |
||||
game_set_scene(GAME_SCENE_RACE); |
||||
} |
||||
} |
@ -0,0 +1,8 @@ |
||||
#ifndef TITLE_H |
||||
#define TITLE_H |
||||
|
||||
void title_init(); |
||||
void title_update(); |
||||
void title_cleanup(); |
||||
|
||||
#endif |
@ -0,0 +1,387 @@ |
||||
#include "../mem.h" |
||||
#include "../utils.h" |
||||
#include "../render.h" |
||||
#include "../system.h" |
||||
|
||||
#include "object.h" |
||||
#include "track.h" |
||||
#include "camera.h" |
||||
#include "object.h" |
||||
#include "game.h" |
||||
|
||||
void track_load(const char *base_path) { |
||||
// Load and assemble high res track tiles
|
||||
|
||||
g.track.textures.start = render_textures_len(); |
||||
g.track.textures.len = 0; |
||||
|
||||
ttf_t *ttf = track_load_tile_format(get_path(base_path, "library.ttf")); |
||||
cmp_t *cmp = image_load_compressed(get_path(base_path, "library.cmp")); |
||||
|
||||
image_t *temp_tile = image_alloc(128, 128); |
||||
for (int i = 0; i < ttf->len; i++) { |
||||
for (int tx = 0; tx < 4; tx++) { |
||||
for (int ty = 0; ty < 4; ty++) { |
||||
uint32_t sub_tile_index = ttf->tiles[i].near[ty * 4 + tx]; |
||||
image_t *sub_tile = image_load_from_bytes(cmp->entries[sub_tile_index], false); |
||||
image_copy(sub_tile, temp_tile, 0, 0, 32, 32, tx * 32, ty * 32); |
||||
mem_temp_free(sub_tile); |
||||
} |
||||
} |
||||
render_texture_create(temp_tile->width, temp_tile->height, temp_tile->pixels); |
||||
g.track.textures.len++; |
||||
} |
||||
|
||||
mem_temp_free(temp_tile); |
||||
mem_temp_free(cmp); |
||||
mem_temp_free(ttf); |
||||
|
||||
vec3_t *vertices = track_load_vertices(get_path(base_path, "track.trv")); |
||||
track_load_faces(get_path(base_path, "track.trf"), vertices); |
||||
mem_temp_free(vertices); |
||||
|
||||
track_load_sections(get_path(base_path, "track.trs")); |
||||
|
||||
g.track.pickups_len = 0; |
||||
section_t *s = g.track.sections; |
||||
section_t *j = NULL; |
||||
|
||||
// Nummerate all sections; take care to give both stretches at a junction
|
||||
// the same numbers.
|
||||
int num = 0; |
||||
do { |
||||
s->num = num++; |
||||
if (s->junction) { // start junction
|
||||
j = s->junction; |
||||
do { |
||||
j->num = num++; |
||||
j = j->next; |
||||
} while (!j->junction); // end junction
|
||||
num = s->num; |
||||
} |
||||
s = s->next; |
||||
} while (s != g.track.sections); |
||||
g.track.total_section_nums = num; |
||||
|
||||
g.track.pickups = mem_mark(); |
||||
for (int i = 0; i < g.track.section_count; i++) { |
||||
track_face_t *face = track_section_get_base_face(&g.track.sections[i]); |
||||
|
||||
for (int f = 0; f < 2; f++) { |
||||
if (flags_any(face->flags, FACE_PICKUP_RIGHT | FACE_PICKUP_LEFT)) { |
||||
mem_bump(sizeof(track_pickup_t)); |
||||
g.track.pickups[g.track.pickups_len].face = face; |
||||
g.track.pickups[g.track.pickups_len].cooldown_timer = 0; |
||||
g.track.pickups_len++; |
||||
} |
||||
|
||||
if (flags_is(face->flags, FACE_BOOST)) { |
||||
track_face_set_color(face, rgba(0, 0, 255, 255)); |
||||
} |
||||
face++; |
||||
} |
||||
|
||||
error_if(g.track.pickups_len > TRACK_PICKUPS_MAX-1, "Track %s exceeds TRACK_PICKUPS_MAX", base_path); |
||||
} |
||||
} |
||||
|
||||
ttf_t *track_load_tile_format(char *ttf_name) { |
||||
uint32_t ttf_size; |
||||
uint8_t *ttf_bytes = file_load(ttf_name, &ttf_size); |
||||
|
||||
uint32_t p = 0; |
||||
uint32_t num_tiles = ttf_size / 42; |
||||
|
||||
ttf_t *ttf = mem_temp_alloc(sizeof(ttf_t) + sizeof(ttf_tile_t) * num_tiles); |
||||
ttf->len = num_tiles; |
||||
|
||||
for (int t = 0; t < num_tiles; t++) { |
||||
for (int i = 0; i < 16; i++) { |
||||
ttf->tiles[t].near[i] = get_i16(ttf_bytes, &p); |
||||
} |
||||
for (int i = 0; i < 4; i++) { |
||||
ttf->tiles[t].med[i] = get_i16(ttf_bytes, &p); |
||||
} |
||||
ttf->tiles[t].far = get_i16(ttf_bytes, &p); |
||||
} |
||||
mem_temp_free(ttf_bytes); |
||||
|
||||
return ttf; |
||||
} |
||||
|
||||
bool track_collect_pickups(track_face_t *face) { |
||||
if (flags_is(face->flags, FACE_PICKUP_ACTIVE)) { |
||||
flags_rm(face->flags, FACE_PICKUP_ACTIVE); |
||||
flags_add(face->flags, FACE_PICKUP_COLLECTED); |
||||
track_face_set_color(face, rgba(255, 255, 255, 255)); |
||||
return true; |
||||
} |
||||
else { |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
vec3_t *track_load_vertices(char *file_name) { |
||||
uint32_t size; |
||||
uint8_t *bytes = file_load(file_name, &size); |
||||
|
||||
g.track.vertex_count = size / 16; // VECTOR_SIZE
|
||||
vec3_t *vertices = mem_temp_alloc(sizeof(vec3_t) * g.track.vertex_count); |
||||
|
||||
uint32_t p = 0; |
||||
for (int i = 0; i < g.track.vertex_count; i++) { |
||||
vertices[i].x = get_i32(bytes, &p); |
||||
vertices[i].y = get_i32(bytes, &p); |
||||
vertices[i].z = get_i32(bytes, &p); |
||||
p += 4; // padding
|
||||
} |
||||
|
||||
mem_temp_free(bytes); |
||||
return vertices; |
||||
} |
||||
|
||||
static const vec2_t track_uv[2][4] = { |
||||
{{128, 0}, { 0, 0}, { 0, 128}, {128, 128}}, |
||||
{{ 0, 0}, {128, 0}, {128, 128}, { 0, 128}} |
||||
}; |
||||
|
||||
void track_load_faces(char *file_name, vec3_t *vertices) { |
||||
uint32_t size; |
||||
uint8_t *bytes = file_load(file_name, &size); |
||||
|
||||
g.track.face_count = size / 20; // TRACK_FACE_DATA_SIZE
|
||||
g.track.faces = mem_bump(sizeof(track_face_t) * g.track.face_count); |
||||
|
||||
uint32_t p = 0; |
||||
track_face_t *tf = g.track.faces; |
||||
|
||||
|
||||
for (int i = 0; i < g.track.face_count; i++) { |
||||
|
||||
vec3_t v0 = vertices[get_i16(bytes, &p)]; |
||||
vec3_t v1 = vertices[get_i16(bytes, &p)]; |
||||
vec3_t v2 = vertices[get_i16(bytes, &p)]; |
||||
vec3_t v3 = vertices[get_i16(bytes, &p)]; |
||||
tf->normal.x = (float)get_i16(bytes, &p) / 4096.0; |
||||
tf->normal.y = (float)get_i16(bytes, &p) / 4096.0; |
||||
tf->normal.z = (float)get_i16(bytes, &p) / 4096.0; |
||||
|
||||
tf->texture = get_i8(bytes, &p); |
||||
tf->flags = get_i8(bytes, &p); |
||||
|
||||
rgba_t color = {.as_uint32 = get_i32_le(bytes, &p) | 0xff000000}; |
||||
const vec2_t *uv = track_uv[flags_is(tf->flags, FACE_FLIP_TEXTURE) ? 1 : 0]; |
||||
|
||||
tf->tris[0] = (tris_t){ |
||||
.vertices = { |
||||
{.pos = v0, .uv = uv[0], .color = color}, |
||||
{.pos = v1, .uv = uv[1], .color = color}, |
||||
{.pos = v2, .uv = uv[2], .color = color}, |
||||
} |
||||
}; |
||||
tf->tris[1] = (tris_t){ |
||||
.vertices = { |
||||
{.pos = v3, .uv = uv[3], .color = color}, |
||||
{.pos = v0, .uv = uv[0], .color = color}, |
||||
{.pos = v2, .uv = uv[2], .color = color}, |
||||
} |
||||
}; |
||||
|
||||
tf++; |
||||
} |
||||
|
||||
mem_temp_free(bytes); |
||||
} |
||||
|
||||
|
||||
void track_load_sections(char *file_name) { |
||||
uint32_t size; |
||||
uint8_t *bytes = file_load(file_name, &size); |
||||
|
||||
g.track.section_count = size / 156; // SECTION_DATA_SIZE
|
||||
g.track.sections = mem_bump(sizeof(section_t) * g.track.section_count); |
||||
|
||||
uint32_t p = 0; |
||||
section_t *ts = g.track.sections; |
||||
for (int i = 0; i < g.track.section_count; i++) { |
||||
int32_t junction_index = get_i32(bytes, &p); |
||||
if (junction_index != -1) { |
||||
ts->junction = g.track.sections + junction_index; |
||||
} |
||||
else { |
||||
ts->junction = NULL; |
||||
} |
||||
|
||||
ts->prev = g.track.sections + get_i32(bytes, &p); |
||||
ts->next = g.track.sections + get_i32(bytes, &p); |
||||
|
||||
ts->center.x = get_i32(bytes, &p); |
||||
ts->center.y = get_i32(bytes, &p); |
||||
ts->center.z = get_i32(bytes, &p); |
||||
|
||||
int16_t version = get_i16(bytes, &p); |
||||
error_if(version != TRACK_VERSION, "Convert track with track10: section: %d Track: %d\n", version, TRACK_VERSION); |
||||
p += 2; // padding
|
||||
|
||||
p += 4 + 4; // objects pointer, objectCount
|
||||
p += 5 * 3 * 4; // view section pointers
|
||||
p += 5 * 3 * 2; // view section counts
|
||||
|
||||
for (int j = 0; j < 4; j++) { |
||||
ts->high[j] = get_i16(bytes, &p); |
||||
} |
||||
for (int j = 0; j < 4; j++) { |
||||
ts->med[j] = get_i16(bytes, &p); |
||||
} |
||||
|
||||
ts->face_start = get_i16(bytes, &p); |
||||
ts->face_count = get_i16(bytes, &p); |
||||
|
||||
p += 2 * 2; // global/local radius
|
||||
|
||||
ts->flags = get_i16(bytes, &p); |
||||
ts->num = get_i16(bytes, &p); |
||||
p += 2; // padding
|
||||
ts++; |
||||
} |
||||
|
||||
mem_temp_free(bytes); |
||||
} |
||||
|
||||
|
||||
|
||||
|
||||
void track_draw_section(section_t *section) { |
||||
track_face_t *face = g.track.faces + section->face_start; |
||||
int16_t face_count = section->face_count; |
||||
|
||||
|
||||
for (uint32_t j = 0; j < face_count; j++) { |
||||
uint16_t tex_index = texture_from_list(g.track.textures, face->texture); |
||||
render_push_tris(face->tris[0], tex_index); |
||||
render_push_tris(face->tris[1], tex_index); |
||||
face++; |
||||
} |
||||
} |
||||
|
||||
void track_draw(camera_t *camera) {
|
||||
render_set_model_mat(&mat4_identity());
|
||||
|
||||
float max_dist_sq = RENDER_FADEOUT_FAR * RENDER_FADEOUT_FAR; |
||||
vec3_t cam_pos = camera->position; |
||||
|
||||
section_t *s = g.track.sections; |
||||
section_t *j = NULL; |
||||
do { |
||||
vec3_t d = vec3_sub(cam_pos, s->center); |
||||
float dist_sq = d.x * d.x + d.y * d.y + d.z * d.z; |
||||
if (dist_sq < max_dist_sq) { |
||||
track_draw_section(s); |
||||
} |
||||
|
||||
if (s->junction) { // start junction
|
||||
j = s->junction; |
||||
do { |
||||
vec3_t d = vec3_sub(cam_pos, j->center); |
||||
float dist_sq = d.x * d.x + d.y * d.y + d.z * d.z; |
||||
if (dist_sq < max_dist_sq) { |
||||
track_draw_section(j); |
||||
} |
||||
j = j->next; |
||||
} while (!j->junction); // end junction
|
||||
} |
||||
s = s->next; |
||||
} while (s != g.track.sections); |
||||
|
||||
} |
||||
|
||||
void track_cycle_pickups() { |
||||
float pickup_cycle_time = 1.5 * system_cycle_time(); |
||||
|
||||
for (int i = 0; i < g.track.pickups_len; i++) { |
||||
if (flags_is(g.track.pickups[i].face->flags, FACE_PICKUP_COLLECTED)) { |
||||
flags_rm(g.track.pickups[i].face->flags, FACE_PICKUP_COLLECTED); |
||||
g.track.pickups[i].cooldown_timer = TRACK_PICKUP_COOLDOWN_TIME; |
||||
} |
||||
else if (g.track.pickups[i].cooldown_timer <= 0) { |
||||
flags_add(g.track.pickups[i].face->flags, FACE_PICKUP_ACTIVE); |
||||
track_face_set_color(g.track.pickups[i].face, rgba( |
||||
sin( pickup_cycle_time + i) * 127 + 128, |
||||
cos( pickup_cycle_time + i) * 127 + 128, |
||||
sin(-pickup_cycle_time - i) * 127 + 128, |
||||
255 |
||||
)); |
||||
} |
||||
else{ |
||||
g.track.pickups[i].cooldown_timer -= system_tick(); |
||||
} |
||||
} |
||||
} |
||||
|
||||
void track_face_set_color(track_face_t *face, rgba_t color) { |
||||
face->tris[0].vertices[0].color = color; |
||||
face->tris[0].vertices[1].color = color; |
||||
face->tris[0].vertices[2].color = color; |
||||
|
||||
face->tris[1].vertices[0].color = color; |
||||
face->tris[1].vertices[1].color = color; |
||||
face->tris[1].vertices[2].color = color; |
||||
} |
||||
|
||||
track_face_t *track_section_get_base_face(section_t *section) { |
||||
track_face_t *face = g.track.faces +section->face_start; |
||||
while(flags_not(face->flags, FACE_TRACK_BASE)) { |
||||
face++; |
||||
} |
||||
return face; |
||||
} |
||||
|
||||
section_t *track_nearest_section(vec3_t pos, section_t *section, float *distance) { |
||||
// Start search several sections before current section
|
||||
|
||||
for (int i = 0; i < TRACK_SEARCH_LOOK_BACK; i++) { |
||||
section = section->prev; |
||||
} |
||||
|
||||
// Find vector from ship center to track section under
|
||||
// consideration
|
||||
float shortest_distance = 1000000000.0; |
||||
section_t *nearest_section = section; |
||||
section_t *junction = NULL; |
||||
for (int i = 0; i < TRACK_SEARCH_LOOK_AHEAD; i++) { |
||||
if (section->junction) { |
||||
junction = section->junction; |
||||
} |
||||
|
||||
float d = vec3_len(vec3_sub(pos, section->center)); |
||||
if (d < shortest_distance) { |
||||
shortest_distance = d; |
||||
nearest_section = section; |
||||
} |
||||
|
||||
section = section->next; |
||||
} |
||||
|
||||
if (junction) { |
||||
section = junction; |
||||
for (int i = 0; i < TRACK_SEARCH_LOOK_AHEAD; i++) { |
||||
float d = vec3_len(vec3_sub(pos, section->center)); |
||||
if (d < shortest_distance) { |
||||
shortest_distance = d; |
||||
nearest_section = section; |
||||
} |
||||
|
||||
if (flags_is(junction->flags, SECTION_JUNCTION_START)) { |
||||
section = section->next; |
||||
} |
||||
else { |
||||
section = section->prev; |
||||
} |
||||
} |
||||
} |
||||
|
||||
if (distance != NULL) { |
||||
*distance = shortest_distance; |
||||
} |
||||
return nearest_section; |
||||
} |
@ -0,0 +1,104 @@ |
||||
#ifndef TRACK_H |
||||
#define TRACK_H |
||||
|
||||
|
||||
#include "../types.h" |
||||
#include "object.h" |
||||
#include "image.h" |
||||
|
||||
#define TRACK_VERSION 8 |
||||
|
||||
#define TRACK_VERTS_MAX 4096 |
||||
#define TRACK_FACES_MAX 3072 |
||||
#define TRACK_SECTIONS_MAX 1024 |
||||
#define TRACK_PICKUPS_MAX 64 |
||||
|
||||
#define TRACK_PICKUP_COOLDOWN_TIME 1 |
||||
|
||||
#define TRACK_SEARCH_LOOK_BACK 3 |
||||
#define TRACK_SEARCH_LOOK_AHEAD 6 |
||||
|
||||
typedef struct track_face_t { |
||||
tris_t tris[2]; |
||||
vec3_t normal; |
||||
uint8_t flags; |
||||
uint8_t texture; |
||||
} track_face_t; |
||||
|
||||
#define FACE_TRACK_BASE (1<<0) |
||||
#define FACE_PICKUP_LEFT (1<<1) |
||||
#define FACE_FLIP_TEXTURE (1<<2) |
||||
#define FACE_PICKUP_RIGHT (1<<3) |
||||
#define FACE_START_GRID (1<<4) |
||||
#define FACE_BOOST (1<<5) |
||||
#define FACE_PICKUP_COLLECTED (1<<6) |
||||
#define FACE_PICKUP_ACTIVE (1<<7) |
||||
|
||||
typedef struct { |
||||
uint16_t near[16]; |
||||
uint16_t med[4]; |
||||
uint16_t far; |
||||
} ttf_tile_t; |
||||
|
||||
typedef struct { |
||||
uint32_t len; |
||||
ttf_tile_t tiles[]; |
||||
} ttf_t; |
||||
|
||||
typedef struct section_t { |
||||
struct section_t *junction; |
||||
struct section_t *prev; |
||||
struct section_t *next; |
||||
|
||||
vec3_t center; |
||||
|
||||
int16_t high[4]; |
||||
int16_t med[4]; |
||||
|
||||
int16_t face_start; |
||||
int16_t face_count; |
||||
|
||||
int16_t flags; |
||||
int16_t num; |
||||
} section_t; |
||||
|
||||
#define SECTION_JUMP 1 |
||||
#define SECTION_JUNCTION_END 8 |
||||
#define SECTION_JUNCTION_START 16 |
||||
#define SECTION_JUNCTION 32 |
||||
|
||||
typedef struct { |
||||
track_face_t *face; |
||||
float cooldown_timer; |
||||
} track_pickup_t; |
||||
|
||||
typedef struct track_t { |
||||
int32_t vertex_count; |
||||
int32_t face_count; |
||||
int32_t section_count; |
||||
int32_t pickups_len; |
||||
int32_t total_section_nums; |
||||
texture_list_t textures; |
||||
|
||||
track_face_t *faces; |
||||
section_t *sections; |
||||
track_pickup_t *pickups; |
||||
} track_t; |
||||
|
||||
|
||||
void track_load(const char *base_path); |
||||
ttf_t *track_load_tile_format(char *ttf_name); |
||||
vec3_t *track_load_vertices(char *file); |
||||
void track_load_faces(char *file, vec3_t *vertices); |
||||
void track_load_sections(char *file); |
||||
bool track_collect_pickups(track_face_t *face); |
||||
void track_face_set_color(track_face_t *face, rgba_t color); |
||||
track_face_t *track_section_get_base_face(section_t *section); |
||||
section_t *track_nearest_section(vec3_t pos, section_t *section, float *distance); |
||||
|
||||
struct camera_t; |
||||
void track_draw(struct camera_t *camera); |
||||
|
||||
void track_cycle_pickups(); |
||||
|
||||
#endif |
@ -0,0 +1,213 @@ |
||||
#include "../render.h" |
||||
#include "../utils.h" |
||||
|
||||
#include "ui.h" |
||||
#include "image.h" |
||||
|
||||
typedef struct { |
||||
vec2i_t offset; |
||||
uint16_t width; |
||||
} glyph_t; |
||||
|
||||
typedef struct { |
||||
uint16_t texture; |
||||
uint16_t height; |
||||
glyph_t glyphs[40]; |
||||
} char_set_t; |
||||
|
||||
int ui_scale = 2; |
||||
|
||||
char_set_t char_set[UI_SIZE_MAX] = { |
||||
[UI_SIZE_16] = { |
||||
.texture = 0, |
||||
.height = 16, |
||||
.glyphs = { |
||||
{{ 0, 0}, 25}, {{ 25, 0}, 24}, {{ 49, 0}, 17}, {{ 66, 0}, 24}, {{ 90, 0}, 24}, {{114, 0}, 17}, {{131, 0}, 25}, {{156, 0}, 18}, |
||||
{{174, 0}, 7}, {{181, 0}, 17}, {{ 0, 16}, 17}, {{ 17, 16}, 17}, {{ 34, 16}, 28}, {{ 62, 16}, 17}, {{ 79, 16}, 24}, {{103, 16}, 24}, |
||||
{{127, 16}, 26}, {{153, 16}, 24}, {{177, 16}, 18}, {{195, 16}, 17}, {{ 0, 32}, 17}, {{ 17, 32}, 17}, {{ 34, 32}, 29}, {{ 63, 32}, 24}, |
||||
{{ 87, 32}, 17}, {{104, 32}, 18}, {{122, 32}, 24}, {{146, 32}, 10}, {{156, 32}, 18}, {{174, 32}, 17}, {{191, 32}, 18}, {{ 0, 48}, 18}, |
||||
{{ 18, 48}, 18}, {{ 36, 48}, 18}, {{ 54, 48}, 22}, {{ 76, 48}, 25}, {{101, 48}, 7}, {{108, 48}, 7}, {{198, 0}, 0}, {{198, 0}, 0} |
||||
} |
||||
}, |
||||
[UI_SIZE_12] = { |
||||
.texture = 0, |
||||
.height = 12, |
||||
.glyphs = { |
||||
{{ 0, 0}, 19}, {{ 19, 0}, 19}, {{ 38, 0}, 14}, {{ 52, 0}, 19}, {{ 71, 0}, 19}, {{ 90, 0}, 13}, {{103, 0}, 19}, {{122, 0}, 14}, |
||||
{{136, 0}, 6}, {{142, 0}, 13}, {{155, 0}, 14}, {{169, 0}, 14}, {{ 0, 12}, 22}, {{ 22, 12}, 14}, {{ 36, 12}, 19}, {{ 55, 12}, 18}, |
||||
{{ 73, 12}, 20}, {{ 93, 12}, 19}, {{112, 12}, 15}, {{127, 12}, 14}, {{141, 12}, 13}, {{154, 12}, 13}, {{167, 12}, 22}, {{ 0, 24}, 19}, |
||||
{{ 19, 24}, 13}, {{ 32, 24}, 14}, {{ 46, 24}, 19}, {{ 65, 24}, 8}, {{ 73, 24}, 15}, {{ 88, 24}, 13}, {{101, 24}, 14}, {{115, 24}, 15}, |
||||
{{130, 24}, 14}, {{144, 24}, 15}, {{159, 24}, 18}, {{177, 24}, 19}, {{196, 24}, 5}, {{201, 24}, 5}, {{183, 0}, 0}, {{183, 0}, 0} |
||||
} |
||||
}, |
||||
[UI_SIZE_8] = { |
||||
.texture = 0, |
||||
.height = 8, |
||||
.glyphs = { |
||||
{{ 0, 0}, 13}, {{ 13, 0}, 13}, {{ 26, 0}, 10}, {{ 36, 0}, 13}, {{ 49, 0}, 13}, {{ 62, 0}, 9}, {{ 71, 0}, 13}, {{ 84, 0}, 10}, |
||||
{{ 94, 0}, 4}, {{ 98, 0}, 9}, {{107, 0}, 10}, {{117, 0}, 10}, {{127, 0}, 16}, {{143, 0}, 10}, {{153, 0}, 13}, {{166, 0}, 13}, |
||||
{{179, 0}, 14}, {{ 0, 8}, 13}, {{ 13, 8}, 10}, {{ 23, 8}, 9}, {{ 32, 8}, 9}, {{ 41, 8}, 9}, {{ 50, 8}, 16}, {{ 66, 8}, 14}, |
||||
{{ 80, 8}, 9}, {{ 89, 8}, 10}, {{ 99, 8}, 13}, {{112, 8}, 6}, {{118, 8}, 11}, {{129, 8}, 10}, {{139, 8}, 10}, {{149, 8}, 11}, |
||||
{{160, 8}, 10}, {{170, 8}, 10}, {{180, 8}, 12}, {{192, 8}, 14}, {{206, 8}, 4}, {{210, 8}, 4}, {{193, 0}, 0}, {{193, 0}, 0} |
||||
} |
||||
}, |
||||
}; |
||||
|
||||
uint16_t icon_textures[UI_ICON_MAX]; |
||||
|
||||
void ui_load() { |
||||
texture_list_t tl = image_get_compressed_textures("wipeout/textures/drfonts.cmp"); |
||||
char_set[UI_SIZE_16].texture = texture_from_list(tl, 0); |
||||
char_set[UI_SIZE_12].texture = texture_from_list(tl, 1); |
||||
char_set[UI_SIZE_8 ].texture = texture_from_list(tl, 2); |
||||
icon_textures[UI_ICON_HAND] = texture_from_list(tl, 3); |
||||
icon_textures[UI_ICON_CONFIRM] = texture_from_list(tl, 5); |
||||
icon_textures[UI_ICON_CANCEL] = texture_from_list(tl, 6); |
||||
icon_textures[UI_ICON_END] = texture_from_list(tl, 7); |
||||
icon_textures[UI_ICON_DEL] = texture_from_list(tl, 8); |
||||
icon_textures[UI_ICON_STAR] = texture_from_list(tl, 9); |
||||
} |
||||
|
||||
int ui_get_scale() { |
||||
return ui_scale; |
||||
} |
||||
|
||||
void ui_set_scale(int scale) { |
||||
ui_scale = scale; |
||||
} |
||||
|
||||
|
||||
vec2i_t ui_scaled(vec2i_t v) { |
||||
return vec2i(v.x * ui_scale, v.y * ui_scale); |
||||
} |
||||
|
||||
vec2i_t ui_scaled_screen() { |
||||
return vec2i_mulf(render_size(), ui_scale); |
||||
} |
||||
|
||||
vec2i_t ui_scaled_pos(ui_pos_t anchor, vec2i_t offset) { |
||||
vec2i_t pos; |
||||
vec2i_t screen_size = render_size(); |
||||
|
||||
if (flags_is(anchor, UI_POS_LEFT)) { |
||||
pos.x = offset.x * ui_scale; |
||||
} |
||||
else if (flags_is(anchor, UI_POS_CENTER)) { |
||||
pos.x = (screen_size.x >> 1) + offset.x * ui_scale; |
||||
} |
||||
else if (flags_is(anchor, UI_POS_RIGHT)) { |
||||
pos.x = screen_size.x + offset.x * ui_scale; |
||||
} |
||||
|
||||
if (flags_is(anchor, UI_POS_TOP)) { |
||||
pos.y = offset.y * ui_scale; |
||||
} |
||||
else if (flags_is(anchor, UI_POS_MIDDLE)) { |
||||
pos.y = (screen_size.y >> 1) + offset.y * ui_scale; |
||||
} |
||||
else if (flags_is(anchor, UI_POS_BOTTOM)) { |
||||
pos.y = screen_size.y + offset.y * ui_scale; |
||||
} |
||||
|
||||
return pos; |
||||
} |
||||
|
||||
#define char_to_glyph_index(C) (C >= '0' && C <= '9' ? (C - '0' + 26) : C - 'A') |
||||
|
||||
int ui_char_width(char c, ui_text_size_t size) { |
||||
if (c == ' ') { |
||||
return 8; |
||||
} |
||||
return char_set[size].glyphs[char_to_glyph_index(c)].width; |
||||
} |
||||
|
||||
int ui_text_width(const char *text, ui_text_size_t size) { |
||||
int width = 0; |
||||
char_set_t *cs = &char_set[size]; |
||||
|
||||
for (int i = 0; text[i] != 0; i++) { |
||||
width += text[i] != ' ' |
||||
? cs->glyphs[char_to_glyph_index(text[i])].width |
||||
: 8; |
||||
} |
||||
|
||||
return width; |
||||
} |
||||
|
||||
int ui_number_width(int num, ui_text_size_t size) { |
||||
char text_buffer[16]; |
||||
text_buffer[15] = '\0'; |
||||
|
||||
int i; |
||||
for (i = 14; i > 0; i--) { |
||||
text_buffer[i] = '0' + (num % 10); |
||||
num = num / 10; |
||||
if (num == 0) { |
||||
break; |
||||
} |
||||
} |
||||
return ui_text_width(text_buffer + i, size); |
||||
} |
||||
|
||||
void ui_draw_time(float time, vec2i_t pos, ui_text_size_t size, rgba_t color) { |
||||
int msec = time * 1000; |
||||
int tenths = (msec / 100) % 10; |
||||
int secs = (msec / 1000) % 60; |
||||
int mins = msec / (60 * 1000); |
||||
|
||||
char text_buffer[8]; |
||||
text_buffer[0] = '0' + (mins / 10) % 10; |
||||
text_buffer[1] = '0' + mins % 10; |
||||
text_buffer[2] = 'e'; // ":"
|
||||
text_buffer[3] = '0' + secs / 10; |
||||
text_buffer[4] = '0' + secs % 10; |
||||
text_buffer[5] = 'f'; // "."
|
||||
text_buffer[6] = '0' + tenths; |
||||
text_buffer[7] = '\0'; |
||||
ui_draw_text(text_buffer, pos, size, color); |
||||
} |
||||
|
||||
void ui_draw_number(int num, vec2i_t pos, ui_text_size_t size, rgba_t color) { |
||||
char text_buffer[16]; |
||||
text_buffer[15] = '\0'; |
||||
|
||||
int i; |
||||
for (i = 14; i > 0; i--) { |
||||
text_buffer[i] = '0' + (num % 10); |
||||
num = num / 10; |
||||
if (num == 0) { |
||||
break; |
||||
} |
||||
} |
||||
ui_draw_text(text_buffer + i, pos, size, color); |
||||
} |
||||
|
||||
void ui_draw_text(const char *text, vec2i_t pos, ui_text_size_t size, rgba_t color) { |
||||
char_set_t *cs = &char_set[size]; |
||||
|
||||
for (int i = 0; text[i] != 0; i++) { |
||||
if (text[i] != ' ') { |
||||
glyph_t *glyph = &cs->glyphs[char_to_glyph_index(text[i])]; |
||||
vec2i_t size = vec2i(glyph->width, cs->height); |
||||
render_push_2d_tile(pos, glyph->offset, size, ui_scaled(size), color, cs->texture); |
||||
pos.x += glyph->width * ui_scale; |
||||
} |
||||
else { |
||||
pos.x += 8 * ui_scale; |
||||
} |
||||
} |
||||
} |
||||
|
||||
void ui_draw_image(vec2i_t pos, uint16_t texture) { |
||||
vec2i_t scaled_size = ui_scaled(render_texture_size(texture)); |
||||
render_push_2d(pos, scaled_size, rgba(128, 128, 128, 255), texture); |
||||
} |
||||
|
||||
void ui_draw_icon(ui_icon_type_t icon, vec2i_t pos, rgba_t color) { |
||||
render_push_2d(pos, ui_scaled(render_texture_size(icon_textures[icon])), color, icon_textures[icon]); |
||||
} |
||||
|
||||
void ui_draw_text_centered(const char *text, vec2i_t pos, ui_text_size_t size, rgba_t color) { |
||||
pos.x -= (ui_text_width(text, size) * ui_scale) >> 1; |
||||
ui_draw_text(text, pos, size, color); |
||||
} |
@ -0,0 +1,56 @@ |
||||
#ifndef UI_H |
||||
#define UI_H |
||||
|
||||
#include "../types.h" |
||||
|
||||
typedef enum { |
||||
UI_SIZE_16, |
||||
UI_SIZE_12, |
||||
UI_SIZE_8, |
||||
UI_SIZE_MAX |
||||
} ui_text_size_t; |
||||
|
||||
#define UI_COLOR_ACCENT rgba(123, 98, 12, 255) |
||||
#define UI_COLOR_DEFAULT rgba(128, 128, 128, 255) |
||||
|
||||
typedef enum { |
||||
UI_ICON_HAND, |
||||
UI_ICON_CONFIRM, |
||||
UI_ICON_CANCEL, |
||||
UI_ICON_END, |
||||
UI_ICON_DEL, |
||||
UI_ICON_STAR, |
||||
UI_ICON_MAX |
||||
} ui_icon_type_t; |
||||
|
||||
typedef enum { |
||||
UI_POS_LEFT = 1 << 0, |
||||
UI_POS_CENTER = 1 << 1, |
||||
UI_POS_RIGHT = 1 << 2, |
||||
UI_POS_TOP = 1 << 3, |
||||
UI_POS_MIDDLE = 1 << 4, |
||||
UI_POS_BOTTOM = 1 << 5, |
||||
} ui_pos_t; |
||||
|
||||
void ui_load(); |
||||
void ui_cleanup(); |
||||
|
||||
int ui_get_scale(); |
||||
void ui_set_scale(int scale); |
||||
vec2i_t ui_scaled(vec2i_t v); |
||||
vec2i_t ui_scaled_screen(); |
||||
vec2i_t ui_scaled_pos(ui_pos_t anchor, vec2i_t offset); |
||||
|
||||
int ui_char_width(char c, ui_text_size_t size); |
||||
int ui_text_width(const char *text, ui_text_size_t size); |
||||
int ui_number_width(int num, ui_text_size_t size); |
||||
|
||||
void ui_draw_text(const char *text, vec2i_t pos, ui_text_size_t size, rgba_t color); |
||||
void ui_draw_time(float time, vec2i_t pos, ui_text_size_t size, rgba_t color); |
||||
void ui_draw_number(int num, vec2i_t pos, ui_text_size_t size, rgba_t color); |
||||
|
||||
void ui_draw_image(vec2i_t pos, uint16_t texture); |
||||
void ui_draw_icon(ui_icon_type_t icon, vec2i_t pos, rgba_t color); |
||||
void ui_draw_text_centered(const char *text, vec2i_t pos, ui_text_size_t size, rgba_t color); |
||||
|
||||
#endif |
@ -0,0 +1,689 @@ |
||||
#include "../mem.h" |
||||
#include "../utils.h" |
||||
#include "../system.h" |
||||
|
||||
#include "track.h" |
||||
#include "ship.h" |
||||
#include "weapon.h" |
||||
#include "object.h" |
||||
#include "game.h" |
||||
#include "image.h" |
||||
#include "particle.h" |
||||
|
||||
extern int32_t ctrlNeedTargetIcon; |
||||
extern int ctrlnearShip; |
||||
int16_t Shielded = 0; |
||||
|
||||
typedef struct weapon_t { |
||||
float timer; |
||||
ship_t *owner; |
||||
ship_t *target; |
||||
section_t *section; |
||||
Object *model; |
||||
bool active; |
||||
|
||||
int16_t trail_particle; |
||||
int16_t track_hit_particle; |
||||
int16_t ship_hit_particle; |
||||
float trail_spawn_timer; |
||||
|
||||
int16_t type; |
||||
vec3_t acceleration; |
||||
vec3_t velocity; |
||||
vec3_t position; |
||||
vec3_t angle; |
||||
float drag; |
||||
|
||||
void (*update_func)(struct weapon_t *); |
||||
} weapon_t; |
||||
|
||||
|
||||
weapon_t *weapons; |
||||
int weapons_active = 0; |
||||
|
||||
struct { |
||||
uint16_t reticle; |
||||
Object *rocket; |
||||
Object *mine; |
||||
Object *missile; |
||||
Object *shield; |
||||
Object *shield_internal; |
||||
Object *ebolt; |
||||
} weapon_assets; |
||||
|
||||
void weapon_update_wait_for_delay(weapon_t *self); |
||||
|
||||
void weapon_fire_mine(ship_t *ship); |
||||
void weapon_update_mine_wait_for_release(weapon_t *self); |
||||
void weapon_update_mine(weapon_t *self); |
||||
void weapon_update_mine_lights(weapon_t *self, int index); |
||||
|
||||
void weapon_fire_missile(ship_t *ship); |
||||
void weapon_update_missile(weapon_t *self); |
||||
|
||||
void weapon_fire_rocket(ship_t *ship); |
||||
void weapon_update_rocket(weapon_t *self); |
||||
|
||||
void weapon_fire_ebolt(ship_t *ship); |
||||
void weapon_update_ebolt(weapon_t *self); |
||||
|
||||
void weapon_fire_shield(ship_t *ship); |
||||
void weapon_update_shield(weapon_t *self); |
||||
|
||||
void weapon_fire_turbo(ship_t *ship); |
||||
|
||||
void invert_shield_polys(Object *shield); |
||||
|
||||
void weapons_load() { |
||||
weapons = mem_bump(sizeof(weapon_t) * WEAPONS_MAX); |
||||
weapon_assets.reticle = image_get_texture("wipeout/textures/target2.tim"); |
||||
|
||||
texture_list_t weapon_textures = image_get_compressed_textures("wipeout/common/mine.cmp"); |
||||
weapon_assets.rocket = objects_load("wipeout/common/rock.prm", weapon_textures); |
||||
weapon_assets.mine = objects_load("wipeout/common/mine.prm", weapon_textures); |
||||
weapon_assets.missile = objects_load("wipeout/common/miss.prm", weapon_textures); |
||||
weapon_assets.shield = objects_load("wipeout/common/shld.prm", weapon_textures); |
||||
weapon_assets.shield_internal = objects_load("wipeout/common/shld.prm", weapon_textures); |
||||
weapon_assets.ebolt = objects_load("wipeout/common/ebolt.prm", weapon_textures); |
||||
|
||||
// Invert shield polys for internal view
|
||||
Prm poly = {.primitive = weapon_assets.shield_internal->primitives}; |
||||
int primitives_len = weapon_assets.shield_internal->primitives_len; |
||||
for (int k = 0; k < primitives_len; k++) { |
||||
switch (poly.primitive->type) { |
||||
case PRM_TYPE_G3 : |
||||
swap(poly.g3->coords[0], poly.g3->coords[2]); |
||||
poly.g3 += 1; |
||||
break; |
||||
|
||||
case PRM_TYPE_G4 : |
||||
swap(poly.g4->coords[0], poly.g4->coords[3]); |
||||
poly.g4 += 1; |
||||
break; |
||||
} |
||||
} |
||||
|
||||
weapons_init(); |
||||
} |
||||
|
||||
void weapons_init() { |
||||
weapons_active = 0; |
||||
} |
||||
|
||||
weapon_t *weapon_init(ship_t *ship) { |
||||
if (weapons_active == WEAPONS_MAX) { |
||||
return NULL; |
||||
} |
||||
|
||||
weapon_t *weapon = &weapons[weapons_active++]; |
||||
weapon->timer = 0; |
||||
weapon->owner = ship; |
||||
weapon->section = ship->section; |
||||
weapon->position = ship->position; |
||||
weapon->angle = ship->angle;
|
||||
weapon->acceleration = vec3(0, 0, 0); |
||||
weapon->velocity = vec3(0, 0, 0); |
||||
weapon->acceleration = vec3(0, 0, 0); |
||||
weapon->target = NULL; |
||||
weapon->model = NULL; |
||||
weapon->active = true; |
||||
weapon->trail_particle = PARTICLE_TYPE_NONE; |
||||
weapon->track_hit_particle = PARTICLE_TYPE_NONE; |
||||
weapon->ship_hit_particle = PARTICLE_TYPE_NONE; |
||||
weapon->trail_spawn_timer = 0; |
||||
weapon->drag = 0; |
||||
return weapon; |
||||
} |
||||
|
||||
void weapons_fire(ship_t *ship, int weapon_type) { |
||||
switch (weapon_type) { |
||||
case WEAPON_TYPE_MINE: weapon_fire_mine(ship); break; |
||||
case WEAPON_TYPE_MISSILE: weapon_fire_missile(ship); break; |
||||
case WEAPON_TYPE_ROCKET: weapon_fire_rocket(ship); break; |
||||
case WEAPON_TYPE_EBOLT: weapon_fire_ebolt(ship); break; |
||||
case WEAPON_TYPE_SHIELD: weapon_fire_shield(ship); break; |
||||
case WEAPON_TYPE_TURBO: weapon_fire_turbo(ship); break; |
||||
default: die("Inavlid weapon type %d", weapon_type); |
||||
} |
||||
ship->weapon_type = WEAPON_TYPE_NONE; |
||||
} |
||||
|
||||
void weapons_fire_delayed(ship_t *ship, int weapon_type) { |
||||
weapon_t *weapon = weapon_init(ship); |
||||
if (!weapon) { |
||||
return; |
||||
} |
||||
weapon->type = weapon_type; |
||||
weapon->timer = WEAPON_AI_DELAY; |
||||
weapon->update_func = weapon_update_wait_for_delay; |
||||
} |
||||
|
||||
bool weapon_collides_with_track(weapon_t *self); |
||||
|
||||
void weapons_update() { |
||||
for (int i = 0; i < weapons_active; i++) { |
||||
weapon_t *weapon = &weapons[i]; |
||||
|
||||
weapon->timer -= system_tick(); |
||||
(weapon->update_func)(weapon); |
||||
|
||||
// Handle projectiles
|
||||
if (weapon->acceleration.x != 0 || weapon->acceleration.z != 0) { |
||||
weapon->velocity = vec3_add(weapon->velocity, vec3_mulf(weapon->acceleration, 30 * system_tick())); |
||||
weapon->velocity = vec3_sub(weapon->velocity, vec3_mulf(weapon->velocity, weapon->drag * 30 * system_tick())); |
||||
weapon->position = vec3_add(weapon->position, vec3_mulf(weapon->velocity, 30 * system_tick())); |
||||
|
||||
// Move along track normal
|
||||
track_face_t *face = track_section_get_base_face(weapon->section); |
||||
vec3_t face_point = face->tris[0].vertices[0].pos; |
||||
vec3_t face_normal = face->normal; |
||||
float height = vec3_distance_to_plane(weapon->position, face_point, face_normal); |
||||
|
||||
if (height < 2000) { |
||||
weapon->position = vec3_add(weapon->position, vec3_mulf(face_normal, (200 - height) * 30 * system_tick())); |
||||
} |
||||
|
||||
// Trail
|
||||
if (weapon->trail_particle != PARTICLE_TYPE_NONE) { |
||||
weapon->trail_spawn_timer += system_tick(); |
||||
while (weapon->trail_spawn_timer > 0) { |
||||
vec3_t pos = vec3_sub(weapon->position, vec3_mulf(weapon->velocity, 30 * system_tick() * weapon->trail_spawn_timer)); |
||||
vec3_t velocity = vec3(rand_float(-128, 128), rand_float(-128, 128), rand_float(-128, 128)); |
||||
particles_spawn(pos, weapon->trail_particle, velocity, 128); |
||||
weapon->trail_spawn_timer -= WEAPON_PARTICLE_SPAWN_RATE; |
||||
} |
||||
} |
||||
|
||||
// Track collision
|
||||
weapon->section = track_nearest_section(weapon->position, weapon->section, NULL); |
||||
if (weapon_collides_with_track(weapon)) { |
||||
for (int p = 0; p < 32; p++) { |
||||
vec3_t velocity = vec3(rand_float(-512, 512), rand_float(-512, 512), rand_float(-512, 512)); |
||||
particles_spawn(weapon->position, weapon->track_hit_particle, velocity, 256); |
||||
} |
||||
sfx_play_at(SFX_EXPLOSION_2, weapon->position, vec3(0,0,0), 1); |
||||
weapon->active = false; |
||||
} |
||||
} |
||||
|
||||
// If this weapon is released, we have to rewind one step
|
||||
if (!weapon->active) { |
||||
weapons[i--] = weapons[--weapons_active]; |
||||
continue; |
||||
} |
||||
} |
||||
} |
||||
|
||||
void weapons_draw() { |
||||
mat4_t mat = mat4_identity(); |
||||
for (int i = 0; i < weapons_active; i++) { |
||||
weapon_t *weapon = &weapons[i]; |
||||
if (weapon->model) { |
||||
mat4_set_translation(&mat, weapon->position); |
||||
mat4_set_yaw_pitch_roll(&mat, weapon->angle); |
||||
if (weapon->model == weapon_assets.mine) { |
||||
weapon_update_mine_lights(weapon, i); |
||||
} |
||||
object_draw(weapon->model, &mat); |
||||
} |
||||
} |
||||
} |
||||
|
||||
|
||||
|
||||
void weapon_set_trajectory(weapon_t *self) { |
||||
ship_t *ship = self->owner; |
||||
track_face_t *face = track_section_get_base_face(ship->section); |
||||
|
||||
vec3_t face_point = face->tris[0].vertices[0].pos; |
||||
vec3_t target = vec3_add(ship->position, vec3_mulf(ship->dir_forward, 64)); |
||||
float target_height = vec3_distance_to_plane(target, face_point, face->normal); |
||||
float ship_height = vec3_distance_to_plane(target, face_point, face->normal); |
||||
|
||||
float nudge = target_height * 0.95 - ship_height; |
||||
|
||||
self->acceleration = vec3_sub(vec3_sub(target, vec3_mulf(face->normal, nudge)), ship->position); |
||||
self->velocity = vec3_mulf(ship->velocity, 0.015625); |
||||
self->angle = ship->angle; |
||||
} |
||||
|
||||
void weapon_follow_target(weapon_t *self) { |
||||
vec3_t angular_velocity = vec3(0, 0, 0); |
||||
if (self->target) { |
||||
vec3_t dir = vec3_mulf(vec3_sub(self->target->position, self->position), 0.125 * 30 * system_tick()); |
||||
float height = sqrt(dir.x * dir.x + dir.z * dir.z); |
||||
angular_velocity.y = -atan2(dir.x, dir.z) - self->angle.y; |
||||
angular_velocity.x = -atan2(dir.y, height) - self->angle.x; |
||||
} |
||||
|
||||
angular_velocity = vec3_wrap_angle(angular_velocity); |
||||
self->angle = vec3_add(self->angle, vec3_mulf(angular_velocity, 30 * system_tick() * 0.25)); |
||||
self->angle = vec3_wrap_angle(self->angle); |
||||
|
||||
self->acceleration.x = -sin(self->angle.y) * cos(self->angle.x) * 256; |
||||
self->acceleration.y = -sin(self->angle.x) * 256; |
||||
self->acceleration.z = cos(self->angle.y) * cos(self->angle.x) * 256; |
||||
} |
||||
|
||||
ship_t *weapon_collides_with_ship(weapon_t *self) { |
||||
for (int i = 0; i < NUM_PILOTS; i++) { |
||||
ship_t *ship = &g.ships[i]; |
||||
if (ship == self->owner) { |
||||
continue; |
||||
} |
||||
|
||||
float distance = vec3_len(vec3_sub(ship->position, self->position)); |
||||
if (distance < 512) { |
||||
for (int p = 0; p < 32; p++) { |
||||
vec3_t velocity = vec3(rand_float(-512, 512), rand_float(-512, 512), rand_float(-512, 512)); |
||||
velocity = vec3_add(velocity, vec3_mulf(ship->velocity, 0.25)); |
||||
particles_spawn(self->position, self->ship_hit_particle, velocity, 256); |
||||
} |
||||
return ship; |
||||
} |
||||
} |
||||
|
||||
return NULL; |
||||
} |
||||
|
||||
|
||||
bool weapon_collides_with_track(weapon_t *self) { |
||||
if (flags_is(self->section->flags, SECTION_JUMP)) { |
||||
return false; |
||||
} |
||||
|
||||
track_face_t *face = g.track.faces + self->section->face_start; |
||||
for (int i = 0; i < self->section->face_count; i++) { |
||||
vec3_t face_point = face->tris[0].vertices[0].pos; |
||||
float distance = vec3_distance_to_plane(self->position, face_point, face->normal); |
||||
if (distance < 0) { |
||||
return true; |
||||
} |
||||
face++; |
||||
} |
||||
|
||||
return false; |
||||
} |
||||
|
||||
void weapon_update_wait_for_delay(weapon_t *self) { |
||||
if (self->timer <= 0) { |
||||
weapons_fire(self->owner, self->type); |
||||
self->active = false; |
||||
} |
||||
} |
||||
|
||||
|
||||
void weapon_fire_mine(ship_t *ship) { |
||||
float timer = 0; |
||||
for (int i = 0; i < WEAPON_MINE_COUNT; i++) { |
||||
weapon_t *self = weapon_init(ship); |
||||
if (!self) { |
||||
return; |
||||
} |
||||
timer += WEAPON_MINE_RELEASE_RATE; |
||||
self->timer = timer; |
||||
self->update_func = weapon_update_mine_wait_for_release; |
||||
} |
||||
} |
||||
|
||||
|
||||
|
||||
void weapon_update_mine_wait_for_release(weapon_t *self) { |
||||
if (self->timer <= 0) { |
||||
self->timer = WEAPON_MINE_DURATION; |
||||
self->update_func = weapon_update_mine; |
||||
self->model = weapon_assets.mine; |
||||
self->position = self->owner->position; |
||||
self->angle.y = rand_float(0, M_PI * 2); |
||||
|
||||
self->trail_particle = PARTICLE_TYPE_NONE; |
||||
self->track_hit_particle = PARTICLE_TYPE_NONE; |
||||
self->ship_hit_particle = PARTICLE_TYPE_FIRE; |
||||
|
||||
if (self->owner->pilot == g.pilot) { |
||||
sfx_play(SFX_MINE_DROP); |
||||
} |
||||
} |
||||
} |
||||
|
||||
void weapon_update_mine_lights(weapon_t *self, int index) { |
||||
Prm prm = {.primitive = self->model->primitives}; |
||||
|
||||
uint8_t r = sin(system_cycle_time() * M_PI * 2 + index * 0.66) * 128 + 128; |
||||
for (int i = 0; i < 8; i++) { |
||||
switch (prm.primitive->type) { |
||||
case PRM_TYPE_GT3: |
||||
prm.gt3->colour[0].as_rgba.r = 230; |
||||
prm.gt3->colour[1].as_rgba.r = r; |
||||
prm.gt3->colour[2].as_rgba.r = r; |
||||
prm.gt3->colour[0].as_rgba.g = 0; |
||||
prm.gt3->colour[1].as_rgba.g = 0x40; |
||||
prm.gt3->colour[2].as_rgba.g = 0x40; |
||||
prm.gt3->colour[0].as_rgba.b = 0; |
||||
prm.gt3->colour[1].as_rgba.b = 0; |
||||
prm.gt3->colour[2].as_rgba.b = 0; |
||||
prm.gt3 += 1; |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
|
||||
void weapon_update_mine(weapon_t *self) { |
||||
if (self->timer <= 0) { |
||||
self->active = false; |
||||
return; |
||||
} |
||||
|
||||
// TODO: oscilate perpendicular to track!?
|
||||
self->angle.y += system_tick(); |
||||
|
||||
ship_t *ship = weapon_collides_with_ship(self); |
||||
if (ship) { |
||||
sfx_play_at(SFX_EXPLOSION_1, self->position, vec3(0,0,0), 1); |
||||
self->active = false; |
||||
if (flags_not(ship->flags, SHIP_SHIELDED)) { |
||||
if (ship->pilot == g.pilot) { |
||||
ship->velocity = vec3_sub(ship->velocity, vec3_mulf(ship->velocity, 0.125)); |
||||
// SetShake(20); // FIXME
|
||||
} |
||||
else { |
||||
ship->speed = ship->speed * 0.125; |
||||
} |
||||
} |
||||
}
|
||||
} |
||||
|
||||
|
||||
void weapon_fire_missile(ship_t *ship) { |
||||
weapon_t *self = weapon_init(ship); |
||||
if (!self) { |
||||
return; |
||||
} |
||||
|
||||
self->timer = WEAPON_MISSILE_DURATION; |
||||
self->model = weapon_assets.missile; |
||||
self->update_func = weapon_update_missile; |
||||
self->trail_particle = PARTICLE_TYPE_SMOKE; |
||||
self->track_hit_particle = PARTICLE_TYPE_FIRE_WHITE; |
||||
self->ship_hit_particle = PARTICLE_TYPE_FIRE; |
||||
self->target = ship->weapon_target; |
||||
self->drag = 0.25; |
||||
weapon_set_trajectory(self); |
||||
|
||||
if (self->owner->pilot == g.pilot) { |
||||
sfx_play(SFX_MISSILE_FIRE); |
||||
} |
||||
} |
||||
|
||||
void weapon_update_missile(weapon_t *self) { |
||||
if (self->timer <= 0) { |
||||
self->active = false; |
||||
return; |
||||
} |
||||
|
||||
weapon_follow_target(self); |
||||
|
||||
// Collision with other ships
|
||||
ship_t *ship = weapon_collides_with_ship(self); |
||||
if (ship) { |
||||
sfx_play_at(SFX_EXPLOSION_1, self->position, vec3(0,0,0), 1); |
||||
self->active = false; |
||||
|
||||
if (flags_not(ship->flags, SHIP_SHIELDED)) { |
||||
if (ship->pilot == g.pilot) { |
||||
ship->velocity = vec3_sub(ship->velocity, vec3_mulf(ship->velocity, 0.75)); |
||||
ship->angular_velocity.z += rand_float(-0.1, 0.1); |
||||
ship->turn_rate_from_hit = rand_float(-0.1, 0.1); |
||||
// SetShake(20); // FIXME
|
||||
} |
||||
else { |
||||
ship->speed = ship->speed * 0.03125; |
||||
ship->angular_velocity.z += 10 * M_PI; |
||||
ship->turn_rate_from_hit = rand_float(-M_PI, M_PI); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
void weapon_fire_rocket(ship_t *ship) { |
||||
weapon_t *self = weapon_init(ship); |
||||
if (!self) { |
||||
return; |
||||
} |
||||
|
||||
self->timer = WEAPON_ROCKET_DURATION; |
||||
self->model = weapon_assets.rocket; |
||||
self->update_func = weapon_update_rocket; |
||||
self->trail_particle = PARTICLE_TYPE_SMOKE; |
||||
self->track_hit_particle = PARTICLE_TYPE_FIRE_WHITE; |
||||
self->ship_hit_particle = PARTICLE_TYPE_FIRE; |
||||
self->drag = 0.03125; |
||||
weapon_set_trajectory(self); |
||||
|
||||
if (self->owner->pilot == g.pilot) { |
||||
sfx_play(SFX_MISSILE_FIRE); |
||||
} |
||||
} |
||||
|
||||
void weapon_update_rocket(weapon_t *self) { |
||||
if (self->timer <= 0) { |
||||
self->active = false; |
||||
return; |
||||
} |
||||
|
||||
// Collision with other ships
|
||||
ship_t *ship = weapon_collides_with_ship(self); |
||||
if (ship) { |
||||
sfx_play_at(SFX_EXPLOSION_1, self->position, vec3(0,0,0), 1); |
||||
self->active = false; |
||||
|
||||
if (flags_not(ship->flags, SHIP_SHIELDED)) { |
||||
if (ship->pilot == g.pilot) { |
||||
ship->velocity = vec3_sub(ship->velocity, vec3_mulf(ship->velocity, 0.75)); |
||||
ship->angular_velocity.z += rand_float(-0.1, 0.1);; |
||||
ship->turn_rate_from_hit = rand_float(-0.1, 0.1);; |
||||
// SetShake(20); // FIXME
|
||||
} |
||||
else { |
||||
ship->speed = ship->speed * 0.03125; |
||||
ship->angular_velocity.z += 10 * M_PI; |
||||
ship->turn_rate_from_hit = rand_float(-M_PI, M_PI); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
|
||||
void weapon_fire_ebolt(ship_t *ship) { |
||||
weapon_t *self = weapon_init(ship); |
||||
if (!self) { |
||||
return; |
||||
} |
||||
|
||||
self->timer = WEAPON_EBOLT_DURATION; |
||||
self->model = weapon_assets.ebolt; |
||||
self->update_func = weapon_update_ebolt; |
||||
self->trail_particle = PARTICLE_TYPE_EBOLT; |
||||
self->track_hit_particle = PARTICLE_TYPE_EBOLT; |
||||
self->ship_hit_particle = PARTICLE_TYPE_GREENY; |
||||
self->target = ship->weapon_target; |
||||
self->drag = 0.25; |
||||
weapon_set_trajectory(self); |
||||
|
||||
if (self->owner->pilot == g.pilot) { |
||||
sfx_play(SFX_EBOLT); |
||||
} |
||||
} |
||||
|
||||
void weapon_update_ebolt(weapon_t *self) { |
||||
if (self->timer <= 0) { |
||||
self->active = false; |
||||
return; |
||||
} |
||||
|
||||
weapon_follow_target(self); |
||||
|
||||
// Collision with other ships
|
||||
ship_t *ship = weapon_collides_with_ship(self); |
||||
if (ship) { |
||||
sfx_play_at(SFX_EXPLOSION_1, self->position, vec3(0,0,0), 1); |
||||
self->active = false; |
||||
|
||||
if (flags_not(ship->flags, SHIP_SHIELDED)) { |
||||
flags_add(ship->flags, SHIP_ELECTROED); |
||||
ship->ebolt_timer = WEAPON_EBOLT_DURATION; |
||||
} |
||||
} |
||||
} |
||||
|
||||
void weapon_fire_shield(ship_t *ship) { |
||||
weapon_t *self = weapon_init(ship); |
||||
if (!self) { |
||||
return; |
||||
} |
||||
|
||||
self->timer = WEAPON_SHIELD_DURATION; |
||||
self->model = weapon_assets.shield; |
||||
self->update_func = weapon_update_shield; |
||||
|
||||
flags_add(self->owner->flags, SHIP_SHIELDED); |
||||
} |
||||
|
||||
void weapon_update_shield(weapon_t *self) { |
||||
if (self->timer <= 0) { |
||||
self->active = false; |
||||
flags_rm(self->owner->flags, SHIP_SHIELDED); |
||||
return; |
||||
} |
||||
|
||||
|
||||
if (flags_is(self->owner->flags, SHIP_VIEW_INTERNAL)) { |
||||
self->position = ship_cockpit(self->owner); |
||||
self->model = weapon_assets.shield_internal; |
||||
} |
||||
else { |
||||
self->position = self->owner->position; |
||||
self->model = weapon_assets.shield; |
||||
} |
||||
self->angle = self->owner->angle; |
||||
|
||||
|
||||
Prm poly = {.primitive = self->model->primitives}; |
||||
int primitives_len = self->model->primitives_len; |
||||
uint8_t col0, col1, col2, col3; |
||||
int16_t *coords; |
||||
uint8_t shield_alpha = 48; |
||||
|
||||
// FIXME: this looks kinda close to the PSX original!?
|
||||
float color_timer = self->timer * 0.05; |
||||
for (int k = 0; k < primitives_len; k++) { |
||||
switch (poly.primitive->type) { |
||||
case PRM_TYPE_G3 : |
||||
coords = poly.g3->coords; |
||||
|
||||
col0 = sin(color_timer * coords[0]) * 127 + 128; |
||||
col1 = sin(color_timer * coords[1]) * 127 + 128; |
||||
col2 = sin(color_timer * coords[2]) * 127 + 128; |
||||
|
||||
poly.g3->colour[0].as_rgba.r = col0; |
||||
poly.g3->colour[0].as_rgba.g = col0; |
||||
poly.g3->colour[0].as_rgba.b = 255; |
||||
poly.g3->colour[0].as_rgba.a = shield_alpha; |
||||
|
||||
poly.g3->colour[1].as_rgba.r = col1; |
||||
poly.g3->colour[1].as_rgba.g = col1; |
||||
poly.g3->colour[1].as_rgba.b = 255; |
||||
poly.g3->colour[1].as_rgba.a = shield_alpha; |
||||
|
||||
poly.g3->colour[2].as_rgba.r = col2; |
||||
poly.g3->colour[2].as_rgba.g = col2; |
||||
poly.g3->colour[2].as_rgba.b = 255; |
||||
poly.g3->colour[2].as_rgba.a = shield_alpha; |
||||
poly.g3 += 1; |
||||
break; |
||||
|
||||
case PRM_TYPE_G4 : |
||||
coords = poly.g4->coords; |
||||
|
||||
col0 = sin(color_timer * coords[0]) * 127 + 128; |
||||
col1 = sin(color_timer * coords[1]) * 127 + 128; |
||||
col2 = sin(color_timer * coords[2]) * 127 + 128; |
||||
col3 = sin(color_timer * coords[3]) * 127 + 128; |
||||
|
||||
poly.g4->colour[0].as_rgba.r = col0; |
||||
poly.g4->colour[0].as_rgba.g = col0; |
||||
poly.g4->colour[0].as_rgba.b = 255; |
||||
poly.g4->colour[0].as_rgba.a = shield_alpha; |
||||
|
||||
poly.g4->colour[1].as_rgba.r = col1; |
||||
poly.g4->colour[1].as_rgba.g = col1; |
||||
poly.g4->colour[1].as_rgba.b = 255; |
||||
poly.g4->colour[1].as_rgba.a = shield_alpha; |
||||
|
||||
poly.g4->colour[2].as_rgba.r = col2; |
||||
poly.g4->colour[2].as_rgba.g = col2; |
||||
poly.g4->colour[2].as_rgba.b = 255; |
||||
poly.g4->colour[2].as_rgba.a = shield_alpha; |
||||
|
||||
poly.g4->colour[3].as_rgba.r = col3; |
||||
poly.g4->colour[3].as_rgba.g = col3; |
||||
poly.g4->colour[3].as_rgba.b = 255; |
||||
poly.g4->colour[3].as_rgba.a = shield_alpha; |
||||
poly.g4 += 1; |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
|
||||
|
||||
void weapon_fire_turbo(ship_t *ship) { |
||||
ship->velocity = vec3_add(ship->velocity, vec3_mulf(ship->dir_forward, 39321)); // unitVecNose.vx) << 3) * FR60) / 50
|
||||
|
||||
if (ship->pilot == g.pilot) { |
||||
sfx_t *sfx = sfx_play(SFX_MISSILE_FIRE); |
||||
sfx->pitch = 0.25; |
||||
} |
||||
} |
||||
|
||||
int weapon_get_random_type(int type_class) { |
||||
if (type_class == WEAPON_CLASS_ANY) { |
||||
int index = rand_int(0, 65); |
||||
if (index < 17) { |
||||
return WEAPON_TYPE_ROCKET; |
||||
} |
||||
else if (index < 35) { |
||||
return WEAPON_TYPE_MINE; |
||||
} |
||||
else if (index < 45) { |
||||
return WEAPON_TYPE_SHIELD; |
||||
} |
||||
else if (index < 53) { |
||||
return WEAPON_TYPE_MISSILE; |
||||
} |
||||
else if (index < 59) { |
||||
return WEAPON_TYPE_TURBO; |
||||
} |
||||
else { |
||||
return WEAPON_TYPE_EBOLT; |
||||
} |
||||
} |
||||
else if (type_class == WEAPON_CLASS_PROJECTILE) {
|
||||
int index = rand_int(0, 60); |
||||
if (index < 27) { |
||||
return WEAPON_TYPE_ROCKET; |
||||
} |
||||
else if (index < 40) { |
||||
return WEAPON_TYPE_MISSILE; |
||||
} |
||||
else if (index < 50) { |
||||
return WEAPON_TYPE_TURBO; |
||||
} |
||||
else { |
||||
return WEAPON_TYPE_EBOLT; |
||||
} |
||||
} |
||||
else { |
||||
die("Unknown WEAPON_CLASS_ %d", type_class); |
||||
} |
||||
} |
||||
|
@ -0,0 +1,51 @@ |
||||
#ifndef WEAPON_H |
||||
#define WEAPON_H |
||||
|
||||
#define WEAPONS_MAX 64 |
||||
|
||||
#define WEAPON_MINE_DURATION (450 * (1.0/30.0)) |
||||
#define WEAPON_ROCKET_DURATION (200 * (1.0/30.0)) |
||||
#define WEAPON_EBOLT_DURATION (140 * (1.0/30.0)) |
||||
#define WEAPON_REV_CON_DURATION (60 * (1.0/30.0)) |
||||
#define WEAPON_MISSILE_DURATION (200 * (1.0/30.0)) |
||||
#define WEAPON_SHIELD_DURATION (200 * (1.0/30.0)) |
||||
#define WEAPON_FLARE_DURATION (200 * (1.0/30.0)) |
||||
#define WEAPON_SPECIAL_DURATION (400 * (1.0/30.0)) |
||||
|
||||
#define WEAPON_MINE_RELEASE_RATE (3 * (1.0/30.0)) |
||||
#define WEAPON_DELAY (40 * (1.0/30.0)) |
||||
|
||||
#define WEAPON_TYPE_NONE 0 |
||||
#define WEAPON_TYPE_MINE 1 |
||||
#define WEAPON_TYPE_MISSILE 2 |
||||
#define WEAPON_TYPE_ROCKET 3 |
||||
#define WEAPON_TYPE_SPECIAL 4 |
||||
#define WEAPON_TYPE_EBOLT 5 |
||||
#define WEAPON_TYPE_FLARE 6 |
||||
#define WEAPON_TYPE_REV_CON 7 |
||||
#define WEAPON_TYPE_SHIELD 8 |
||||
#define WEAPON_TYPE_TURBO 9 |
||||
#define WEAPON_TYPE_MAX 10 |
||||
|
||||
#define WEAPON_MINE_COUNT 5 |
||||
|
||||
#define WEAPON_HIT_NONE 0 |
||||
#define WEAPON_HIT_SHIP 1 |
||||
#define WEAPON_HIT_TRACK 2 |
||||
|
||||
#define WEAPON_PARTICLE_SPAWN_RATE 0.011 |
||||
#define WEAPON_AI_DELAY 1.1 |
||||
|
||||
#define WEAPON_CLASS_ANY 1 |
||||
#define WEAPON_CLASS_PROJECTILE 2 |
||||
|
||||
|
||||
void weapons_load(); |
||||
void weapons_init(); |
||||
void weapons_fire(ship_t *ship, int weapon_type); |
||||
void weapons_fire_delayed(ship_t *ship, int weapon_type); |
||||
void weapons_update(); |
||||
void weapons_draw(); |
||||
int weapon_get_random_type(int type_class); |
||||
|
||||
#endif |
@ -0,0 +1,2 @@ |
||||
replace this directory with the one from the wipeout NTSC version and copy |
||||
the common/ models from the PC version in. |
Loading…
Reference in new issue