Initial commit
This commit is contained in:
6
.build/.added_strings_w3.jai
Normal file
6
.build/.added_strings_w3.jai
Normal 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
6
.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
bin/*.exe
|
||||
bin/*.pdb
|
||||
.build
|
||||
range_index
|
||||
bin/mnar.exe
|
||||
.build/*.obj
|
||||
285
animation/animator.jai
Normal file
285
animation/animator.jai
Normal 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
86
audio/audio.jai
Normal 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
345
core/camera.jai
Normal 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
271
core/console.jai
Normal 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
189
core/entity.jai
Normal 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
19
core/fps.jai
Normal 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
366
core/math.jai
Normal 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
87
core/parray.jai
Normal 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
239
core/ray.jai
Normal 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
142
core/scene.jai
Normal 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
29
core/stack.jai
Normal 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
21
core/static_array.jai
Normal 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
6
core/string_helpers.jai
Normal 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
172
core/transform.jai
Normal 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
378
editor/editor.jai
Normal 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
310
editor/editor_ui.jai
Normal 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
389
input/input.jai
Normal 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
260
input/sdl_input.jai
Normal 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
334
metaprogram.jai
Normal 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
2
module.jai
Normal file
@@ -0,0 +1,2 @@
|
||||
#load "core/entity.jai";
|
||||
#load "core/scene.jai";
|
||||
596
networking/networking.jai
Normal file
596
networking/networking.jai
Normal 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
363
physics/gjk.jai
Normal 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
421
physics/physics.jai
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
14
renderer/directional_light.jai
Normal file
14
renderer/directional_light.jai
Normal 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
1503
renderer/dx11_renderer.jai
Normal file
File diff suppressed because it is too large
Load Diff
105
renderer/font.jai
Normal file
105
renderer/font.jai
Normal 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
465
renderer/material.jai
Normal 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
191
renderer/mesh.jai
Normal 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
668
renderer/model.jai
Normal 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
140
renderer/render_graph.jai
Normal 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
97
renderer/render_pass.jai
Normal 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
1630
renderer/renderer.jai
Normal file
File diff suppressed because it is too large
Load Diff
9
renderer/vertex.jai
Normal file
9
renderer/vertex.jai
Normal 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
974
ui/ui.jai
Normal 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
184
ui/widgets.jai
Normal 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
61
windowing/window.jai
Normal 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);
|
||||
}
|
||||
Reference in New Issue
Block a user