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