Initial commit

This commit is contained in:
2024-10-11 22:21:32 +02:00
commit 1bdd01e9b2
38 changed files with 11363 additions and 0 deletions

View File

@@ -0,0 +1,6 @@
// Workspace: Game
//
// String added via add_build_string() from c:/Personal/games/Coven/metaprogram.jai:30.
//
GAME_NAME :: "Test";

6
.gitignore vendored Normal file
View File

@@ -0,0 +1,6 @@
bin/*.exe
bin/*.pdb
.build
range_index
bin/mnar.exe
.build/*.obj

285
animation/animator.jai Normal file
View File

@@ -0,0 +1,285 @@
Animation_State_Type :: enum {
SINGLE;
BLEND_TREE_1D;
}
Animation_Point :: struct {
position: float;
index: s32;
time: float;
multiplier: float = 1.0;
looping: bool;
}
Animation_Transition :: struct {
time: float;
duration: float;
from_index: s32;
to_index: s32;
}
Animation_State :: struct {
type: Animation_State_Type;
union {
single : struct {
time: float;
multiplier: float;
looping: bool;
index: s32;
}
blend_tree : struct {
value: float; // Range is 0.0 - 1.0
points: [..] Animation_Point;
}
}
}
Animator :: struct {
playing: bool;
state_index: s32;
states: [..] Animation_State;
playing_transition: bool;
transition : Animation_Transition;
}
sample_animation_node :: (animation: Animation, f0: s32, f1: s32, t: float, node_index: s32) -> position: Vector3, rotation: Quaternion, scale: Vector3 {
if node_index >= animation.nodes.count return .{0,0,0}, .{0,0,0,1}, .{1,1,1};
node_anim := animation.nodes[node_index];
pos : Vector3 = ---;
rot : Quaternion = ---;
scale : Vector3 = ---;
if node_anim.position.count == 0 {
pos = node_anim.const_position;
} else {
pos = lerp(node_anim.position[f0], node_anim.position[f1], t);
}
if node_anim.rotation.count == 0 {
rot = node_anim.const_rotation;
} else {
rot = slerp(node_anim.rotation[f0], node_anim.rotation[f1], t);
}
if node_anim.scale.count == 0 {
scale = node_anim.const_scale;
} else {
scale = lerp(node_anim.scale[f0], node_anim.scale[f1], t);
}
return pos, rot, scale;
}
sample_animation :: (e: *Entity, animation: Animation, time: float, weight: float) {
frame_time := time * animation.framerate;
// Sample!
f0 := min(cast(s32)frame_time + 0, animation.num_frames - 1);
f1 := min(cast(s32)frame_time + 1, animation.num_frames - 1);
t := cast(float)min(frame_time - cast(float)f0, 1.0);
for * e.renderable.nodes {
pos, rot, scale := sample_animation_node(animation, f0, f1, t, cast(s32)it_index);
it.transform.position += pos * weight;
// Make sure that we interpolate with the shortest path
if dot(it.transform.orientation, rot) < 0.0 {
rot = -rot;
}
it.transform.orientation += rot * weight;
it.transform.scale += scale * weight;
}
}
animation_is_done :: (e: *Entity) -> bool {
assert(cast(bool)(e.flags & Entity_Flags.ANIMATED));
animator := e.animator;
if animator.playing_transition return false;
state := animator.states[animator.state_index];
if state.type == {
case .SINGLE;
animation := e.renderable.model.animations[state.single.index];
return state.single.time >= animation.duration - 0.0001;
case .BLEND_TREE_1D;
}
return false;
}
animation_time :: (e: *Entity) -> float {
assert(cast(bool)(e.flags & Entity_Flags.ANIMATED));
animator := e.animator;
if animator.playing_transition return 0.0;
state := animator.states[animator.state_index];
if state.type == {
case .SINGLE;
animation := e.renderable.model.animations[state.single.index];
return state.single.time;
case .BLEND_TREE_1D;
}
return 0.0;
}
animation_time_normalized :: (e: *Entity) -> float {
assert(cast(bool)(e.flags & Entity_Flags.ANIMATED));
animator := e.animator;
if animator.playing_transition return 0.0;
state := animator.states[animator.state_index];
if state.type == {
case .SINGLE;
animation := e.renderable.model.animations[state.single.index];
return state.single.time / xx animation.duration;
case .BLEND_TREE_1D;
}
return 0.0;
}
sample_animation_state :: (e: *Entity, index: s32, weight: float) {
state := *e.animator.states[index];
if state.type == {
case .SINGLE;
{
animation := *e.renderable.model.animations[state.single.index];
sample_animation(e, animation, min(state.single.time, cast(float)animation.duration - 0.0001), weight);
}
case .BLEND_TREE_1D;
{
p0 : s32;
p1 : s32;
found := false;
current_value := state.blend_tree.value;
for index: 0..state.blend_tree.points.count-1 {
p := *state.blend_tree.points[index];
if current_value >= p.position && index != state.blend_tree.points.count-1 {
p0 = xx index;
p1 = p0 + 1;
}
}
p0_position := state.blend_tree.points[p0].position;
p1_position := state.blend_tree.points[p1].position;
position := state.blend_tree.value - p0_position;
range := p1_position - p0_position;
alpha := position / range;
anim0 := e.renderable.model.animations[state.blend_tree.points[p0].index];
anim1 := e.renderable.model.animations[state.blend_tree.points[p1].index];
time0 := state.blend_tree.points[p0].time;
time1 := state.blend_tree.points[p1].time;
sample_animation(e, anim0, time0, (1.0 - alpha) * weight);
sample_animation(e, anim1, time1, alpha * weight);
}
}
}
transition_to_animation_state :: (e: *Entity, index: s32, transition_duration: float) {
assert(cast(bool)(e.flags & Entity_Flags.ANIMATED));
e.animator.playing_transition = true;
e.animator.transition.time = 0.0;
e.animator.transition.duration = transition_duration;
e.animator.transition.from_index = e.animator.state_index;
e.animator.transition.to_index = index;
// @Incomplete: Missing blend tree
e.animator.states[index].single.time = 0.0;
}
update_animation_state :: (e: *Entity, index: s32, dt: float) {
state := *e.animator.states[index];
if state.type == {
case .SINGLE;
{
state.single.time += dt * state.single.multiplier;
animation := *e.renderable.model.animations[state.single.index];
if state.single.time > animation.duration {
if state.single.looping {
state.single.time = cast(float)(state.single.time - animation.duration);
} else {
state.single.time = xx animation.duration;
}
}
}
case .BLEND_TREE_1D;
{
p0 : s32;
p1 : s32;
found := false;
for *p: state.blend_tree.points {
p.time += dt * p.multiplier;
animation := *e.renderable.model.animations[p.index];
if p.time > animation.duration {
if p.looping {
p.time = cast(float)(p.time - animation.duration);
} else {
p.time = xx animation.duration - 0.0001; // Is this correct?
}
}
}
}
}
}
update_animator :: (e: *Entity, animator: *Animator, dt: float) {
if animator.playing {
// Reset all transforms
for * e.renderable.nodes {
it.transform.position = .{0,0,0};
it.transform.scale = .{0,0,0};
it.transform.orientation = .{0,0,0,0};
it.has_sampled_animation = false;
}
if animator.playing_transition {
update_animation_state(e, animator.transition.from_index, dt);
update_animation_state(e, animator.transition.to_index, dt);
animator.transition.time += dt;
alpha := animator.transition.time / animator.transition.duration;
clamped := clamp(alpha, 0.0, 1.0);
sample_animation_state(e, animator.transition.from_index, 1.0 - clamped);
sample_animation_state(e, animator.transition.to_index, clamped);
if alpha >= 1.0 {
animator.playing_transition = false;
animator.state_index = animator.transition.to_index;
}
} else {
update_animation_state(e, animator.state_index, dt);
sample_animation_state(e, animator.state_index, 1.0);
}
}
}

86
audio/audio.jai Normal file
View File

@@ -0,0 +1,86 @@
Sound_Handle :: #type, distinct u32;
audio_system : *Audio_System;
Sound :: struct {
fmod_sound: *FMOD_SOUND;
}
Audio_Event :: struct {
instance : *FMOD_STUDIO_EVENTINSTANCE;
};
Delayed_Audio_Event :: struct {
event: Audio_Event;
time_left: float;
}
Audio_System :: struct {
fmod: *FMOD_SYSTEM;
studio: *FMOD_STUDIO_SYSTEM;
sounds: PArray(Sound, Sound_Handle);
delayed_events: [..] Delayed_Audio_Event;
}
init_audio_system :: () {
audio_system = New(Audio_System);
assert(FMOD_System_Create(*audio_system.fmod, FMOD_VERSION) == .FMOD_OK);
assert(FMOD_System_Init(audio_system.fmod, 32, 0, null) == .FMOD_OK);
assert(FMOD_Studio_System_Create(*audio_system.studio, FMOD_VERSION) == .FMOD_OK);
assert(FMOD_Studio_System_Initialize(audio_system.studio, 512, FMOD_STUDIO_INIT_LIVEUPDATE, FMOD_INIT_PROFILE_ENABLE, null) == .FMOD_OK);
}
instance : *FMOD_STUDIO_EVENTINSTANCE;
load_fmod_bank :: (path: string) {
bank : *FMOD_STUDIO_BANK;
result := FMOD_Studio_System_LoadBankFile(audio_system.studio, to_temp_c_string(path), FMOD_STUDIO_LOAD_BANK_NORMAL, *bank);
assert(result == .FMOD_OK);
loading_state : FMOD_STUDIO_LOADING_STATE;
res := FMOD_Studio_Bank_GetLoadingState(bank, *loading_state);
}
create_audio_event :: (path: string) -> Audio_Event {
event : *FMOD_STUDIO_EVENTDESCRIPTION;
result := FMOD_Studio_System_GetEvent(audio_system.studio, to_temp_c_string(path), *event);
assert(result == .FMOD_OK);
evt : Audio_Event;
assert(FMOD_Studio_EventDescription_CreateInstance(event, *evt.instance) == .FMOD_OK);
return evt;
}
play_audio_event :: (evt: Audio_Event, delay: float = -1.0) {
if delay > 0.0 {
array_add(*audio_system.delayed_events, .{evt, delay});
} else {
assert(FMOD_Studio_EventInstance_Start(evt.instance) == .FMOD_OK);
}
}
create_sound :: (path: string) -> Sound_Handle {
sound: Sound;
assert(FMOD_System_CreateSound(audio_system.fmod, to_temp_c_string(path), FMOD_DEFAULT, null, *sound.fmod_sound) == .FMOD_OK);
return parray_add(*audio_system.sounds, sound);
}
play_sound :: (handle: Sound_Handle) {
sound := parray_get(audio_system.sounds, handle);
FMOD_System_PlaySound(audio_system.fmod, sound.fmod_sound, null, 0, null);
}
update_audio :: (dt: float) {
for * audio_system.delayed_events {
if it.time_left <= 0.0 {
play_audio_event(it.event);
remove it;
} else {
it.time_left -= dt;
}
}
FMOD_Studio_System_Update(audio_system.studio);
}
#import "fmod";

345
core/camera.jai Normal file
View File

@@ -0,0 +1,345 @@
Camera_Type :: enum {
ORTHOGRAPHIC;
PERSPECTIVE;
}
Camera_Buffer_Data :: struct {
projection_matrix: Matrix4;
view_matrix: Matrix4;
position: Vector4;
}
Camera :: struct {
type : Camera_Type;
position : Vector3;
rotation : struct {
yaw : float;
pitch : float;
roll : float;
}
aspect_ratio : float;
fov : float;
z_near : float;
z_far : float;
forward : Vector3;
up : Vector3;
right : Vector3;
world_up : Vector3 = .{0,1,0};
projection_matrix : Matrix4;
view_matrix : Matrix4;
dirty : bool;
}
create_perspective_camera :: (position: Vector3 = .{}, fov: float, aspect: float, yaw: float = 0.0, pitch: float = 0.0, roll: float = 0.0, z_near: float = 0.1, z_far: float = 1000.0) -> Camera {
camera : Camera;
camera.type = .PERSPECTIVE;
camera.world_up = .{0,1,0};
camera.position = position;
camera.rotation.yaw = yaw;
camera.rotation.pitch = pitch;
camera.rotation.roll = roll;
camera.aspect_ratio = aspect;
camera.z_near = z_near;
camera.z_far = z_far;
camera.fov = fov;
camera.projection_matrix = make_lh_projection_matrix(fov * (TAU / 360.0), aspect, z_near, z_far);
update_view_matrix(*camera);
return camera;
}
create_orthographic_camera :: (position: Vector3 = .{}, yaw: float = 0.0, pitch: float = 0.0, roll: float = 0.0, left: float, right: float, bottom: float, top: float, z_near: float = 0.1, z_far: float = 1000.0) -> Camera {
camera : Camera;
camera.type = .ORTHOGRAPHIC;
camera.world_up = .{0,1,0};
camera.position = position;
camera.rotation.yaw = yaw;
camera.rotation.pitch = pitch;
camera.rotation.roll = roll;
camera.aspect_ratio = 0.1;//aspect;
camera.z_near = z_near;
camera.z_far = z_far;
camera.fov = 0.0;
camera.projection_matrix = orthographic_lh_projection_matrix(left, right, bottom, top, z_near, z_far);
update_view_matrix(*camera);
return camera;
}
orthographic_lh_projection_matrix :: (left: float, right: float, bottom: float, top: float, near: float, far: float) -> Matrix4
{
m : Matrix4;
width := right - left;
height := top - bottom;
m._11 = 2.0 / width;
m._22 = 2.0 / height;
m._33 = 1 / (far - near);
m._34 = -near / (near-far);
m._44 = 1.0;
return m;
}
make_lh_projection_matrix :: (fov_vertical: float, aspect_ratio_horizontal_over_vertical: float, z_near: float, z_far: float, x_offset:=0.0, y_offset:=0.0, depth_range_01:=false) -> Matrix4 {
result := Matrix4_Identity;
tan_theta := tan(fov_vertical * 0.5);
y_scale := 1 / tan_theta;
x_scale := y_scale / aspect_ratio_horizontal_over_vertical;
result._11 = x_scale;
result._22 = y_scale;
result._33 = z_far / (z_far - z_near);
result._34 = -z_near * z_far / (z_far - z_near);
result._43 = 1;
result._44 = 0;
result._13 = x_offset; // / w;
result._23 = y_offset; // / h;
if depth_range_01 {
// To map -1,1 depth range to 0,1 we transform z as follows: z' = z * 0.5 + 0.5
result._33 = result._33 * 0.5 + result._43 * 0.5;
result._34 = result._34 * 0.5 + result._44 * 0.5;
}
return result;
}
world_to_screen :: (camera: Camera, world_position: Vector3) -> Vector3 {
pos : Vector4;
pos.x = world_position.x;
pos.y = world_position.y;
pos.z = world_position.z;
pos.w = 1.0;
position := camera.projection_matrix * camera.view_matrix * pos;
position.x /= position.w;
position.y /= position.w;
position.z /= position.w;
screen_position : Vector3;
screen_position.x = (position.x + 1.0) * 0.5 * cast(float)renderer.render_target_width;
screen_position.y = (position.y + 1.0) * 0.5 * cast(float)renderer.render_target_height;
screen_position.z = position.z;
return screen_position;
}
screen_to_world :: (camera: Camera, screen_position: Vector2) -> Vector3 {
pos : Vector4;
pos.x = (screen_position.x / cast(float)renderer.render_target_width) * 2.0 - 1.0;
pos.y = (screen_position.y / cast(float)renderer.render_target_height) * 2.0 - 1.0;
pos.z = 0.0;
pos.w = 1.0;
result := inverse(camera.projection_matrix * camera.view_matrix) * pos;
result.x /= result.w;
result.y /= result.w;
result.z /= result.w;
world_position : Vector3;
world_position.x = result.x;
world_position.y = result.y;
world_position.z = result.z;
return world_position;
}
normalized_screen_to_ray_v2 :: (camera: *Camera, screen_position: Vector2) -> Ray {
nds : Vector2;
nds.x = (2.0 * screen_position.x) - 1.0;
nds.y = (2.0 * screen_position.y) - 1.0;
origin : Vector4;
origin.x = nds.x;
origin.y = nds.y;
origin.z = 0.0;
origin.w = 1.0;
far : Vector4;
far.x = nds.x;
far.y = nds.y;
far.z = 1.0;
far.w = 1.0;
inverse_view_proj := inverse(camera.projection_matrix * camera.view_matrix);
ray_origin := inverse_view_proj * origin;
ray_end := inverse_view_proj * far;
// The hero we didn't know we needed
ray_origin /= ray_origin.w;
ray_end /= ray_end.w;
ray : Ray;
ray.origin = to_v3(ray_origin);
ray.direction = normalize(to_v3(ray_end) - ray.origin);
return ray;
}
screen_to_ray_v2 :: (camera: Camera, screen_position: Vector2, screen_size: Vector2) -> Ray {
nds : Vector2;
nds.x = (2.0 * screen_position.x) / screen_size.x - 1.0;
nds.y = (2.0 * screen_position.y) / screen_size.y - 1.0;
origin : Vector4;
origin.x = nds.x;
origin.y = nds.y;
origin.z = 0.0;
origin.w = 1.0;
far : Vector4;
far.x = nds.x;
far.y = nds.y;
far.z = 1.0;
far.w = 1.0;
inverse_view_proj := inverse(camera.projection_matrix * camera.view_matrix);
ray_origin := inverse_view_proj * origin;
ray_end := inverse_view_proj * far;
// The hero we didn't know we needed
ray_origin /= ray_origin.w;
ray_end /= ray_end.w;
ray : Ray;
ray.origin = to_v3(ray_origin);
ray.direction = normalize(to_v3(ray_end) - ray.origin);
return ray;
}
screen_to_ray :: (camera: *Camera, screen_position: Vector2, screen_size: Vector2) -> Ray {
ray : Ray;
ray.origin = camera.position;
ray_nds : Vector3;
ray_nds.x = (2.0 * screen_position.x) / screen_size.x - 1.0;
ray_nds.y = (2.0 * screen_position.y) / screen_size.y - 1.0;
ray_nds.z = 0.0;
ray_clip : Vector4;
ray_clip.x = ray_nds.x;
ray_clip.y = ray_nds.y;
ray_clip.z = -1.0;
ray_clip.w = 1.0;
ray_eye := inverse(camera.projection_matrix) * ray_clip;
ray_eye.z = 1.0;
ray_eye.w = 0.0;
ray_world := to_v3(inverse(camera.view_matrix) * ray_eye);
ray.direction = normalize(ray_world);
return ray;
}
set_fov :: (camera: *Camera, fov: float) {
camera.fov = fov;
camera.projection_matrix = make_lh_projection_matrix(fov * (TAU / 360.0), camera.aspect_ratio, camera.z_near, camera.z_far);
camera.dirty = true;
}
set_position :: (camera: *Camera, position: Vector3) {
camera.position = position;
update_view_matrix(camera);
}
set_yaw :: (camera: *Camera, yaw: float) {
camera.rotation.yaw = yaw;
update_view_matrix(camera);
}
set_pitch :: (camera: *Camera, pitch: float) {
camera.rotation.pitch = pitch;
update_view_matrix(camera);
}
set_roll :: (camera: *Camera, roll: float) {
camera.rotation.roll = roll;
update_view_matrix(camera);
}
set_pitch_yaw_roll :: (camera: *Camera, pitch: float, yaw: float, roll: float) {
camera.rotation.pitch = pitch;
camera.rotation.yaw = yaw;
camera.rotation.roll = roll;
update_view_matrix(camera);
}
update_view_matrix :: (using camera: *Camera) {
camera.rotation.pitch = clamp(camera.rotation.pitch, -89.0, 89.0);
pitch := rotation.pitch * DEGREES_TO_RADIANS;
yaw := rotation.yaw * DEGREES_TO_RADIANS;
roll := rotation.roll * DEGREES_TO_RADIANS;
pitch_mat := Matrix4.{
1, 0, 0, 0,
0, cos(pitch), sin(pitch), 0,
0, -sin(pitch), cos(pitch), 0,
0, 0, 0, 1};
yaw_mat := Matrix4.{
cos(yaw), 0, -sin(yaw), 0,
0, 1, 0, 0,
sin(yaw), 0, cos(yaw), 0,
0, 0, 0, 1};
roll_mat := Matrix4.{
cos(roll), sin(roll), 0, 0,
-sin(roll), cos(roll), 0, 0,
0, 0, 1, 0,
0, 0, 0, 1};
matrix := yaw_mat * pitch_mat * roll_mat;
camera.forward = .{0,0,1};
camera.right = .{1,0,0};
camera.up = .{0,1,0};
camera.forward = normalize(to_v3(matrix * Vector4.{camera.forward.x, camera.forward.y, camera.forward.z, 0.0}));
camera.right = normalize(to_v3(matrix * Vector4.{camera.right.x, camera.right.y, camera.right.z, 0.0}));
camera.up = normalize(to_v3(matrix * Vector4.{camera.up.x, camera.up.y, camera.up.z, 0.0}));
eye := camera.position + camera.forward;
//camera.forward = normalize(direction);
//camera.right = normalize(cross_product(Vector3.{0,1,0}, camera.forward));
//camera.up = normalize(cross_product(camera.forward, camera.right));
m := Matrix4_Identity;
m._11 = camera.right.x;
m._12 = camera.right.y;
m._13 = camera.right.z;
m._14 = -dot(camera.right, eye);
m._21 = camera.up.x;
m._22 = camera.up.y;
m._23 = camera.up.z;
m._24 = -dot(camera.up, eye);
m._31 = camera.forward.x;
m._32 = camera.forward.y;
m._33 = camera.forward.z;
m._34 = -dot(camera.forward, eye);
m._41 = 0.0;
m._42 = 0.0;
m._43 = 0.0;
m._44 = 1.0;
camera.view_matrix = m;
camera.dirty = true;
}

271
core/console.jai Normal file
View File

@@ -0,0 +1,271 @@
#import "File_Utilities";
console : *Console;
Command_Proc :: struct {
name: string;
proc: (arguments: [] string, buffer: *[..] Buffer_Entry);
}
Buffer_Entry :: struct {
text: string;
color: Color;
}
Console :: struct {
buffer: [..] Buffer_Entry;
current_string: string;
commands: [..] Command_Proc;
active: bool;
visible: bool;
open_amount: float;
animation_position: float; // 1.0 means fully visible
font: Font_Handle;
rect_pipeline: Pipeline_State_Handle;
text_pipeline: Pipeline_State_Handle;
vb: Buffer_Handle;
cursor_vb: Buffer_Handle;
verts: [..] Colored_Vert;
}
add_command :: (console: *Console, cmd: string, proc: (arguments: [] string, buffer: *[..] Buffer_Entry)) {
command : Command_Proc;
command.name = copy_string(cmd);
command.proc = proc;
array_add(*console.commands, command);
}
init_console :: () {
console = New(Console);
console.verts.allocator = temp;
buffer_size := size_of(Colored_Vert) * 12;
console.vb = create_vertex_buffer(renderer, null, xx buffer_size, stride=size_of(Colored_Vert), mappable=true);
console.cursor_vb = create_vertex_buffer(renderer, null, xx buffer_size, stride=size_of(Colored_Vert), mappable=true);
{
vs := create_vertex_shader(renderer, "../assets/shaders/ui_rect.hlsl", "VS");
ps := create_pixel_shader(renderer, "../assets/shaders/ui_rect.hlsl", "PS");
layout : [2] Vertex_Data_Info;
layout[0] = .{0, .POSITION2D, 0};
layout[1] = .{0, .COLOR_WITH_ALPHA, 0};
params : [0] Shader_Parameter;
console.rect_pipeline = create_pipeline_state(renderer, vs, ps, layout, params, blend_type=.TRANSPARENT);
}
{
vs := create_vertex_shader(renderer, "../assets/shaders/font.hlsl", "VS");
ps := create_pixel_shader(renderer, "../assets/shaders/font.hlsl", "PS");
layout : [3] Vertex_Data_Info;
layout[0] = .{0,.POSITION2D, 0};
layout[1] = .{0,.TEXCOORD0, 0};
layout[2] = .{0,.COLOR_WITH_ALPHA, 0};
params : [0] Shader_Parameter;
console.text_pipeline = create_pipeline_state(renderer, vs, ps, layout, params, blend_type=.TRANSPARENT);
}
console.font = create_font(renderer, "../assets/fonts/Inconsolata-Regular.ttf", 18);
console.current_string = alloc_string(256);
console.current_string.count = 0;
//add_command(console, "stats", toggle_stats);
//add_command(console, "camset", save_camera);
//add_command(console, "load", load_scene);
//add_command(console, "copy", copy_scene);
//add_command(console, "vsync", set_vsync);
//engine.console = console;
}
find_command :: (console: *Console, cmd_str: string) -> Command_Proc, bool {
for console.commands {
if equal(it.name, cmd_str) {
return it, true;
}
}
return .{}, false;
}
update_console :: () {
if key_down(.TILDE) {
console.active = !console.active;
input.has_char = false; // Make sure that the tilde is not used as the first input character in the console
}
if console.active {
if key_down(.BACKSPACE) {
if console.current_string.count > 0 {
console.current_string.count -= 1;
}
}
if key_down(.RETURN) {
if console.current_string.count > 0 {
// @Incomplete(niels): Using split with space will take spaces as part of the args.
// So each subsequent space will be an argument itself.
// This is a bit of a hacky fix for now
arguments := split(console.current_string, " ");
index := 0;
while index < arguments.count - 1 {
if arguments[index].count == 0 {
arguments[index] = arguments[arguments.count - 1];
arguments.count -= 1;
} else {
index += 1;
}
}
if arguments[arguments.count - 1].count == 0 {
arguments.count -= 1;
}
cmd, success := find_command(console, arguments[0]);
push_entry(*console.buffer, console.current_string, .{1,1,1,1});
if success {
cmd.proc(arguments, *console.buffer);
} else {
push_entry(*console.buffer, console.current_string, .{1,0,0,1});
}
console.current_string.count = 0;
}
}
if input.has_char {
if console.current_string.count < 256 {
console.current_string.data[console.current_string.count] = xx input.current_char;
console.current_string.count += 1;
input.has_char = false;
}
}
eat_all_input(*input);
}
}
make_vert :: (x: float, y: float, color: Color) -> Colored_Vert {
vert : Colored_Vert;
vert.position.x = x;
vert.position.y = y;
vert.color = color;
return vert;
}
add_rect :: (renderer: *Renderer, x: float, y: float, width: float, height: float, color: Color, verts: *[..] Colored_Vert) {
inv_w := 1.0 / cast(float)renderer.render_target_width;
inv_h := 1.0 / cast(float)renderer.render_target_height;
x = x * inv_w * 2.0 - 1.0;
y = (cast(float)renderer.render_target_height - y) * inv_h * 2.0 - 1.0;
w : float = width * inv_w * 2.0;
h : float = height * inv_h * 2.0;
array_add(verts, make_vert(x + w, y - h, color));
array_add(verts, make_vert(x, y - h, color));
array_add(verts, make_vert(x, y, color));
array_add(verts, make_vert(x + w, y - h, color));
array_add(verts, make_vert(x, y, color));
array_add(verts, make_vert(x + w, y, color));
}
render_console :: () {
if console.active {
console.visible = true;
console.open_amount += dt * 3.0;
} else {
console.open_amount -= dt * 3.0;
if console.open_amount <= 0.0 {
console.visible = false;
}
}
console.open_amount = clamp(console.open_amount, 0.0, 1.0);
if !console.visible return;
offset_y := (1.0 - console.open_amount) * 200.0 - 75;
console.verts.count = 0;
console_color : Color = .{48.0/255.0, 64.0/255.0, 36.0/255.0, 0.7};
console_line_color : Color = .{18.0/255.0, 34.0/255.0, 6.0/255.0, 0.7};
add_rect(renderer, 0.0, cast(float)renderer.render_target_height - 200 + offset_y, cast(float)renderer.render_target_width, 200.0, console_color, *console.verts);
add_rect(renderer, 0.0, cast(float)renderer.render_target_height - 30 + offset_y, cast(float)renderer.render_target_width, 30.0, console_line_color , *console.verts);
upload_data_to_buffer(renderer, console.vb, console.verts.data, cast(s32)console.verts.count * size_of(Colored_Vert));
push_cmd_set_draw_mode(renderer, .FILL);
push_cmd_set_depth_write(renderer, false);
push_cmd_set_pipeline_state(renderer, console.rect_pipeline);
push_cmd_set_vertex_buffer(renderer, console.vb);
push_cmd_draw(renderer, console.verts.count);
push_cmd_set_pipeline_state(renderer, console.text_pipeline);
font := *renderer.fonts[console.font - 1];
push_cmd_set_texture(renderer, 0, font.texture);
size := get_text_size(renderer, ">", console.font);
render_data := bake_text(renderer, 5.0, size.y - offset_y, ">", console.font, .{1,1,1,1});
push_cmd_set_vertex_buffer(renderer, render_data.vb);
push_cmd_draw(renderer, render_data.vert_count);
x := 15.0;
if console.current_string.count > 0 {
render_data := bake_text(renderer, x, size.y - offset_y, console.current_string, console.font, .{1,1,1,1});
push_cmd_set_vertex_buffer(renderer, render_data.vb);
push_cmd_draw(renderer, render_data.vert_count);
x = 10 + render_data.size.x + 2.0;
}
y := 37.0;
i := console.buffer.count - 1;
count := 0;
while i >= 0 && count < 8 {
defer i -= 1;
defer count += 1;
entry := console.buffer[i];
render_data := bake_text(renderer, 15.0, y - offset_y, entry.text, console.font, entry.color);
push_cmd_set_vertex_buffer(renderer, render_data.vb);
push_cmd_draw(renderer, render_data.vert_count);
y += 20.0;
}
console.verts.count = 0;
//#if !NEW_UI {
add_rect(renderer, x, cast(float)renderer.render_target_height - 25 + offset_y, 10, 20.0, .{1,1,1,1}, *console.verts);
//}
upload_data_to_buffer(renderer, console.cursor_vb, console.verts.data, cast(s32)console.verts.count * size_of(Colored_Vert));
push_cmd_set_draw_mode(renderer, .FILL);
push_cmd_set_depth_write(renderer, false);
push_cmd_set_pipeline_state(renderer, console.rect_pipeline);
push_cmd_set_vertex_buffer(renderer, console.cursor_vb);
push_cmd_draw(renderer, console.verts.count);
}
push_entry :: (buffer: *[..] Buffer_Entry, text: string, color: Color) {
entry : Buffer_Entry;
entry.text = copy_string(text);
entry.color = color;
array_add(buffer, entry);
}

189
core/entity.jai Normal file
View File

@@ -0,0 +1,189 @@
Entity_Id :: #type, isa s64;
Entity_Flags :: enum_flags u8 {
NONE;
RENDERABLE;
COLLISION;
PHYSICS;
STATIC;
TRIGGER;
ANIMATED;
DONT_SAVE;
}
Renderable_Type :: enum {
MODEL;
}
Entity_Material :: struct {
base_color : Vector4;
}
MAX_NODES :: 256;
Node_Render_Data :: struct {
enabled : bool = true;
transform: Transform;
material : Entity_Material;
has_sampled_animation: bool;
// Buffers
transform_buffer: Buffer_Handle;
material_buffer: Buffer_Handle;
bone_buffers : [MAX_BONES] Buffer_Handle;
num_bones: s64;
}
Renderable :: struct {
visible: bool = true;
type : Renderable_Type;
model: *Model;
nodes: [MAX_NODES] Node_Render_Data;
num_nodes: s64;
}
MAX_CHILDREN :: 16;
Entity :: struct {
id: Entity_Id;
type : Type;
enabled: bool = true;
name: string;
parent: *Entity; @DontSerialize
children: [MAX_CHILDREN] *Entity; @DontSerialize
num_children: s64; @DontSerialize
attach_node_index: s64 = -1; @DontSerialize
flags : Entity_Flags;
transform: Transform;
grid_position: Vector3i;
snap_offset: Vector3;
renderable: Renderable; @DontSerialize
animator: Animator; @DontSerialize
// Physics
body : Physics_Body; @DontSerialize
collider : Collider; @DontSerialize
// End physics
remote_id: Entity_Id; @DontSerialize
is_proxy: bool; @DontSerialize
last_replication_time: float; @DontSerialize
_locator: Bucket_Locator; @DontSerialize
scene: *Scene; @DontSerialize
}
add_child :: (e: *Entity, child: *Entity, node_name: string = "") {
set_parent(child, e, node_name);
}
set_parent :: (e: *Entity, parent: *Entity, node_name: string = "") {
parent.children[parent.num_children] = e;
e.parent = parent;
parent.num_children += 1;
for node, index: parent.renderable.model.nodes {
if node.name == node_name {
e.attach_node_index = index;
break;
}
}
}
set_base_color :: (e: *Entity, color: Vector4, node_name: string = "") {
if e.renderable.type == .MODEL {
for i: 0..e.renderable.num_nodes-1 {
actual_node := e.renderable.model.nodes[i];
if node_name.count == 0 || node_name == actual_node.name {
data := *e.renderable.nodes[i];
if data.material_buffer > 0 {
material : Entity_Material = ---;
material.base_color = color;
upload_data_to_buffer(renderer, data.material_buffer, *material, size_of(Entity_Material));
}
}
}
}
}
set_node_enabled :: (e: *Entity, node_name: string, enabled : bool) {
if e.renderable.type == .MODEL {
for i: 0..e.renderable.num_nodes-1 {
actual_node := e.renderable.model.nodes[i];
if node_name.count == 0 || node_name == actual_node.name {
data := *e.renderable.nodes[i];
data.enabled = enabled;
}
}
}
}
load_model_into_entity :: (e: *Entity, model: *Model) {
e.renderable.type = .MODEL;
assert(model.nodes.count <= MAX_NODES);
e.renderable.num_nodes = model.nodes.count;
e.renderable.model = model;
for *model.nodes {
data : Node_Render_Data;
data.transform = it.transform;
update_matrix(*it.transform);
if it.meshes.count > 0 {
material : Entity_Material = ---;
material.base_color = it.material_defaults[0].base_color; // @Incomplete: What if there are multiple meshes?
data.material = material;
data.transform_buffer = create_constant_buffer(renderer, null, size_of(Matrix4), mappable=true);
data.material_buffer = create_constant_buffer(renderer, *material, size_of(Entity_Material), mappable=true);
data.num_bones = it.num_bones;
if it.num_bones > 0 {
for bone_index: 0..it.num_bones-1 {
data.bone_buffers[bone_index] = create_constant_buffer(renderer, null, size_of(Matrix4) * MAX_BONES, mappable=true);
}
}
}
e.renderable.nodes[it_index] = data;
}
}
destroy_entity :: (e: *Entity, remove_from_scene: bool = true) {
free(e.name);
for 0..e.renderable.num_nodes-1 {
node_data := e.renderable.nodes[it];
if node_data.transform_buffer > 0 {
destroy_buffer(renderer, node_data.transform_buffer);
}
if node_data.material_buffer > 0 {
destroy_buffer(renderer, node_data.material_buffer);
}
for bi: 0..node_data.num_bones-1 {
destroy_buffer(renderer, node_data.bone_buffers[bi]);
}
}
if remove_from_scene {
array_unordered_remove_by_value(*game_state.current_scene.entities, e);
if e.type == {
case Block; bucket_array_remove(*game_state.current_scene.by_type._Block, e._locator);
}
}
}

19
core/fps.jai Normal file
View File

@@ -0,0 +1,19 @@
FPS_COUNT_AMOUNT :: 8;
average_fps: int;
fps_counts: [FPS_COUNT_AMOUNT] int;
fps_count_cursor: int;
update_fps_counter :: (dt: float) {
fps_counts[fps_count_cursor] = cast(int)(1.0/dt);
fps_count_cursor += 1;
if fps_count_cursor == FPS_COUNT_AMOUNT {
fps_count_cursor = 0;
}
total_fps : int;
for 0..FPS_COUNT_AMOUNT-1 {
total_fps += fps_counts[it];
}
average_fps = total_fps / FPS_COUNT_AMOUNT;
}

366
core/math.jai Normal file
View File

@@ -0,0 +1,366 @@
DEGREES_TO_RADIANS : float : 0.017453292;
RADIANS_TO_DEGREES : float : 57.295779;
hfov_to_vfov :: (hfov: float, aspect_ratio: float) -> float {
return 2.0 * atan(tan(hfov * DEGREES_TO_RADIANS * 0.5) / aspect_ratio) * RADIANS_TO_DEGREES;
}
lerp_angle :: (from: float, to: float, weight: float) -> float {
start_angle := fmod_cycling(from, 2*PI);
end_angle := fmod_cycling(to, 2*PI);
angle_distance := fmod_cycling(end_angle - start_angle + PI, 2 * PI) - PI;
interpolated_angle := start_angle + angle_distance * weight;
return fmod_cycling(interpolated_angle, 2*PI);
}
short_angle_dist :: (from: float, to: float) -> float {
max_angle :: PI * 2.0;
difference := fmod_cycling(to - from, max_angle);
return fmod_cycling(2.0 * difference, max_angle) - difference;
}
degrees_to_radians :: (degrees: float) -> float#expand {
constant : float : 0.01745329;
return degrees * constant;
}
radians_to_degrees :: (radians: float) -> float#expand {
constant : float : 57.295779;
return radians * constant;
}
Vector2i :: struct {
x: s32;
y: s32;
}
Vector3i :: struct {
x: s32;
y: s32;
z: s32;
}
Color :: #type,isa Vector4;
Colored_Vert :: struct {
position: Vector2;
color : Color;
}
AABB :: struct {
min: Vector3 = .{FLOAT32_INFINITY, FLOAT32_INFINITY, FLOAT32_INFINITY};
max: Vector3 = .{-FLOAT32_INFINITY, -FLOAT32_INFINITY, -FLOAT32_INFINITY};
}
apply_min_max :: (min: *Vector3, max: *Vector3, p: Vector3) {
if p.x < min.x min.x = p.x;
if p.y < min.y min.y = p.y;
if p.z < min.z min.z = p.z;
if p.x > max.x max.x = p.x;
if p.y > max.y max.y = p.y;
if p.z > max.z max.z = p.z;
}
point_inside_aabb :: (aabb: AABB, point: Vector3) -> bool {
return point.x >= aabb.min.x && point.x <= aabb.max.x
&& point.y >= aabb.min.y && point.y <= aabb.max.y
&& point.z >= aabb.min.z && point.z <= aabb.max.z;
}
operator == :: inline (a: Vector3i, b: Vector3i) -> bool {
return a.x == b.x && a.y == b.y && a.z == b.z;
}
operator + :: inline (a: Vector3i, b: Vector3i) -> Vector3i {
return .{a.x + b.x, a.y + b.y, a.z + b.z};
}
operator - :: inline (a: Vector3i, b: Vector3i) -> Vector3i {
return .{a.x - b.x, a.y - b.y, a.z - b.z};
}
to_v4 :: (v3: Vector3) -> Vector4 {
v4 : Vector4;
v4.x = v3.x;
v4.y = v3.y;
v4.z = v3.z;
v4.w = 1.0;
return v4;
}
to_v3 :: (v4: Vector4) -> Vector3 {
v3 : Vector3;
v3.x = v4.x;
v3.y = v4.y;
v3.z = v4.z;
return v3;
}
to_v3 :: (v2: Vector2) -> Vector3 {
v : Vector3;
v.x = v2.x;
v.y = v2.y;
return v;
}
to_v2 :: (v3: Vector3) -> Vector2 {
v : Vector2;
v.x = v3.x;
v.y = v3.y;
return v;
}
round :: (val: float) -> float {
return floor(val + 0.5);
}
move_towards :: (origin: float, target: float, amount: float) -> float {
if origin < target {
return min(origin + amount, target);
} else if origin > target {
return max(origin - amount, target);
} else {
return target;
}
}
move_towards :: (origin: Vector3, target: Vector3, amount: float) -> Vector3 {
result : Vector3;
dir := normalize(target - origin);
result.x = move_towards(origin.x, target.x, abs(dir.x) * amount);
result.y = move_towards(origin.y, target.y, abs(dir.y) * amount);
result.z = move_towards(origin.z, target.z, abs(dir.z) * amount);
return result;
}
smooth_damp :: (current: Vector3, target: Vector3, current_velocity: *Vector3, smooth_time: float, max_speed: float, delta_time: float) -> Vector3 {
output_x := 0.0;
output_y := 0.0;
output_z := 0.0;
// Based on Game Programming Gems 4 Chapter 1.10
smooth_time = max(0.0001, smooth_time);
omega := 2.0 / smooth_time;
x := omega * delta_time;
exp := 1.0 / (1.0 + x + 0.48 * x * x + 0.235 * x * x * x);
change_x := current.x - target.x;
change_y := current.y - target.y;
change_z := current.z - target.z;
original_to := target;
// Clamp maximum speed
max_change := max_speed * smooth_time;
max_change_sq := max_change * max_change;
sqrmag := change_x * change_x + change_y * change_y + change_z * change_z;
if sqrmag > max_change_sq {
mag := cast(float)sqrt(sqrmag);
change_x = change_x / mag * max_change;
change_y = change_y / mag * max_change;
change_z = change_z / mag * max_change;
}
final_target := target;
final_target.x = current.x - change_x;
final_target.y = current.y - change_y;
final_target.z = current.z - change_z;
temp_x := (current_velocity.x + omega * change_x) * delta_time;
temp_y := (current_velocity.y + omega * change_y) * delta_time;
temp_z := (current_velocity.z + omega * change_z) * delta_time;
current_velocity.x = (current_velocity.x - omega * temp_x) * exp;
current_velocity.y = (current_velocity.y - omega * temp_y) * exp;
current_velocity.z = (current_velocity.z - omega * temp_z) * exp;
output_x = final_target.x + (change_x + temp_x) * exp;
output_y = final_target.y + (change_y + temp_y) * exp;
output_z = final_target.z + (change_z + temp_z) * exp;
// Prevent overshooting
orig_minus_current_x := original_to.x - current.x;
orig_minus_current_y := original_to.y - current.y;
orig_minus_current_z := original_to.z - current.z;
out_minus_orig_x := output_x - original_to.x;
out_minus_orig_y := output_y - original_to.y;
out_minus_orig_z := output_z - original_to.z;
if orig_minus_current_x * out_minus_orig_x + orig_minus_current_y * out_minus_orig_y + orig_minus_current_z * out_minus_orig_z > 0 {
output_x = original_to.x;
output_y = original_to.y;
output_z = original_to.z;
current_velocity.x = (output_x - original_to.x) / delta_time;
current_velocity.y = (output_y - original_to.y) / delta_time;
current_velocity.z = (output_z - original_to.z) / delta_time;
}
return .{output_x, output_y, output_z};
}
smooth_damp :: (current: float, target: float, current_velocity: *float, smooth_time: float, max_speed: float, delta_time: float) -> float {
smooth_time = max(0.0001, smooth_time);
omega := 2.0 / smooth_time;
x := omega * delta_time;
exp := 1.0 / (1.0 + x + 0.48 * x * x + 0.235 * x * x * x);
change := current - target;
original_to := target;
// Clamp maximum speed
max_change := max_speed * smooth_time;
change = clamp(change, -max_change, max_change);
target = current - change;
temp := (<<current_velocity + omega * change) * delta_time;
<<current_velocity = (<<current_velocity - omega * temp) * exp;
output := target + (change + temp) * exp;
// Prevent overshooting
if (original_to - current > 0.0) == (output > original_to) {
output = original_to;
<<current_velocity = (output - original_to) / delta_time;
}
return output;
}
random_in_unit_circle :: () -> Vector2 {
result : Vector2;
angle := random_get_within_range(0.0, PI * 2.0);
radius := random_get_within_range(0.0, 1.0);
result.x = radius * cos(angle);
result.y = radius * sin(angle);
return result;
}
random_in_unit_sphere :: () -> Vector3 {
result : Vector3;
result.x = random_get_within_range(-1.0, 1.0);
result.y = random_get_within_range(-1.0, 1.0);
result.z = random_get_within_range(-1.0, 1.0);
return normalize(result);
}
look_at_lh :: (position: Vector3, target: Vector3, up: Vector3) -> Matrix4 {
z_axis := normalize(target - position);
x_axis := normalize(cross(up, z_axis));
y_axis := cross(z_axis, x_axis);
m : Matrix4;
m._11 = x_axis.x;
m._12 = x_axis.y;
m._13 = x_axis.z;
m._14 = -dot(x_axis, position);
m._21 = y_axis.x;
m._22 = y_axis.y;
m._23 = y_axis.z;
m._24 = -dot(y_axis, position);
m._31 = z_axis.x;
m._32 = z_axis.y;
m._33 = z_axis.z;
m._34 = -dot(z_axis, position);
m._41 = 0.0;
m._42 = 0.0;
m._43 = 0.0;
m._44 = 1.0;
return m;
}
project :: (v1: Vector3, v2: Vector3) -> Vector3 {
return (v2*v1)/(v2*v2)*v2;
}
reject :: (v1: Vector3, v2: Vector3) -> Vector3 {
return v1 - project(v1, v2);
}
horizontal_distance :: inline (v1: Vector3, v2: Vector3) -> float {
return distance(Vector2.{v1.x,v1.z}, Vector2.{v2.x,v2.z});
}
horizontal_direction :: inline (from: Vector3, to: Vector3) -> Vector3 {
adjusted_from := from;
adjusted_from.y = to.y;
return normalize(to - adjusted_from);
}
closest_point_on_line_segment :: (a: Vector3, b: Vector3, point: Vector3, ignore_y: bool = false) -> Vector3, float {
actual_a := a;
actual_b := b;
if ignore_y {
actual_a.y = point.y;
actual_b.y = point.y;
}
ab := actual_b - actual_a;
ap := point - actual_a;
proj := dot(ap, ab);
ab_len_sq := length_squared(ab);
d := proj / ab_len_sq;
cp : Vector3 = ---;
if d <= 0.0 {
cp = actual_a;
} else if d >= 1.0 {
cp = actual_b;
} else {
cp = actual_a + ab * d;
}
return cp, distance(point, cp);
}
reflect :: (incident: Vector3, normal: Vector3) -> Vector3 {
dot_product := dot(incident, normal);
reflected : Vector3 = ---;
reflected.x = incident.x - 2.0 * dot_product * normal.x;
reflected.y = incident.y - 2.0 * dot_product * normal.y;
reflected.z = incident.z - 2.0 * dot_product * normal.z;
return reflected;
}
quat_to_pitch_yaw_roll :: (using q: Quaternion) -> pitch: float, yaw: float, roll: float {
roll := atan2(2*y*w - 2*x*z, 1 - 2*y*y - 2*z*z);
pitch := atan2(2*x*w - 2*y*z, 1 - 2*x*x - 2*z*z);
yaw := asin(2*x*y + 2*z*w);
return pitch, yaw, roll;
}
ease_in :: (x : float, exp : int = 2) -> float {
return pow(x, xx exp);
}
ease_out :: (x : float, exp : int = 2) -> float {
return 1.0 - pow(1 - x, xx exp);
}
ease_in_sine :: (x: float) -> float {
return 1.0 - cos((x * PI) * 0.5);
}
ease_out_sine :: (x: float) -> float {
return 1.0 - sin((x * PI) * 0.5);
}
ease_in_out_sine :: (x: float) -> float {
return -(cos(PI * x) - 1.0) * 0.5;
}
#import "PCG";
#import "Math";

87
core/parray.jai Normal file
View File

@@ -0,0 +1,87 @@
PArray :: struct (Data_Type : Type, Handle_Type : Type) {
data: [..] Data_Type;
indices: [..] u32;
}
parray_reset :: (using array: *PArray) {
array_reset(*array.data);
array_reset(*array.indices);
}
parray_free :: (using array: PArray) {
array_free(array.data);
array_free(array.indices);
}
parray_reserve :: (using parray: *PArray, capacity: s32) {
if capacity > 0 {
array_reserve(*data, capacity);
array_reserve(*indices, capacity);
}
}
parray_get :: (using parray: PArray, handle: parray.Handle_Type) -> *parray.Data_Type {
index := indices[handle - 1] - 1;
return *data[index];
}
parray_get_val :: (using parray: PArray, handle: parray.Handle_Type) -> parray.Data_Type {
assert(xx handle > 0 && xx handle <= indices.count);
index := indices[handle - 1] - 1;
return data[index];
}
parray_add :: (using parray: *PArray, value: parray.Data_Type) -> parray.Handle_Type {
array_add(*data, value);
index := cast(u32)data.count;
handle : Handle_Type = 0;
// find the next empty index
for *val, i: indices {
if <<val == 0 {
<<val = index;
handle = cast(Handle_Type)i + 1;
break;
}
}
if handle == 0 {
array_add(*indices, index);
handle = cast(Handle_Type)indices.count;
}
return handle;
}
parray_remove :: (using parray: *PArray, handle: parray.Handle_Type) {
index := indices[handle - 1] - 1;
indices[handle - 1] = 0;
if data.count == 1 {
data.count = 0;
return;
}
data[index] = data[data.count-1];
for * indices {
if <<it == data.count {
<<it = index + 1;
break;
}
}
data.count -= 1;
}
for_expansion :: (parray: *PArray, body: Code, flags: For_Flags) #expand {
for *value, `it_index: parray.data {
#if flags & .POINTER {
`it := value;
} else {
`it := <<value;
}
#insert body;
}
}

239
core/ray.jai Normal file
View File

@@ -0,0 +1,239 @@
Ray :: struct {
origin : Vector3;
direction : Vector3;
}
ray_aabb_intersection :: (origin: Vector3, direction: Vector3, aabb: AABB) -> float {
t1 := (aabb.min.x - origin.x) / direction.x;
t2 := (aabb.max.x - origin.x) / direction.x;
t3 := (aabb.min.y - origin.y) / direction.y;
t4 := (aabb.max.y - origin.y) / direction.y;
t5 := (aabb.min.z - origin.z) / direction.z;
t6 := (aabb.max.z - origin.z) / direction.z;
tmin := max(max(min(t1, t2), min(t3, t4)), min(t5, t6));
tmax := min(min(max(t1, t2), max(t3, t4)), max(t5, t6));
// if tmax < 0, ray (line) is intersecting AABB, but whole AABB is behing us
if tmax < 0 {
return -1;
}
// if tmin > tmax, ray doesn't intersect AABB
if tmin > tmax {
return -1;
}
if tmin < 0 {
return tmax;
}
return tmin;
}
ray_sphere_intersect :: (ray: Ray, center: Vector3, radius: float) -> bool {
t : float;
oc := ray.origin - center;
a := dot(ray.direction, ray.direction);
b := 2.0 * dot(oc, ray.direction);
c := dot(oc, oc) - radius * radius;
discriminant := b * b - 4 * a * c;
if discriminant > 0 {
// Two intersection points, choose the closest one (smallest positive t)
t1 := (-b - sqrt(discriminant)) / (2.0 * a);
t2 := (-b + sqrt(discriminant)) / (2.0 * a);
if t1 > 0 || t2 > 0 {
t = ifx (t1 < t2) then t1 else t2;
return true;
}
} else if discriminant == 0 {
// One intersection point
t = -b / (2.0 * a);
return t > 0;
}
// No intersection
return false;
}
ray_plane_intersection :: (ray: Ray, p: Vector3, n: Vector3) -> bool, float {
denom := dot(n, ray.direction);
if abs(denom) > 0.0001 {
offset := p - ray.origin;
t := dot(offset, n) / denom;
return t >= 0.0, t;
}
return false, 0.0;
}
ray_triangle_intersect :: (origin: Vector3, dir: Vector3, v0: Vector3, v1: Vector3, v2: Vector3) -> bool, float, Vector3 {
EPSILON := 0.0000001;
edge1 := v1 - v0;
edge2 := v2 - v0;
h := cross(dir, edge2);
a := dot(edge1, h);
if a > -EPSILON && a < EPSILON return false, 0.0, .{0,0,0}; // This ray is parallel to this triangle.
f := 1.0/a;
s := origin - v0;
u := f * dot(s, h);
if u < 0.0 || u > 1.0 return false, 0.0, .{0,0,0};
q := cross(s, edge1);
v := f * dot(dir, q);
if v < 0.0 || u + v > 1.0 return false, 0.0, .{0,0,0};
// At this stage we can compute t to find out where the intersection point is on the line.
t := f * dot(edge2, q);
if t > EPSILON { // ray intersection
//outIntersectionPoint = rayOrigin + rayVector * t;
normal := normalize(cross(edge1, edge2));
return true, t, normal;
}
else // This means that there is a line intersection but not a ray intersection.
return false, 0.0, .{0,0,0};
//e0 := v1 - v0;
//e1 := v2 - v0;
//pvec := cross(dir, e1);
//det := dot(pvec, e0);
//if det > 0 {
// inv_det := 1.0 / det;
// tvec := origin - v0;
// u := inv_det * dot(tvec, pvec);
// if u < 0.0 || u > 1.0 {
// return false, 0.0;
// }
// qvec := cross(tvec, e0);
// v := inv_det * dot(qvec, dir);
// if v < 0.0 || u + v > 1.0 {
// return false, 0.0;
// }
// return true, dot(e1, qvec) * inv_det;
//}
//return false, 0.0;
}
distance_from_ray_to_point :: (ray: Ray, p: Vector3) -> float {
pb := p - ray.origin;
t0 := dot(ray.direction, pb) / dot(ray.direction, ray.direction);
if t0 <= 0.0 {
return length(pb);
} else if t0 > 0.0 && t0 < 1.0 {
return length(p - (ray.origin + t0 * ray.direction));
} else {
return length(p - (ray.origin + ray.direction));
}
}
closest_point_on_ray :: (from: Vector3, dir: Vector3, p: Vector3) -> Vector3 {
lhs := p - from;
dot_p := dot(lhs, dir);
return from + dir * dot_p;
}
closest_distance_between_rays :: (l1: Ray, l2: Ray) -> distance: float, t1: float, t2: float {
u := l1.direction;
v := l2.direction;
w := l1.origin - l2.origin;
a := dot(u, u);
b := dot(u, v);
c := dot(v, v);
d := dot(u, w);
e := dot(v, w);
D := a * c - b * b;
sc: float;
tc: float;
if D < 0.00001 {
sc = 0.0;
if b > c {
tc = d / b;
} else {
tc = e / c;
}
} else {
sc = ((b * e) - (c * d)) / D;
tc = ((a * e) - (b * d)) / D;
}
P1 := u * sc;
P2 := v * tc;
dP := w + P1 - P2;
return length(dP), sc, tc;
}
get_positions :: (e : *Entity, n : Node_Render_Data, m : Mesh, i0 : u32, i1 : u32, i2 : u32) -> Vector3, Vector3, Vector3 {
p0 : Vector3;
p1 : Vector3;
p2 : Vector3;
p0 = transform_point(n.transform.world_matrix, m.positions[i0]);
p1 = transform_point(n.transform.world_matrix, m.positions[i1]);
p2 = transform_point(n.transform.world_matrix, m.positions[i2]);
return p0, p1, p2;
}
ray_entity_intersect :: (ray: Ray, e: *Entity) -> bool, float, Vector3 {
has_hit := false;
closest : float = 10000000;
closest_normal : Vector3;
for n: e.renderable.model.nodes {
render_node := e.renderable.nodes[it_index];
for handle: n.meshes {
m := parray_get(*renderer.meshes, handle);
index := 0;
if m.indices.count > 0 {
while index < m.indices.count - 1 {
i0 := m.indices[index];
i1 := m.indices[index + 1];
i2 := m.indices[index + 2];
p2, p1, p0 := get_positions(e, render_node, m, i0, i1, i2);
result, t, normal := ray_triangle_intersect(ray.origin, ray.direction, p0, p1, p2);
if result && t < closest {
has_hit = true;
closest = t;
closest_normal = normal;
}
index += 3;
}
} else {
while index < m.positions.count - 1 {
p2, p1, p0 := get_positions(e, render_node, m, xx index, xx (index + 1), xx (index + 2));
result, t, normal := ray_triangle_intersect(ray.origin, ray.direction, p0, p1, p2);
if result && t < closest {
has_hit = true;
closest = t;
closest_normal = normal;
}
index += 3;
}
}
}
}
return has_hit, closest, closest_normal;
}

142
core/scene.jai Normal file
View File

@@ -0,0 +1,142 @@
#load "../renderer/directional_light.jai";
#load "particles.jai";
#placeholder Entity_Storage;
MAX_CACHED_PILES :: 8;
Scene :: struct {
name: string;
entities : [..] *Entity;
particle_systems : Bucket_Array(Particle_System, 64);
bullet_impact_particle_systems : [..] *Particle_System;
by_type : Entity_Storage;
pool : Flat_Pool;
allocator : Allocator;
camera : Camera;
directional_light : Directional_Light;
}
Entity_File_Info :: struct {
id: s64;
full_path: string;
}
visitor :: (info : *File_Visit_Info, files: *[..] Entity_File_Info) {
if info.is_directory
return;
path, basename, ext := path_decomp (info.full_name);
// Entity text files
if ext == "ent" && basename != "cam" {
file_info : Entity_File_Info;
file_info.id = cast(s32)string_to_int(basename);
file_info.full_path = copy_temporary_string(info.full_name);
array_add(files, file_info);
}
}
load_scene :: (path: string) -> *Scene {
scene := create_scene("", 1024);
files : [..] Entity_File_Info;
files.allocator = temp;
visit_files(path, true, *files, visitor);
for file: files {
deserialize_entity(scene, file.full_path);
}
return scene;
}
save_scene :: (scene: *Scene, path: string) {
scene.camera = game_state.camera;
builder : String_Builder;
builder.allocator = temp;
full_path := tprint("%/%", path, scene.name);
make_directory_if_it_does_not_exist(full_path);
for scene.entities {
if it.flags & .DONT_SAVE continue;
serialize_entity(it, full_path);
}
// Save camera
//print_to_builder(*builder, "Camera: % % % % %\n", scene.camera.position.x, scene.camera.position.y, scene.camera.position.z, scene.camera.rotation.yaw, scene.camera.rotation.pitch);
//write_entire_file(path, builder_to_string(*builder));
}
unload_scene :: (scene: *Scene) {
for e: scene.entities {
destroy_entity(e, false);
}
fini(*scene.pool);
free(scene);
}
create_scene :: (name: string, max_entities: s64 = 256) -> *Scene {
scene := New(Scene);
scene.name = copy_string(name);
// Setup allocator
scene.pool = .{};
scene.allocator.data = *scene.pool;
scene.allocator.proc = flat_pool_allocator_proc;
// Assign allocator to everything that needs allocations
scene.entities.allocator = scene.allocator;
scene.particle_systems.allocator = scene.allocator;
scene.bullet_impact_particle_systems.allocator = scene.allocator;
array_reserve(*scene.entities, max_entities);
scene.directional_light.color_and_intensity = .{1,1,1,1};
scene.directional_light.direction = to_v4(normalize(Vector3.{0.3, -0.3, 0.5}));
dir_light_data : Directional_Light_Buffer_Data;
dir_light_data.color_and_intensity = scene.directional_light.color_and_intensity;
dir_light_data.direction = scene.directional_light.direction;
upload_data_to_buffer(renderer, directional_light_buffer, *dir_light_data, size_of(Directional_Light_Buffer_Data));
array_resize(*scene.bullet_impact_particle_systems, 32);
for 0..31 {
scene.bullet_impact_particle_systems[it] = create_particle_system(particle_pipeline, on_update_bullet_hit_particles, null, scene);
}
return scene;
}
register_entity :: (scene: *Scene, entity: *Entity) {
entity.scene = scene;
entity.id = next_entity_id;
array_add(*scene.entities, entity);
next_entity_id += 1;
if net_data.net_mode == {
case .LISTEN_SERVER; #through;
case .DEDICATED_SERVER; {
//net_spawn_entity(entity);
}
}
}
unregister_entity :: (scene: *Scene, entity: *Entity) {
array_unordered_remove_by_value(*scene.entities, entity);
}
#scope_file
next_entity_id: Entity_Id;

29
core/stack.jai Normal file
View File

@@ -0,0 +1,29 @@
Stack :: struct(Value_Type : Type) {
values: [..] Value_Type;
}
stack_push :: (stack: *Stack, value: stack.Value_Type) {
array_add(*stack.values, value);
}
stack_pop :: (stack: *Stack) -> stack.Value_Type {
if stack.values.count > 0 {
index := stack.values.count - 1;
stack.values.count -= 1;
return stack.values.data[index];
}
return null;
}
stack_peek :: (stack: *Stack) -> stack.Value_Type {
if stack.values.count > 0 {
return stack.values[stack.values.count-1];
}
return null;
}
is_stack_empty :: (stack: *Stack) -> bool {
return stack.values.count == 0;
}

21
core/static_array.jai Normal file
View File

@@ -0,0 +1,21 @@
Static_Array :: struct (Data_Type : Type, Count: s64) {
data: [Count] Data_Type;
count: s64;
}
array_add :: (static_array: *Static_Array, value: static_array.Data_Type) {
assert(static_array.count <= static_array.Count);
static_array.data[static_array.count] = value;
static_array.count += 1;
}
for_expansion :: (static_array: *Static_Array, body: Code, flags: For_Flags) #expand {
for `it_index: 0..static_array.count-1 {
#if flags & .POINTER {
`it := *static_array.data[it_index];
} else {
`it := static_array.data[it_index];
}
#insert body;
}
}

6
core/string_helpers.jai Normal file
View File

@@ -0,0 +1,6 @@
to_temp_c_string :: (s: string) -> *u8 {
result : *u8 = alloc(s.count + 1,, allocator=temp);
memcpy(result, s.data, s.count);
result[s.count] = 0;
return result;
}

172
core/transform.jai Normal file
View File

@@ -0,0 +1,172 @@
#import "Math";
Transform_Identity : Transform : .{ .{0,0,0}, .{0,0,0,1}, .{1,1,1}, Matrix4_Identity, Matrix4_Identity, false };
Transform :: struct {
position: Vector3;
orientation: Quaternion;
scale : Vector3;
model_matrix: Matrix4 = Matrix4_Identity; @DontSerialize
world_matrix: Matrix4; @DontSerialize
dirty: bool; @DontSerialize
}
create_identity_transform :: () -> Transform {
transform : Transform;
transform.position = .{};
transform.orientation = .{0,0,0,1};
transform.scale = .{1,1,1};
transform.model_matrix = Matrix4_Identity;
update_matrix(*transform);
return transform;
}
make_matrix :: (position: Vector3, orientation: Quaternion, scale: Vector3) -> Matrix4 {
trans_mat := make_translation_matrix4(position);
scale_mat := make_scale_matrix4(scale);
rot_mat := rotation_matrix(Matrix4, orientation);
return trans_mat * rot_mat * scale_mat;
}
create_transform :: (position: Vector3, orientation: Quaternion, scale: Vector3) -> Transform {
transform : Transform;
transform.position = position;
transform.orientation = orientation;
transform.scale = scale;
transform.model_matrix = make_matrix(position, orientation, scale);
return transform;
}
update_matrix :: (transform: *Transform) {
transform.model_matrix = make_matrix(transform.position, transform.orientation, transform.scale);
transform.dirty = true;
}
set_position :: (transform: *Transform, position: Vector3, calculate_matrix: bool = true) {
transform.position = position;
if calculate_matrix update_matrix(transform);
}
set_position :: (transform: *Transform, x: float, y: float, z: float, calculate_matrix: bool = true) {
transform.position.x = x;
transform.position.y = y;
transform.position.z = z;
if calculate_matrix update_matrix(transform);
}
translate :: (transform: *Transform, translation: Vector3, calculate_matrix: bool = true) {
transform.position += translation;
if calculate_matrix update_matrix(transform);
}
euler_to_quaternion :: (yaw: float, pitch: float, roll: float) -> Quaternion {
cy := cos(yaw * 0.5);
sy := sin(yaw * 0.5);
cp := cos(pitch * 0.5);
sp := sin(pitch * 0.5);
cr := cos(roll * 0.5);
sr := sin(roll * 0.5);
q: Quaternion;
q.w = cr * cp * cy + sr * sp * sy;
q.x = sr * cp * cy - cr * sp * sy;
q.y = cr * sp * cy + sr * cp * sy;
q.z = cr * cp * sy - sr * sp * cy;
return q;
}
sign :: (val: $T) -> T {
if val < 0 return -1.0;
if val > 0 return 1.0;
return 0.0;
}
quaternion_to_euler_v3 :: (q: Quaternion) -> Vector3 {
yaw, pitch, roll := quaternion_to_euler(q);
v : Vector3 = ---;
v.x = yaw;
v.y = pitch;
v.z = roll;
return v;
}
quaternion_to_euler :: (q: Quaternion) -> yaw: float, pitch: float, roll: float {
yaw: float;
pitch: float;
roll: float;
sinr_cosp := 2.0 * (q.w * q.x + q.y * q.z);
cosr_cosp := 1.0 - 2.0 * (q.x * q.x + q.y * q.y);
roll = atan2(sinr_cosp, cosr_cosp);
// pitch (y-axis rotation)
sinp := 2.0 * (q.w * q.y - q.z * q.x);
if abs(sinp) >= 1.0
pitch = sign(sinp) * (PI / 2); // use 90 degrees if out of range
else
pitch = asin(sinp);
// yaw (z-axis rotation)
siny_cosp := 2.0 * (q.w * q.z + q.x * q.y);
cosy_cosp := 1.0 - 2.0 * (q.y * q.y + q.z * q.z);
yaw = atan2(siny_cosp, cosy_cosp);
return yaw, pitch, roll;
}
set_rotation :: (transform: *Transform, orientation: Quaternion, calculate_matrix: bool = true) {
transform.orientation = orientation;
if calculate_matrix update_matrix(transform);
}
set_rotation :: (transform: *Transform, euler_angles: Vector3, calculate_matrix: bool = true) {
orientation := euler_to_quaternion(degrees_to_radians(euler_angles.y), degrees_to_radians(euler_angles.x), degrees_to_radians(euler_angles.z));
transform.orientation = orientation;
if calculate_matrix update_matrix(transform);
}
set_scale :: (transform: *Transform, scale: Vector3, calculate_matrix: bool = true) {
transform.scale = scale;
if calculate_matrix update_matrix(transform);
}
set_scale :: (transform: *Transform, scale: float, calculate_matrix: bool = true) {
transform.scale = .{scale, scale, scale};
if calculate_matrix update_matrix(transform);
}
get_forward :: (transform: Transform) -> Vector3 {
v := rotation_matrix(Matrix4, transform.orientation) * Vector4.{0,0,1,0};
return .{v.x, v.y, v.z};
}
get_right :: (transform: Transform) -> Vector3 {
v := rotation_matrix(Matrix4, transform.orientation) * Vector4.{1,0,0,0};
return .{v.x, v.y, v.z};
}
get_up :: (transform: Transform) -> Vector3 {
v := rotation_matrix(Matrix4, transform.orientation) * Vector4.{0,1,0,0};
return .{v.x, v.y, v.z};
}
get_position :: (transform: Transform) -> Vector3 {
return .{transform.model_matrix._14, transform.model_matrix._24, transform.model_matrix._34};
}
get_rotation :: (transform: Transform) -> Vector3 {
return .{transform.model_matrix._14, transform.model_matrix._24, transform.model_matrix._34};
}

378
editor/editor.jai Normal file
View File

@@ -0,0 +1,378 @@
Gizmo_Space :: enum {
LOCAL;
WORLD;
}
Transform_Axis :: enum {
NONE;
UP;
FORWARD;
RIGHT;
CENTER;
}
Transform_Type :: enum {
TRANSLATION;
ROTATION;
SCALE;
}
Buffer_Info :: struct {
buffer: Buffer_Handle;
vertex_count: u32;
}
Transform_Gizmo :: struct {
active: bool = false;
transform_type: Transform_Type = .TRANSLATION;
space: Gizmo_Space;
selected_axis: Transform_Axis;
first_hit_position: Vector3;
actual_entity_position: Vector3; // The actual position of the selected entity. Used for snapping
actual_entity_scale: Vector3; // The actual position of the selected entity. Used for snapping
last_circle_dir: Vector3;
clicked: bool;
transform: Transform;
can_use: bool = true;
center_model_buffer: Buffer_Handle;
up_model_buffer: Buffer_Handle;
forward_model_buffer: Buffer_Handle;
right_model_buffer: Buffer_Handle;
color_center: Color;
color_up: Color;
color_forward: Color;
color_right: Color;
color_center_buffer: Buffer_Handle;
color_up_buffer: Buffer_Handle;
color_forward_buffer: Buffer_Handle;
color_right_buffer: Buffer_Handle;
uniform_gizmo_scale: float;
}
Editor :: struct {
show_menu: bool;
should_check_entities: bool;
camera: Camera;
transform_gizmo: Transform_Gizmo;
selected_entity: *Entity;
mouse_viewport_state: Interaction_State;
last_right_mouse_click_time: float;
menu_position: Vector2;
}
editor : Editor;
update_transform_gizmo :: (ray: Ray, mouse_position: Vector2) -> bool {
return false;
// selected_entity := engine.editor.selected_entity;
//
// if key_down(.TAB) {
// if engine.editor.transform_gizmo.space == {
// case .WORLD;
// engine.editor.transform_gizmo.space = .LOCAL;
// case .LOCAL;
// engine.editor.transform_gizmo.space = .WORLD;
// }
// }
//
// if engine.editor.transform_gizmo.space == {
// case .WORLD;
// set_rotation(*engine.editor.transform_gizmo.transform, .{0,0,0,1});
// case .LOCAL;
// set_rotation(*engine.editor.transform_gizmo.transform, selected_entity.transform.orientation);
// }
//
// if engine.editor.transform_gizmo.transform_type == {
// case .TRANSLATION;
// if !key_pressed(.MOUSE_LEFT) {
// selected_axis, t := intersect_translation_gizmo(ray);
// engine.editor.transform_gizmo.selected_axis = selected_axis;
// } else if engine.editor.transform_gizmo.can_use && engine.editor.transform_gizmo.selected_axis != .NONE {
// first_update := key_down(.MOUSE_LEFT);
//
// if first_update {
// engine.editor.transform_gizmo.actual_entity_position = selected_entity.transform.position;
// }
//
// // Move the currently selected entity along the selected axis
// axis_vec : Vector3;
//
// if engine.editor.transform_gizmo.selected_axis == {
// case .UP;
// axis_vec.y = 1;
// case .FORWARD;
// axis_vec.z = 1;
// case .RIGHT;
// axis_vec.x = 1;
// }
//
// r1 : Ray;
// r1.origin = selected_entity.transform.position;
// r1.direction = rotate(axis_vec, engine.editor.transform_gizmo.transform.orientation);
//
// r2 := normalized_screen_to_ray_v2(*engine.editor.camera, mouse_position);
//
// d, t1, t2 := closest_distance_between_rays(r1, r2);
//
// new_position := r1.origin + r1.direction * t1;
//
// if first_update {
// engine.editor.transform_gizmo.first_hit_position = new_position;
// }
//
// position_change := new_position - engine.editor.transform_gizmo.first_hit_position;
//
// entity_position := engine.editor.transform_gizmo.actual_entity_position + position_change;
//
// if selected_entity.flags & Entity_Flags.SNAP_TO_GRID {
// entity_position.x -= fmod_cycling(entity_position.x - selected_entity.snap_offset.x, selected_entity.snap_intervals.x);// + selected_entity.snap_offset.x;
// entity_position.y -= fmod_cycling(entity_position.y - selected_entity.snap_offset.y, selected_entity.snap_intervals.y);// + selected_entity.snap_offset.y;
// entity_position.z -= fmod_cycling(entity_position.z - selected_entity.snap_offset.z, selected_entity.snap_intervals.z);// + selected_entity.snap_offset.z;
// }
//
// selected_entity.transform.position = entity_position;
// set_position(*engine.editor.transform_gizmo.transform, entity_position);
// }
//
// color_up := engine.editor.transform_gizmo.color_up;
// color_forward := engine.editor.transform_gizmo.color_forward;
// color_right := engine.editor.transform_gizmo.color_right;
//
// if engine.editor.transform_gizmo.selected_axis == {
// case .NONE;
// case .UP;
// color_up = Color.{1,1,0,1};
// case .FORWARD;
// color_forward = Color.{1,1,0,1};
// case .RIGHT;
// color_right = Color.{1,1,0,1};
// }
//
// upload_data_to_buffer(engine.renderer, engine.editor.transform_gizmo.color_up_buffer, *color_up, size_of(Color));
// upload_data_to_buffer(engine.renderer, engine.editor.transform_gizmo.color_forward_buffer, *color_forward, size_of(Color));
// upload_data_to_buffer(engine.renderer, engine.editor.transform_gizmo.color_right_buffer, *color_right, size_of(Color));
// case .ROTATION;
// selected_entity := engine.editor.selected_entity;
//
// if !engine.editor.transform_gizmo.clicked {
// selected_axis, point := intersect_rotation_gizmo(ray);
// engine.editor.transform_gizmo.selected_axis = selected_axis;
//
// if engine.editor.transform_gizmo.selected_axis != .NONE && key_down(.MOUSE_LEFT) {
// engine.editor.transform_gizmo.clicked = true;
// engine.editor.transform_gizmo.last_circle_dir = normalize(point - selected_entity.transform.position);
// }
// } else if !key_pressed(.MOUSE_LEFT) {
// engine.editor.transform_gizmo.clicked = false;
// } else {
// direction : Vector3;
// if engine.editor.transform_gizmo.selected_axis == {
// case .UP;
// direction = .{0,1,0};
// case .FORWARD;
// direction = .{0,0,1};
// case .RIGHT;
// direction = .{1,0,0};
// }
// direction = rotate(direction, engine.editor.transform_gizmo.transform.orientation);
//
// // Find the rotation
// circle : Circle;
// circle.radius = engine.editor.transform_gizmo.uniform_gizmo_scale;
// circle.center = selected_entity.transform.position;
// circle.orientation = direction;
//
// distance, point := closest_distance_ray_circle(ray, circle);
// new_dir := normalize(point - selected_entity.transform.position);
//
// dotp := dot(engine.editor.transform_gizmo.last_circle_dir, new_dir);
// angle := acos(clamp(dotp, -1.0, 1.0));
// cp := cross(engine.editor.transform_gizmo.last_circle_dir, new_dir);
//
// if dot(direction, cp) < 0 {
// angle *= -1.0;
// }
//
// q : Quaternion;
// set_from_axis_and_angle(*q, direction, angle);
//
// selected_entity.transform.orientation = q * selected_entity.transform.orientation;
// update_matrix(*selected_entity.transform);
//
// set_rotation(*engine.editor.transform_gizmo.transform, selected_entity.transform.orientation);
//
// engine.editor.transform_gizmo.last_circle_dir = new_dir;
// }
//
// color_up := engine.editor.transform_gizmo.color_up;
// color_forward := engine.editor.transform_gizmo.color_forward;
// color_right := engine.editor.transform_gizmo.color_right;
//
// if engine.editor.transform_gizmo.selected_axis == {
// case .NONE;
// case .UP;
// color_up = Color.{1,1,0,1};
// case .FORWARD;
// color_forward = Color.{1,1,0,1};
// case .RIGHT;
// color_right = Color.{1,1,0,1};
// }
//
// upload_data_to_buffer(engine.renderer, engine.editor.transform_gizmo.color_up_buffer, *color_up, size_of(Color));
// upload_data_to_buffer(engine.renderer, engine.editor.transform_gizmo.color_forward_buffer, *color_forward, size_of(Color));
// upload_data_to_buffer(engine.renderer, engine.editor.transform_gizmo.color_right_buffer, *color_right, size_of(Color));
// case .SCALE;
// if !key_pressed(.MOUSE_LEFT) {
// selected_axis, t := intersect_scale_gizmo(ray);
// engine.editor.transform_gizmo.selected_axis = selected_axis;
// } else if engine.editor.transform_gizmo.selected_axis != .NONE {
// selected_entity := engine.editor.selected_entity;
// first_update := key_down(.MOUSE_LEFT);
//
// if first_update {
// engine.editor.transform_gizmo.actual_entity_position = selected_entity.transform.position;
// engine.editor.transform_gizmo.actual_entity_scale = selected_entity.transform.scale;
// }
//
// // Move the currently selected entity along the selected axis
// axis_vec : Vector3;
//
// if engine.editor.transform_gizmo.selected_axis == {
// case .UP;
// axis_vec.y = 1;
// case .FORWARD;
// axis_vec.z = -1;
// case .RIGHT;
// axis_vec.x = 1;
// case .CENTER; axis_vec = .{1,1,1};
// }
//
// r1 : Ray;
// r1.origin = selected_entity.transform.position;
// r1.direction = rotate(axis_vec, engine.editor.transform_gizmo.transform.orientation);
// // Shoot a ray from screen to world
// mouse_position : Vector2;
// mouse_position.x = xx engine.input.mouse.x;
// mouse_position.y = xx engine.input.mouse.y;
//
// screen_size : Vector2;
// screen_size.x = cast(float)engine.window.width;
// screen_size.y = cast(float)engine.window.height;
//
// r2 := screen_to_ray_v2(*engine.editor.camera, mouse_position, screen_size);
//
// d, t1, t2 := closest_distance_between_rays(r1, r2);
// new_position := r1.origin + r1.direction * t1;
//
// if first_update {
// engine.editor.transform_gizmo.first_hit_position = new_position;
// }
//
// position_change := new_position - engine.editor.transform_gizmo.first_hit_position;
// scale_speed := ifx key_pressed(.SHIFT) then 4.0 else 1.0;
//
// if selected_entity.uses_uniform_scale {
// current_scale := selected_entity.transform.scale;
//
// if engine.editor.transform_gizmo.selected_axis == {
// case .UP; {
// current_scale.y = engine.editor.transform_gizmo.actual_entity_scale.y + position_change.y * scale_speed;
// position_change.x = 0;
// position_change.z = 0;
// }
// case .FORWARD; {
// current_scale.z = engine.editor.transform_gizmo.actual_entity_scale.z + position_change.z * scale_speed;
// position_change.x = 0;
// position_change.y = 0;
// }
// case .RIGHT; {
// current_scale.x = engine.editor.transform_gizmo.actual_entity_scale.x + position_change.x * scale_speed;
// position_change.y = 0;
// position_change.z = 0;
// }
// case .CENTER; {
// current_scale.x = engine.editor.transform_gizmo.actual_entity_scale.x + position_change.x * scale_speed;
// current_scale.y = current_scale.x; // @Incomplete: This is most definitely wrong!
// current_scale.z = current_scale.x;
//
// position_change.y = 0;
// position_change.z = 0;
// }
// }
//
// set_scale(*selected_entity.transform, current_scale);
//
// } else {
// if engine.editor.transform_gizmo.selected_axis == {
// case .UP;
// position_change.x = 0;
// position_change.z = 0;
// case .FORWARD;
// position_change.x = 0;
// position_change.y = 0;
// case .RIGHT;
// position_change.y = 0;
// position_change.z = 0;
// }
//
// entity_scale := engine.editor.transform_gizmo.actual_entity_scale + position_change * scale_speed;
// set_scale(*selected_entity.transform, entity_scale);
// }
// }
//
// color_up := engine.editor.transform_gizmo.color_up;
// color_forward := engine.editor.transform_gizmo.color_forward;
// color_right := engine.editor.transform_gizmo.color_right;
// color_center := Color.{1,1,1,1};
//
// if engine.editor.transform_gizmo.selected_axis == {
// case .NONE;
// case .UP;
// color_up = Color.{1,1,0,1};
// case .FORWARD;
// color_forward = Color.{1,1,0,1};
// case .RIGHT;
// color_right = Color.{1,1,0,1};
// case .CENTER;
// color_center = Color.{1,1,0,1};
// }
//
// upload_data_to_buffer(engine.renderer, engine.editor.transform_gizmo.color_center_buffer, *color_center, size_of(Color));
// upload_data_to_buffer(engine.renderer, engine.editor.transform_gizmo.color_up_buffer, *color_up, size_of(Color));
// upload_data_to_buffer(engine.renderer, engine.editor.transform_gizmo.color_forward_buffer, *color_forward, size_of(Color));
// upload_data_to_buffer(engine.renderer, engine.editor.transform_gizmo.color_right_buffer, *color_right, size_of(Color));
// }
//
// return engine.editor.transform_gizmo.selected_axis != .NONE;
}
update_gizmo_buffers :: () {
// update_matrix(*engine.editor.transform_gizmo.transform);
//
// up_rotation := rotation_matrix(Matrix3, euler_to_quaternion(degrees_to_radians(90), degrees_to_radians(90), degrees_to_radians(0)));
// right_rotation := rotation_matrix(Matrix3, euler_to_quaternion(degrees_to_radians(90), degrees_to_radians(0), degrees_to_radians(90)));
// up_model := engine.editor.transform_gizmo.transform.model_matrix * up_rotation;
// right_model := engine.editor.transform_gizmo.transform.model_matrix * right_rotation;
// center_scale := make_scale_matrix4(.{0.15, 0.15, 0.15});
// center_model := engine.editor.transform_gizmo.transform.model_matrix * center_scale;
//
// upload_data_to_buffer(engine.renderer, engine.editor.transform_gizmo.center_model_buffer, *center_model, size_of(Matrix4));
// upload_data_to_buffer(engine.renderer, engine.editor.transform_gizmo.up_model_buffer, *up_model, size_of(Matrix4));
// upload_data_to_buffer(engine.renderer, engine.editor.transform_gizmo.forward_model_buffer, *engine.editor.transform_gizmo.transform.model_matrix, size_of(Matrix4));
// upload_data_to_buffer(engine.renderer, engine.editor.transform_gizmo.right_model_buffer, *right_model, size_of(Matrix4));
}
#load "editor_ui.jai";

310
editor/editor_ui.jai Normal file
View File

@@ -0,0 +1,310 @@
#load "../ui/widgets.jai";
pick_scene_view_at :: (coordinates: Vector2) {
ray := normalized_screen_to_ray_v2(*editor.camera, coordinates);
if editor.should_check_entities {
hit_entity : *Entity;
closest : float = 100000000;
for game_state.current_scene.entities {
if !(it.flags & .RENDERABLE) continue;
//if it.flags & Entity_Flags.DELETED || !it.selectable || it.parent != null continue;
success, dist := ray_entity_intersect(ray, it);
if success && dist < closest {
closest = dist;
hit_entity = it;
}
}
if hit_entity != null {
if cast(*Entity)hit_entity != editor.selected_entity {
editor.selected_entity = hit_entity;
}
} else if editor.selected_entity != null {
editor.selected_entity = null;
}
}
}
editor_ui :: () {
ui_full_size_background();
ui_push_parent(ui_state.last_box, alignment=.LEFT, axis=.VERTICAL);
{
ui_set_next_padding(1);
ui_toolbar();
ui_push_parent(ui_state.last_box, alignment=.LEFT, axis=.HORIZONTAL);
{
if ui_toolbar_button("File") {
}
//ui_space(15, 10);
if ui_toolbar_button("Edit") {
}
//ui_space(15, 10);
if ui_toolbar_button("View") {
}
ui_space(20, 0);
//if mode == {
// case .PLAYING; {
// ui_set_next_background_color(.{0,0.6,0,1});
// if ui_button_with_texture(editor.icons.stop) {
// edit_current_scene();
// }
// ui_label(tprint("Playing '%'", current_scene.name), .{0,0.7,0,1});
// }
// case .EDITING; {
ui_set_next_background_color(.{0.6,0,0,1});
//if ui_button_with_texture(editor.icons.play) {
// play_current_editor_scene();
//}
ui_label(tprint("Editing '%'", game_state.current_scene.name), .{1,1,1,1});
//}
//}
}
ui_pop_parent();
ui_set_next_size_x(.PCT, 1.0);
ui_set_next_size_y(.PCT, 0.965);
ui_set_next_background_color(.{0,0,0,1});
background := ui_box(.NONE);
ui_push_parent(ui_state.last_box, alignment=.LEFT, axis=.HORIZONTAL);
{
ui_set_next_size_x(.PCT, 0.1);
ui_set_next_size_y(.PCT, 1.0);
ui_set_next_padding(1);
first_panel := ui_box(.NONE);
ui_push_parent(first_panel, alignment=.LEFT, axis=.VERTICAL);
{
ui_set_next_size_x(.PCT, 1.0);
ui_set_next_size_y(.PCT, 1.0);
ui_set_next_background_color(.{0.04, 0.04, 0.04, 1.0});
background := ui_box(.DRAW_BACKGROUND|.DRAW_BORDER);
ui_push_parent(background, alignment=.LEFT, axis=.VERTICAL);
{
ui_set_next_size_x(.PCT, 1.0);
ui_tab_title_bar("ENTITIES");
for game_state.current_scene.entities {
ui_set_next_padding(20);
clicked := false;
if it.name.count == 0 {
clicked = ui_clickable_label(tprint("%", it.type), it == editor.selected_entity, it_index);
} else {
clicked = ui_clickable_label(it.name, it == editor.selected_entity, it_index);
}
if clicked {
editor.selected_entity = it;
}
//ui_space(0, 5);
}
}
ui_pop_parent();
}
ui_pop_parent();
// Scene
ui_set_next_size_x(.PCT, 0.7);
ui_set_next_size_y(.PCT, 1.0);
ui_set_next_padding(1);
viewport_layer := ui_box(.NONE);
ui_push_parent(viewport_layer, alignment=.LEFT, axis=.VERTICAL);
{
ui_set_next_size_x(.PCT, 1.0);
ui_tab_title_bar("SCENE");
ui_set_next_size_x(.PCT, 1.0);
ui_set_next_size_y(.PCT, 0.9);
state := ui_interactable_texture(get_texture_from_pass("FXAA"));
if state.left_mouse_down {
//pick_scene_view_at(.{state.normalized_local_mouse_coordinates.x, 1.0 - state.normalized_local_mouse_coordinates.y});
}
editor.mouse_viewport_state = state;
ui_set_next_size_x(.PCT, 1.0);
ui_set_next_size_y(.PCT, 0.1);
ui_tab_title_bar("SCENES");
}
ui_pop_parent();
// Properties
ui_set_next_size_x(.PCT, 0.2);
ui_set_next_size_y(.PCT, 1.0);
ui_set_next_background_color(.{0.04, 0.04, 0.04, 1.0});
ui_set_next_padding(1);
inspector := ui_box(.DRAW_BACKGROUND | .DRAW_BORDER);
ui_push_parent(inspector, alignment=.LEFT, axis=.VERTICAL);
{
ui_set_next_size_x(.PCT, 1.0);
ui_tab_title_bar("PROPERTIES");
if editor.selected_entity != null {
ui_slider(*slider_value, 0.0, 1.0);
ui_label(tprint("Name: %", editor.selected_entity.name));
ui_label(tprint("Id: %", editor.selected_entity.id));
ui_label(tprint("Position: % % %", editor.selected_entity.transform.position.x, editor.selected_entity.transform.position.y, editor.selected_entity.transform.position.z));
ui_label(tprint("Rotation: % % % %", editor.selected_entity.transform.orientation.x, editor.selected_entity.transform.orientation.y, editor.selected_entity.transform.orientation.z, editor.selected_entity.transform.orientation.w));
ui_label(tprint("Scale: % % %", editor.selected_entity.transform.scale.x, editor.selected_entity.transform.scale.y, editor.selected_entity.transform.scale.z));
}
}
ui_pop_parent();
}
ui_pop_parent();
}
ui_pop_parent();
}
slider_value : float = 0.0;
base_editor_update :: () {
if editor.show_menu && (key_down(.MOUSE_LEFT) || key_down(.ESCAPE)) {
editor.show_menu = false;
eat_key(.MOUSE_LEFT);
}
// Check if we hit the gizmo
// @Incomplete: MOVE THIS
editor.should_check_entities = true;
if editor.selected_entity != null {
gizmo_scale := distance(editor.selected_entity.transform.position, editor.camera.position) * 0.1 * 0.5;
editor.transform_gizmo.uniform_gizmo_scale = gizmo_scale;
set_scale(*editor.transform_gizmo.transform, .{gizmo_scale, gizmo_scale, gizmo_scale});
coordinates := Vector2.{editor.mouse_viewport_state.normalized_local_mouse_coordinates.x, 1.0 - editor.mouse_viewport_state.normalized_local_mouse_coordinates.y};
ray := normalized_screen_to_ray_v2(*editor.camera, coordinates);
if update_transform_gizmo(ray, coordinates) {
editor.should_check_entities = false;
}
}
editor_ui();
//if editor.show_menu {
// placed_entity := imgui_show_place_entity();
// if placed_entity != null {
// // @Incomplete: At some point we want this to place entities at either a set distance or a distance depending on a raycast in the camera's forward direction so that we don't place things behind other entities
// set_position(*placed_entity.transform, editor.camera.position + editor.camera.forward * 30);
// editor.show_menu = false;
// editor.selected_entity = placed_entity;
// }
//}
camera := *editor.camera;
if key_pressed(.CTRL) && key_down(.S) {
save_scene(game_state.current_scene, "../assets/scenes/");
//show_message("Saved scene");
}
if editor.selected_entity != null {
// @Incomplete:@Incomplete: Duplicate
//if key_pressed(.CTRL) && key_down(.D) {
// make_directory_if_it_does_not_exist("../temp");
// save_entity(editor.selected_entity, "../temp/", "temp");
// duplicated := load_entity(editor_scene, "../temp/temp.ent");
// editor.selected_entity = duplicated;
//}
// DELETE
// DELETE
//if key_down(.DELETE) || key_down(.BACKSPACE) {
// delete_entity(editor.selected_entity);
// editor.selected_entity = null;
// editor.transform_gizmo.selected_axis = .NONE;
//}
}
//if key_pressed(.CTRL) && key_down(.Z) {
// editor_undo();
//}
if game_state.mode == .EDITING {
if key_pressed(.MOUSE_RIGHT) {
set_show_cursor(false);
// Update camera
mouse_speed :: 54.0;
dir : Vector3;
if key_pressed(.W) {
dir += camera.forward;
} else if key_pressed(.S) {
dir += -camera.forward;
}
if key_pressed(.A) {
dir += -camera.right;
} else if key_pressed(.D) {
dir += camera.right;
}
if key_pressed(.Q) {
dir += -camera.up;
} else if key_pressed(.E) {
dir += camera.up;
}
dir = normalize(dir);
if length(dir) > 0.05 {
multiplier := ifx key_pressed(.SHIFT) then 10.0 else 1.0;
camera.position += dir * 20.0 * multiplier * dt;
//print("Camera position %\n", camera.position);
}
MOUSE_SENSITIVITY :: 0.06;
camera.rotation.yaw -= input.mouse.delta_x * MOUSE_SENSITIVITY;
camera.rotation.pitch += -input.mouse.delta_y * MOUSE_SENSITIVITY;
if key_down(.MOUSE_RIGHT) {
editor.last_right_mouse_click_time = time;
}
} else {
set_show_cursor(true);
if time - editor.last_right_mouse_click_time < 0.2 {
mouse_position : Vector2;
mouse_position.x = xx input.mouse.x;
mouse_position.y = xx input.mouse.y;
editor.show_menu = true;
editor.menu_position.x = mouse_position.x;
editor.menu_position.y = cast(float)renderer.render_target_height - mouse_position.y;
}
if key_down(.W) {
editor.transform_gizmo.transform_type = .TRANSLATION;
}
if key_down(.E) {
editor.transform_gizmo.transform_type = .ROTATION;
}
if key_down(.R) {
editor.transform_gizmo.transform_type = .SCALE;
}
}
update_view_matrix(camera);
}
if editor.selected_entity != null {
e := editor.selected_entity;
set_position(*editor.transform_gizmo.transform, e.transform.position);
update_gizmo_buffers();
}
//edit_scene_settings(*game_state.current_scene.settings);
}

389
input/input.jai Normal file
View File

@@ -0,0 +1,389 @@
#load "sdl_input.jai";
LEFT_STICK_DEADZONE :: 0.239;
RIGHT_STICK_DEADZONE :: 0.265;
Key_Flags :: enum_flags u8 {
NONE :: 0;
DOWN :: 1;
START :: 2;
END :: 4;
EATEN :: 8;
}
Key_Code :: enum {
INVALID :: 0;
A;
B;
C;
D;
E;
F;
G;
H;
I;
J;
K;
L;
M;
N;
O;
P;
Q;
R;
S;
T;
U;
V;
W;
X;
Y;
Z;
ZERO;
ONE;
TWO;
THREE;
FOUR;
FIVE;
SIX;
SEVEN;
EIGHT;
NINE;
F1;
F2;
F3;
F4;
F5;
F6;
F7;
F8;
F9;
F10;
F11;
F12;
SPACE;
BACKSPACE;
RETURN;
TAB;
SHIFT;
CTRL;
ALT;
DELETE;
TILDE;
ESCAPE;
LEFT;
RIGHT;
UP;
DOWN;
MOUSE_LEFT;
MOUSE_RIGHT;
KEYS_MAX;
}
Gamepad_Button :: enum {
SPECIAL_BOTTOM;
SPECIAL_TOP;
SPECIAL_LEFT;
SPECIAL_RIGHT;
LEFT_STICK_LEFT;
LEFT_STICK_RIGHT;
LEFT_STICK_UP;
LEFT_STICK_DOWN;
LEFT_STICK_BUTTON;
RIGHT_STICK_BUTTON;
DPAD_LEFT;
DPAD_RIGHT;
DPAD_UP;
DPAD_DOWN;
LEFT_BUMPER;
RIGHT_BUMPER;
LEFT_TRIGGER;
RIGHT_TRIGGER;
GAMEPAD_MAX;
}
Gamepad :: struct {
id : s32;
left_stick_x : float;
left_stick_y : float;
right_stick_x : float;
right_stick_y : float;
buttons : [Gamepad_Button.GAMEPAD_MAX] Key_Flags;
}
MAX_GAMEPADS :: 4;
MAX_MAPPINGS :: 4;
Action :: enum {}
Action_Mapping :: struct {
gamepad_buttons: [MAX_MAPPINGS] Gamepad_Button;
keys: [MAX_MAPPINGS] Key_Code;
gamepad_mapping_count: s32;
key_mapping_count: s32;
}
Last_Touched_State :: enum {
KEYBOARD_AND_MOUSE;
GAMEPAD;
}
Input_State :: struct {
exit : bool;
action_mappings: [..] Action_Mapping;
keys: [Key_Code.KEYS_MAX] Key_Flags;
mouse : struct {
x : float;
y : float;
delta_x : float;
delta_y : float;
first: bool = true;
wheel: float;
}
gamepads: [MAX_GAMEPADS] Gamepad;
num_gamepads: s32;
has_char : bool;
current_char: s8;
last_touched: Last_Touched_State;
}
input : Input_State;
init_input :: () {
input = .{};
input.has_char = false;
init_sdl_input();
}
update_key_state :: (key: Key_Code, down: bool) {
using input;
flags := keys[key];
if down {
if !(flags & Key_Flags.DOWN) {
flags = Key_Flags.DOWN | Key_Flags.START;
}
} else {
flags = Key_Flags.END;
}
keys[key] = flags;
input.last_touched = .KEYBOARD_AND_MOUSE;
}
update_gamepad_state :: (gamepad: *Gamepad, button: Gamepad_Button, down: bool) {
flags := gamepad.buttons[button];
if down {
if !(flags & Key_Flags.DOWN) {
flags = Key_Flags.DOWN | Key_Flags.START;
input.last_touched = .GAMEPAD;
}
} else if flags & Key_Flags.DOWN {
flags = Key_Flags.END;
}
gamepad.buttons[button] = flags;
}
update_input :: () {
input.mouse.delta_x = 0.0;
input.mouse.delta_y = 0.0;
input.mouse.wheel = 0.0;
input.has_char = false;
remove_all_temp_key_flags(*input);
update_sdl_input();
update_gamepad_input();
}
remove_all_temp_key_flags :: (using input_state: *Input_State) {
for 0..Key_Code.KEYS_MAX - 1 {
keys[it] &= ~Key_Flags.START;
keys[it] &= ~Key_Flags.END;
keys[it] &= ~Key_Flags.EATEN;
}
for g: 0..num_gamepads-1 {
for 0..Gamepad_Button.GAMEPAD_MAX-1 {
gamepads[g].buttons[it] &= ~Key_Flags.START;
gamepads[g].buttons[it] &= ~Key_Flags.END;
}
}
}
eat_all_input :: (using input_state: *Input_State) {
for 0..Key_Code.KEYS_MAX - 1 {
eat_key(cast(Key_Code)it);
}
}
eat_mouse_input :: (using input_state: *Input_State) {
eat_key(.MOUSE_LEFT);
eat_key(.MOUSE_RIGHT);
}
eat_key :: (key: Key_Code) {
input.keys[key] |= Key_Flags.EATEN;
}
action_down :: (action: Action) -> bool {
mapping := input.action_mappings[action];
for 0..mapping.key_mapping_count-1 {
if key_down(mapping.keys[it]) {
return true;
}
}
for 0..mapping.gamepad_mapping_count-1 {
if gamepad_down(0, mapping.gamepad_buttons[it]) {
return true;
}
}
return false;
}
action_pressed :: (action: Action) -> bool {
mapping := input.action_mappings[action];
for 0..mapping.key_mapping_count-1 {
if key_pressed(mapping.keys[it]) {
return true;
}
}
for 0..mapping.gamepad_mapping_count-1 {
if gamepad_pressed(0, mapping.gamepad_buttons[it]) {
return true;
}
}
return false;
}
action_up :: (action: Action) -> bool {
mapping := input.action_mappings[action];
for 0..mapping.key_mapping_count-1 {
if key_up(mapping.keys[it]) {
return true;
}
}
for 0..mapping.gamepad_mapping_count-1 {
if gamepad_up(0, mapping.gamepad_buttons[it]) {
return true;
}
}
return false;
}
get_mouse_delta_x :: inline () -> float {
return input.mouse.delta_x;
}
get_mouse_delta_y :: inline () -> float {
return input.mouse.delta_y;
}
get_mouse_delta :: () -> float, float {
return get_mouse_delta_x(), get_mouse_delta_y();
}
get_mouse_wheel_input :: () -> float {
return input.mouse.wheel;
}
get_horizontal_axis :: () -> float {
if #complete input.last_touched == {
case .KEYBOARD_AND_MOUSE;
if key_pressed(.A) || key_pressed(.LEFT) return -1.0;
else if key_pressed(.D) || key_pressed(.RIGHT) return 1.0;
else return 0.0;
case .GAMEPAD;
if gamepad_pressed(0, .DPAD_LEFT) return -1.0;
else if gamepad_pressed(0, .DPAD_RIGHT) return 1.0;
return input.gamepads[0].left_stick_x;
}
}
get_vertical_axis :: () -> float {
if #complete input.last_touched == {
case .KEYBOARD_AND_MOUSE;
if key_pressed(.S) || key_pressed(.DOWN) return -1.0;
else if key_pressed(.W) || key_pressed(.UP) return 1.0;
else return 0.0;
case .GAMEPAD;
if gamepad_pressed(0, .DPAD_DOWN) return -1.0;
else if gamepad_pressed(0, .DPAD_UP) return 1.0;
return -input.gamepads[0].left_stick_y;
}
}
get_right_horizontal_axis :: () -> float {
if #complete input.last_touched == {
case .KEYBOARD_AND_MOUSE;
return input.mouse.delta_x;
case .GAMEPAD;
return input.gamepads[0].right_stick_x;
}
}
get_right_vertical_axis :: () -> float {
if #complete input.last_touched == {
case .KEYBOARD_AND_MOUSE;
return input.mouse.delta_y;
case .GAMEPAD;
return -input.gamepads[0].right_stick_y;
}
}
key_down :: (key: Key_Code) -> bool {
flags := input.keys[key];
return flags & .START && !(flags & .EATEN);
}
key_pressed :: (key: Key_Code) -> bool {
flags := input.keys[key];
return flags & .DOWN && !(flags & .EATEN);
}
key_up :: (key: Key_Code) -> bool {
flags := input.keys[key];
return flags & .END && !(flags & .EATEN);
}
gamepad_down :: (index: s32, button: Gamepad_Button) -> bool {
return cast(bool)(input.gamepads[index].buttons[button] & .START);
}
gamepad_pressed :: (index: s32, button: Gamepad_Button) -> bool {
return cast(bool)(input.gamepads[index].buttons[button] & .DOWN);
}
gamepad_up :: (index: s32, button: Gamepad_Button) -> bool {
return cast(bool)(input.gamepads[index].buttons[button] & .END);
}

260
input/sdl_input.jai Normal file
View File

@@ -0,0 +1,260 @@
key_mappings : Table(SDL_Keycode, Key_Code);
sdl_button_map : [SDL_CONTROLLER_BUTTON_MAX] Gamepad_Button;
init_sdl_input :: () {
table_add(*key_mappings, SDLK_a, .A);//[SDLK_a] = .A;
table_add(*key_mappings, SDLK_b, .B);
table_add(*key_mappings, SDLK_c, .C);
table_add(*key_mappings, SDLK_d, .D);
table_add(*key_mappings, SDLK_e, .E);
table_add(*key_mappings, SDLK_f, .F);
table_add(*key_mappings, SDLK_g, .G);
table_add(*key_mappings, SDLK_h, .H);
table_add(*key_mappings, SDLK_i, .I);
table_add(*key_mappings, SDLK_j, .J);
table_add(*key_mappings, SDLK_k, .K);
table_add(*key_mappings, SDLK_l, .L);
table_add(*key_mappings, SDLK_m, .M);
table_add(*key_mappings, SDLK_n, .N);
table_add(*key_mappings, SDLK_o, .O);
table_add(*key_mappings, SDLK_p, .P);
table_add(*key_mappings, SDLK_q, .Q);
table_add(*key_mappings, SDLK_r, .R);
table_add(*key_mappings, SDLK_s, .S);
table_add(*key_mappings, SDLK_t, .T);
table_add(*key_mappings, SDLK_u, .U);
table_add(*key_mappings, SDLK_v, .V);
table_add(*key_mappings, SDLK_w, .W);
table_add(*key_mappings, SDLK_x, .X);
table_add(*key_mappings, SDLK_y, .Y);
table_add(*key_mappings, SDLK_z, .Z);
table_add(*key_mappings, SDLK_0, .ZERO);
table_add(*key_mappings, SDLK_1, .ONE);
table_add(*key_mappings, SDLK_2, .TWO);
table_add(*key_mappings, SDLK_3, .THREE);
table_add(*key_mappings, SDLK_4, .FOUR);
table_add(*key_mappings, SDLK_5, .FIVE);
table_add(*key_mappings, SDLK_6, .SIX);
table_add(*key_mappings, SDLK_7, .SEVEN);
table_add(*key_mappings, SDLK_8, .EIGHT);
table_add(*key_mappings, SDLK_9, .NINE);
table_add(*key_mappings, SDLK_F1, .F1);
table_add(*key_mappings, SDLK_F1, .F2);
table_add(*key_mappings, SDLK_F2, .F3);
table_add(*key_mappings, SDLK_F3, .F3);
table_add(*key_mappings, SDLK_F4, .F4);
table_add(*key_mappings, SDLK_F5, .F5);
table_add(*key_mappings, SDLK_F6, .F6);
table_add(*key_mappings, SDLK_F7, .F7);
table_add(*key_mappings, SDLK_F8, .F8);
table_add(*key_mappings, SDLK_F9, .F9);
table_add(*key_mappings, SDLK_F10, .F10);
table_add(*key_mappings, SDLK_F11, .F11);
table_add(*key_mappings, SDLK_F12, .F12);
table_add(*key_mappings, SDLK_SPACE, .SPACE);
table_add(*key_mappings, SDLK_BACKSPACE, .BACKSPACE);
table_add(*key_mappings, SDLK_ESCAPE, .ESCAPE);
table_add(*key_mappings, SDLK_RETURN, .RETURN);
table_add(*key_mappings, SDLK_TAB, .TAB);
table_add(*key_mappings, SDLK_LSHIFT, .SHIFT); // @Incomplete
table_add(*key_mappings, SDLK_LCTRL, .CTRL); // @Incomplete
table_add(*key_mappings, SDLK_LALT, .ALT); // @Incomplete
table_add(*key_mappings, SDLK_DELETE, .DELETE);
table_add(*key_mappings, SDLK_BACKQUOTE, .TILDE);
table_add(*key_mappings, SDLK_LEFT, .LEFT);
table_add(*key_mappings, SDLK_RIGHT, .RIGHT);
table_add(*key_mappings, SDLK_UP, .UP);
table_add(*key_mappings, SDLK_DOWN, .DOWN);
//glfw_key_map[GLFW_MOUSE_BUTTON_LEFT] = .MOUSE_LEFT;
//glfw_key_map[GLFW_MOUSE_BUTTON_RIGHT] = .MOUSE_RIGHT;
}
update_sdl_input :: () {
event : SDL_Event;
while SDL_PollEvent(*event) {
if event.type == {
case SDL_QUIT; {
input.exit = true;
}
case SDL_KEYDOWN; {
keycode, success := table_find(*key_mappings, event.key.keysym.sym);
if success {
update_key_state(keycode, true);
}
}
case SDL_KEYUP; {
keycode, success := table_find(*key_mappings, event.key.keysym.sym);
if success {
update_key_state(keycode, false);
}
}
case SDL_TEXTINPUT; {
input.current_char = event.text.text[0];
input.has_char = true;
}
case .SDL_MOUSEMOTION; {
input.mouse.delta_x += xx event.motion.xrel;
input.mouse.delta_y += xx event.motion.yrel;
input.mouse.x = xx event.motion.x;
input.mouse.y = xx event.motion.y;
}
case .SDL_MOUSEWHEEL; {
input.mouse.wheel += event.wheel.y;
}
case .SDL_CONTROLLERAXISMOTION; {
sdl_gamepad_axis_update(event.caxis);
}
case .SDL_MOUSEBUTTONDOWN; {
button := event.button.button;
if button == SDL_BUTTON_LEFT {
update_key_state(.MOUSE_LEFT, true);
} else if button == SDL_BUTTON_RIGHT {
update_key_state(.MOUSE_RIGHT, true);
}
}
case .SDL_MOUSEBUTTONUP; {
button := event.button.button;
if button == SDL_BUTTON_LEFT {
update_key_state(.MOUSE_LEFT, false);
} else if button == SDL_BUTTON_RIGHT {
update_key_state(.MOUSE_RIGHT, false);
}
}
case SDL_CONTROLLERBUTTONDOWN; #through;
case SDL_CONTROLLERBUTTONUP; {
sdl_gamepad_button_update(event.cbutton);
}
case SDL_CONTROLLERDEVICEADDED; #through;
case SDL_CONTROLLERDEVICEREMOVED; {
sdl_gamepad_device_event(event.cdevice);
}
case SDL_CONTROLLERDEVICEREMAPPED; {
}
}
}
//mouse_x, mouse_y : s32;
//SDL_GetRelativeMouseState(*mouse_x, *mouse_y);
//input.mouse.delta_x = xx mouse_x;
//input.mouse.delta_y = xx mouse_y;
}
sdl_connect_gamepad :: (input : *Input_State, id : SDL_JoystickID) {
if !SDL_IsGameController(id) {
return;
} else if input.num_gamepads >= MAX_GAMEPADS {
return;
}
// @Incomplete: Is this necessary?
//if input.gamepads[id].controller != null {
// print("Already connected gamepad with id %\n", id);
// return;
//}
gamepad : Gamepad;
controller := xx SDL_GameControllerOpen(id);
input.gamepads[id] = gamepad;
input.num_gamepads += 1;
}
sdl_disconnect_gamepad :: (input : *Input_State, id : SDL_JoystickID) {
if !SDL_IsGameController(id) {
return;
}
gamepad := input.gamepads[id];
//if !gamepad.controller {
// return;
//}
//input.gamepads[id].controller = null;
}
update_gamepad_input :: () {
// Deadzone checks
for 0..input.num_gamepads-1 {
gamepad := *input.gamepads[it];
if abs(gamepad.left_stick_x) + abs(gamepad.left_stick_y) < LEFT_STICK_DEADZONE {
gamepad.left_stick_x = 0.0;
gamepad.left_stick_y = 0.0;
}
if abs(gamepad.right_stick_x) + abs(gamepad.right_stick_y) < RIGHT_STICK_DEADZONE {
gamepad.right_stick_x = 0.0;
gamepad.right_stick_y = 0.0;
}
}
}
sdl_gamepad_axis_update :: (using event : SDL_ControllerAxisEvent) {
if which < 0 || which >= input.num_gamepads {
return;
}
gamepad := *input.gamepads[which];
if cast(SDL_GameControllerAxis) axis == {
case SDL_CONTROLLER_AXIS_LEFTX; {
gamepad.left_stick_x = cast(float)value / 32767.0;
//update_gamepad_state(gamepad, .LEFT_STICK_LEFT, gamepad.left_stick_x < -0.1);
//update_gamepad_state(gamepad, .LEFT_STICK_RIGHT, gamepad.left_stick_x > 0.1);
}
case SDL_CONTROLLER_AXIS_LEFTY; {
gamepad.left_stick_y = cast(float)value / 32767.0;
//update_gamepad_state(gamepad, .LEFT_STICK_UP, gamepad.left_stick_y > 0.1);
//update_gamepad_state(gamepad, .LEFT_STICK_DOWN, gamepad.left_stick_y < -0.1);
}
case SDL_CONTROLLER_AXIS_RIGHTX; {
gamepad.right_stick_x = cast(float)value / 32767.0;
//update_gamepad_state(gamepad, .RIGHT_STICK_LEFT, gamepad.right_stick_x < -0.1);
//update_gamepad_state(gamepad, .RIGHT_STICK_RIGHT, gamepad.right_stick_x > 0.1);
}
case SDL_CONTROLLER_AXIS_RIGHTY; {
gamepad.right_stick_y = cast(float)value / 32767.0;
///update_gamepad_state(gamepad, .RIGHT_STICK_UP, gamepad.right_stick_y > 0.1);
///update_gamepad_state(gamepad, .RIGHT_STICK_DOWN, gamepad.right_stick_y < -0.1);
}
case SDL_CONTROLLER_AXIS_TRIGGERLEFT; {
//gamepad.trigger_left = cast(float)value;
}
case SDL_CONTROLLER_AXIS_TRIGGERRIGHT; {
//gamepad.trigger_right = cast(float)value;
}
}
}
sdl_gamepad_button_update :: (using event : SDL_ControllerButtonEvent) {
if which < 0 || which >= input.num_gamepads {
return;
}
down := state == SDL_PRESSED;
gamepad_button := sdl_button_map[button];
gamepad := *input.gamepads[which];
update_gamepad_state(gamepad, gamepad_button, down);
}
sdl_gamepad_device_event :: (using event : SDL_ControllerDeviceEvent) {
if which < 0 || which >= MAX_GAMEPADS {
return;
}
if type == {
case SDL_CONTROLLERDEVICEADDED; {
sdl_connect_gamepad(*input, which);
}
case SDL_CONTROLLERDEVICEREMOVED; {
sdl_disconnect_gamepad(*input, which);
}
}
}

334
metaprogram.jai Normal file
View File

@@ -0,0 +1,334 @@
#import "Compiler";
#import "Basic";
#import "String";
#import "Sort";
build_release := false;
build :: (main_path: string, game_name: string) {
set_working_directory(#filepath);
w := compiler_create_workspace("Game");
opts := get_build_options(w);
opts.output_type = .EXECUTABLE;
opts.output_executable_name = game_name;
opts.output_path = "bin/";
new_path: [..] string;
array_add(*new_path, ..opts.import_path);
array_add(*new_path, "modules");
opts.import_path = new_path;
if build_release {
set_optimization(*opts, .VERY_OPTIMIZED);
}
compiler_begin_intercept(w);
set_build_options(opts, w);
add_build_string(tprint("GAME_NAME :: \"%\";", game_name), w);
add_build_file(main_path, w);
message_loop();
compiler_end_intercept(w);
set_build_options_dc(.{do_output=false});
}
// Contains all the auto-generated serialization procedures
entity_serialize_proc_string: [..] string;
should_serialize :: (type: *Type_Info_Struct, member: Type_Info_Struct_Member) -> bool {
if type!= null {
if type.name == {
case "Vector2"; {
if member.name == {
case "x"; #through;
case "y"; #through;
case; return false;
}
}
case "Vector3"; {
if member.name == {
case "x"; #through;
case "y"; #through;
case "z"; return true;
case; return false;
}
}
case "Vector4"; #through;
case "Quaternion"; {
if member.name == {
case "x"; #through;
case "y"; #through;
case "z"; #through;
case "w"; return true;
case; return false;
}
}
}
}
for member.notes {
if to_lower_copy_new(it,, allocator = temp) == "dontserialize" return false;
}
return true;
}
generate_member_serialization :: (type: *Type_Info_Struct, builder: *String_Builder, path: string = "") {
for type.members {
if should_serialize(type, it) {
new_path : string;
if path.count == 0 {
new_path = it.name;
} else {
new_path = tprint("%1.%2", path, it.name);
}
if it.type.type == {
case .STRUCT; {
info_struct := cast(*Type_Info_Struct) it.type;
generate_member_serialization(info_struct, builder, new_path);
}
case .BOOL; #through;
case .FLOAT; #through;
case .ENUM; #through;
case .INTEGER; {
print_to_builder(builder, "\tprint_to_builder(builder, \"%: \%\\n\", e.%);\n", new_path, new_path);
}
}
}
}
}
generate_member_deserialization :: (type: *Type_Info_Struct, builder: *String_Builder, path: string = "") {
for type.members {
if should_serialize(type, it) {
new_path : string;
if path.count == 0 {
new_path = it.name;
} else {
new_path = tprint("%1.%2", path, it.name);
}
if it.type.type == {
case .STRUCT; {
info_struct := cast(*Type_Info_Struct) it.type;
generate_member_deserialization(info_struct, builder, new_path);
}
case .BOOL; #through;
case .FLOAT; #through;
case .INTEGER; {
print_to_builder(builder, "\t\t\t\tcase \"%\";\n", new_path);
print_to_builder(builder, "\t\t\t\t\tscan2(values[1], \"\%\", *e.%);\n", new_path);
}
}
}
}
}
generate_serialize_procedure_for_entity :: (code_struct: *Code_Struct) {
name := code_struct.defined_type.name;
// Serialize
serialize : String_Builder;
print_to_builder(*serialize, "serialize_entity :: (e: *%, builder: *String_Builder) {\n", name);
print_to_builder(*serialize, "\tprint_to_builder(builder, \"type: %\\n\");\n", name);
generate_member_serialization(code_struct.defined_type, *serialize);
print_to_builder(*serialize, "}\n");
array_add(*entity_serialize_proc_string, builder_to_string(*serialize));
// Deserialize
deserialize : String_Builder;
print_to_builder(*deserialize, "deserialize_entity :: (scene: *Scene, lines: [] string, e: *%) {\n", name);
print_to_builder(*deserialize, "\tfor line: lines {\n");
print_to_builder(*deserialize, "\t\tvalues := split(line, \":\");\n");
print_to_builder(*deserialize, "\t\tif values.count == 2 {\n");
print_to_builder(*deserialize, "\t\t\tif trim(values[0], \" \") == {\n");
generate_member_deserialization(code_struct.defined_type, *deserialize);
print_to_builder(*deserialize, "\t\t\t}\n");
print_to_builder(*deserialize, "\t\t}\n");
print_to_builder(*deserialize, "\t}\n");
print_to_builder(*deserialize, "}\n");
array_add(*entity_serialize_proc_string, builder_to_string(*deserialize));
}
note_struct :: (code_struct: *Code_Struct) {
if is_subclass_of(code_struct.defined_type, "Entity") {
name := code_struct.defined_type.name;
added := array_add_if_unique(*entity_type_names, name);
if added {
print("Detected entity '%'.\n", name);
generate_serialize_procedure_for_entity(code_struct);
}
}
}
message_loop :: () {
while true {
message := compiler_wait_for_message();
// We ignore most message types. We mainly want to know about code that has been typechecked,
// so that we can look for the data structures we care about; or, when all the code is done
// being typechecked, we want to generate our new code.
if message.kind == {
case .TYPECHECKED;
typechecked := cast(*Message_Typechecked) message;
for typechecked.structs note_struct(it.expression);
case .PHASE;
phase := cast(*Message_Phase) message;
if phase.phase == .TYPECHECKED_ALL_WE_CAN {
if !generated_code {
generate_code(message.workspace);
generated_code = true;
}
}
case .COMPLETE;
break;
}
}
}
generate_code :: (w: Workspace) {
// Let's sort the entity_type_names alphabetically, which is nice, but also
// gives us a reproducible build order, since our metaprogram may see these
// entities in any order:
quick_sort(entity_type_names, compare_strings);
// A piece of the code we want to generate is a comma-separated string of entity type
// names, so, let's make that using join():
type_names := join(..entity_type_names, ", ");
// We will also want to generate entity_storage_string, which contains a bunch of Bucket_Arrays
// for allocating each entity type. We might be able to do this with join() again, but
// we'll use String_Builder here, since that is a good example of how to do arbitrarily complex things:
entity_storage_string: string;
{
builder: String_Builder;
for entity_type_names {
// Let's say it's always 20 items per bucket,
// unless it's Player, in which case we want 64:
items_per_bucket := 20;
if it == "Player" items_per_bucket = 64;
print_to_builder(*builder, " _%1: Bucket_Array(%1, %2, true);\n", it, items_per_bucket);
}
entity_storage_string = builder_to_string(*builder);
}
{
builder: String_Builder;
for entity_type_names {
print_to_builder(*builder, "\tcase %1; serialize_entity(cast(*%1)e, *builder);\n", it);
}
build_string := sprint(SERIALIZE_ENTITY, builder_to_string(*builder));
add_build_string(build_string, w);
}
{
builder: String_Builder;
for entity_type_names {
print_to_builder(*builder, "\tcase \"%1\"; p, locator := find_and_occupy_empty_slot(*scene.by_type._%1); p._locator = locator; e = p; register_entity(scene, p); init_entity(p); deserialize_entity(scene, lines, cast(*%1)e); update_matrix(*e.transform);\n", it);
}
build_string := sprint(DESERIALIZE_ENTITY, builder_to_string(*builder));
add_build_string(build_string, w);
}
{
builder: String_Builder;
for entity_type_names {
print_to_builder(*builder, "new_%1 :: (scene: *Scene = null) -> *%2 { _scene := scene;\nif _scene == null { \n_scene = game_state.current_scene; }\np, locator := find_and_occupy_empty_slot(*_scene.by_type._%2); p._locator = locator; register_entity(_scene, p); p.transform = create_identity_transform(); init_entity(p); return p; }\n", to_lower_copy_new(it,, allocator=temp), it);
}
add_build_string(builder_to_string(*builder), w);
}
for entity_serialize_proc_string {
add_build_string(it, w);
}
// We fill out INSERTION_STRING to create the code we want to insert:
build_string := sprint(INSERTION_STRING, entity_type_names.count, type_names, entity_storage_string);
// Add this string to the target program:
add_build_string(build_string, w);
// We'll print out the added code just to show at compile-time what we are doing:
print("Adding build string:\n%\n", build_string);
}
generated_code := false;
entity_type_names: [..] string;
// INSERTION_STRING represents the code we want to add to the target program.
// We'll use print to insert useful things where the % markers are.
INSERTION_STRING :: #string DONE
// NUM_ENTITY_TYPES tells the target program how many entity types there are.
NUM_ENTITY_TYPES :: %1;
// entity_types is an array containing all the entity types.
entity_types : [%1] Type : .[ %2 ];
Entity_Storage :: struct {
%3
}
DONE
SERIALIZE_ENTITY :: #string DONE
serialize_entity :: (e: *Entity, path: string) {
builder: String_Builder;
builder.allocator = temp;
if e.type == {
%1
}
write_entire_file(tprint("%%/%%.ent", path, e.id), builder_to_string(*builder));
}
DONE
DESERIALIZE_ENTITY :: #string DONE
deserialize_entity :: (scene: *Scene, path: string) -> *Entity {
content := read_entire_file(path);
if content.count == 0 return null;
lines := split(content, "\n");
first_line := split(lines[0], ":");
if first_line.count != 2 return null;
e: *Entity;
type := trim(first_line[1], " \n\r");
if type == {
%1
}
return e;
}
DONE
#run build("Test");

2
module.jai Normal file
View File

@@ -0,0 +1,2 @@
#load "core/entity.jai";
#load "core/scene.jai";

596
networking/networking.jai Normal file
View File

@@ -0,0 +1,596 @@
Net_Mode :: enum {
DEDICATED_SERVER;
LISTEN_SERVER;
CLIENT;
}
LOBBY_PORT :: 27015;
MAX_LOBBIES :: 8;
lobbies : [MAX_LOBBIES] Lobby;
num_lobbies: s32;
Lobby :: struct {
name: string;
address: Net_Address;
}
Client_Id :: #type, isa s64;
Client_Connection :: struct {
id: Client_Id;
address: Net_Address;
}
Server_Connection :: struct {
address: Net_Address;
}
Server_Data :: struct {
client_connections: [16] Client_Connection;
num_client_connections: s64;
}
Client_Data :: struct {
client_id: Client_Id;
server_connection: Server_Connection;
}
Net_Data :: struct {
net_mode: Net_Mode;
socket: Socket.SOCKET;
server: Server_Data;
client: Client_Data;
}
net_data: Net_Data;
Net_Address :: struct {
address: Socket.sockaddr_in;
valid: bool;
}
Entity_Network_State :: enum {
LOCAL;
LOCAL_REPLICATED;
PROXY;
}
pending_net_messages : [..] Net_Message;
messages_to_send : [..] Prepared_Net_Message;
Join_Lobby_Data :: struct {
}
Net_Message_Type :: enum {
INVALID;
LOOKING_FOR_LOBBY;
LOBBY_INFO;
JOIN_LOBBY;
CLIENT_ACCEPTED;
MESSAGE_RESPONSE;
START_GAME;
CLIENT_INPUT;
SPAWN_ENTITY;
PICKUP_EVENT;
TRANSFORM_UPDATE;
}
Net_Message_Header :: struct {
type : Net_Message_Type;
size: s64;
timestamp: float64;
}
Prepared_Net_Message :: struct {
to: Net_Address;
message: Net_Message;
}
Net_Message :: struct {
header: Net_Message_Header;
body : [1024] u8;
}
// Create a lobby/game
// Search for lobbies
// Join a lobby
// Start a game
Net_Networking_State :: enum {
IDLE;
SHOULD_LOOK_FOR_LOBBIES;
LOOKING_FOR_LOBBIES;
CREATING_LOBBY;
CREATED_LOBBY;
JOINING_LOBBY;
IN_LOBBY;
READY_FOR_GAME_START;
IN_GAME;
}
LOBBY_NAME_SIZE :: 32;
Lobby_Info_Data :: struct {
lobby_name: [LOBBY_NAME_SIZE] u8;
name_len: u8;
}
Client_Joined_Data :: struct {
accepted: bool;
id: Client_Id;
}
Message_Response_Data :: struct {
accepted: bool;
}
Client_Input :: enum_flags {
UP;
DOWN;
LEFT;
RIGHT;
JUMP;
SHOOT;
}
Client_Input_Data :: struct {
client_id: Client_Id;
flags: Client_Input;
}
Spawn_Entity_Data :: struct {
type: Type;
remote_id: Entity_Id;
client_id: Client_Id;
spawn_position: Vector3;
}
Transform_Update_Data :: struct {
entity_id: Entity_Id;
position: Vector3;
rotation: Quaternion;
}
Player_Update_Data :: struct {
entity_id: Entity_Id;
position: Vector3;
yaw: float;
pitch: float;
}
message_mutex: Mutex;
update_mutex: Mutex;
net_init :: () {
net_log("Init\n");
data : Socket.WSAData;
if Socket.WSAStartup(0x0202, *data) != 0 {
assert(false);
net_log("Couldn't initialize Lyn\n");
}
if thread_init(*thread, networking_thread_proc) {
thread_start(*thread);;
}
if thread_init(*message_thread, net_check_for_messages) {
thread_start(*message_thread);;
}
init(*update_mutex);
init(*message_mutex);
init(*internal_message_mutex);
}
net_get_state :: () -> Net_Networking_State {
return state;
}
net_is_multiplayer :: () -> bool {
return state != .IDLE;
}
net_is_client :: () -> bool {
return net_data.net_mode == .CLIENT;
}
net_is_server :: () -> bool {
return net_data.net_mode != .CLIENT;
}
net_can_start_game :: () -> bool {
return state == .READY_FOR_GAME_START;
}
net_create_lobby :: () {
state = .CREATING_LOBBY;
}
net_check_for_lobbies :: () {
state = .SHOULD_LOOK_FOR_LOBBIES;
}
net_start_game :: () {
message := net_new_message(.START_GAME, 0);
for 0..net_data.server.num_client_connections - 1 {
client := net_data.server.client_connections[it];
net_send_message(*message, net_data.socket, client.address);
}
//state = .IN_GAME;
}
net_cancel :: () {
// @Incomplete: Tell all clients to disconnect
Socket.closesocket(net_data.socket);
state = .IDLE;
}
//net_pickup_item :: (player: *Player, item: *Item) {
// message := net_new_message(.PICKUP_EVENT, 0);
// net_send_message(*message, net_game_info.socket, net_game_info.other);
// state = .IN_GAME;
//}
net_new_message :: (type: Net_Message_Type, size: s64) -> Net_Message {
message : Net_Message;
message.header.type = type;
message.header.size = size;
//message.body = alloc(size,temp);
return message;
}
//net_send_message :: (message: *Net_Message) {
// net_send_message(message, net_game_info.socket, net_game_info.other);
//}
#scope_file
net_log :: inline (message: string, args: .. Any) {
log(tprint("NET: %", message), args);
}
net_log_error :: inline (message: string, args: .. Any) {
log_error(tprint("NET: %", message), args);
}
networking_thread_proc :: (thread: *Thread) -> s64 {
while true {
update_networking();
sleep_milliseconds(10);
}
return 0;
}
net_check_for_messages :: (thread: *Thread) -> s64 {
while true {
success, message, address := net_read_message(net_data.socket);
if success {
lock(*internal_message_mutex);
defer unlock(*internal_message_mutex);
pending: Internal_Pending_Message;
pending.message = message;
pending.address = address;
array_add(*internal_pending_messages, pending);
}
}
return 0;
}
net_broadcast_message :: (message: *Net_Message) {
net_send_message(message, net_data.socket, broadcast=true);
}
net_send_message :: (msg: Net_Message, s: Socket.SOCKET, address: Net_Address = .{}, broadcast: bool = false) {
message := msg;
message.header.timestamp = to_float64_seconds(current_time_monotonic());
buffer : [1024] u8;
memcpy(buffer.data, *message.header, size_of(Net_Message_Header));
memcpy(*buffer[size_of(Net_Message_Header)], message.body.data, message.header.size);
wanted_size := size_of(Net_Message_Header) + message.header.size;
bytes_sent : s32;
if broadcast {
broadcast_addr : Socket.sockaddr_in;
broadcast_addr.sin_family = Socket.AF_INET;
broadcast_addr.sin_port = Socket.htons(LOBBY_PORT);
broadcast_addr.sin_addr.S_un.S_addr = Socket.INADDR_BROADCAST;
bytes_sent = Socket.sendto(net_data.socket, buffer.data, xx wanted_size, 0, cast(*Socket.sockaddr)*broadcast_addr, size_of(Socket.sockaddr_in));
} else {
if address.valid {
bytes_sent = Socket.sendto(net_data.socket, buffer.data, xx wanted_size, 0, cast(*Socket.sockaddr)*address.address, size_of(Socket.sockaddr_in));
} else {
bytes_sent = Socket.send(s, buffer.data, xx wanted_size, 0);
}
}
if bytes_sent != wanted_size {
net_log_error("Couldn't send message through socket. Wrong size.\n");
}
if bytes_sent == Socket.SOCKET_ERROR {
net_log_error("Couldn't send message through socket. Socket error %.\n", Socket.WSAGetLastError());
}
}
net_read_message :: (s: Socket.SOCKET) -> bool, Net_Message, Net_Address {
BUFFER_SIZE :: 1024;
buffer : [BUFFER_SIZE] u8;
addr: Net_Address;
addr.valid = true;
addr_len : Socket.socklen_t = size_of(Socket.sockaddr_in);
// First receive the header
bytes_received := Socket.recvfrom(s, buffer.data, BUFFER_SIZE, 0, cast(*Socket.sockaddr)*addr.address, *addr_len);
if bytes_received <= 0 return false, .{}, addr;
message : Net_Message;
memcpy(*message.header, buffer.data, size_of(Net_Message_Header));
//assert(bytes_received == message.header.size + size_of(Message_Header));
//if bytes_received != size_of(Message_Header) + message.header.size return false, .{};
//message.body = alloc(message.header.size,, temp);
memcpy(message.body.data, *buffer[size_of(Net_Message_Header)], message.header.size);
return true, message, addr;
}
net_send_message :: (type: Net_Message_Type, data: $T, s: Socket.SOCKET, address: Net_Address) {
message: Net_Message;
message.header.timestamp = to_float64_seconds(current_time_monotonic());
message.header.size = size_of(T);
message.header.type = type;
buffer : [1024] u8;
memcpy(buffer.data, *message.header, size_of(Net_Message_Header));
memcpy(*buffer[size_of(Net_Message_Header)], *data, message.header.size);
wanted_size := size_of(Net_Message_Header) + message.header.size;
bytes_sent : s32;
if address.valid {
bytes_sent = Socket.sendto(net_data.socket, buffer.data, xx wanted_size, 0, cast(*Socket.sockaddr)*address.address, size_of(Socket.sockaddr_in));
} else {
bytes_sent = Socket.send(s, buffer.data, xx wanted_size, 0);
}
if bytes_sent != wanted_size {
net_log_error("Couldn't send message through socket. Wrong size.\n");
}
if bytes_sent == Socket.SOCKET_ERROR {
net_log_error("Couldn't send message through socket. Socket error.\n");
}
}
on_message_received :: (message: Net_Message, address: Net_Address) {
if message.header.type == {
case .LOOKING_FOR_LOBBY; {
if net_is_server() {
message := net_new_message(.LOBBY_INFO, size_of(Lobby_Info_Data));
data : Lobby_Info_Data;
str := "My First Lobby";
data.name_len = xx str.count;
memcpy(data.lobby_name.data, str.data, str.count);
memcpy(message.body.data, *data, size_of(Lobby_Info_Data));
data.lobby_name[data.name_len] = #char "\0";
net_send_message(*message, net_data.socket, address);
}
}
case .LOBBY_INFO; {
data := cast(*Lobby_Info_Data)message.body.data;
lobby : Lobby;
lobby.address = address;
lobby.name = alloc_string(data.name_len);
memcpy(lobby.name.data, data.lobby_name.data, data.name_len);
duplicate := false;
for 0..num_lobbies-1 {
if lobbies[it].name == lobby.name {
duplicate = true;
break;
}
}
if !duplicate {
lobbies[num_lobbies] = lobby;
num_lobbies += 1;
net_log("Got a lobby info message: %\n", lobby.name);
net_send_message(.JOIN_LOBBY, Join_Lobby_Data.{}, net_data.socket, address);
state = .JOINING_LOBBY;
} else {
free(lobby.name);
}
}
case .CLIENT_ACCEPTED; {
data := cast(*Client_Joined_Data)message.body.data;
state = .IN_LOBBY;
net_data.client.client_id = data.id;
net_data.client.server_connection.address = address;
net_log("Joined lobby as Client with id %!\n", net_data.client.client_id);
}
case .JOIN_LOBBY; {
assert(net_is_server());
connection: Client_Connection;
connection.address = address;
connection.id = cast(Client_Id)(net_data.server.num_client_connections + 1);
net_data.server.client_connections[net_data.server.num_client_connections] = connection;
net_data.server.num_client_connections += 1;
data: Client_Joined_Data;
data.accepted = true;
data.id = connection.id;
net_send_message(.CLIENT_ACCEPTED, data, net_data.socket, address);
state = .READY_FOR_GAME_START;
}
case .START_GAME; {
{
lock(*update_mutex);
defer unlock(*update_mutex);
array_add(*pending_net_messages, message);
}
}
case .SPAWN_ENTITY; {
{
lock(*update_mutex);
defer unlock(*update_mutex);
array_add(*pending_net_messages, message);
}
}
case .TRANSFORM_UPDATE; {
lock(*update_mutex);
defer unlock(*update_mutex);
array_add(*pending_net_messages, message);
}
case; {
lock(*update_mutex);
defer unlock(*update_mutex);
array_add(*pending_net_messages, message);
}
}
}
update_networking :: () {
reset_temporary_storage();
{
lock(*message_mutex);
defer unlock(*message_mutex);
for * messages_to_send {
if net_is_client() {
print("Sending message of type %\n", it.message.header.type);
}
net_send_message(it.message, net_data.socket, it.to);
}
messages_to_send.count = 0;
}
{
lock(*internal_message_mutex);
defer unlock(*internal_message_mutex);
for internal_pending_messages {
on_message_received(it.message, it.address);
}
internal_pending_messages.count = 0;
}
if state == {
case .IDLE; {
}
case .CREATING_LOBBY; {
net_data.socket = Socket.socket(Socket.AF_INET, .SOCK_DGRAM, xx 0);
if net_data.socket < 0 {
net_log_error("Could not create socket for lobby\n");
state = .IDLE;
} else {
server_address : Socket.sockaddr_in;
server_address.sin_family = Socket.AF_INET;
server_address.sin_port = Socket.htons(LOBBY_PORT); // or whatever port you'd like to listen to
server_address.sin_addr.S_un.S_addr = Socket.INADDR_ANY;
if Socket.bind(net_data.socket, cast(*Socket.sockaddr)*server_address, size_of(Socket.sockaddr_in)) < 0 {
Socket.closesocket(net_data.socket);
net_log_error("Could not bind socket for lobby to port %\n");
state = .IDLE;
} else {
state = .CREATED_LOBBY;
net_log("Created lobby\n");
net_data.net_mode = .LISTEN_SERVER;
connection: Client_Connection;
connection.address = .{};
connection.id = cast(Client_Id)(net_data.server.num_client_connections + 1);
net_data.server.client_connections[net_data.server.num_client_connections] = connection;
net_data.server.num_client_connections += 1;
}
}
}
case .CREATED_LOBBY; {
}
case .SHOULD_LOOK_FOR_LOBBIES; {
net_data.net_mode = .CLIENT;
net_data.socket = Socket.socket(Socket.AF_INET, .SOCK_DGRAM, xx 0);
if net_data.socket < 0 {
net_log_error("Could not create socket to look for lobbies\n");
state = .IDLE;
} else {
true_flag : u8 = 1;
if Socket.setsockopt(net_data.socket, Socket.SOL_SOCKET, Socket.SO_BROADCAST, *true_flag, size_of(int)) < 0 {
Socket.closesocket(net_data.socket);
state = .IDLE;
net_log_error("Could not set broadcast setting on socket\n");
} else {
state = .LOOKING_FOR_LOBBIES;
net_log("Looking for lobbies\n");
}
}
}
case .LOOKING_FOR_LOBBIES; {
message := net_new_message(.LOOKING_FOR_LOBBY, 0);
net_broadcast_message(*message);
}
}
//net_check_for_messages();
}
Internal_Pending_Message :: struct {
message: Net_Message;
address: Net_Address;
}
internal_pending_messages : [..] Internal_Pending_Message;
internal_message_mutex: Mutex;
thread : Thread;
message_thread : Thread;
state : Net_Networking_State;
#import "Thread";
#import "Basic";
Socket :: #import "Socket";

363
physics/gjk.jai Normal file
View File

@@ -0,0 +1,363 @@
Simplex :: struct {
points: [4] Vector3;
size: s32;
}
Edge :: struct {
i1: s64;
i2: s64;
}
Collision_Point :: struct {
normal: Vector3;
penetration_depth: float;
has_collision: bool;
}
get_face_normals :: (polytope: [] Vector3, faces: [] s64) -> [..] Vector4, s64 {
normals : [..] Vector4;
normals.allocator = temp;
min_triangle : s64 = 0;
min_distance := FLOAT32_MAX;
i := 0;
while i < faces.count {
defer i += 3;
a := polytope[faces[i+0]];
b := polytope[faces[i+1]];
c := polytope[faces[i+2]];
normal := normalize(cross(b - a, c - a));
distance := dot(normal, a);
if distance < 0 {
normal *= -1.0;
distance *= -1.0;
}
data : Vector4;
data.x = normal.x;
data.y = normal.y;
data.z = normal.z;
data.w = distance;
array_add(*normals, data);
if distance < min_distance {
min_triangle = i / 3;
min_distance = distance;
}
}
return normals, min_triangle;
}
add_if_unique_edge :: (edges: *[..] Edge, faces: [] s64, a: s64, b: s64) {
i := 0;
found := false;
while i < edges.count {
defer i += 1;
edge := edges.*[i];
if edge.i1 == faces[b] && edge.i2 == faces[a] {
array_unordered_remove_by_index(edges, i);
found = true;
break;
}
}
if !found {
array_add(edges, .{faces[a], faces[b]});
}
}
nearly_equal :: (a: float, b: float) -> bool {
return abs(a - b) < 0.00001;
}
epa :: (simplex: Simplex, collider_a: Collider, collider_b: Collider) -> Collision_Point {
polytope : [..] Vector3;
polytope.allocator = temp;
array_reserve(*polytope, simplex.size);
for simplex.points {
array_add(*polytope, it);
}
faces : [..] s64;
faces.allocator = temp;
array_resize(*faces, 12);
faces[0] = 0;
faces[1] = 1;
faces[2] = 2;
faces[3] = 0;
faces[4] = 3;
faces[5] = 1;
faces[6] = 0;
faces[7] = 2;
faces[8] = 3;
faces[9] = 1;
faces[10] = 3;
faces[11] = 2;
normals, min_face := get_face_normals(polytope, faces);
min_normal : Vector3;
min_distance := FLOAT32_MAX;
while nearly_equal(min_distance, FLOAT32_MAX) {
min_normal = to_v3(normals[min_face]);
min_distance = normals[min_face].w;
s := support(collider_a, collider_b, min_normal);
s_distance := dot(min_normal, s);
if abs(s_distance - min_distance) > 0.01 {
min_distance = FLOAT32_MAX;
unique_edges : [..] Edge;
unique_edges.allocator = temp;
i := 0;
while i < normals.count {
defer i += 1;
if dot(to_v3(normals[i]), s) > dot(to_v3(normals[i]), polytope[faces[i*3]]) { //same_direction(to_v3(normals[i]), s) {
f := i * 3;
add_if_unique_edge(*unique_edges, faces, f, f + 1);
add_if_unique_edge(*unique_edges, faces, f + 1, f + 2);
add_if_unique_edge(*unique_edges, faces, f + 2, f);
faces[f+2] = faces[faces.count-1];
faces[f+1] = faces[faces.count-2];
faces[f+0] = faces[faces.count-3];
faces.count -= 3;
normals[i] = normals[normals.count-1];
normals.count -= 1;
i -= 1;
}
}
new_faces : [..] s64;
new_faces.allocator = temp;
for e: unique_edges {
array_add(*new_faces, e.i1);
array_add(*new_faces, e.i2);
array_add(*new_faces, polytope.count);
}
array_add(*polytope, s);
new_normals, new_min_face := get_face_normals(polytope, new_faces);
old_min_distance := FLOAT32_MAX;
for n: normals {
if n.w < old_min_distance {
old_min_distance = n.w;
min_face = it_index;
}
}
if new_normals[new_min_face].w < old_min_distance {
min_face = new_min_face + normals.count;
}
for f: new_faces {
array_add(*faces, f);
}
for n: new_normals {
array_add(*normals, n);
}
}
}
point : Collision_Point;
point.normal = min_normal;
point.penetration_depth = min_distance + 0.001;
point.has_collision = true;
return point;
}
find_furthest_point :: (collider: Collider, direction: Vector3) -> Vector3 {
max_point : Vector3;
max_distance := -FLOAT32_MAX;
if collider.type == {
case .MESH; {
for collider.mesh.vertices {
distance := dot(it, direction);
if distance > max_distance {
max_distance = distance;
max_point = it;
}
}
}
}
return max_point;
}
push_front :: (simplex: *Simplex, p: Vector3) {
simplex.points[3] = simplex.points[2];
simplex.points[2] = simplex.points[1];
simplex.points[1] = simplex.points[0];
simplex.points[0] = p;
simplex.size = min(simplex.size + 1, 4);
}
tetrahedron :: (simplex: *Simplex, direction: *Vector3) -> bool {
a := simplex.points[0];
b := simplex.points[1];
c := simplex.points[2];
d := simplex.points[3];
ab := b - a;
ac := c - a;
ad := d - a;
ao := - a;
abc := cross(ab, ac);
acd := cross(ac, ad);
adb := cross(ad, ab);
if same_direction(abc, ao) {
simplex.points[0] = a;
simplex.points[1] = b;
simplex.points[2] = c;
simplex.size = 3;
return triangle(simplex, direction);
}
if same_direction(acd, ao) {
simplex.points[0] = a;
simplex.points[1] = c;
simplex.points[2] = d;
simplex.size = 3;
return triangle(simplex, direction);
}
if same_direction(adb, ao) {
simplex.points[0] = a;
simplex.points[1] = d;
simplex.points[2] = b;
simplex.size = 3;
return triangle(simplex, direction);
}
return true;
}
triangle :: (simplex: *Simplex, direction: *Vector3) -> bool {
a := simplex.points[0];
b := simplex.points[1];
c := simplex.points[2];
ab := b - a;
ac := c - a;
ao := - a;
abc := cross(ab, ac);
if same_direction(cross(abc, ac), ao) {
if same_direction(ac, ao) {
simplex.points[0] = a;
simplex.points[1] = c;
simplex.size = 2;
direction.* = cross(cross(ac, ao), ac);
} else {
simplex.points[0] = a;
simplex.points[1] = b;
simplex.size = 2;
return line(simplex, direction);
}
} else {
if same_direction(cross(ab, abc), ao) {
simplex.points[0] = a;
simplex.points[1] = b;
simplex.size = 2;
return line(simplex, direction);
} else {
if same_direction(abc, ao) {
direction.* = abc;
} else {
simplex.points[0] = a;
simplex.points[1] = c;
simplex.points[2] = b;
simplex.size = 3;
direction.* = -abc;
}
}
}
return false;
}
same_direction :: (direction: Vector3, ao: Vector3) -> bool {
return dot(direction, ao) > 0;
}
line :: (simplex: *Simplex, direction: *Vector3) -> bool {
a := simplex.points[0];
b := simplex.points[1];
ab := b - a;
ao := - a;
if same_direction(ab, ao) {
direction.* = cross(cross(ab, ao), ab);
} else {
simplex.points[0] = a;
simplex.size = 1;
direction.* = ao;
}
return false;
}
next_simplex :: (simplex: *Simplex, direction: *Vector3) -> bool {
if simplex.size == {
case 2; return line(simplex, direction);
case 3; return triangle(simplex, direction);
case 4; return tetrahedron(simplex, direction);
}
return false;
}
support :: (a: Collider, b: Collider, direction: Vector3) -> Vector3 {
return find_furthest_point(a, direction) - find_furthest_point(b, -direction);
}
gjk :: (a: Collider, b: Collider) -> Collision_Point {
sup := support(a, b, .{1,0,0});
points : Simplex;
push_front(*points, sup);
direction := -sup;
while true {
sup = support(a, b, direction);
if dot(sup, direction) <= 0 {
return .{};
}
push_front(*points, sup);
if next_simplex(*points, *direction) {
collision_point := epa(points, a, b);
return collision_point;
}
}
return .{};
}

421
physics/physics.jai Normal file
View File

@@ -0,0 +1,421 @@
#load "gjk.jai";
WORLD_UP :: Vector3.{0,1,0};
GRAVITY :: -20.8;
Collider_Type :: enum {
AABB;
SPHERE;
MESH;
BOX;
}
Sphere :: struct {
radius: float;
}
Mesh_Collider :: struct {
vertices : [..] Vector3;
is_baked: bool;
}
Trigger_Overlap :: struct {
entity: *Entity;
frame_index: u64;
}
MAX_TRIGGER_OVERLAPS :: 16;
Collider :: struct {
type : Collider_Type;
aabb: AABB;
override_aabb: bool;
union {
sphere: Sphere;
mesh : Mesh_Collider;
}
overlaps: [MAX_TRIGGER_OVERLAPS] Trigger_Overlap;
num_overlaps: s64;
ignore: bool;
}
Physics_Body :: struct {
velocity: Vector3;
friction : float = 0.0;
bounciness : float = 0.0;
linear_damping : float = 0.0;
check_for_grounded: bool;
grounded: bool;
}
update_mesh_collider :: (e: *Entity) {
if e.collider.mesh.vertices.count == 0 {
array_resize(*e.collider.mesh.vertices, 8);
}
m := e.transform.model_matrix;
e.collider.mesh.vertices[0] = transform_position(e.collider.aabb.min, m);
e.collider.mesh.vertices[1] = transform_position(e.collider.aabb.max, m);
e.collider.mesh.vertices[2] = transform_position(.{e.collider.aabb.min.x, e.collider.aabb.min.y, e.collider.aabb.max.z}, m);
e.collider.mesh.vertices[3] = transform_position(.{e.collider.aabb.max.x, e.collider.aabb.min.y, e.collider.aabb.max.z}, m);
e.collider.mesh.vertices[4] = transform_position(.{e.collider.aabb.max.x, e.collider.aabb.min.y, e.collider.aabb.min.z}, m);
e.collider.mesh.vertices[5] = transform_position(.{e.collider.aabb.min.x, e.collider.aabb.max.y, e.collider.aabb.max.z}, m);
e.collider.mesh.vertices[6] = transform_position(.{e.collider.aabb.max.x, e.collider.aabb.max.y, e.collider.aabb.min.z}, m);
e.collider.mesh.vertices[7] = transform_position(.{e.collider.aabb.min.x, e.collider.aabb.max.y, e.collider.aabb.min.z}, m);
e.collider.mesh.is_baked = true;
}
update_mesh_colliders :: (scene: *Scene) {
for e: scene.entities {
if e.flags & .COLLISION {
if e.collider.type == .MESH {
if e.flags & .STATIC && e.collider.mesh.is_baked continue;
update_mesh_collider(e);
}
}
}
}
make_sure_entity_does_not_collide :: (e: *Entity, scene: *Scene) {
if e.flags & .PHYSICS {
aabb := e.collider.aabb;
aabb.min += e.transform.position;
aabb.max += e.transform.position;
for other_e: scene.entities {
if e == other_e continue;
if other_e.flags & .COLLISION {
other_aabb := other_e.collider.aabb;
other_aabb.min += other_e.transform.position;
other_aabb.max += other_e.transform.position;
if aabb_vs_aabb(aabb, other_aabb) {
offset := resolve_aabb_vs_aabb(aabb, other_aabb);
set_position(*e.transform, e.transform.position + offset * 1.0001);
}
}
}
}
}
make_sure_nothing_collides :: (scene: *Scene) {
for e: scene.entities {
make_sure_entity_does_not_collide(e, scene);
}
}
update_gravity :: (scene: *Scene, dt: float) {
for e: scene.entities {
if !e.enabled continue;
if e.flags & .PHYSICS {
if e.is_proxy continue;
if e.collider.ignore continue;
e.body.velocity.y += GRAVITY * dt;
}
}
}
update_positions :: (scene: *Scene, dt: float) {
for e: scene.entities {
if !e.enabled continue;
if e.is_proxy continue;
if e.collider.ignore continue;
if e.flags & .PHYSICS {
delta := e.body.velocity * dt;
set_position(*e.transform, e.transform.position + delta);
// @Speed: Only do this, if we actually moved
m := e.transform.model_matrix;
if e.collider.mesh.vertices.count < 8 continue;
e.collider.mesh.vertices[0] = transform_position(e.collider.aabb.min, m);
e.collider.mesh.vertices[1] = transform_position(e.collider.aabb.max, m);
e.collider.mesh.vertices[2] = transform_position(.{e.collider.aabb.min.x, e.collider.aabb.min.y, e.collider.aabb.max.z}, m);
e.collider.mesh.vertices[3] = transform_position(.{e.collider.aabb.max.x, e.collider.aabb.min.y, e.collider.aabb.max.z}, m);
e.collider.mesh.vertices[4] = transform_position(.{e.collider.aabb.max.x, e.collider.aabb.min.y, e.collider.aabb.min.z}, m);
e.collider.mesh.vertices[5] = transform_position(.{e.collider.aabb.min.x, e.collider.aabb.max.y, e.collider.aabb.max.z}, m);
e.collider.mesh.vertices[6] = transform_position(.{e.collider.aabb.max.x, e.collider.aabb.max.y, e.collider.aabb.min.z}, m);
e.collider.mesh.vertices[7] = transform_position(.{e.collider.aabb.min.x, e.collider.aabb.max.y, e.collider.aabb.min.z}, m);
}
}
}
add_trigger_overlap_if_new :: (e: *Entity, other_e: *Entity) {
for 0..e.collider.num_overlaps-1 {
overlap := *e.collider.overlaps[it];
if overlap.entity == other_e {
overlap.frame_index = frame_index;
return;
}
}
on_trigger_enter(e, other_e);
e.collider.overlaps[e.collider.num_overlaps] = .{ other_e, frame_index };
e.collider.num_overlaps += 1;
}
physics_step :: (scene: *Scene, timestep: float) {
update_gravity(scene, timestep);
update_positions(scene, timestep);
for e: scene.entities {
if !e.enabled continue;
if e.is_proxy continue;
if e.collider.ignore continue;
if e.flags & .PHYSICS {
for other_e: scene.entities {
if e == other_e continue;
if other_e.collider.ignore continue;
if other_e.flags & .COLLISION {
point := gjk(e.collider, other_e.collider);
if point.has_collision {
if other_e.flags & .TRIGGER {
// TRIGGER CALLBACK
add_trigger_overlap_if_new(e, other_e);
} else {
n := -point.normal;
speed_along_normal := dot(e.body.velocity, n);
restitution := e.body.bounciness;
impulse := n * (-(1.0 + restitution) * speed_along_normal);
e.body.velocity += impulse;
percent := 0.1;
slop := 0.005;
correction := n * max(point.penetration_depth - slop, 0.0) / (1.0 / percent);
set_position(*e.transform, e.transform.position + correction);
if e.body.check_for_grounded {
e.body.grounded = dot(n, WORLD_UP) > 0.6; // @Incomplete: Add allowed angle variable at some point?
}
// @Incomplete: This shouldn't be in here
//if e.type == Diamond && length(impulse) > 2.0 {
// play_audio_event(sfx_diamond_hit);
//}
}
}
}
}
}
}
}
on_trigger_enter :: (e: *Entity, other_e: *Entity) {
//if (e.type == Player || e.type == Npc) && other_e.type == Door {
// other_e.enabled = false;
//}
}
on_trigger_exit :: (e: *Entity, other_e: *Entity) {
//if other_e.type == Door {
// other_e.enabled = true;
//}
}
update_physics :: (scene: *Scene, dt: float) {
for scene.entities {
if it.collider.type == .MESH {
if !it.collider.mesh.is_baked {
update_mesh_collider(it);
}
}
}
iterations := 4;
step_time := dt / cast(float)iterations;
for 0..iterations-1 {
physics_step(scene, step_time);
for e: scene.entities {
if e.flags & .PHYSICS {
//if e.body.friction > 0.0 {
// e.body.velocity *= 1.0 - (e.body.friction / cast(float)iterations);
//}
e.body.velocity *= (1.0 - e.body.linear_damping / cast(float)iterations);
}
}
}
for e: scene.entities {
index := 0;
while index < e.collider.num_overlaps {
defer index += 1;
if e.collider.overlaps[index].frame_index < frame_index {
on_trigger_exit(e, e.collider.overlaps[index].entity);
if e.collider.num_overlaps > 1 {
e.collider.overlaps[index] = e.collider.overlaps[e.collider.num_overlaps-1];
}
e.collider.num_overlaps -= 1;
}
}
}
}
// DISCRETE
aabb_vs_aabb :: (box1: AABB, box2: AABB) -> bool {
// Check for no overlap along any axis
if box1.max.x < box2.min.x || box1.min.x > box2.max.x return false;
if box1.max.y < box2.min.y || box1.min.y > box2.max.y return false;
if box1.max.z < box2.min.z || box1.min.z > box2.max.z return false;
return true;
}
resolve_aabb_vs_aabb :: (moving_box: AABB, static_box: AABB) -> Vector3 {
overlap_x := min(moving_box.max.x, static_box.max.x) - max(moving_box.min.x, static_box.min.x);
overlap_y := min(moving_box.max.y, static_box.max.y) - max(moving_box.min.y, static_box.min.y);
overlap_z := min(moving_box.max.z, static_box.max.z) - max(moving_box.min.z, static_box.min.z);
offset : Vector3;
// Resolve overlap on each axis
if (overlap_x > 0 && overlap_y > 0 && overlap_z > 0) {
// Determine which axis has the smallest overlap
if (overlap_x <= overlap_y && overlap_x <= overlap_z) {
// Resolve overlap on X-axis
if (moving_box.max.x < static_box.max.x) {
offset.x -= overlap_x;
} else {
offset.x += overlap_x;
}
} else if (overlap_y <= overlap_x && overlap_y <= overlap_z) {
// Resolve overlap on Y-axis
if (moving_box.max.y < static_box.max.y) {
offset.y -= overlap_y;
} else {
offset.y += overlap_y;
}
} else {
// Resolve overlap on Z-axis
if (moving_box.max.z < static_box.max.z) {
offset.z -= overlap_z;
} else {
offset.z += overlap_z;
}
}
}
return offset;
}
// SWEPT
// Check for collision between two AABBs over a specified time interval
swept_aabb_collision :: (box1: AABB, box2: AABB, delta: Vector3) -> bool, t_enter: float, t_exit: float {
d_inv := 1.0 / delta;
tx_enter, tx_exit, ty_enter, ty_exit, tz_enter, tz_exit : float;
if (delta.x >= 0) {
tx_enter = (box2.min.x - box1.max.x) * d_inv.x;
tx_exit = (box2.max.x - box1.min.x) * d_inv.x;
} else {
tx_enter = (box2.max.x - box1.min.x) * d_inv.x;
tx_exit = (box2.min.x - box1.max.x) * d_inv.x;
}
if (delta.y >= 0) {
ty_enter = (box2.min.y - box1.max.y) * d_inv.y;
ty_exit = (box2.max.y - box1.min.y) * d_inv.y;
} else {
ty_enter = (box2.max.y - box1.min.y) * d_inv.y;
ty_exit = (box2.min.y - box1.max.y) * d_inv.y;
}
if (delta.z >= 0) {
tz_enter = (box2.min.z - box1.max.z) * d_inv.z;
tz_exit = (box2.max.z - box1.min.z) * d_inv.z;
} else {
tz_enter = (box2.max.z - box1.min.z) * d_inv.z;
tz_exit = (box2.min.z - box1.max.z) * d_inv.z;
}
t_enter := max(max(tx_enter, ty_enter), tz_enter);
t_exit := min(min(tx_exit, ty_exit), tz_exit);
return t_enter <= t_exit && t_exit >= 0 && t_enter <= 1, t_enter, t_exit;
}
calculate_aabbs :: (scene: *Scene) {
for e: scene.entities {
if e.flags & .COLLISION && e.flags & .RENDERABLE {
if e.collider.override_aabb continue;
aabb : AABB;
for n : e.renderable.model.nodes {
if n.parent == 0 {
bake_aabb(*aabb, Matrix4_Identity, e, n);
}
}
e.collider.aabb = aabb;
}
}
}
bake_aabb :: (aabb: *AABB, parent_matrix: Matrix4, e: *Entity, n: Node) {
update_matrix(*n.transform);
node_matrix := parent_matrix * n.transform.model_matrix;
for handle : n.meshes {
index := 0;
m := parray_get(*renderer.meshes, handle);
if m.indices.count > 0 {
while index < m.indices.count {
i1 := m.indices[index];
i2 := m.indices[index + 1];
i3 := m.indices[index + 2];
p0 := to_v3(node_matrix * to_v4(m.positions[i1]));
p1 := to_v3(node_matrix * to_v4(m.positions[i2]));
p2 := to_v3(node_matrix * to_v4(m.positions[i3]));
apply_min_max(*aabb.min, *aabb.max, p0);
apply_min_max(*aabb.min, *aabb.max, p1);
apply_min_max(*aabb.min, *aabb.max, p2);
index += 3;
}
// assert("Meshes with indices currently for aabb collision baking." && false);
} else {
while index < m.positions.count - 1 {
p0 := to_v3(node_matrix * to_v4(m.positions[index]));
p1 := to_v3(node_matrix * to_v4(m.positions[index + 1]));
p2 := to_v3(node_matrix * to_v4(m.positions[index + 2]));
apply_min_max(*aabb.min, *aabb.max, p0);
apply_min_max(*aabb.min, *aabb.max, p1);
apply_min_max(*aabb.min, *aabb.max, p2);
index += 3;
}
}
}
for n.children {
child := *e.renderable.model.nodes[it - 1];
bake_aabb(aabb, node_matrix, e, child);
}
if abs(aabb.min.y - aabb.max.y) < 0.00001 {
aabb.min.y -= 0.001;
}
}

View File

@@ -0,0 +1,14 @@
Directional_Light :: struct {
color_and_intensity : Vector4;
direction : Vector4;
view_position : Vector3;
}
Directional_Light_Buffer_Data :: struct {
color_and_intensity : Vector4;
direction : Vector4;
//light_matrix: Matrix4;
}

1503
renderer/dx11_renderer.jai Normal file

File diff suppressed because it is too large Load Diff

105
renderer/font.jai Normal file
View File

@@ -0,0 +1,105 @@
Font_Handle :: #type, distinct u32;
Glyph :: struct {
ax: float; // advance.x
ay: float; // advance.y
bw: float; // bitmap.width;
bh: float; // bitmap.rows;
bl: float; // bitmap_left;
bt: float; // bitmap_top;
tx: float; // x offset of glyph in texture coordinates
}
Point :: struct {
x: float;
y: float;
s: float;
t: float;
color: Vector4;
};
Font :: struct {
glyphs: [..] Glyph;
face: FT_Face;
pixel_size: u32;
atlas_width: u32;
atlas_height: u32;
texture: Texture_Handle;
}
ft : FT_Library;
init_freetype :: () {
if FT_Init_FreeType(*ft) {
log("Couldn't init Freetype\n");
}
}
create_font :: (renderer: *Renderer, path: string, pixel_size: u32) -> Font_Handle {
font: Font;
font.pixel_size = pixel_size;
array_resize(*font.glyphs, 128);
if FT_New_Face(ft, path.data, 0, *font.face) {
log("Couldn't create new face\n");
assert(false);
} else {
FT_Set_Pixel_Sizes(font.face, 0, pixel_size);
g := font.face.glyph;
w : u32 = 0;
h : u32 = 0;
index := 0;
for 32..128-1 {
if FT_Load_Char(font.face, xx it, FT_LOAD_RENDER) {
continue;
}
w += g.bitmap.width + 1;
h = max(h, g.bitmap.rows);
}
atlas_width := w;
bytes : [..] u8;
bytes.allocator = temp;
array_resize(*bytes, w * h);
texture := create_texture(renderer, bytes.data, w, h, 1, generate_mips=false, format=.R8_UNORM);
font.atlas_width = w;
font.atlas_height = h;
font.texture = texture;
x : u32 = 0;
pixel_width := 1.0 / cast(float)atlas_width;
for 32..128-1 {
if FT_Load_Char(font.face, xx it, FT_LOAD_RENDER) continue;
// @Speed: We should put this buffer data into an array and upload everything once after this loop
update_texture_region(renderer, texture, x, 0, g.bitmap.width, g.bitmap.rows, 1, g.bitmap.buffer);
font.glyphs[it].ax = cast(float)(g.advance.x >> 6);
font.glyphs[it].ay = cast(float)(g.advance.y >> 6);
font.glyphs[it].bw = cast(float)g.bitmap.width;
font.glyphs[it].bh = cast(float)g.bitmap.rows;
font.glyphs[it].bl = cast(float)g.bitmap_left;
font.glyphs[it].bt = cast(float)g.bitmap_top;
font.glyphs[it].tx = cast(float)x / cast(float)w;
x += g.bitmap.width + 1;
}
array_add(*renderer.fonts, font);
return xx renderer.fonts.count;
}
return 0;
}
#import "freetype-2.12.1";

465
renderer/material.jai Normal file
View File

@@ -0,0 +1,465 @@
MAX_BUFFER_MAPPINGS :: 4;
MAX_PARAMETERS :: 16;
MAX_PROPERTY_REFERENCES :: 4;
Pass_Reference :: struct {
pass_index: s64;
parameter_index: s64;
offset: u32; // Only for buffers
}
Material_Property :: struct {
name: string;
type: Shader_Property_Type;
union {
float_val : float;
float2_val : Vector2;
float3_val : Vector3;
float4_val : Vector4;
mat3_val : Matrix3;
mat4_val : Matrix4;
int_val : int;
bool_val : bool;
texture_val : Texture_Handle;
sampler_val : Sampler_Handle;
}
references : [MAX_PROPERTY_REFERENCES] Pass_Reference;
num_references: s64;
}
Material_Pass_Parameter :: struct {
slot: u32;
type: Shader_Parameter_Type;
shader: Shader_Type;
mapping: Shader_Parameter_Mapping;
mapping_str: string;
union {
buffer_info : struct {
buffer: Buffer_Handle;
size: u32;
data: *void;
dirty: bool;
}
sampler: Sampler_Handle;
texture: Texture_Handle;
}
}
Material_Pass :: struct {
pass: Render_Pass_Handle;
pipeline: Pipeline_State_Handle;
parameters: [MAX_PARAMETERS] Material_Pass_Parameter;
num_parameters: s64;
}
Material :: struct {
name: string;
properties: [..] Material_Property;
passes: [..] Material_Pass;
allocator: Allocator;
pool: Flat_Pool;
// For removing the material later
_locator: Bucket_Locator;
}
Material_Pass_Info :: struct {
pass: Render_Pass_Handle;
pipeline: Pipeline_State_Handle;
}
materials: Bucket_Array(Material, 32);
delete_material :: (mat: *Material) {
for pass: mat.passes {
for pi: 0..pass.num_parameters-1 {
param := pass.parameters[pi];
if param.type != .BUFFER continue;
if param.buffer_info.data != null && param.mapping == .NONE && param.buffer_info.buffer != 0 { // I guess if there is a mapping, the data ptr is null as well, but checking for both, if this changes at a later point
destroy_buffer(renderer, param.buffer_info.buffer);
}
param.buffer_info.buffer = 0;
}
}
fini(*mat.pool);
bucket_array_remove(*materials, mat._locator);
}
create_material :: (name: string) -> *Material {
p, locator := find_and_occupy_empty_slot(*materials);
p.pool = .{};
p.allocator.proc = flat_pool_allocator_proc;
p.allocator.data = *p.pool;
p.name = copy_string(name,, p.allocator);
p.passes.allocator = p.allocator;
p.properties.allocator = p.allocator;
p._locator = locator;
return p;
}
// Create the material, but reuse the material's arrays?
//copy_material :: (material: *Material) -> *Material {
// new_material := create_material(material.name);
//
// new_context := context;
// new_context.allocator = new_material.allocator;
//
// push_context new_context {
// array_reserve(*new_material.passes, material.passes.count);
// array_reserve(*new_material.properties, material.properties.count);
//
// for pass: material.passes {
// new_pass := pass;
// for pi: 0..new_pass.num_parameters-1 {
// new_pass.parameters[pi].mapping_str = copy_string(pass.parameters[pi].mapping_str);
// }
// array_add(*new_material.passes, new_pass);
// }
//
// for prop: material.properties {
// new_prop := prop;
// new_prop.name = copy_string(prop.name);
// array_add(*new_material.properties, new_prop);
// }
// }
//
// return new_material;
//}
create_material_for_passes :: (name: string, material_pass_infos: [] Material_Pass_Info) -> *Material {
add_properties_from_shader :: (mat: *Material, pass_index: s64, shader: Shader) {
pass := *mat.passes[pass_index];
for *param: shader.info.parameters {
pass_param : Material_Pass_Parameter;
pass_param.type = param.type;
pass_param.slot = param.slot;
pass_param.mapping = param.mapping;
pass_param.mapping_str = copy_string(param.mapping_str); // @Incomplete: MEMORY
pass_param.shader = param.shader;
pass_param.buffer_info.size = param.size;
pass_param.buffer_info.data = alloc(param.size);
memset(pass_param.buffer_info.data, 0, param.size);
if pass_param.type == {
case .BUFFER; {
if param.mapping == .NONE { // Only allocate if the parameter doesn't have a mapping
// allocate the buffer
param.mapping = .NONE;
pass_param.buffer_info.buffer = create_constant_buffer(renderer, pass_param.buffer_info.data, pass_param.buffer_info.size, mappable=true);
}
}
case .TEXTURE; {
// @Incomplete: Set default texture
}
case .SAMPLER; {
// @Incomplete: Set default texture
}
}
pass.parameters[pass.num_parameters] = pass_param;
pass.num_parameters += 1;
if param.mapping == .NONE {
for pi: 0..param.num_properties-1 {
param_prop := param.properties[pi];
prop : *Material_Property;
// Check if property exists
for *existing: mat.properties {
if existing.name == param_prop.name && existing.type == param_prop.type {
// We found one!
prop = existing;
break;
}
}
if prop == null {
array_add(*mat.properties, .{});
prop = *mat.properties[mat.properties.count-1];
prop.name = param_prop.name;
prop.type = param_prop.type;
}
// Add the data about the pass buffer, texture, etc here
reference : Pass_Reference;
reference.pass_index = pass_index;
reference.parameter_index = pass.num_parameters - 1;
reference.offset = xx param_prop.buffer_offset;
prop.references[prop.num_references] = reference;
prop.num_references += 1;
}
}
}
}
mat := create_material(name);
new_context := context;
new_context.allocator = mat.allocator;
push_context new_context {
for info: material_pass_infos {
pipeline_state := *renderer.pipeline_states[info.pipeline - 1];
vs := get_shader(pipeline_state.vs);
ps := get_shader(pipeline_state.ps);
pass : Material_Pass;
pass.pipeline = info.pipeline;
pass.pass = info.pass;
array_add(*mat.passes, pass);
add_properties_from_shader(mat, mat.passes.count-1, vs);
add_properties_from_shader(mat, mat.passes.count-1, ps);
}
}
return mat;
}
set_material_property :: (material: *Material, name: string, val: float) {
found := false;
for *prop: material.properties {
if prop.name == name && prop.type == .FLOAT {
prop.float_val = val;
for 0..prop.num_references-1 {
ref := prop.references[it];
pass := *material.passes[ref.pass_index];
param := *pass.parameters[ref.parameter_index];
memcpy(*param.buffer_info.data[ref.offset], *val, size_of(float));
param.buffer_info.dirty = true;
}
found = true;
break;
}
}
if !found {
log_error("MATERIAL: Tried setting float property '%' on material '%' but the property wasn't found\n", name, material.name);
}
}
set_material_property :: (material: *Material, name: string, val: Vector2) {
found := false;
for *prop: material.properties {
if prop.name == name && prop.type == .FLOAT2 {
prop.float2_val = val;
for 0..prop.num_references-1 {
ref := prop.references[it];
pass := *material.passes[ref.pass_index];
param := *pass.parameters[ref.parameter_index];
memcpy(*param.buffer_info.data[ref.offset], *val, size_of(Vector2));
param.buffer_info.dirty = true;
}
found = true;
break;
}
}
if !found {
log_error("MATERIAL: Tried setting Vector2 property '%' on material '%' but the property wasn't found\n", name, material.name);
}
}
set_material_property :: (material: *Material, name: string, val: Vector3) {
found := false;
for *prop: material.properties {
if prop.name == name && prop.type == .FLOAT3 {
prop.float3_val = val;
for 0..prop.num_references-1 {
ref := prop.references[it];
pass := *material.passes[ref.pass_index];
param := *pass.parameters[ref.parameter_index];
memcpy(*param.buffer_info.data[ref.offset], *val, size_of(Vector3));
param.buffer_info.dirty = true;
}
found = true;
break;
}
}
if !found {
log_error("MATERIAL: Tried setting Vector3 property '%' on material '%' but the property wasn't found\n", name, material.name);
}
}
set_material_property :: (material: *Material, name: string, val: Vector4) {
found := false;
for *prop: material.properties {
if prop.name == name && prop.type == .FLOAT4 {
prop.float4_val = val;
for 0..prop.num_references-1 {
ref := prop.references[it];
pass := *material.passes[ref.pass_index];
param := *pass.parameters[ref.parameter_index];
memcpy(*param.buffer_info.data[ref.offset], *val, size_of(Vector4));
param.buffer_info.dirty = true;
}
found = true;
break;
}
}
if !found {
log_error("MATERIAL: Tried setting Vector4 property '%' on material '%' but the property wasn't found\n", name, material.name);
}
}
set_material_property :: (material: *Material, name: string, val: Texture_Handle) {
found := false;
for *prop: material.properties {
if prop.name == name && prop.type == .TEXTURE {
prop.texture_val = val;
for 0..prop.num_references-1 {
ref := prop.references[it];
pass := *material.passes[ref.pass_index];
param := *pass.parameters[ref.parameter_index];
param.texture = val;
}
found = true;
break;
}
}
if !found {
log_error("MATERIAL: Tried setting texture property '%' on material '%' but the property wasn't found\n", name, material.name);
}
}
set_material_pass_parameters :: (material: *Material, pass_index: s64, render_pass: Render_Pass, transform_buffer: Buffer_Handle, material_buffer: Buffer_Handle, bone_buffer: Buffer_Handle, defaults: Model_Material) {
mat_pass := material.passes[pass_index];
for 0..mat_pass.num_parameters-1 {
param := mat_pass.parameters[it];
if param.buffer_info.dirty {
param.buffer_info.dirty = false;
upload_data_to_buffer(renderer, param.buffer_info.buffer, param.buffer_info.data, param.buffer_info.size);
}
if param.type == {
case .BUFFER; {
if param.mapping == {
case .TIME; {
//param.buffer_info.buffer = engine.time_buffer; // @Incomplete
}
case .MODEL_MATRIX; {
param.buffer_info.buffer = transform_buffer;
}
case .CAMERA_DATA; {
//param.buffer_info.buffer = renderer.engine_buffers.camera; // @Incomplete
}
case .MATERIAL; {
param.buffer_info.buffer = material_buffer;
}
case .DIRECTIONAL_LIGHT; {
//param.buffer_info.buffer = renderer.engine_buffers.directional_light; // @Incomplete
}
case .POINT_LIGHTS; {
//param.buffer_info.buffer = renderer.engine_buffers.point_lights; // @Incomplete
}
case .BONE_MATRICES; {
param.buffer_info.buffer = bone_buffer;
}
case .CUSTOM; {
if renderer.callbacks.get_custom_material_parameter_mapping != null {
success, mapping_info := renderer.callbacks.get_custom_material_parameter_mapping(param.mapping_str);
if success {
param.buffer_info.buffer = mapping_info.buffer;
}
}
}
}
if param.buffer_info.buffer > 0 {
push_cmd_set_constant_buffer(renderer, param.slot, param.buffer_info.buffer, param.shader);
}
}
case .SAMPLER;
if param.mapping == {
case .REPEAT_SAMPLER; {
param.sampler = renderer.default_samplers.repeat;
}
case .CLAMP_SAMPLER; {
param.sampler = renderer.default_samplers.clamp;
}
}
if param.sampler != 0 {
push_cmd_set_sampler(renderer, param.slot, param.sampler);
}
case .TEXTURE;
{
is_texture_input := false;
input_index := 0;
if param.mapping == {
case .BASE_COLOR_TEXTURE; {
param.texture = defaults.textures.base_color;
}
case .NORMAL_MAP; {
param.texture = defaults.textures.normal;
}
case .SHADER_ATTACHMENT0; {
is_texture_input = true;
input_index = 0;
}
case .SHADER_ATTACHMENT1; {
is_texture_input = true;
input_index = 1;
}
case .SHADER_ATTACHMENT2; {
is_texture_input = true;
input_index = 2;
}
case .SHADER_ATTACHMENT3; {
is_texture_input = true;
input_index = 3;
}
case .CUSTOM; {
if renderer.callbacks.get_custom_material_parameter_mapping != null {
success, mapping_info := renderer.callbacks.get_custom_material_parameter_mapping(param.mapping_str);
if success {
param.texture = mapping_info.texture;
}
}
}
}
if is_texture_input {
input := render_pass.inputs[input_index];
owning_pass := parray_get(*renderer.render_graph.render_passes, input.pass_handle);
if input.rt_index == DEPTH_STENCIL_SLOT {
push_cmd_set_texture(renderer, param.slot, owning_pass.depth_stencil);
} else {
push_cmd_set_texture(renderer, param.slot, owning_pass.render_targets[input.rt_index]);
}
} else {
if param.texture > 0 {
push_cmd_set_texture(renderer, param.slot, param.texture);
}
}
}
}
}
}

191
renderer/mesh.jai Normal file
View File

@@ -0,0 +1,191 @@
Skin_Vertex :: struct {
bone_index: [4] float;
bone_weight: [4] float;
}
Mesh_Vertex_Data_Type :: enum {
NONE;
POSITION;
NORMAL;
TEXCOORD;
TANGENT;
COLOR;
BITANGENT;
BONE_INDICES;
BONE_WEIGHTS;
}
Mesh_Handle :: #type, distinct u32;
Mesh :: struct {
name : string;
positions : [..] Vector3;
normals : [..] Vector3;
texcoords : [..] Vector2;
tangents : [..] Vector3;
bitangents : [..] Vector3;
colors : [..] Vector4;
skin_data : [..] Skin_Vertex;
bone_indices: [..] s32;
bone_matrices: [..] Matrix4;
num_bones: s32;
indices : [..] u32;
ib : Buffer_Handle;
vbs : Table(u32, Buffer_Handle);
//vb : Buffer_Handle;
}
get_mesh_vb :: (mesh: *Mesh, pipeline_handle: Pipeline_State_Handle = 0) -> Buffer_Handle {
handle := ifx pipeline_handle == 0 then renderer.current_state.last_set_pipeline else pipeline_handle;
pipeline_state := *renderer.pipeline_states[handle-1];
return get_mesh_vb(mesh, pipeline_state.mesh_data_types);
}
get_mesh_vb :: (mesh: *Mesh, input: [] Mesh_Vertex_Data_Type) -> Buffer_Handle {
hash : u32 = 0;
nums : [8] u32 : .[13, 61, 84, 86, 65, 10000, 100000, 126];
for input, i: input {
hash += nums[i] * cast(u32)input;
}
if !table_contains(*mesh.vbs, hash) {
final_vertices : [..] float;
final_vertices.allocator = temp;
stride : u32 = 0;
for input: input {
if input == {
case .POSITION; stride += 3;
case .NORMAL; stride += 3;
case .TEXCOORD; stride += 2;
case .COLOR; stride += 4;
case .TANGENT; stride += 3;
case .BITANGENT; stride += 3;
case .BONE_INDICES; stride += 4;
case .BONE_WEIGHTS; stride += 4;
}
}
for 0..mesh.positions.count-1 {
for input: input {
if input == {
case .POSITION; {
array_add(*final_vertices, mesh.positions[it].x);
array_add(*final_vertices, mesh.positions[it].y);
array_add(*final_vertices, mesh.positions[it].z);
}
case .NORMAL; {
if mesh.normals.count > 0 {
array_add(*final_vertices, mesh.normals[it].x);
array_add(*final_vertices, mesh.normals[it].y);
array_add(*final_vertices, mesh.normals[it].z);
} else {
array_add(*final_vertices, 0.0);
array_add(*final_vertices, 0.0);
array_add(*final_vertices, 0.0);
log_error("Mesh didn't have required normals\n");
}
}
case .TEXCOORD; {
if mesh.texcoords.count > 0 {
array_add(*final_vertices, mesh.texcoords[it].x);
array_add(*final_vertices, mesh.texcoords[it].y);
} else {
array_add(*final_vertices, 0.0);
array_add(*final_vertices, 0.0);
log_error("Mesh didn't have required texcoords\n");
}
}
case .COLOR; {
if mesh.colors.count > 0 {
array_add(*final_vertices, mesh.colors[it].x);
array_add(*final_vertices, mesh.colors[it].y);
array_add(*final_vertices, mesh.colors[it].z);
array_add(*final_vertices, mesh.colors[it].w);
} else {
array_add(*final_vertices, 0.0);
array_add(*final_vertices, 0.0);
array_add(*final_vertices, 0.0);
array_add(*final_vertices, 0.0);
log_error("Mesh didn't have required colors\n");
}
}
case .TANGENT; {
if mesh.tangents.count > 0 {
array_add(*final_vertices, mesh.tangents[it].x);
array_add(*final_vertices, mesh.tangents[it].y);
array_add(*final_vertices, mesh.tangents[it].z);
} else {
array_add(*final_vertices, 0.0);
array_add(*final_vertices, 0.0);
array_add(*final_vertices, 0.0);
log_error("Mesh didn't have required tangents\n");
}
}
case .BITANGENT; {
if mesh.bitangents.count > 0 {
array_add(*final_vertices, mesh.bitangents[it].x);
array_add(*final_vertices, mesh.bitangents[it].y);
array_add(*final_vertices, mesh.bitangents[it].z);
} else {
array_add(*final_vertices, 0.0);
array_add(*final_vertices, 0.0);
array_add(*final_vertices, 0.0);
log_error("Mesh didn't have required bitangents\n");
}
}
case .BONE_INDICES; {
if mesh.skin_data.count > 0 {
for index: 0..3 {
array_add(*final_vertices, mesh.skin_data[it].bone_index[index]);
}
} else {
array_add(*final_vertices, 0.0);
array_add(*final_vertices, 0.0);
array_add(*final_vertices, 0.0);
array_add(*final_vertices, 0.0);
log_error("Mesh didn't have required bone indices\n");
}
}
case .BONE_WEIGHTS; {
if mesh.skin_data.count > 0 {
for index: 0..3 {
array_add(*final_vertices, mesh.skin_data[it].bone_weight[index]);
}
} else {
array_add(*final_vertices, 0.0);
array_add(*final_vertices, 0.0);
array_add(*final_vertices, 0.0);
array_add(*final_vertices, 0.0);
log_error("Mesh didn't have required bone weights\n");
}
}
}
}
}
vb_size := size_of(float)*stride*mesh.positions.count;
vb := create_vertex_buffer(renderer, final_vertices.data, xx vb_size, stride=size_of(float)*stride);
table_add(*mesh.vbs, hash, vb);
}
value, success := table_find(*mesh.vbs, hash);
return value;
}
delete_mesh :: (handle: Mesh_Handle) {
mesh := parray_get(*renderer.meshes, handle);
for mesh.vbs {
destroy_buffer(renderer, it);
}
deinit(*mesh.vbs);
parray_remove(*renderer.meshes, handle);
}

668
renderer/model.jai Normal file
View File

@@ -0,0 +1,668 @@
Node_Handle :: #type, distinct u32;
Material_Handle :: #type, distinct u32;
MAX_BONES :: 128;
MAX_WEIGHTS :: 4;
Node :: struct {
name : string;
path : string;
transform : Transform;
meshes : [..] Mesh_Handle;
material_defaults : [..] Model_Material;
materials_old : [..] Material_Old;
num_bones: s32;
parent : Node_Handle;
children : [..] Node_Handle;
// Instance variables
materials : [..] *Material;
has_sampled_animation: bool;
}
Node_Animation :: struct {
framerate : float64;
num_frames : s32;
rotation : [..] Quaternion;
position : [..] Vector3;
scale : [..] Vector3;
constant : bool;
const_rotation : Quaternion;
const_position : Vector3;
const_scale : Vector3;
is_set: bool;
}
Animation :: struct {
name: string;
duration: float64;
num_frames: s32;
framerate: float64;
nodes : [..] Node_Animation;
}
Model_Material :: struct {
base_color : Vector4;
textures : struct {
base_color: Texture_Handle;
normal: Texture_Handle;
}
}
Model :: struct {
name : string;
path : string;
nodes : [..] Node;
animations : [..] Animation;
materials : [..] Model_Material;
}
get_first_mesh_from_model :: (model: Model) -> Mesh_Handle, bool {
for model.nodes {
for m: it.meshes {
return m, true;
}
}
return 0, false;
}
get_mesh_by_name :: (model: Model, name: string) -> Mesh_Handle, bool {
for model.nodes {
for m: it.meshes {
mesh := parray_get(*renderer.meshes, m);
if mesh.name == name {
return m, true;
}
}
}
return 0, false;
}
Coloru16 :: struct {
r: u16;
g: u16;
b: u16;
a: u16;
}
parse_fbx_node :: (model: *Model, fbx_node: *ufbx_node) {
node : Node;
node.name = copy_string(to_string(fbx_node.name.data));
if fbx_node.parent != null {
node.parent = cast(Node_Handle) fbx_node.parent.typed_id + 1; // UFBX ids are 0-indexed, but 0 means none in our API
}
t := create_identity_transform();
transform := ufbx_matrix_to_transform(*fbx_node.node_to_parent);
t.position.x = cast(float)transform.translation.x;
t.position.y = cast(float)transform.translation.y;
t.position.z = cast(float)transform.translation.z;
t.scale.x = xx transform.scale.x;
t.scale.y = xx transform.scale.y;
t.scale.z = xx transform.scale.z;
t.orientation.x = cast(float)transform.rotation.x;
t.orientation.y = cast(float)transform.rotation.y;
t.orientation.z = cast(float)transform.rotation.z;
t.orientation.w = cast(float)transform.rotation.w;
update_matrix(*t);
//t.model_matrix = make_matrix(world_position, world_orientation, world_scale);
t.dirty = true;
node.transform = t;
Mesh_Vertex :: struct {
position: Vector3;
normal : Vector3;
uv : Vector2;
};
if fbx_node.mesh != null {
fbx_mesh := fbx_node.mesh;
max_parts : size_t = 0;
max_triangles : size_t = 0;
for pi: 0..cast(s32)fbx_mesh.materials.count-1 {
mesh_mat := fbx_mesh.materials.data[pi];
if mesh_mat.num_triangles == 0 continue;
max_parts += 1;
max_triangles = max(max_triangles, mesh_mat.num_triangles);
}
num_tri_indices := fbx_mesh.max_face_triangles * 3;
tri_indices : [..] u32;
vertices : [..] Mesh_Vertex;
indices : [..] u32;
skin_vertices : [..] Skin_Vertex;
mesh_skin_vertices : [..] Skin_Vertex;
tri_indices.allocator = temp;
vertices.allocator = temp;
indices.allocator = temp;
skin_vertices.allocator = temp;
mesh_skin_vertices.allocator = temp;
indices.allocator = temp;
array_resize(*tri_indices, xx num_tri_indices);
array_resize(*vertices, xx max_triangles * 3);
array_resize(*indices, xx max_triangles * 3);
array_resize(*skin_vertices, xx max_triangles * 3);
array_resize(*mesh_skin_vertices, xx max_triangles * 3);
num_bones : s32 = 0;
skin : *ufbx_skin_deformer = null;
skinned : bool;
bone_indices: [..] s32;
bone_matrices: [..] Matrix4;
bone_indices.allocator = temp;
bone_matrices.allocator = temp;
if fbx_mesh.skin_deformers.count > 0 && fbx_mesh.skin_deformers.data[0].clusters.count > 0 {
skinned = true;
skin = fbx_mesh.skin_deformers.data[0];
for ci: 0..skin.clusters.count-1 {
cluster := skin.clusters.data[ci];
if num_bones < MAX_BONES {
array_add(*bone_indices, cast(s32)cluster.bone_node.typed_id);
array_add(*bone_matrices, ufbx_to_mat4(cluster.geometry_to_bone));
num_bones += 1;
}
}
for vi: 0..fbx_mesh.num_vertices-1 {
num_weights := 0;
total_weight := 0.0;
weights : [4] float;
clusters : [4] float;
vertex_weights := skin.vertices.data[vi];
for wi: 0..vertex_weights.num_weights-1 {
if num_weights >= 4 break;
weight := skin.weights.data[vertex_weights.weight_begin + wi];
if weight.cluster_index < MAX_BONES {
total_weight += cast(float)weight.weight;
clusters[num_weights] = cast(float)weight.cluster_index;
weights[num_weights] = cast(float)weight.weight;
num_weights += 1;
}
}
if total_weight > 0.0 {
skin_vert := *mesh_skin_vertices[vi];
quantized_sum : float = 0;
for i: 0..3 {
quantized_weight := weights[i] / total_weight;
quantized_sum += quantized_weight;
skin_vert.bone_index[i] = clusters[i];
skin_vert.bone_weight[i] = quantized_weight;
}
skin_vert.bone_weight[0] += 1.0 - quantized_sum;
}
}
}
node.num_bones = num_bones;
for pi: 0..fbx_mesh.materials.count-1 {
mesh : Mesh;
mesh.name = copy_string(to_string(fbx_mesh.name.data));
mesh.num_bones = num_bones;
array_resize(*mesh.bone_indices, bone_indices.count);
array_resize(*mesh.bone_matrices, bone_matrices.count);
memcpy(mesh.bone_indices.data, bone_indices.data, size_of(s32) * bone_indices.count);
memcpy(mesh.bone_matrices.data, bone_matrices.data, size_of(Matrix4) * bone_matrices.count);
mesh_mat := fbx_mesh.materials.data[pi];
if mesh_mat.num_triangles == 0 continue;
num_indices : size_t = 0;
for fi: 0..mesh_mat.num_faces-1 {
face := fbx_mesh.faces.data[mesh_mat.face_indices.data[fi]];
num_tris := ufbx_catch_triangulate_face(null, tri_indices.data, num_tri_indices, fbx_mesh, face);
default_uv : ufbx_vec2;
for vi: 0..num_tris * 3 - 1 {
ix := tri_indices.data[num_tris * 3 - 1 -vi];
vert := *vertices.data[num_indices];
pos := ufbx_catch_get_vertex_vec3(null, *fbx_mesh.vertex_position, ix);
normal := ufbx_catch_get_vertex_vec3(null, *fbx_mesh.vertex_normal, ix);
uv : ufbx_vec2;
if fbx_mesh.vertex_uv.exists {
uv = ufbx_catch_get_vertex_vec2(null, *fbx_mesh.vertex_uv, ix);
}
vert.position.x = xx pos.x;
vert.position.y = xx pos.y;
vert.position.z = xx pos.z;
vert.normal.x = xx normal.x;
vert.normal.y = xx normal.y;
vert.normal.z = xx normal.z;
vert.normal = normalize(vert.normal);
vert.uv.x = xx uv.x;
vert.uv.y = xx uv.y;
if skin {
skin_vertices[num_indices] = mesh_skin_vertices[fbx_mesh.vertex_indices.data[ix]];
}
num_indices += 1;
}
}
num_streams : u64 = 1;
streams : [2] ufbx_vertex_stream;
streams[0].data = vertices.data;
streams[0].vertex_size = size_of(Mesh_Vertex);
if skin {
num_streams = 2;
streams[1].data = skin_vertices.data;
streams[1].vertex_size = size_of(Skin_Vertex);
}
error : ufbx_error;
num_vertices := ufbx_generate_indices(streams.data, num_streams, indices.data, num_indices, null, *error);
if error.type != .UFBX_ERROR_NONE {
log_error("Failed to generate index buffer\n");
}
array_reserve(*mesh.indices, xx num_indices);
for 0..num_indices-1 {
array_add(*mesh.indices, cast(u32)indices[it]);
}
array_reserve(*mesh.positions, xx num_vertices);
array_reserve(*mesh.normals, xx num_vertices);
array_reserve(*mesh.texcoords, xx num_vertices);
if skin {
array_reserve(*mesh.skin_data, xx num_vertices);
}
for 0..num_vertices-1 {
v := vertices[it];
array_add(*mesh.positions, v.position);
array_add(*mesh.normals, v.normal);
array_add(*mesh.texcoords, v.uv);
if skin {
array_add(*mesh.skin_data, skin_vertices[it]);
}
}
// Generate tangents + bitangents
if mesh.texcoords.count > 0 {
array_resize(*mesh.tangents, mesh.positions.count);
array_resize(*mesh.bitangents, mesh.positions.count);
i : u64 = 0;
while i < num_indices - 1 {
defer i += 3;
i0 := mesh.indices[i+0];
i1 := mesh.indices[i+1];
i2 := mesh.indices[i+2];
pos0 := mesh.positions[i0];
pos1 := mesh.positions[i1];
pos2 := mesh.positions[i2];
uv0 := mesh.texcoords[i0];
uv1 := mesh.texcoords[i1];
uv2 := mesh.texcoords[i2];
edge1 := pos1 - pos0;
edge2 := pos2 - pos0;
delta_uv1 := uv1 - uv0;
delta_uv2 := uv2 - uv0;
f := 1.0 / (delta_uv1.x * delta_uv2.y - delta_uv2.x * delta_uv1.y);
tangent : Vector3 = ---;
bitangent : Vector3 = ---;
tangent.x = f * (delta_uv2.y * edge1.x - delta_uv1.y * edge2.x);
tangent.y = f * (delta_uv2.y * edge1.y - delta_uv1.y * edge2.y);
tangent.z = f * (delta_uv2.y * edge1.z - delta_uv1.y * edge2.z);
bitangent.x = f * (-delta_uv2.x * edge1.x + delta_uv1.x * edge2.x);
bitangent.y = f * (-delta_uv2.x * edge1.y + delta_uv1.x * edge2.y);
bitangent.z = f * (-delta_uv2.x * edge1.z + delta_uv1.x * edge2.z);
mesh.tangents[i0] = tangent;
mesh.tangents[i1] = tangent;
mesh.tangents[i2] = tangent;
mesh.bitangents[i0] = bitangent;
mesh.bitangents[i1] = bitangent;
mesh.bitangents[i2] = bitangent;
}
}
ib_size := size_of(u32)*mesh.indices.count;
mesh.ib = create_index_buffer(renderer, mesh.indices.data, xx ib_size);
//material : Base_Material;
//if mesh_mat.material != null {
// for 0..mesh_mat.material.props.props.count-1 {
// prop := mesh_mat.material.props.props.data[it];
// prop_name := to_string(prop.name.data);
// if prop_name == "DiffuseColor" {
// material.base_color.x = xx prop.value_vec3.x;
// material.base_color.y = xx prop.value_vec3.y;
// material.base_color.z = xx prop.value_vec3.z;
// material.base_color.w = 1.0;
// }
// }
//}
array_add(*node.meshes, parray_add(*renderer.meshes, mesh));
array_add(*node.material_defaults, model.materials[mesh_mat.material.typed_id]); // @Incomplete
}
}
array_reserve(*node.children, xx fbx_node.children.count);
for 0..cast(s32)fbx_node.children.count-1 {
array_add(*node.children, cast(Node_Handle)fbx_node.children.data[it].typed_id + 1);
}
array_add(*model.nodes, node);
}
ufbx_to_v3 :: (ufbx_v3: ufbx_vec3) -> Vector3 {
v : Vector3 = ---;
v.x = cast(float)ufbx_v3.x;
v.y = cast(float)ufbx_v3.y;
v.z = cast(float)ufbx_v3.z;
return v;
}
ufbx_to_quat :: (ufbx_q: ufbx_quat) -> Quaternion {
q : Quaternion = ---;
q.x = cast(float)ufbx_q.x;
q.y = cast(float)ufbx_q.y;
q.z = cast(float)ufbx_q.z;
q.w = cast(float)ufbx_q.w;
return q;
}
ufbx_to_mat4 :: (m: ufbx_matrix) -> Matrix4 {
result : Matrix4;
result._11 = cast(float)m.m00;
result._12 = cast(float)m.m01;
result._13 = cast(float)m.m02;
result._14 = cast(float)m.m03;
result._21 = cast(float)m.m10;
result._22 = cast(float)m.m11;
result._23 = cast(float)m.m12;
result._24 = cast(float)m.m13;
result._31 = cast(float)m.m20;
result._32 = cast(float)m.m21;
result._33 = cast(float)m.m22;
result._34 = cast(float)m.m23;
result._41 = 0.0;
result._42 = 0.0;
result._43 = 0.0;
result._44 = 1.0;
return result;
}
v3_equal :: (v1: Vector3, v2: Vector3) -> bool {
return abs(v1.x - v2.x) < 0.0001 && abs(v1.y - v2.y) < 0.0001 && abs(v1.z - v2.z) < 0.0001;
}
quat_equal :: (q1: Quaternion, q2: Quaternion) -> bool {
return abs(q1.x - q2.x) < 0.0001 && abs(q1.y - q2.y) < 0.0001 && abs(q1.z - q2.z) < 0.0001 && abs(q1.w - q2.w) < 0.0001;
}
parse_node_anim :: (animation: *Animation, node_anim: *Node_Animation, stack: *ufbx_anim_stack, node: *ufbx_node) {
array_resize(*node_anim.rotation, animation.num_frames);
array_resize(*node_anim.position, animation.num_frames);
array_resize(*node_anim.scale, animation.num_frames);
const_rotation := true;
const_position := true;
const_scale := true;
if animation.num_frames == 0 {
node_anim.const_rotation = .{0,0,0,1};
node_anim.const_position = .{0,0,0};
node_anim.const_scale = .{1,1,1};
} else {
// Sample the node's transform evenly for the whole animation stack duration
for i: 0..animation.num_frames-1 {
time := stack.time_begin + cast(float64)i / animation.framerate;
transform := ufbx_evaluate_transform(*stack.anim, node, time);
node_anim.rotation[i] = ufbx_to_quat(transform.rotation);
node_anim.position[i] = ufbx_to_v3(transform.translation);
node_anim.scale[i] = ufbx_to_v3(transform.scale);
if i > 0 {
// Negated quaternions are equivalent, but interpolating between ones of different
// polarity takes a the longer path, so flip the quaternion if necessary.
if dot(node_anim.rotation[i], node_anim.rotation[i - 1]) < 0.0 {
node_anim.rotation[i].x = -node_anim.rotation[i].x;
node_anim.rotation[i].y = -node_anim.rotation[i].y;
node_anim.rotation[i].z = -node_anim.rotation[i].z;
node_anim.rotation[i].w = -node_anim.rotation[i].w;
}
// Keep track of which channels are constant for the whole animation as an optimization
if !quat_equal(node_anim.rotation[i - 1], node_anim.rotation[i]) const_rotation = false;
if !v3_equal(node_anim.position[i - 1], node_anim.position[i]) const_position = false;
if !v3_equal(node_anim.scale[i - 1], node_anim.scale[i]) const_scale = false;
}
}
if const_rotation { node_anim.const_rotation = node_anim.rotation[0]; array_free(node_anim.rotation); node_anim.rotation.count = 0; }
if const_position { node_anim.const_position = node_anim.position[0]; array_free(node_anim.position); node_anim.position.count = 0; }
if const_scale { node_anim.const_scale = node_anim.scale[0]; array_free(node_anim.scale); node_anim.scale.count = 0; }
}
node_anim.is_set = true;
}
parse_anim_stack :: (animation: *Animation, stack: *ufbx_anim_stack, scene: *ufbx_scene, model: *Model) {
animation.name = copy_string(to_string(stack.name.data));
animation.framerate = 24.0;
animation.duration = cast(float)(stack.time_end - stack.time_begin);
animation.num_frames = cast(s32)(animation.duration * animation.framerate);
animation.framerate = cast(float)(animation.num_frames - 1) / animation.duration;
array_resize(*animation.nodes, xx model.nodes.count);
if scene.nodes.count != xx model.nodes.count {
for 0..scene.nodes.count-1 {
scene_node := scene.nodes.data[it];
for j: 0..model.nodes.count-1 {
model_node := model.nodes[j];
if to_string(scene_node.name.data) == model_node.name {
animation.nodes[j] = .{};
parse_node_anim(animation, *animation.nodes[j], stack, scene_node);
break;
}
}
}
for 0..model.nodes.count-1 {
if !animation.nodes[it].is_set {
animation.nodes[it].const_position = model.nodes[it].transform.position;
animation.nodes[it].const_rotation = model.nodes[it].transform.orientation;
animation.nodes[it].const_scale = model.nodes[it].transform.scale;
animation.nodes[it].is_set = true;
}
}
} else {
for 0..scene.nodes.count-1 {
parse_node_anim(animation, *animation.nodes[it], stack, scene.nodes.data[it]);
}
}
}
load_fbx_animations_into_existing_model :: (path: string, model: *Model) {
// Load the file as normal, but only loop through the animations
opts : ufbx_load_opts = .{};
opts.load_external_files = true;
opts.evaluate_skinning = true;
opts.target_axes.right = .POSITIVE_X;
opts.target_axes.up = .POSITIVE_Y;
opts.target_axes.front = .NEGATIVE_Z;
opts.target_unit_meters = 1.0;
//opts.space_conversion = ufbx_space_conversion.UFBX_SPACE_CONVERSION_ADJUST_TRANSFORMS;
error : ufbx_error;
scene := ufbx_load_file(to_temp_c_string(path), *opts, *error);
if scene == null {
log_error("FBX '%' could not be loaded\n", path);
}
// The rig and nodes should be the same as the specified model
//if cast(s32)scene.nodes.count != model.nodes.count {
// log_error("Node count for '%' (%) not the same as original model (%)\n", path, scene.nodes.count, model.nodes.count);
// ufbx_free_scene(scene);
// return;
//}
for 0..scene.anim_stacks.count-1 {
animation : Animation;
parse_anim_stack(*animation, scene.anim_stacks.data[it], scene, model);
array_add(*model.animations, animation);
}
ufbx_free_scene(scene);
}
get_model_by_path :: (path: string) -> *Model {
for * renderer.model_lib {
if it.path == path {
return it;
}
}
return null;
}
load_fbx_texture :: (map: ufbx_material_map, format: Format) -> Texture_Handle {
if map.texture != null && map.texture.content.size > 0 {
return load_texture_from_data(renderer, map.texture.content.data, map.texture.content.size, format=format);
}
return 0;
}
load_fbx :: (path: string) -> *Model, bool {
opts : ufbx_load_opts = .{};
opts.load_external_files = true;
opts.evaluate_skinning = true;
opts.target_axes.right = .POSITIVE_X;
opts.target_axes.up = .POSITIVE_Y;
opts.target_axes.front = .NEGATIVE_Z;
opts.target_unit_meters = 1.0;
//opts.space_conversion = ufbx_space_conversion.UFBX_SPACE_CONVERSION_ADJUST_TRANSFORMS;
error : ufbx_error;
scene := ufbx_load_file(to_temp_c_string(path), *opts, *error);
if scene == null {
log_error("FBX '%' could not be loaded", path);
return null, false;
}
model, locator := find_and_occupy_empty_slot(*renderer.model_lib);
model.path = copy_string(path);
model.name = copy_string(path);
// Materials
for i: 0..scene.materials.count - 1 {
mat := scene.materials.data[i];
model_material : Model_Material;
model_material.textures.base_color = load_fbx_texture(mat.pbr.base_color, format=.R8G8B8A8_UNORM_SRGB);
model_material.textures.normal = load_fbx_texture(mat.pbr.normal_map, format=.R8G8B8A8_UNORM);
for 0..mat.props.props.count-1 {
prop := mat.props.props.data[it];
prop_name := to_string(prop.name.data,, allocator=temp);
if prop_name == "DiffuseColor" {
model_material.base_color.x = xx prop.value_vec3.x;
model_material.base_color.y = xx prop.value_vec3.y;
model_material.base_color.z = xx prop.value_vec3.z;
model_material.base_color.w = 1.0;
}
}
//mat.pbr.base_factor;
//create_texture :: (using renderer: *Renderer, data: *void, width: u32, height: u32, channels: u32, path: string = "", generate_mips: bool = true) -> Texture_Handle {
//Material &dst = materials[i + 1];
//dst.base_factor.value.x = 1.0f;
//setup_texture(dst.base_factor, mat->pbr.base_factor);
//setup_texture(dst.base_color, mat->pbr.base_color);
//setup_texture(dst.roughness, mat->pbr.roughness);
//setup_texture(dst.metallic, mat->pbr.metalness);
//setup_texture(dst.emission_factor, mat->pbr.emission_factor);
//setup_texture(dst.emission_color, mat->pbr.emission_color);
//dst.base_color.image.srgb = true;
//dst.emission_color.image.srgb = true;
array_add(*model.materials, model_material);
}
array_reserve(*model.nodes, xx scene.nodes.count);
zero_handle : Node_Handle = 0;
for i: 0..scene.nodes.count-1 {
node := scene.nodes.data[i];
parse_fbx_node(model, node);
}
// Load animations
array_resize(*model.animations, xx scene.anim_stacks.count);
for 0..model.animations.count-1 {
parse_anim_stack(*model.animations[it], scene.anim_stacks.data[it], scene, model);
}
ufbx_free_scene(scene);
return model, false;
}
get_or_load_model :: (path: string) -> *Model {
for * renderer.model_lib {
if it.path == path {
return it;
}
}
return load_fbx(path);
}
#import "ufbx";

140
renderer/render_graph.jai Normal file
View File

@@ -0,0 +1,140 @@
Render_Pass_Handle :: #type, distinct u32;
Pass_Buffer_Info :: struct {
shader_type: Shader_Type;
slot: u32;
buffer: Buffer_Handle;
}
Render_Graph :: struct {
temp_sampler: Sampler_Handle;
render_passes: PArray(Render_Pass, Render_Pass_Handle);
}
new_render_graph :: () -> *Render_Graph {
graph := New(Render_Graph);
return graph;
}
execute_render_graph :: (graph: *Render_Graph) {
for graph.render_passes {
execute_render_pass(it);
}
}
push_material_pass_properties :: (render_pass: Render_Pass, mat_pass: Material_Pass_Old) {
for mat_pass.properties {
if it.parameter.type == {
case .BUFFER;
push_cmd_set_constant_buffer(renderer, it.parameter.slot, it.buffer, it.parameter.shader);
case .SAMPLER;
push_cmd_set_sampler(renderer, it.parameter.slot, it.sampler);
case .TEXTURE;
{
is_texture_input := false;
input_index := 0;
if it.parameter.mapping == {
case .SHADER_ATTACHMENT0;
is_texture_input = true;
input_index = 0;
case .SHADER_ATTACHMENT1;
is_texture_input = true;
input_index = 1;
case .SHADER_ATTACHMENT2;
is_texture_input = true;
input_index = 2;
case .SHADER_ATTACHMENT3;
is_texture_input = true;
input_index = 3;
}
if is_texture_input {
input := render_pass.inputs[input_index];
owning_pass := parray_get(*renderer.render_graph.render_passes, input.pass_handle);
if input.rt_index == DEPTH_STENCIL_SLOT {
push_cmd_set_texture(renderer, it.parameter.slot, owning_pass.depth_stencil);
} else {
push_cmd_set_texture(renderer, it.parameter.slot, owning_pass.render_targets[input.rt_index]);
}
} else {
if it.texture > 0 {
push_cmd_set_texture(renderer, it.parameter.slot, it.texture);
}
}
}
}
}
}
setup_pass_inputs :: (render_pass: Render_Pass) {
for input: render_pass.inputs {
owning_pass := parray_get(*renderer.render_graph.render_passes, input.pass_handle);
if input.rt_index == DEPTH_STENCIL_SLOT {
push_cmd_set_texture(renderer, xx it_index, owning_pass.depth_stencil);
} else {
push_cmd_set_texture(renderer, xx it_index, owning_pass.render_targets[input.rt_index]);
}
}
}
set_render_pass_clear_color :: (rp: Render_Pass_Handle, input_index: s32, color: Color) {
pass := parray_get(*renderer.render_graph.render_passes, rp);
pass.clear_colors[input_index] = color;
}
execute_render_pass :: (render_pass: Render_Pass) { // @Incomplete: Add command buffer as parameter
if render_pass.render_targets.count == 0 {
if render_pass.has_depth_stencil {
push_cmd_set_render_targets(renderer, depth_stencil_enabled=render_pass.has_depth_stencil, depth_stencil_buffer=render_pass.depth_stencil);
push_cmd_clear_depth_stencil(renderer, render_pass.depth_stencil, 1.0);
width := ifx render_pass.width == SWAPCHAIN_SIZE then renderer.render_target_width else render_pass.width;
height := ifx render_pass.height == SWAPCHAIN_SIZE then renderer.render_target_height else render_pass.height;
push_cmd_set_viewport(renderer, width, height);
} else {
assert(render_pass.uses_backbuffer);
push_cmd_set_backbuffer(renderer);
color: Vector4;
color.x = 0.0;
color.y = 0.0;
color.z = 0.0;
color.w = 1.0;
push_cmd_clear_backbuffer(renderer, color);
push_cmd_set_viewport(renderer, xx renderer.render_target_width, xx renderer.render_target_height);
}
} else {
width := render_pass.width;
height := render_pass.height;
if render_pass.width == SWAPCHAIN_SIZE {
width = renderer.render_target_width;
}
if render_pass.height == SWAPCHAIN_SIZE {
height = renderer.render_target_height;
}
push_cmd_set_render_targets(renderer, ..render_pass.render_targets, render_pass.has_depth_stencil, render_pass.depth_stencil);
push_cmd_set_viewport(renderer, width, height);
for render_pass.render_targets {
push_cmd_clear_render_target(renderer, it, render_pass.clear_colors[it_index]);
}
if render_pass.has_depth_stencil {
push_cmd_clear_depth_stencil(renderer, render_pass.depth_stencil, 1.0);
}
}
// @NOCHECKIN Setup inputs!
setup_pass_inputs(render_pass);
render_pass.callback(render_pass);
}
#load "render_pass.jai";

97
renderer/render_pass.jai Normal file
View File

@@ -0,0 +1,97 @@
DEPTH_STENCIL_SLOT :: 42;
// The slots used by the render pass render targets
// Slots 10-17 are reserved for the passes
Render_Target_Shader_Slots :: enum {
SLOT0 :: 10;
SLOT1 :: 11;
SLOT2 :: 12;
SLOT3 :: 13;
SLOT4 :: 14;
SLOT5 :: 15;
SLOT6 :: 16;
SLOT7 :: 17;
}
Render_Target_Info :: struct {
format: Format;
width: u32;
height: u32;
clear_color: Color;
}
Depth_Stencil_Info :: struct {
enabled: bool;
format: Format;
width: u32;
height: u32;
}
Render_Pass_Input_Info :: struct {
pass_handle: Render_Pass_Handle;
rt_index: u32; // the index into the render pass
}
Render_Pass :: struct {
name: string;
inputs: [..] Render_Pass_Input_Info;
uses_backbuffer: bool;
render_targets: [..] Render_Target_Handle;
clear_colors: [..] Color;
depth_stencil: Depth_Stencil_Buffer_Handle;
has_depth_stencil: bool;
width: u32;
height: u32;
callback: (pass: Render_Pass);
}
create_render_pass :: (graph: *Render_Graph, name: string, callback: (Render_Pass), render_target_infos: [] Render_Target_Info = .[], depth_stencil_info : Depth_Stencil_Info = .{}, uses_backbuffer: bool = false) -> Render_Pass_Handle {
render_pass : Render_Pass;
render_pass.name = copy_string(name);
render_pass.uses_backbuffer = uses_backbuffer;
render_pass.callback = callback;
if depth_stencil_info.enabled {
render_pass.has_depth_stencil = true;
render_pass.depth_stencil = create_depth_stencil_buffer(depth_stencil_info.width, depth_stencil_info.height, 0);
render_pass.width = depth_stencil_info.width;
render_pass.height = depth_stencil_info.height;
}
array_reserve(*render_pass.render_targets, render_target_infos.count);
array_reserve(*render_pass.clear_colors, render_target_infos.count);
for render_target_infos {
array_add(*render_pass.render_targets, create_render_target(it.width, it.height, it.format));
array_add(*render_pass.clear_colors, it.clear_color);
render_pass.width = it.width;
render_pass.height = it.height;
}
return parray_add(*graph.render_passes, render_pass);
}
add_render_pass_input :: (graph: *Render_Graph, to_pass: Render_Pass_Handle, from_pass: Render_Pass_Handle, rt_index: u32) {
info : Render_Pass_Input_Info;
info.pass_handle = from_pass;
info.rt_index = rt_index;
pass := parray_get(*graph.render_passes, to_pass);
array_add(*pass.inputs, info);
}
get_texture_from_pass :: (name: string) -> Texture_Handle {
for renderer.render_graph.render_passes {
if it.name == name {
rt := parray_get(renderer.render_targets, it.render_targets[0]);
return rt.texture;
}
}
return xx 0;
}

1630
renderer/renderer.jai Normal file

File diff suppressed because it is too large Load Diff

9
renderer/vertex.jai Normal file
View File

@@ -0,0 +1,9 @@
Textured_Vert :: struct {
position: Vector2;
texcoord: Vector2;
}
Textured_Vert_3D :: struct {
position: Vector3;
texcoord: Vector2;
}

974
ui/ui.jai Normal file
View File

@@ -0,0 +1,974 @@
MAX_VERT_BUFFERS :: 64;
UI_Box_Flags :: enum_flags u32 {
NONE :: 0;
CLICKABLE :: 1;
DRAW_BORDER :: 2;
DRAW_BACKGROUND :: 4;
DRAW_TEXT :: 8;
CLIP :: 16;
ANIMATE_ON_HOVER :: 32;
}
Rect :: struct {
x: float;
y: float;
w: float;
h: float;
}
Rect_Instance_Data :: struct {
p0 : Vector2;
p1 : Vector2;
colors : [4] Color;
corner_radius : float;
edge_softness : float;
border_thickness : float;
}
Interaction_State :: struct {
clicked: bool;
left_mouse_down: bool;
right_mouse_down: bool;
left_mouse_pressed: bool;
right_mouse_pressed: bool;
normalized_local_mouse_coordinates: Vector2; // Coordinates inside the rect in the range [0,1]
}
UI_Box :: struct {
hash: u32;
last_used_frame_index: u64;
parent: *UI_Box;
first_child: *UI_Box;
last_child: *UI_Box;
num_children: u64;
next: *UI_Box;
prev: *UI_Box;
// Specified per-frame
flags : UI_Box_Flags;
text: string;
alignment_flags: UI_Text_Alignment_Flags;
semantic_size: [2] UI_Size;
padding_left: float;
padding_right: float;
padding_top: float;
padding_bottom: float;
// Computed per-frame
rect : Rect;
size: Vector2;
// Persistent
animation_t: float;
hover_animation_t: float;
interaction : Interaction_State;
style : struct {
texture: Texture_Handle;
background_color: Color;
border_color: Color;
border_width: float;
text_color: Color;
}
layout : struct {
alignment: UI_Child_Alignment;
axis: UI_Child_Axis;
}
}
UI_Child_Axis :: enum {
HORIZONTAL;
VERTICAL;
}
UI_Child_Alignment :: enum {
LEFT;
RIGHT;
CENTERED;
}
UI_Text_Alignment_Flags :: enum_flags u8 {
LEFT_BOTTOM :: 0;
CENTER_HORIZONTALLY :: 1;
CENTER_VERTICALLY :: 2;
RIGHT_BOTTOM :: 4;
}
UI_Size_Kind :: enum {
PIXELS;
TEXT_DIM;
PCT;
CHILDREN_SUM;
}
UI_Size :: struct {
size_kind: UI_Size_Kind;
value: float;
strictness: float;
}
Textured_Vert_Buffer :: struct {
vb : Buffer_Handle;
verts: [6] Textured_Vert;
}
UI_State :: struct {
begun: bool;
frame_index: u64;
root: *UI_Box;
boxes: Table(u32, UI_Box);
last_box: *UI_Box;
parent_stack: Stack(*UI_Box);
allocator: Allocator;
pool: Flat_Pool;
// Rendering
shaders : struct {
ui: Pipeline_State_Handle;
ui_rect: Pipeline_State_Handle;
ui_rect_textured: Pipeline_State_Handle;
text: Pipeline_State_Handle;
}
fonts : struct {
regular: Font_Handle;
button: Font_Handle;
}
rect_vb : Buffer_Handle;
colored_verts: [..] Colored_Vert;
max_verts: u32;
instanced_rects : [..] Rect_Instance_Data;
instance_rect_sb : Buffer_Handle;
texture_vert_buffers : [MAX_VERT_BUFFERS] Textured_Vert_Buffer;
next_available_texture_buffer_index: s64;
sampler: Sampler_Handle;
}
ui_state : UI_State;
ui_box :: (flags: UI_Box_Flags, identifier: s64 = 0, loc := #caller_location) -> *UI_Box {
return ui_box_make(flags, hash=get_hash(loc, identifier));
}
ui_box_make :: (flags: UI_Box_Flags, hash: u32) -> *UI_Box {
actual_hash := hash;
parent := get_current_parent();
if parent != null {
parent.num_children += 1;
//actual_hash += parent.hash;
//actual_hash += xx (parent.num_children*2);
}
box := get_ui_box_or_create_new(actual_hash);
box.last_used_frame_index = ui_state.frame_index;
box.first_child = null;
box.last_child = null;
box.next = null;
box.prev = null;
box.num_children = 0;
box.flags = flags;
box.parent = null;
// Set the links
box.parent = parent;
if box.parent != null {
if box.parent.first_child == null {
box.parent.first_child = box;
}
if box.parent.last_child != null {
box.parent.last_child.next = box;
box.prev = box.parent.last_child;
}
box.parent.last_child = box;
}
box.text = current_text;
box.style.texture = current_texture;
box.style.background_color = background_color;
box.style.text_color = text_color;
box.style.border_color = border_color;
box.style.border_width = border_width;
box.semantic_size[0] = current_size_x;
box.semantic_size[1] = current_size_y;
box.padding_left = padding_left;
box.padding_right = padding_right;
box.padding_top = padding_top;
box.padding_bottom = padding_bottom;
box.alignment_flags = current_text_alignment;
// Reset everything
set_properties_to_defaults();
if ui_state.root == null {
ui_state.root = box;
}
ui_state.last_box = box;
return box;
}
get_hash :: (loc: Source_Code_Location, identifier: s64) -> u32 {
hash := cast(u32) loc.fully_pathed_filename.data * cast,no_check(u32)(loc.line_number + 1) * cast(u32)(identifier + 1);
return hash;
}
get_current_parent :: () -> *UI_Box {
return stack_peek(*ui_state.parent_stack);
}
ui_push_parent :: (box: *UI_Box, alignment: UI_Child_Alignment = .LEFT, axis: UI_Child_Axis = .HORIZONTAL) {
box.layout.alignment = alignment;
box.layout.axis = axis;
stack_push(*ui_state.parent_stack, box);
}
ui_pop_parent :: () {
stack_pop(*ui_state.parent_stack);
}
ui_init :: () {
a: Allocator;
a.proc = flat_pool_allocator_proc;
a.data = *ui_state.pool;
ui_state.allocator = a;
ui_state.colored_verts.allocator = ui_state.allocator;
ui_state.boxes.allocator = ui_state.allocator;
init(*ui_state.boxes, 1024);
ui_state.sampler = create_sampler(renderer);
ui_state.fonts.regular = create_font(renderer, "../assets/fonts/roboto/Roboto-Regular.ttf", 12);
ui_state.fonts.button = create_font(renderer, "../assets/fonts/roboto/Roboto-Regular.ttf", 12);
// ui_rect
{
vs := create_vertex_shader(renderer, "../assets/shaders/ui_rect.hlsl", "VS");
ps := create_pixel_shader(renderer, "../assets/shaders/ui_rect.hlsl", "PS");
layout : [2] Vertex_Data_Info;
layout[0] = .{0,.POSITION2D, 0};
layout[1] = .{0,.COLOR_WITH_ALPHA, 0};
params : [2] Shader_Parameter;
params[0].shader = .PIXEL;
params[0].type = .SAMPLER;
params[0].name = "samp";
params[0].slot = 0;
params[0].mapping = .CLAMP_SAMPLER;
params[1].shader = .PIXEL;
params[1].type = .TEXTURE;
params[1].name = "tex";
params[1].slot = 1;
ui_state.shaders.ui_rect = create_pipeline_state(renderer, vs, ps, layout, params, blend_type=.TRANSPARENT);
}
// ui_rect
{
vs := create_vertex_shader(renderer, "../assets/shaders/ui.hlsl", "VS_Main");
ps := create_pixel_shader(renderer, "../assets/shaders/ui.hlsl", "PS_Main");
layout : [0] Vertex_Data_Info;
params : [2] Shader_Parameter;
params[0].shader = .PIXEL;
params[0].type = .SAMPLER;
params[0].name = "samp";
params[0].slot = 0;
params[0].mapping = .CLAMP_SAMPLER;
params[1].shader = .PIXEL;
params[1].type = .TEXTURE;
params[1].name = "tex";
params[1].slot = 1;
ui_state.shaders.ui = create_pipeline_state(renderer, vs, ps, layout, params, blend_type=.TRANSPARENT);
}
{
vs := create_vertex_shader(renderer, "../assets/shaders/font.hlsl", "VS");
ps := create_pixel_shader(renderer, "../assets/shaders/font.hlsl", "PS");
layout : [3] Vertex_Data_Info;
layout[0] = .{0,.POSITION2D, 0};
layout[1] = .{0,.TEXCOORD0, 0};
layout[2] = .{0,.COLOR_WITH_ALPHA, 0};
params : [2] Shader_Parameter;
params[0].shader = .PIXEL;
params[0].type = .SAMPLER;
params[0].name = "ss";
params[0].slot = 0;
params[0].mapping = .CLAMP_SAMPLER;
params[1].shader = .PIXEL;
params[1].type = .TEXTURE;
params[1].name = "tex";
params[1].slot = 1;
ui_state.shaders.text = create_pipeline_state(renderer, vs, ps, layout, params, blend_type=.TRANSPARENT);
}
// Make into one shader....
// ui_rect textured
{
vs := create_vertex_shader(renderer, "../assets/shaders/ui_rect.hlsl", "VS", string.["USE_TEXTURE"]);
ps := create_pixel_shader(renderer, "../assets/shaders/ui_rect.hlsl", "PS", string.["USE_TEXTURE"]);
layout : [2] Vertex_Data_Info;
layout[0] = .{0,.POSITION2D, 0};
layout[1] = .{0,.TEXCOORD0, 0};
params : [2] Shader_Parameter;
params[0].shader = .PIXEL;
params[0].type = .SAMPLER;
params[0].name = "samp";
params[0].slot = 0;
params[0].mapping = .CLAMP_SAMPLER;
params[1].shader = .PIXEL;
params[1].type = .TEXTURE;
params[1].name = "tex";
params[1].slot = 1;
ui_state.shaders.ui_rect_textured = create_pipeline_state(renderer, vs, ps, layout, params, blend_type=.TRANSPARENT);
}
ui_state.max_verts = 4096;
{
dynamic_buffer_size := size_of(Colored_Vert) * ui_state.max_verts;
ui_state.rect_vb = create_vertex_buffer(renderer, null, dynamic_buffer_size, stride=size_of(Colored_Vert), mappable=true);
for 0..MAX_VERT_BUFFERS-1 {
{
buffer : Textured_Vert_Buffer;
dynamic_buffer_size := size_of(Textured_Vert) * ui_state.max_verts;
buffer.vb = create_vertex_buffer(renderer, null, dynamic_buffer_size, stride=size_of(Textured_Vert), mappable=true);
ui_state.texture_vert_buffers[it] = buffer;
}
}
}
{
dynamic_buffer_size := size_of(Rect_Instance_Data) * ui_state.max_verts;
ui_state.instance_rect_sb = create_structured_buffer(renderer, null, dynamic_buffer_size, stride=size_of(Rect_Instance_Data), mappable=true);
}
}
// # BEGIN # LAYOUT ALGORITHM
ui_figure_out_sizes :: () {
// SET ALL PIXEL AND TEXT SIZES
for *box : ui_state.boxes {
if box.semantic_size[0].size_kind == {
case .PIXELS; {
box.size.x = box.semantic_size[0].value + box.padding_left + box.padding_right;
}
case .TEXT_DIM; {
text_size := get_text_size(renderer, box.text, ui_state.fonts.button);
box.size.x = text_size.x + box.padding_left + box.padding_right;
}
}
if box.semantic_size[1].size_kind == {
case .PIXELS; {
box.size.y = box.semantic_size[1].value + box.padding_top + box.padding_bottom;
}
case .TEXT_DIM; {
text_size := get_text_size(renderer, box.text, ui_state.fonts.button);
box.size.y = text_size.y + box.padding_top + box.padding_bottom;
}
}
}
// Upwards-dependent
for *box : ui_state.boxes {
if box.semantic_size[0].size_kind == .PCT {
assert(box.parent != null);
box.size.x = box.parent.size.x * box.semantic_size[0].value - (box.parent.padding_left + box.parent.padding_right);
}
if box.semantic_size[1].size_kind == .PCT {
assert(box.parent != null);
box.size.y = box.parent.size.y * box.semantic_size[1].value - (box.parent.padding_top + box.parent.padding_bottom);
}
}
// Downwards-dependent
for *box : ui_state.boxes {
if box.semantic_size[0].size_kind == .CHILDREN_SUM {
assert(box.num_children != 0);
size : float = 0.0;
child := box.first_child;
while child != null {
defer child = child.next;
if box.layout.axis == .HORIZONTAL {
size += child.size.x;
} else {
size = max(child.size.x, size);
}
}
box.size.x = size + box.padding_left + box.padding_right;
}
if box.semantic_size[1].size_kind == .CHILDREN_SUM {
assert(box.num_children != 0);
size : float = 0.0;
child := box.first_child;
while child != null {
defer child = child.next;
if box.layout.axis == .VERTICAL {
size += child.size.y;
} else {
size = max(child.size.y, size);
}
}
box.size.y = size + box.padding_top + box.padding_bottom;
}
}
// Find final positions
for *box : ui_state.boxes {
if box.parent == null {
box.rect.x = 0.0;
box.rect.y = 0.0;
box.rect.w = box.size.x;
box.rect.h = box.size.y;
ui_set_rect_recursively(box);
}
}
for *box : ui_state.boxes {
if box.style.texture != 0 {
w, h := get_texture_size(renderer, box.style.texture);
aspect := cast(float)w/cast(float)h;
current_aspect := cast(float)box.rect.w/cast(float)box.rect.h;
rect := box.rect;
if rect.w < rect.h {
box.rect.w = box.rect.h * aspect;
scale := rect.w / box.rect.w;
// @Note(Daniel): My brain can't think straight so this extra scaling might be circumvented by doing the calculations differently....
if scale < 1 {
box.rect.w *= scale;
box.rect.h *= scale;
}
} else {
box.rect.h = box.rect.w / aspect;
scale := rect.h / box.rect.h;
if scale < 1 {
box.rect.w *= scale;
box.rect.h *= scale;
}
}
}
//if box.semantic_size[0].size_kind == .KEEP_ASPECT {
// assert(box.style.texture != 0);
// w, h := get_texture_size(renderer, box.style.texture);
// aspect := cast(float)w/cast(float)h;
// box.size.x = box.size.y * aspect;
//}
//
//if box.semantic_size[1].size_kind == .KEEP_ASPECT {
// assert(box.style.texture != 0);
// w, h := get_texture_size(renderer, box.style.texture);
// aspect := cast(float)h/cast(float)w;
// box.size.y = box.size.x * aspect;
//}
}
//print("\n");
for *box : ui_state.boxes {
//print("RECT % % % % TEXT %\n", box.rect.x, box.rect.y, box.rect.w, box.rect.h, box.text);
}
//print("\n");
}
ui_set_rect_recursively :: (parent: *UI_Box) {
starting_offset_x := parent.rect.x + parent.padding_left;
starting_offset_y := parent.rect.y + parent.padding_bottom;
child := parent.first_child;
while child != null {
defer child = child.next;
child.rect.x = starting_offset_x;
child.rect.y = starting_offset_y;
child.rect.w = child.size.x;
child.rect.h = child.size.y;
if parent.layout.axis == {
case .HORIZONTAL; {
starting_offset_x += child.rect.w;
}
case .VERTICAL; {
starting_offset_y += child.rect.h;
}
}
if child.num_children > 0 {
ui_set_rect_recursively(child);
}
}
}
// # END # LAYOUT ALGORITHM
ui_begin :: () {
assert(!ui_state.begun);
ui_state.frame_index += 1;
ui_state.begun = true;
ui_state.colored_verts.count = 0;
ui_state.instanced_rects.count = 0;
ui_state.next_available_texture_buffer_index = 0;
}
ui_end :: () {
hashes_to_remove : [..] u32;
hashes_to_remove.allocator = temp;
for * ui_state.boxes {
if it.last_used_frame_index != ui_state.frame_index {
array_add(*hashes_to_remove, it.hash);
}
}
for hashes_to_remove {
table_remove(*ui_state.boxes, it);
}
// Do all the layouting work
ui_figure_out_sizes();
// Check for any input events
ui_update_input();
assert(ui_state.begun);
ui_state.begun = false;
ui_state.parent_stack.values.count = 0;
}
// #### BOX PROPERTY FUNCTIONS
ui_set_next_background_color :: (color: Color) {
background_color = color;
}
ui_set_next_text_color :: (color: Color) {
text_color = color;
}
ui_set_next_border_color :: (color: Color) {
border_color = color;
}
ui_set_next_border_width :: (width: float) {
border_width = width;
}
ui_set_next_texture :: (handle: Texture_Handle) {
current_texture = handle;
}
ui_set_next_padding :: (new_padding: float) {
padding_left = new_padding;
padding_right = new_padding;
padding_top = new_padding;
padding_bottom = new_padding;
}
ui_set_next_padding_left :: (new_padding: float) {
padding_left = new_padding;
}
ui_set_next_padding_right :: (new_padding: float) {
padding_right = new_padding;
}
ui_set_next_padding_top :: (new_padding: float) {
padding_top = new_padding;
}
ui_set_next_padding_bottom :: (new_padding: float) {
padding_bottom = new_padding;
}
ui_set_next_text :: (text: string) {
current_text = copy_temporary_string(text);
}
ui_set_next_text_alignment :: (flags: UI_Text_Alignment_Flags) {
current_text_alignment = flags;
}
ui_set_next_size_x :: (size: UI_Size_Kind, value: float = 0.0, strictness: float = 0.0) {
current_size_x = .{size, value, strictness};
}
ui_set_next_size_y :: (size: UI_Size_Kind, value: float = 0.0, strictness: float = 0.0) {
current_size_y = .{size, value, strictness};
}
// # END # BOX PROPERTY FUNCTIONS
ui_render :: () {
// Render background
for * ui_state.boxes {
if it.parent == null {
ui_render_background_recursively(it);
}
}
if ui_state.colored_verts.count > 0 {
//upload_data_to_buffer(renderer, ui_state.rect_vb, ui_state.colored_verts.data, cast(s32)ui_state.colored_verts.count * size_of(Colored_Vert));
//push_cmd_set_draw_mode(renderer, .FILL);
//push_cmd_set_depth_write(renderer, false);
//push_cmd_set_pipeline_state(renderer, ui_state.shaders.ui_rect);
//push_cmd_set_vertex_buffer(renderer, ui_state.rect_vb);
//push_cmd_draw(renderer, ui_state.colored_verts.count);
}
// NEW DRAW TIME
if ui_state.instanced_rects.count > 0 {
upload_data_to_buffer(renderer, ui_state.instance_rect_sb, ui_state.instanced_rects.data, cast(s32)ui_state.instanced_rects.count * size_of(Rect_Instance_Data));
push_cmd_set_draw_mode(renderer, .FILL);
push_cmd_set_depth_write(renderer, false);
push_cmd_set_pipeline_state(renderer, ui_state.shaders.ui);
push_cmd_set_constant_buffer(renderer, 0, screen_data_buffer, .PIXEL);
push_cmd_set_structured_buffer(renderer, 0, ui_state.instance_rect_sb, .VERTEX);
push_cmd_draw_instanced(renderer, 4, ui_state.instanced_rects.count, topology=.TRIANGLE_STRIP);
}
// @Incomplete: this should be part of the same rendering somehow... Although it should happen on a per-texture basis
// Render backgrounds with texture
for * ui_state.boxes {
if it.parent == null {
ui_render_texture_background_recursively(it);
}
}
// Render text
push_cmd_set_pipeline_state(renderer, ui_state.shaders.text);
for * ui_state.boxes {
if it.parent == null {
ui_render_text_recursively(it);
}
}
}
#scope_file
set_properties_to_defaults :: () {
background_color = .{1,1,1,1};
text_color = .{0,0,0,1};
border_color = .{130.0/255.0,95.0/255.0,38.0/255.0,1};
border_width = 2.0;
current_text = "";
current_texture = 0;
current_size_x = .{};
current_size_y = .{};
padding_left = 0;
padding_right = 0;
padding_top = 0;
padding_bottom = 0;
current_text_alignment = .LEFT_BOTTOM;
}
get_ui_box_or_create_new :: (hash: u32) -> *UI_Box, bool {
ptr := table_find_pointer(*ui_state.boxes, hash);
if ptr == null {
table_add(*ui_state.boxes, hash, .{});
ptr = table_find_pointer(*ui_state.boxes, hash);
}
ptr.hash = hash;
return ptr, false;
}
// #### RENDERING
make_vert :: (x: float, y: float, color: Color) -> Colored_Vert {
vert : Colored_Vert;
vert.position.x = x;
vert.position.y = y;
vert.color = color;
return vert;
}
make_vert_textured :: (x: float, y: float, uv_x: float, uv_y: float) -> Textured_Vert {
vert : Textured_Vert;
vert.position.x = x;
vert.position.y = y;
vert.texcoord.x = uv_x;
vert.texcoord.y = uv_y;
return vert;
}
should_draw_hover_animation :: (box: *UI_Box) -> bool {
return box.flags & .ANIMATE_ON_HOVER && box.hover_animation_t > 0.0;
}
instance_data_from_rect :: (x: float, y: float, w: float, h: float, corner_radius: float, edge_softness: float, border_thickness: float, color: .. Color) -> Rect_Instance_Data {
data : Rect_Instance_Data = ---;
data.p0.x = x;
data.p0.y = y;
data.p1.x = x + w;
data.p1.y = y - h;
data.corner_radius = corner_radius;
data.edge_softness = edge_softness;
data.border_thickness = border_thickness;
if color.count == 4 {
data.colors[0] = color[0];
data.colors[1] = color[1];
data.colors[2] = color[2];
data.colors[3] = color[3];
} else {
data.colors[0] = color[0];
data.colors[1] = color[0];
data.colors[2] = color[0];
data.colors[3] = color[0];
}
return data;
}
ui_render_background_recursively :: (box: *UI_Box) {
inv_w := 1.0 / cast(float)renderer.render_target_width;
inv_h := 1.0 / cast(float)renderer.render_target_height;
if box.flags & .DRAW_BACKGROUND && box.style.texture == 0 {
x : float = box.rect.x * inv_w * 2.0 - 1.0;
y : float = (cast(float)renderer.render_target_height - box.rect.y) * inv_h * 2.0 - 1.0;
w : float = box.rect.w * inv_w * 2.0;
h : float = box.rect.h * inv_h * 2.0;
color : Color = box.style.background_color;
instance_data := instance_data_from_rect(x, y, w, h, 0.0, 0.0, 0.0, color);
array_add(*ui_state.instanced_rects, instance_data);
//array_add(*ui_state.colored_verts, make_vert(x + w, y - h, color));
//array_add(*ui_state.colored_verts, make_vert(x, y - h, color));
//array_add(*ui_state.colored_verts, make_vert(x, y, color));
//array_add(*ui_state.colored_verts, make_vert(x + w, y - h, color));
//array_add(*ui_state.colored_verts, make_vert(x, y, color));
//array_add(*ui_state.colored_verts, make_vert(x + w, y, color));
}
if box.flags & .DRAW_BORDER {
x : float = box.rect.x * inv_w * 2.0 - 1.0;
y : float = (cast(float)renderer.render_target_height - box.rect.y) * inv_h * 2.0 - 1.0;
w : float = box.rect.w * inv_w * 2.0;
h : float = box.rect.h * inv_h * 2.0;
color : Color = box.style.border_color;
instance_data := instance_data_from_rect(x, y, w, h, 0.0, 0.0, box.style.border_width, color);
array_add(*ui_state.instanced_rects, instance_data);
}
if should_draw_hover_animation(box) {
x : float = box.rect.x * inv_w * 2.0 - 1.0;
y : float = (cast(float)renderer.render_target_height - box.rect.y) * inv_h * 2.0 - 1.0;
w : float = box.rect.w * inv_w * 2.0;
h : float = box.rect.h * inv_h * 2.0;
color := Color.{1,1,1, box.hover_animation_t * 0.3};
instance_data := instance_data_from_rect(x, y, w, h, 0.0, 0.0, 0.0, color);
array_add(*ui_state.instanced_rects, instance_data);
}
child := box.first_child;
while child != null {
defer child = child.next;
ui_render_background_recursively(child);
}
}
get_next_available_texture_vert_buffer :: () -> *Textured_Vert_Buffer {
index := ui_state.next_available_texture_buffer_index;
assert(index < MAX_VERT_BUFFERS);
ui_state.next_available_texture_buffer_index += 1;
buffer := *ui_state.texture_vert_buffers[index];
return buffer;
}
ui_render_texture_background_recursively :: (box: *UI_Box) {
inv_w := 1.0 / cast(float)renderer.render_target_width;
inv_h := 1.0 / cast(float)renderer.render_target_height;
if box.flags & .DRAW_BACKGROUND && box.style.texture != 0 {
buffer := get_next_available_texture_vert_buffer();
rect := box.rect;
inv_w := 1.0 / cast(float)renderer.render_target_width;
inv_h := 1.0 / cast(float)renderer.render_target_height;
x : float = rect.x * inv_w * 2.0 - 1.0;
y : float = (cast(float)renderer.render_target_height - rect.y) * inv_h * 2.0 - 1.0;
w : float = rect.w * inv_w * 2.0;
h : float = rect.h * inv_h * 2.0;
buffer.verts[0] = make_vert_textured(x + w, y - h, 1.0, 1.0);
buffer.verts[1] = make_vert_textured(x, y - h, 0, 1.0);
buffer.verts[2] = make_vert_textured(x, y, 0.0, 0.0);
buffer.verts[3] = make_vert_textured(x + w, y - h, 1.0, 1.0);
buffer.verts[4] = make_vert_textured(x, y, 0.0, 0.0);
buffer.verts[5] = make_vert_textured(x + w, y, 1.0, 0.0);
upload_data_to_buffer(renderer, buffer.vb, buffer.verts.data, 6 * size_of(Textured_Vert));
push_cmd_set_draw_mode(renderer, .FILL);
push_cmd_set_depth_write(renderer, false);
push_cmd_set_pipeline_state(renderer, ui_state.shaders.ui_rect_textured);
push_cmd_set_sampler(renderer, 0, ui_state.sampler);
push_cmd_set_texture(renderer, 0, box.style.texture);
push_cmd_set_vertex_buffer(renderer, buffer.vb);
push_cmd_draw(renderer, 6);
}
child := box.first_child;
while child != null {
defer child = child.next;
ui_render_texture_background_recursively(child);
}
}
ui_render_text_recursively :: (box: *UI_Box) {
inv_w := 1.0 / cast(float)renderer.render_target_width;
inv_h := 1.0 / cast(float)renderer.render_target_height;
if box.flags & .DRAW_TEXT {
font_handle := ui_state.fonts.regular;
text_size := get_text_size(renderer, box.text, font_handle);
x := box.rect.x + box.padding_left;
y := cast(float)renderer.render_target_height - box.rect.y - box.rect.h + box.padding_bottom;
if box.alignment_flags & .CENTER_HORIZONTALLY {
x += box.rect.w * 0.5 - text_size.x * 0.5;
}
if box.alignment_flags & .CENTER_VERTICALLY {
y += box.rect.h * 0.5 - text_size.y * 0.5;
}
if box.alignment_flags & .RIGHT_BOTTOM {
x += box.rect.w - text_size.x;
}
render_data := bake_text(renderer, x, y, box.text, font_handle, box.style.text_color);
font := *renderer.fonts[font_handle - 1];
push_cmd_set_texture(renderer, 0, font.texture);
push_cmd_set_vertex_buffer(renderer, render_data.vb);
push_cmd_draw(renderer, render_data.vert_count);
}
child := box.first_child;
while child != null {
defer child = child.next;
ui_render_text_recursively(child);
}
}
ADJUSTMENT :: 0.1;
is_mouse_inside :: (x: float, y: float, rect: Rect) -> bool {
return x - ADJUSTMENT >= rect.x && y - ADJUSTMENT >= rect.y && x + ADJUSTMENT <= rect.x + rect.w && y + ADJUSTMENT <= rect.y + rect.h;
}
ui_update_input :: () {
mouse_x := input.mouse.x;
mouse_y := input.mouse.y;
for *box: ui_state.boxes {
if box.flags & .CLICKABLE {
normalized_local_mouse_coordinates: Vector2; // Coordinates inside the rect in the range [0,1]
box.interaction.normalized_local_mouse_coordinates.x = clamp(mouse_x - box.rect.x, 0.0, box.rect.w) / box.rect.w;
box.interaction.normalized_local_mouse_coordinates.y = clamp(mouse_y - box.rect.y, 0.0, box.rect.h) / box.rect.h;
if box.interaction.clicked {
box.interaction.clicked = false;
}
if box.interaction.left_mouse_pressed {
box.interaction.left_mouse_down = false;
if !key_pressed(.MOUSE_LEFT) {
box.interaction.clicked = true;
box.interaction.left_mouse_pressed = false;
}
} else {
if is_mouse_inside(mouse_x, mouse_y, box.rect) && key_pressed(.MOUSE_LEFT) {
box.interaction.left_mouse_pressed = true;
box.interaction.left_mouse_down = true;
}
}
}
if box.flags & .ANIMATE_ON_HOVER {
if is_mouse_inside(mouse_x, mouse_y, box.rect) {
box.hover_animation_t += dt * 10;
} else {
box.hover_animation_t -= dt * 10;
}
box.hover_animation_t = clamp(box.hover_animation_t, 0.0, 1.0);
}
}
}
// style
background_color : Color = .{1,1,1,1};
text_color : Color = .{0,0,0,1};
border_color : Color;
border_width : float;
current_text : string = "";
current_texture : Texture_Handle;
current_size_x : UI_Size;
current_size_y : UI_Size;
current_text_alignment : UI_Text_Alignment_Flags;
padding_left : float;
padding_right : float;
padding_top : float;
padding_bottom : float;
#load "../core/stack.jai";

184
ui/widgets.jai Normal file
View File

@@ -0,0 +1,184 @@
ui_full_size_background :: (identifier: s64 = 0, loc := #caller_location) {
ui_set_next_size_x(.PIXELS, xx renderer.render_target_width);
ui_set_next_size_y(.PIXELS, xx renderer.render_target_height);
ui_set_next_background_color(.{0.0, 0.0, 0.0, 1.0});
background := ui_box_make(.DRAW_BACKGROUND, hash=get_hash(loc, identifier));
}
ui_button :: (text: string, identifier: s64 = 0, loc := #caller_location) -> clicked: bool, Interaction_State {
ui_set_next_text(text);
ui_set_next_background_color(.{0.2,0.2,0.2,1});
ui_set_next_text_color(.{1,1,1,1});
ui_set_next_size_x(.TEXT_DIM);
ui_set_next_size_y(.TEXT_DIM);
ui_set_next_padding(5);
//ui_set_next_text_alignment(CENTER_HORIZONTALLY | .CENTER_VERTICALLY);
box := ui_box_make(.CLICKABLE | .DRAW_BORDER | .DRAW_TEXT | .DRAW_BACKGROUND | .ANIMATE_ON_HOVER, get_hash(loc, identifier));
return box.interaction.clicked, box.interaction;
}
ui_button_with_texture :: (texture_handle: Texture_Handle, identifier: s64 = 0, loc := #caller_location) -> clicked: bool, Interaction_State {
ui_set_next_texture(texture_handle);
ui_set_next_size_x(.PIXELS, 20);
ui_set_next_size_y(.PIXELS, 20);
box := ui_box_make(.CLICKABLE | .DRAW_BACKGROUND | .ANIMATE_ON_HOVER, get_hash(loc, identifier));
return box.interaction.clicked, box.interaction;
}
ui_button_no_bg :: (text: string, identifier: s64 = 0, loc := #caller_location) -> clicked: bool, Interaction_State {
ui_set_next_text(text);
ui_set_next_background_color(.{0.2,0.2,0.2,1});
ui_set_next_text_color(.{1,1,1,1});
ui_set_next_size_x(.TEXT_DIM);
ui_set_next_size_y(.TEXT_DIM);
ui_set_next_padding(5);
//ui_set_next_text_alignment(.CENTER_HORIZONTALLY | .CENTER_VERTICALLY);
box := ui_box_make(.CLICKABLE | .DRAW_BORDER | .DRAW_TEXT | .ANIMATE_ON_HOVER, get_hash(loc, identifier));
return box.interaction.clicked, box.interaction;
}
ui_button_no_border :: (text: string, identifier: s64 = 0, loc := #caller_location) -> clicked: bool, Interaction_State {
ui_set_next_text(text);
ui_set_next_background_color(.{0.2,0.2,0.2,1});
ui_set_next_text_color(.{1,1,1,1});
ui_set_next_size_x(.TEXT_DIM);
ui_set_next_size_y(.TEXT_DIM);
ui_set_next_padding(5);
//ui_set_next_text_alignment(.CENTER_HORIZONTALLY | .CENTER_VERTICALLY);
box := ui_box_make(.CLICKABLE | .DRAW_BORDER | .DRAW_BACKGROUND | .DRAW_TEXT | .ANIMATE_ON_HOVER, get_hash(loc, identifier));
return box.interaction.clicked, box.interaction;
}
ui_toolbar_button :: (text: string, identifier: s64 = 0, loc := #caller_location) -> clicked: bool, Interaction_State {
ui_set_next_text(text);
ui_set_next_border_width(1);
ui_set_next_border_color(.{0.5,0.5,0.5,1});
ui_set_next_background_color(.{0.1,0.1,0.1,1});
ui_set_next_text_color(.{1,1,1,1});
ui_set_next_size_x(.TEXT_DIM);
ui_set_next_size_y(.TEXT_DIM);
ui_set_next_padding_left(20);
ui_set_next_padding_right(20);
ui_set_next_padding_top(5);
ui_set_next_padding_bottom(5);
//ui_set_next_text_alignment(.CENTER_HORIZONTALLY | .CENTER_VERTICALLY);
box := ui_box_make(.CLICKABLE | .DRAW_BORDER | .DRAW_TEXT | .ANIMATE_ON_HOVER, get_hash(loc, identifier));
return box.interaction.clicked, box.interaction;
}
ui_label :: (text: string, text_color: Color = .{1,1,1,1}, identifier: s64 = 0, loc := #caller_location) {
ui_set_next_text(text);
ui_set_next_background_color(.{0.0,1.0,1.0,0.0});
ui_set_next_text_color(text_color);
ui_set_next_size_x(.TEXT_DIM);
ui_set_next_size_y(.TEXT_DIM);
ui_set_next_padding(5);
box := ui_box_make(.DRAW_TEXT | .ANIMATE_ON_HOVER, get_hash(loc, identifier));
}
ui_label_fill_parent_x :: (text: string, identifier: s64 = 0, loc := #caller_location) {
ui_set_next_text(text);
ui_set_next_background_color(.{0.0,1.0,1.0,0.0});
ui_set_next_text_color(.{1,1,1,1});
ui_set_next_size_x(.PCT, 1);
ui_set_next_size_y(.TEXT_DIM);
ui_set_next_padding(5);
//ui_set_next_text_alignment(.CENTER_VERTICALLY);
box := ui_box_make(.DRAW_TEXT | .ANIMATE_ON_HOVER, get_hash(loc, identifier));
}
ui_clickable_label :: (text: string, selected: bool = false, identifier: s64 = 0, loc := #caller_location) -> clicked: bool, Interaction_State {
ui_set_next_text(text);
if selected {
ui_set_next_background_color(.{0.5,0.5,0.5,1.0});
} else {
ui_set_next_background_color(.{0.0,1.0,1.0,0.0});
}
ui_set_next_text_color(.{1,1,1,1});
ui_set_next_size_x(.PCT, 1);
ui_set_next_size_y(.TEXT_DIM);
ui_set_next_padding(5);
//ui_set_next_text_alignment(.CENTER_VERTICALLY);
box := ui_box_make(.DRAW_BACKGROUND | .DRAW_TEXT | .ANIMATE_ON_HOVER | .CLICKABLE, get_hash(loc, identifier));
return box.interaction.clicked, box.interaction;
}
ui_toolbar :: (identifier: s64 = 0, loc := #caller_location) {
ui_set_next_background_color(.{0.2,0.2,0.2,1});
ui_set_next_size_x(.PIXELS, xx renderer.render_target_width);
ui_set_next_size_y(.CHILDREN_SUM);
box := ui_box_make(.DRAW_BACKGROUND | .DRAW_BORDER, get_hash(loc, identifier));
}
ui_tab_title_bar :: (title: string, identifier: s64 = 0, loc := #caller_location) {
ui_set_next_background_color(.{0.03, 0.03, 0.03, 1.0});
ui_set_next_padding(5);
ui_set_next_size_y(.CHILDREN_SUM);
parent := ui_box_make(.DRAW_BACKGROUND | .DRAW_BORDER, get_hash(loc, identifier));
ui_push_parent(parent, alignment=.LEFT, axis=.VERTICAL);
{
ui_set_next_text_alignment(.CENTER_VERTICALLY);
ui_set_next_text(title);
ui_set_next_text_color(.{1,1,1,1});
ui_set_next_size_x(.TEXT_DIM);
ui_set_next_size_y(.TEXT_DIM);
text := ui_box(.DRAW_TEXT);
}
ui_pop_parent();
}
ui_texture :: (texture: Texture_Handle, identifier: s64 = 0, loc := #caller_location) {
ui_set_next_texture(texture);
ui_set_next_background_color(.{0.1,0.1,0.1,1});
box := ui_box_make(.DRAW_BACKGROUND | .DRAW_BORDER, get_hash(loc, identifier));
}
ui_interactable_texture :: (texture: Texture_Handle, identifier: s64 = 0, loc := #caller_location) -> Interaction_State {
ui_set_next_texture(texture);
ui_set_next_background_color(.{0.1,0.1,0.1,1});
box := ui_box_make(.DRAW_BACKGROUND | .CLICKABLE, get_hash(loc, identifier));
return box.interaction;
}
ui_space :: (width: float, height: float, identifier: s64 = 0, loc := #caller_location) {
ui_set_next_background_color(.{0,0,0,0});
ui_set_next_size_x(.PIXELS, width);
ui_set_next_size_y(.PIXELS, height);
box := ui_box_make(.NONE, get_hash(loc, identifier));
}
ui_slider :: (value: *float, min: float = 0.0, max: float = 1.0, identifier: s64 = 0, loc := #caller_location) {
ui_set_next_background_color(.{0.2,0.2,0.2,1});
ui_set_next_text_color(.{1,1,1,1});
ui_set_next_size_x(.PCT, 1.0);
ui_set_next_size_y(.PIXELS, 15);
ui_set_next_text_alignment(.CENTER_HORIZONTALLY | .CENTER_VERTICALLY);
ui_set_next_text(tprint("%", <<value));
box := ui_box_make(.DRAW_BACKGROUND | .DRAW_TEXT | .ANIMATE_ON_HOVER, get_hash(loc, identifier));
ui_push_parent(box, alignment=.LEFT, axis=.HORIZONTAL);
{
percentage := (<<value - min) / (max - min);
ui_set_next_size_x(.PCT, percentage * 0.95);
ui_set_next_size_y(.PCT, 1.0);
ui_box(.NONE);
ui_set_next_size_x(.PCT, 0.05);
ui_set_next_size_y(.PCT, 1.0);
ui_set_next_background_color(.{0.5,0.5,0.5,1});
draggable := ui_box(.DRAW_BACKGROUND | .CLICKABLE | .ANIMATE_ON_HOVER);
mouse_delta_x := input.mouse.delta_x;
if draggable.interaction.left_mouse_pressed {
increment := (max - min) / box.rect.w;
<<value = clamp(<<value + mouse_delta_x * increment, min, max);
}
}
ui_pop_parent();
}
#load "ui.jai";

61
windowing/window.jai Normal file
View File

@@ -0,0 +1,61 @@
Window :: struct {
width: u32;
height: u32;
}
SDL_Window_Type :: struct {
using #as window : Window;
sdl_window: *SDL_Window;
}
// SDL
#import "SDL";
create_window :: (title: string, width: u32, height: u32, fullscreen: bool) -> *Window {
SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMECONTROLLER);
// Get the display resolution
display_mode : SDL_DisplayMode;
if (SDL_GetDesktopDisplayMode(0, *display_mode) != 0) {
log_error("Could not get display mode! SDL_Error: %\n", SDL_GetError());
return null;
}
screen_width : s32 = xx width;
screen_height : s32 = xx height;
flags := SDL_WINDOW_SHOWN;
if fullscreen {
screen_width = display_mode.w;
screen_height = display_mode.h;
flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
}
sdl_window := SDL_CreateWindow(to_c_string(title,, temp), SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, screen_width, screen_height, flags);
if sdl_window == null {
// In the case that the window could not be made...
log_error("Could not create window: %\n", to_string(SDL_GetError()));
return null;
}
window := New(SDL_Window_Type);
window.width = xx screen_width;
window.height = xx screen_height;
window.sdl_window = sdl_window;
return window;
}
get_hwnd :: (window: *Window) -> HWND {
sdl := cast(*SDL_Window_Type)window;
wm_info : SDL_SysWMinfo;
SDL_GetWindowWMInfo(sdl.sdl_window, *wm_info);
return wm_info.info.win.window;
}
set_show_cursor :: (show: bool) {
SDL_ShowCursor(xx show);
SDL_SetRelativeMouseMode(ifx show then SDL_FALSE else SDL_TRUE);
}