Init
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
src.bin
|
||||
.gdbf/
|
||||
.gdbinit
|
||||
build/
|
||||
@@ -0,0 +1,40 @@
|
||||
require("custom.runner").setup({
|
||||
commands = {
|
||||
run = function()
|
||||
local command = require("custom.runner").command
|
||||
command:add("odin run ./src/")
|
||||
command.target = { position = "bottom" }
|
||||
command:run()
|
||||
end,
|
||||
build_debug = function()
|
||||
local command = require("custom.runner").command
|
||||
|
||||
command:add("odin build ./src/ -debug -out:build/debug")
|
||||
|
||||
command.target = { position = "bottom" }
|
||||
command:run()
|
||||
end,
|
||||
debug = function()
|
||||
local command = require("custom.runner").command
|
||||
local current_file = vim.api.nvim_buf_get_name(0)
|
||||
|
||||
command:add("odin build ./src/ -debug -out:build/debug")
|
||||
|
||||
if current_file ~= "" then
|
||||
command:add(string.format("raddbg ./build/debug --open %q", current_file))
|
||||
else
|
||||
command:add("raddbg ./build/debug")
|
||||
end
|
||||
command.target = { position = "bottom" }
|
||||
command:run()
|
||||
end,
|
||||
},
|
||||
|
||||
exclude = {
|
||||
"build",
|
||||
"Tiled",
|
||||
"assets",
|
||||
".gdbf",
|
||||
".gdbinit",
|
||||
},
|
||||
})
|
||||
Executable
+571
@@ -0,0 +1,571 @@
|
||||
// raddbg 0.9.28 user file
|
||||
|
||||
recent_project:
|
||||
{
|
||||
path: debug
|
||||
name: ""
|
||||
}
|
||||
recent_project:
|
||||
{
|
||||
path: "../debug"
|
||||
name: ""
|
||||
}
|
||||
window:
|
||||
{
|
||||
size: 1780 1390
|
||||
panels:
|
||||
{
|
||||
0.273086:
|
||||
{
|
||||
0.333333:
|
||||
{
|
||||
watch:
|
||||
{
|
||||
selected
|
||||
expression: ""
|
||||
watch_expression: expression: state
|
||||
}
|
||||
watch: expression: "query:types"
|
||||
watch: expression: "query:locals"
|
||||
}
|
||||
0.333333:
|
||||
{
|
||||
watch: selected expression: "query:threads"
|
||||
watch:
|
||||
{
|
||||
expression: "query:call_stack"
|
||||
call_stack_frame: 0.050000 0.654968 0.295032
|
||||
}
|
||||
watch: expression: "query:breakpoints"
|
||||
watch: expression: "query:watch_pins"
|
||||
}
|
||||
0.333333:
|
||||
{
|
||||
text:
|
||||
{
|
||||
expression: "query:output"
|
||||
query: input: ""
|
||||
cursor: 0x0
|
||||
mark: 0x0
|
||||
}
|
||||
watch: selected expression: "query:targets"
|
||||
}
|
||||
}
|
||||
0.726914:
|
||||
{
|
||||
selected
|
||||
text:
|
||||
{
|
||||
selected
|
||||
expression: "file:\"/home/alexey/projects/odin/racing/src/main.odin\".data"
|
||||
project: ""
|
||||
query: input: ""
|
||||
cursor: 0x113
|
||||
mark: 0x113
|
||||
}
|
||||
disasm: query: input: ""
|
||||
}
|
||||
}
|
||||
split_x
|
||||
pos: 775 45
|
||||
monitor
|
||||
}
|
||||
keybindings:
|
||||
{
|
||||
{ kill_all f5 shift }
|
||||
{ step_into_inst f11 alt }
|
||||
{ step_over_inst f10 alt }
|
||||
{ step_out f11 shift }
|
||||
{ halt x ctrl shift }
|
||||
{ halt pause }
|
||||
{ run f5 }
|
||||
{ restart f5 ctrl shift }
|
||||
{ step_into f11 }
|
||||
{ step_over f10 }
|
||||
{ run_to_line f10 ctrl }
|
||||
{ set_next_statement f10 ctrl shift }
|
||||
{ inc_window_font_size equal alt }
|
||||
{ dec_window_font_size minus alt }
|
||||
{ toggle_fullscreen return ctrl }
|
||||
{ new_panel_right p ctrl }
|
||||
{ new_panel_down minus ctrl }
|
||||
{ rotate_panel_columns 2 ctrl }
|
||||
{ next_panel comma ctrl }
|
||||
{ prev_panel comma ctrl shift }
|
||||
{ focus_panel_right right ctrl alt }
|
||||
{ focus_panel_left left ctrl alt }
|
||||
{ focus_panel_up up ctrl alt }
|
||||
{ focus_panel_down down ctrl alt }
|
||||
{ undo z ctrl }
|
||||
{ redo y ctrl }
|
||||
{ go_back left alt }
|
||||
{ go_forward right alt }
|
||||
{ close_panel p ctrl shift alt }
|
||||
{ next_tab page_down ctrl }
|
||||
{ prev_tab page_up ctrl }
|
||||
{ next_tab tab ctrl }
|
||||
{ prev_tab tab ctrl shift }
|
||||
{ move_tab_right page_down ctrl shift }
|
||||
{ move_tab_left page_up ctrl shift }
|
||||
{ close_tab w ctrl }
|
||||
{ tab_bar_top up ctrl shift alt }
|
||||
{ tab_bar_bottom down ctrl shift alt }
|
||||
{ open_tab t ctrl }
|
||||
{ tab_settings t ctrl alt }
|
||||
{ open o ctrl }
|
||||
{ open_source_file_from_debug_info i ctrl }
|
||||
{ switch_to_partner_file o alt }
|
||||
{ new_project n ctrl shift }
|
||||
{ open_project o ctrl shift }
|
||||
{ save_project s ctrl shift }
|
||||
{ edit f2 }
|
||||
{ accept return }
|
||||
{ accept space }
|
||||
{ cancel esc }
|
||||
{ focus_menu d alt }
|
||||
{ toggle_lock l ctrl }
|
||||
{ move_left left }
|
||||
{ move_right right }
|
||||
{ move_up up }
|
||||
{ move_down down }
|
||||
{ move_left_select left shift }
|
||||
{ move_right_select right shift }
|
||||
{ move_up_select up shift }
|
||||
{ move_down_select down shift }
|
||||
{ move_left_chunk left ctrl }
|
||||
{ move_right_chunk right ctrl }
|
||||
{ move_up_chunk up ctrl }
|
||||
{ move_down_chunk down ctrl }
|
||||
{ move_up_page page_up }
|
||||
{ move_down_page page_down }
|
||||
{ move_up_whole home ctrl }
|
||||
{ move_down_whole end ctrl }
|
||||
{ move_left_chunk_select left ctrl shift }
|
||||
{ move_right_chunk_select right ctrl shift }
|
||||
{ move_up_chunk_select up ctrl shift }
|
||||
{ move_down_chunk_select down ctrl shift }
|
||||
{ move_up_page_select page_up shift }
|
||||
{ move_down_page_select page_down shift }
|
||||
{ move_up_whole_select home ctrl shift }
|
||||
{ move_down_whole_select end ctrl shift }
|
||||
{ move_up_reorder up alt }
|
||||
{ move_down_reorder down alt }
|
||||
{ move_home home }
|
||||
{ move_end end }
|
||||
{ move_home_select home shift }
|
||||
{ move_end_select end shift }
|
||||
{ select_all a ctrl }
|
||||
{ delete_single delete }
|
||||
{ delete_chunk delete ctrl }
|
||||
{ backspace_single backspace }
|
||||
{ backspace_chunk backspace ctrl }
|
||||
{ copy c ctrl }
|
||||
{ copy insert ctrl }
|
||||
{ cut x ctrl }
|
||||
{ paste v ctrl }
|
||||
{ paste insert shift }
|
||||
{ insert_text null }
|
||||
{ move_next tab }
|
||||
{ move_prev tab shift }
|
||||
{ goto_line g ctrl }
|
||||
{ goto_address g alt }
|
||||
{ search f ctrl }
|
||||
{ search slash }
|
||||
{ search_backwards r ctrl }
|
||||
{ search_backwards slash ctrl }
|
||||
{ find_next f3 }
|
||||
{ find_prev f3 ctrl }
|
||||
{ find_selected_thread f4 }
|
||||
{ goto_name j ctrl }
|
||||
{ goto_name_at_cursor f12 }
|
||||
{ toggle_watch_expr_at_cursor w alt }
|
||||
{ toggle_watch_expr_at_mouse d ctrl }
|
||||
{ toggle_watch_pin f9 ctrl }
|
||||
{ toggle_breakpoint f9 }
|
||||
{ add_address_breakpoint f9 shift }
|
||||
{ add_function_breakpoint f9 ctrl shift }
|
||||
{ attach f6 shift }
|
||||
{ open_palette f1 }
|
||||
{ open_palette p ctrl shift }
|
||||
{ log_marker m ctrl shift alt }
|
||||
{ toggle_dev_menu d ctrl shift alt }
|
||||
}
|
||||
breakpoint:
|
||||
{
|
||||
project: ""
|
||||
source_location: "../../hello/main.odin:56:1"
|
||||
hit_count: 0
|
||||
}
|
||||
current_path: "/home/alexey/projects/odin/racing/.raddbg"
|
||||
debug_info:
|
||||
{
|
||||
path: "/home/alexey/projects/odin/racing/build/debug"
|
||||
timestamp: 66222355938101
|
||||
project: ""
|
||||
}
|
||||
debug_info:
|
||||
{
|
||||
path: "/usr/lib/libX11.so.6"
|
||||
timestamp: 66206040958000
|
||||
project: ""
|
||||
}
|
||||
debug_info:
|
||||
{
|
||||
path: "/usr/lib/libm.so.6"
|
||||
timestamp: 66221135897000
|
||||
project: ""
|
||||
}
|
||||
debug_info:
|
||||
{
|
||||
path: "/usr/lib/libc.so.6"
|
||||
timestamp: 66221135897000
|
||||
project: ""
|
||||
}
|
||||
debug_info:
|
||||
{
|
||||
path: "/lib64/ld-linux-x86-64.so.2"
|
||||
timestamp: 66221135897000
|
||||
project: ""
|
||||
}
|
||||
debug_info:
|
||||
{
|
||||
path: "/usr/lib/libxcb.so.1"
|
||||
timestamp: 66184635927000
|
||||
project: ""
|
||||
}
|
||||
debug_info:
|
||||
{
|
||||
path: "/usr/lib/libXau.so.6"
|
||||
timestamp: 66168334589000
|
||||
project: ""
|
||||
}
|
||||
debug_info:
|
||||
{
|
||||
path: "/usr/lib/libXdmcp.so.6"
|
||||
timestamp: 66211487652000
|
||||
project: ""
|
||||
}
|
||||
debug_info:
|
||||
{
|
||||
path: "/usr/lib/libXxf86vm.so.1"
|
||||
timestamp: 66204799833000
|
||||
project: ""
|
||||
}
|
||||
debug_info:
|
||||
{
|
||||
path: "/usr/lib/libXext.so.6"
|
||||
timestamp: 66204798407000
|
||||
project: ""
|
||||
}
|
||||
debug_info:
|
||||
{
|
||||
path: "/usr/lib/libXi.so.6"
|
||||
timestamp: 66214983870000
|
||||
project: ""
|
||||
}
|
||||
debug_info:
|
||||
{
|
||||
path: "/usr/lib/libXrandr.so.2"
|
||||
timestamp: 66204799447000
|
||||
project: ""
|
||||
}
|
||||
debug_info:
|
||||
{
|
||||
path: "/usr/lib/libXrender.so.1"
|
||||
timestamp: 66168333224000
|
||||
project: ""
|
||||
}
|
||||
debug_info:
|
||||
{
|
||||
path: "/usr/lib/libXcursor.so.1"
|
||||
timestamp: 66164897222000
|
||||
project: ""
|
||||
}
|
||||
debug_info:
|
||||
{
|
||||
path: "/usr/lib/libXfixes.so.3"
|
||||
timestamp: 66191816105000
|
||||
project: ""
|
||||
}
|
||||
debug_info:
|
||||
{
|
||||
path: "/usr/lib/libXinerama.so.1"
|
||||
timestamp: 66204798889000
|
||||
project: ""
|
||||
}
|
||||
debug_info:
|
||||
{
|
||||
path: "/usr/lib/libX11-xcb.so.1"
|
||||
timestamp: 66206040958000
|
||||
project: ""
|
||||
}
|
||||
debug_info:
|
||||
{
|
||||
path: "/usr/lib/libGLX.so.0"
|
||||
timestamp: 66184693464000
|
||||
project: ""
|
||||
}
|
||||
debug_info:
|
||||
{
|
||||
path: "/usr/lib/libGLdispatch.so.0"
|
||||
timestamp: 66184693464000
|
||||
project: ""
|
||||
}
|
||||
debug_info:
|
||||
{
|
||||
path: "/usr/lib/libGLX_mesa.so.0"
|
||||
timestamp: 66221471789000
|
||||
project: ""
|
||||
}
|
||||
debug_info:
|
||||
{
|
||||
path: "/usr/lib/libgallium-26.1.6-arch3.1.so"
|
||||
timestamp: 66221471789000
|
||||
project: ""
|
||||
}
|
||||
debug_info:
|
||||
{
|
||||
path: "/usr/lib/libdrm.so.2"
|
||||
timestamp: 66216328784000
|
||||
project: ""
|
||||
}
|
||||
debug_info:
|
||||
{
|
||||
path: "/usr/lib/libxcb-glx.so.0"
|
||||
timestamp: 66184635927000
|
||||
project: ""
|
||||
}
|
||||
debug_info:
|
||||
{
|
||||
path: "/usr/lib/libxcb-shm.so.0"
|
||||
timestamp: 66184635927000
|
||||
project: ""
|
||||
}
|
||||
debug_info:
|
||||
{
|
||||
path: "/usr/lib/libexpat.so.1"
|
||||
timestamp: 66218340126000
|
||||
project: ""
|
||||
}
|
||||
debug_info:
|
||||
{
|
||||
path: "/usr/lib/libxcb-dri3.so.0"
|
||||
timestamp: 66184635927000
|
||||
project: ""
|
||||
}
|
||||
debug_info:
|
||||
{
|
||||
path: "/usr/lib/libxcb-present.so.0"
|
||||
timestamp: 66184635927000
|
||||
project: ""
|
||||
}
|
||||
debug_info:
|
||||
{
|
||||
path: "/usr/lib/libxcb-xfixes.so.0"
|
||||
timestamp: 66184635927000
|
||||
project: ""
|
||||
}
|
||||
debug_info:
|
||||
{
|
||||
path: "/usr/lib/libgcc_s.so.1"
|
||||
timestamp: 66221675792000
|
||||
project: ""
|
||||
}
|
||||
debug_info:
|
||||
{
|
||||
path: "/usr/lib/libLLVM.so.22.1"
|
||||
timestamp: 66219390635000
|
||||
project: ""
|
||||
}
|
||||
debug_info:
|
||||
{
|
||||
path: "/usr/lib/libz.so.1"
|
||||
timestamp: 66205596487000
|
||||
project: ""
|
||||
}
|
||||
debug_info:
|
||||
{
|
||||
path: "/usr/lib/libzstd.so.1"
|
||||
timestamp: 66174387757000
|
||||
project: ""
|
||||
}
|
||||
debug_info:
|
||||
{
|
||||
path: "/usr/lib/libSPIRV-Tools.so"
|
||||
timestamp: 66221645000000
|
||||
project: ""
|
||||
}
|
||||
debug_info:
|
||||
{
|
||||
path: "/usr/lib/libsensors.so.5"
|
||||
timestamp: 66181926213000
|
||||
project: ""
|
||||
}
|
||||
debug_info:
|
||||
{
|
||||
path: "/usr/lib/libxcb-randr.so.0"
|
||||
timestamp: 66184635927000
|
||||
project: ""
|
||||
}
|
||||
debug_info:
|
||||
{
|
||||
path: "/usr/lib/libxcb-sync.so.1"
|
||||
timestamp: 66184635927000
|
||||
project: ""
|
||||
}
|
||||
debug_info:
|
||||
{
|
||||
path: "/usr/lib/libxshmfence.so.1"
|
||||
timestamp: 66168333263000
|
||||
project: ""
|
||||
}
|
||||
debug_info:
|
||||
{
|
||||
path: "/usr/lib/libelf.so.1"
|
||||
timestamp: 66212257553000
|
||||
project: ""
|
||||
}
|
||||
debug_info:
|
||||
{
|
||||
path: "/usr/lib/libdrm_amdgpu.so.1"
|
||||
timestamp: 66216328784000
|
||||
project: ""
|
||||
}
|
||||
debug_info:
|
||||
{
|
||||
path: "/usr/lib/libdrm_intel.so.1"
|
||||
timestamp: 66216328784000
|
||||
project: ""
|
||||
}
|
||||
debug_info:
|
||||
{
|
||||
path: "/usr/lib/libstdc++.so.6"
|
||||
timestamp: 66221675792000
|
||||
project: ""
|
||||
}
|
||||
debug_info:
|
||||
{
|
||||
path: "/usr/lib/libffi.so.8"
|
||||
timestamp: 66219792944000
|
||||
project: ""
|
||||
}
|
||||
debug_info:
|
||||
{
|
||||
path: "/usr/lib/libedit.so.0"
|
||||
timestamp: 66215003270000
|
||||
project: ""
|
||||
}
|
||||
debug_info:
|
||||
{
|
||||
path: "/usr/lib/libxml2.so.16"
|
||||
timestamp: 66212388766000
|
||||
project: ""
|
||||
}
|
||||
debug_info:
|
||||
{
|
||||
path: "/usr/lib/libpciaccess.so.0"
|
||||
timestamp: 66209386711000
|
||||
project: ""
|
||||
}
|
||||
debug_info:
|
||||
{
|
||||
path: "/usr/lib/libncursesw.so.6"
|
||||
timestamp: 66209896673000
|
||||
project: ""
|
||||
}
|
||||
debug_info:
|
||||
{
|
||||
path: "/usr/lib/libicuuc.so.78"
|
||||
timestamp: 66209517782000
|
||||
project: ""
|
||||
}
|
||||
debug_info:
|
||||
{
|
||||
path: "/usr/lib/libicudata.so.78"
|
||||
timestamp: 66209517782000
|
||||
project: ""
|
||||
}
|
||||
debug_info:
|
||||
{
|
||||
path: "/usr/lib/libpulse.so"
|
||||
timestamp: 66201445208000
|
||||
project: ""
|
||||
}
|
||||
debug_info:
|
||||
{
|
||||
path: "/usr/lib/pulseaudio/libpulsecommon-17.0.so"
|
||||
timestamp: 66201445208000
|
||||
project: ""
|
||||
}
|
||||
debug_info:
|
||||
{
|
||||
path: "/usr/lib/libdbus-1.so.3"
|
||||
timestamp: 66175513452000
|
||||
project: ""
|
||||
}
|
||||
debug_info:
|
||||
{
|
||||
path: "/usr/lib/libsndfile.so.1"
|
||||
timestamp: 66196036422000
|
||||
project: ""
|
||||
}
|
||||
debug_info:
|
||||
{
|
||||
path: "/usr/lib/libsystemd.so.0"
|
||||
timestamp: 66220884102000
|
||||
project: ""
|
||||
}
|
||||
debug_info:
|
||||
{
|
||||
path: "/usr/lib/libasyncns.so.0"
|
||||
timestamp: 66184643894000
|
||||
project: ""
|
||||
}
|
||||
debug_info:
|
||||
{
|
||||
path: "/usr/lib/libogg.so.0"
|
||||
timestamp: 66187436605000
|
||||
project: ""
|
||||
}
|
||||
debug_info:
|
||||
{
|
||||
path: "/usr/lib/libvorbisenc.so.2"
|
||||
timestamp: 66172240374000
|
||||
project: ""
|
||||
}
|
||||
debug_info:
|
||||
{
|
||||
path: "/usr/lib/libFLAC.so.14"
|
||||
timestamp: 66184660199000
|
||||
project: ""
|
||||
}
|
||||
debug_info:
|
||||
{
|
||||
path: "/usr/lib/libopus.so.0"
|
||||
timestamp: 66203885564000
|
||||
project: ""
|
||||
}
|
||||
debug_info:
|
||||
{
|
||||
path: "/usr/lib/libmpg123.so.0"
|
||||
timestamp: 66212931451000
|
||||
project: ""
|
||||
}
|
||||
debug_info:
|
||||
{
|
||||
path: "/usr/lib/libmp3lame.so.0"
|
||||
timestamp: 66220534211000
|
||||
project: ""
|
||||
}
|
||||
debug_info:
|
||||
{
|
||||
path: "/usr/lib/libvorbis.so.0"
|
||||
timestamp: 66172240374000
|
||||
project: ""
|
||||
}
|
||||
debug_info:
|
||||
{
|
||||
path: "/usr/lib/libmvec.so.1"
|
||||
timestamp: 66221135897000
|
||||
project: ""
|
||||
}
|
||||
Executable
+8
@@ -0,0 +1,8 @@
|
||||
// raddbg 0.9.28 project file
|
||||
|
||||
target:
|
||||
{
|
||||
executable: "../build/debug"
|
||||
working_directory: "../../racing"
|
||||
enabled: 1
|
||||
}
|
||||
Vendored
BIN
Binary file not shown.
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"$schema": "https://raw.githubusercontent.com/DanielGavin/ols/master/misc/odinfmt.schema.json",
|
||||
"character_width": 80,
|
||||
"tabs": true,
|
||||
"spaces": 2,
|
||||
"tabs_width": 4
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"$schema": "https://raw.githubusercontent.com/DanielGavin/ols/master/misc/ols.schema.json",
|
||||
"enable_semantic_tokens": true,
|
||||
"enable_document_symbols": true,
|
||||
"enable_hover": true,
|
||||
"enable_snippets": true,
|
||||
"enable_checker_only_saved": true,
|
||||
"enable_inlay_hints_params": true,
|
||||
"enable_inlay_hints_default_params": true,
|
||||
"enable_inlay_hints_implicit_return": true,
|
||||
"enable_inlay_hints_optional_result": true,
|
||||
"enable_inlay_hints_struct_fields": true
|
||||
}
|
||||
+579
@@ -0,0 +1,579 @@
|
||||
package main
|
||||
|
||||
import "core:fmt"
|
||||
import "core:math"
|
||||
import "core:math/linalg"
|
||||
import "core:math/rand"
|
||||
import rl "vendor:raylib"
|
||||
|
||||
Car :: struct {
|
||||
pos: rl.Vector3,
|
||||
vel: rl.Vector3,
|
||||
engine_state: EngineState,
|
||||
drivetrain: Drivetrain,
|
||||
speed: f32,
|
||||
forward: rl.Vector3,
|
||||
wheel_yaw: f32,
|
||||
}
|
||||
|
||||
EngineState :: struct {
|
||||
throttle: f32,
|
||||
brake: f32,
|
||||
rpm: f32,
|
||||
torque: f32,
|
||||
fuel_cut: bool,
|
||||
engine: Engine,
|
||||
graph: Graph,
|
||||
}
|
||||
|
||||
MIN_RPM :: 1000
|
||||
MAX_RPM :: 7000
|
||||
RPM_STEP :: 20
|
||||
|
||||
Engine :: struct {
|
||||
idle_rpm: f32,
|
||||
max_rpm: f32,
|
||||
bypass: f32,
|
||||
fric: f32,
|
||||
inertia: f32,
|
||||
hyst: f32,
|
||||
rpm_lut: []f32,
|
||||
}
|
||||
|
||||
MASS :: 1200
|
||||
DRAG_COEF :: 0.40
|
||||
ROLL_FORCE :: 176
|
||||
CLUTCH_K :: 0.15
|
||||
CLUTCH_MAX :: 300 // предел трения сцепления, N·m
|
||||
BRAKE_MAX :: 10500 // ~0.9g для 1200 кг
|
||||
|
||||
Drivetrain :: struct {
|
||||
ratios: [5]f32,
|
||||
final_ratio: f32,
|
||||
wheel_radius: f32,
|
||||
efficiency: f32,
|
||||
gear: i32,
|
||||
}
|
||||
|
||||
Graph :: struct {
|
||||
points: []rl.Vector2,
|
||||
pos: rl.Vector2,
|
||||
opts: GraphOpts,
|
||||
}
|
||||
|
||||
|
||||
GRAPH_STEP :: 75
|
||||
GRAPH_MIN_VAL_Y :: 0
|
||||
GRAPH_MAX_VAL_Y :: 500
|
||||
GRAPH_MIN_VAL_X :: 1000
|
||||
GRAPH_MAX_VAL_X :: 7000
|
||||
|
||||
GRAPH_WIDTH :: 300
|
||||
GRAPH_HEIGHT :: 200
|
||||
GRAPH_PADDING :: 5
|
||||
|
||||
RPMSample :: struct {
|
||||
rpm: f32,
|
||||
torq: f32,
|
||||
}
|
||||
|
||||
update_car :: proc(car: ^Car) {
|
||||
update_gear(&car.drivetrain, car.speed)
|
||||
update_engine_state(car)
|
||||
update_turning(car)
|
||||
car.pos += car.vel * car.speed * rl.GetFrameTime()
|
||||
}
|
||||
|
||||
update_turning :: proc(car: ^Car) {
|
||||
dt := rl.GetFrameTime()
|
||||
|
||||
// 1. Get input direction (-1 for left, 1 for right)
|
||||
input: f32 = 0
|
||||
if rl.IsKeyDown(.D) || rl.IsKeyDown(.RIGHT) {input -= 1}
|
||||
if rl.IsKeyDown(.A) || rl.IsKeyDown(.LEFT) {input += 1}
|
||||
|
||||
// 2. Turn sensitivity scaling based on speed
|
||||
speed_scale := math.clamp(car.speed / 200.0, 0.0, 1.0)
|
||||
turn_speed := f32(6.5) // Adjust turn speed in radians per second
|
||||
|
||||
// 3. Update the car's orientation angle (yaw)
|
||||
car.wheel_yaw += input * turn_speed * speed_scale * dt
|
||||
|
||||
// 4. Calculate forward vector directly from yaw
|
||||
// (X = sin, Z = cos gives standard 3D forward direction where 0 rad points North/Forward)
|
||||
car.forward = rl.Vector3 {
|
||||
math.sin(car.wheel_yaw),
|
||||
0,
|
||||
math.cos(car.wheel_yaw),
|
||||
}
|
||||
|
||||
// 5. Arcade Grip / Blend Velocity towards Forward Vector
|
||||
// Lower grip value (e.g. 5.0) = loose / drifting turn
|
||||
// Higher grip value (e.g. 20.0) = tight / glued to road
|
||||
grip_factor: f32 = 5.0
|
||||
car.vel = linalg.lerp(car.vel, car.forward, grip_factor * dt)
|
||||
|
||||
// Normalize velocity vector to prevent unintended scaling
|
||||
if rl.Vector3Length(car.vel) > 0 {
|
||||
car.vel = rl.Vector3Normalize(car.vel)
|
||||
}
|
||||
}
|
||||
|
||||
update_gear :: proc(drivetrain: ^Drivetrain, speed: f32) {
|
||||
if rl.IsKeyPressed(.E) {
|
||||
if try_shift(drivetrain, speed, drivetrain.gear + 1) do drivetrain.gear += 1
|
||||
}
|
||||
if rl.IsKeyPressed(.Q) {
|
||||
if try_shift(drivetrain, speed, drivetrain.gear - 1) do drivetrain.gear -= 1
|
||||
}
|
||||
}
|
||||
|
||||
try_shift :: proc(drivetrain: ^Drivetrain, speed: f32, new_gear: i32) -> bool {
|
||||
if new_gear < 0 || new_gear > 5 do return false
|
||||
if new_gear == 0 do return true
|
||||
G := drivetrain.ratios[new_gear - 1] * drivetrain.final_ratio
|
||||
projected_rpm := speed * G * 9.549 / drivetrain.wheel_radius
|
||||
return projected_rpm <= MAX_RPM
|
||||
}
|
||||
|
||||
update_engine_state :: proc(car: ^Car) {
|
||||
dt := rl.GetFrameTime()
|
||||
if rl.IsKeyDown(.S) {
|
||||
car.engine_state.brake = linalg.lerp(car.engine_state.brake, 1, 0.2)
|
||||
} else {
|
||||
if car.engine_state.brake >= 0.1 {
|
||||
car.engine_state.brake = linalg.lerp(
|
||||
car.engine_state.brake,
|
||||
0,
|
||||
0.2,
|
||||
)
|
||||
} else {
|
||||
car.engine_state.brake = 0
|
||||
}
|
||||
}
|
||||
if rl.IsKeyDown(.W) {
|
||||
car.engine_state.throttle = linalg.lerp(
|
||||
car.engine_state.throttle,
|
||||
1,
|
||||
0.2,
|
||||
)
|
||||
} else {
|
||||
if car.engine_state.throttle >= 0.1 {
|
||||
car.engine_state.throttle = linalg.lerp(
|
||||
car.engine_state.throttle,
|
||||
0,
|
||||
0.2,
|
||||
)
|
||||
} else {
|
||||
car.engine_state.throttle = 0
|
||||
}
|
||||
}
|
||||
|
||||
if car.engine_state.brake > 0 && car.drivetrain.gear != 0 do car.engine_state.throttle = 0
|
||||
|
||||
|
||||
if car.engine_state.rpm > MAX_RPM do car.engine_state.fuel_cut = true
|
||||
else if car.engine_state.rpm < MAX_RPM - car.engine_state.engine.hyst do car.engine_state.fuel_cut = false
|
||||
|
||||
eng_torq :=
|
||||
car.engine_state.fuel_cut ? 0 : car.engine_state.throttle * torq_from_lut(car.engine_state.rpm, &car.engine_state.engine) + car.engine_state.engine.bypass
|
||||
t_net := eng_torq - car.engine_state.engine.fric * car.engine_state.rpm
|
||||
|
||||
// сила двигателя на колёсах (0 на нейтрали) и эффективная масса
|
||||
force: f32 = 0
|
||||
mass_eff := f32(MASS)
|
||||
|
||||
if car.drivetrain.gear == 0 {
|
||||
// нейтраль: свободный оборот, на колёса ничего не идёт
|
||||
car.engine_state.rpm +=
|
||||
t_net / car.engine_state.engine.inertia * 9.549 * dt
|
||||
} else {
|
||||
G :=
|
||||
car.drivetrain.ratios[car.drivetrain.gear - 1] *
|
||||
car.drivetrain.final_ratio
|
||||
coupled_rpm := car.speed * G * 9.549 / car.drivetrain.wheel_radius
|
||||
|
||||
if coupled_rpm < car.engine_state.rpm {
|
||||
clutch_tq := min(
|
||||
CLUTCH_K * (car.engine_state.rpm - coupled_rpm),
|
||||
CLUTCH_MAX,
|
||||
)
|
||||
clutch_tq = max(clutch_tq, 0.0)
|
||||
car.engine_state.rpm +=
|
||||
(t_net - clutch_tq) /
|
||||
car.engine_state.engine.inertia *
|
||||
9.549 *
|
||||
dt
|
||||
car.engine_state.rpm = max(
|
||||
car.engine_state.rpm,
|
||||
car.engine_state.engine.idle_rpm,
|
||||
)
|
||||
force =
|
||||
clutch_tq *
|
||||
G *
|
||||
car.drivetrain.efficiency /
|
||||
car.drivetrain.wheel_radius
|
||||
} else {
|
||||
force =
|
||||
t_net *
|
||||
G *
|
||||
car.drivetrain.efficiency /
|
||||
car.drivetrain.wheel_radius
|
||||
mass_eff =
|
||||
MASS +
|
||||
car.engine_state.engine.inertia *
|
||||
G *
|
||||
G /
|
||||
(car.drivetrain.wheel_radius * car.drivetrain.wheel_radius)
|
||||
car.engine_state.rpm = max(
|
||||
coupled_rpm,
|
||||
car.engine_state.engine.idle_rpm,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// скорость — всегда, с тормозом (и на нейтрали тоже)
|
||||
brake_force := car.engine_state.brake * BRAKE_MAX
|
||||
car.speed +=
|
||||
(force -
|
||||
DRAG_COEF * car.speed * car.speed -
|
||||
ROLL_FORCE -
|
||||
brake_force) /
|
||||
mass_eff *
|
||||
dt
|
||||
car.speed = max(car.speed, 0)
|
||||
|
||||
car.engine_state.torque = torq_from_lut(
|
||||
car.engine_state.rpm,
|
||||
&car.engine_state.engine,
|
||||
)
|
||||
|
||||
// eng_torq :=
|
||||
// state.fuel_cut ? 0 : state.throttle * torq_from_lut(state.rpm, &state.engine) + state.engine.bypass
|
||||
//
|
||||
// load_torq := state.engine.fric * state.rpm
|
||||
//
|
||||
// state.rpm += (eng_torq - load_torq) / state.engine.inertia * 9.549 * dt
|
||||
//
|
||||
// state.torque = torq_from_lut(state.rpm, &state.engine)
|
||||
|
||||
}
|
||||
|
||||
init_car :: proc() -> Car {
|
||||
car: Car
|
||||
car.engine_state.engine = init_engine()
|
||||
car.pos = rl.Vector3{0, 0, 0}
|
||||
|
||||
car.engine_state.graph.pos = rl.Vector2{950, 50}
|
||||
|
||||
steps := (GRAPH_MAX_VAL_X - GRAPH_MIN_VAL_X) / GRAPH_STEP + 1
|
||||
|
||||
car.engine_state.graph.opts = GraphOpts {
|
||||
min_x = i32(car.engine_state.graph.pos.x) + GRAPH_PADDING,
|
||||
max_x = i32(
|
||||
car.engine_state.graph.pos.x,
|
||||
) + GRAPH_WIDTH - GRAPH_PADDING,
|
||||
min_y = i32(car.engine_state.graph.pos.y) + GRAPH_PADDING,
|
||||
max_y = i32(
|
||||
car.engine_state.graph.pos.y,
|
||||
) + GRAPH_HEIGHT - GRAPH_PADDING,
|
||||
}
|
||||
|
||||
car.engine_state.graph.points = make([]rl.Vector2, steps)
|
||||
|
||||
|
||||
for i in 0 ..< steps {
|
||||
rpm := f32(GRAPH_MIN_VAL_X + i * GRAPH_STEP)
|
||||
draw_coords := get_graph_coords(
|
||||
rpm,
|
||||
&car.engine_state.engine,
|
||||
car.engine_state.graph.opts,
|
||||
)
|
||||
car.engine_state.graph.points[i] = draw_coords
|
||||
}
|
||||
|
||||
car.engine_state.rpm = MIN_RPM
|
||||
|
||||
car.drivetrain = Drivetrain {
|
||||
ratios = {3.4, 2.1, 1.5, 1.15, 0.9},
|
||||
final_ratio = 3.3,
|
||||
wheel_radius = 0.33,
|
||||
efficiency = 0.85,
|
||||
gear = 0,
|
||||
}
|
||||
|
||||
car.forward = rl.Vector3{0, 0, 1}
|
||||
|
||||
return car
|
||||
}
|
||||
|
||||
init_engine :: proc() -> Engine {
|
||||
engine: Engine
|
||||
dyno_points := []RPMSample {
|
||||
RPMSample{rpm = 1000, torq = 190},
|
||||
RPMSample{rpm = 2000, torq = 205},
|
||||
RPMSample{rpm = 2700, torq = 220},
|
||||
RPMSample{rpm = 2850, torq = 230},
|
||||
RPMSample{rpm = 2950, torq = 240},
|
||||
RPMSample{rpm = 3100, torq = 250},
|
||||
RPMSample{rpm = 3300, torq = 260},
|
||||
RPMSample{rpm = 4050, torq = 390},
|
||||
RPMSample{rpm = 4500, torq = 400},
|
||||
RPMSample{rpm = 5000, torq = 390},
|
||||
RPMSample{rpm = 5500, torq = 361},
|
||||
RPMSample{rpm = 6000, torq = 320},
|
||||
RPMSample{rpm = 6500, torq = 280},
|
||||
RPMSample{rpm = 7000, torq = 240},
|
||||
}
|
||||
|
||||
engine.idle_rpm = 1000
|
||||
engine.max_rpm = 7000
|
||||
|
||||
engine.bypass = 25
|
||||
engine.fric = 0.025
|
||||
engine.inertia = 0.4
|
||||
engine.hyst = 150
|
||||
|
||||
lut := make([]f32, (MAX_RPM - MIN_RPM) / RPM_STEP + 1)
|
||||
|
||||
for i in 0 ..< len(lut) {
|
||||
rpm := f32(MIN_RPM + i * RPM_STEP)
|
||||
lut[i] = torq_at_rpm(dyno_points, rpm)
|
||||
}
|
||||
|
||||
engine.rpm_lut = lut
|
||||
|
||||
|
||||
return engine
|
||||
}
|
||||
|
||||
GraphOpts :: struct {
|
||||
min_x: i32,
|
||||
min_y: i32,
|
||||
max_x: i32,
|
||||
max_y: i32,
|
||||
}
|
||||
|
||||
draw_graph :: proc(state: ^EngineState) {
|
||||
|
||||
pos := state.graph.pos
|
||||
|
||||
rl.DrawRectangle(
|
||||
i32(pos.x),
|
||||
i32(pos.y),
|
||||
GRAPH_WIDTH,
|
||||
GRAPH_HEIGHT,
|
||||
rl.WHITE,
|
||||
)
|
||||
rl.DrawRectangleLines(
|
||||
i32(pos.x),
|
||||
i32(pos.y),
|
||||
GRAPH_WIDTH,
|
||||
GRAPH_HEIGHT,
|
||||
rl.BLACK,
|
||||
)
|
||||
|
||||
rl.DrawLine(
|
||||
state.graph.opts.min_x,
|
||||
state.graph.opts.min_y,
|
||||
state.graph.opts.min_x,
|
||||
state.graph.opts.max_y,
|
||||
rl.BLACK,
|
||||
)
|
||||
rl.DrawLine(
|
||||
state.graph.opts.min_x,
|
||||
state.graph.opts.max_y,
|
||||
state.graph.opts.max_x,
|
||||
state.graph.opts.max_y,
|
||||
rl.BLACK,
|
||||
)
|
||||
|
||||
for p_pos in state.graph.points {
|
||||
rl.DrawCircle(i32(p_pos.x), i32(p_pos.y), 2, rl.BLUE)
|
||||
}
|
||||
|
||||
|
||||
{
|
||||
rpm := state.rpm
|
||||
draw_coords := get_graph_coords(rpm, &state.engine, state.graph.opts)
|
||||
rl.DrawCircle(i32(draw_coords.x), i32(draw_coords.y), 2, rl.RED)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
get_graph_coords :: proc(
|
||||
rpm: f32,
|
||||
engine: ^Engine,
|
||||
opts: GraphOpts,
|
||||
) -> rl.Vector2 {
|
||||
torq := torq_from_lut(rpm, engine)
|
||||
x := map_to_range(
|
||||
f32(GRAPH_MIN_VAL_X),
|
||||
f32(GRAPH_MAX_VAL_X),
|
||||
f32(opts.min_x),
|
||||
f32(opts.max_x),
|
||||
rpm,
|
||||
)
|
||||
y := map_to_range(
|
||||
f32(GRAPH_MIN_VAL_Y),
|
||||
f32(GRAPH_MAX_VAL_Y),
|
||||
f32(opts.max_y),
|
||||
f32(opts.min_y),
|
||||
torq,
|
||||
)
|
||||
return rl.Vector2{x, y}
|
||||
}
|
||||
|
||||
map_to_range :: proc(
|
||||
in_min, in_max: f32,
|
||||
out_min, out_max: f32,
|
||||
val: f32,
|
||||
) -> f32 {
|
||||
t := (val - in_min) / (in_max - in_min)
|
||||
return out_min + t * (out_max - out_min)
|
||||
}
|
||||
|
||||
torq_from_lut :: proc(rpm: f32, engine: ^Engine) -> f32 {
|
||||
x := (rpm - f32(MIN_RPM)) / f32(RPM_STEP)
|
||||
|
||||
i := int(x)
|
||||
t := x - f32(i)
|
||||
|
||||
if i < 0 {
|
||||
return engine.rpm_lut[0]
|
||||
}
|
||||
|
||||
if i >= len(engine.rpm_lut) - 1 {
|
||||
return engine.rpm_lut[len(engine.rpm_lut) - 1]
|
||||
}
|
||||
|
||||
return linalg.lerp(engine.rpm_lut[i], engine.rpm_lut[i + 1], t)
|
||||
}
|
||||
|
||||
hermite :: proc(y0, y1: f32, m0, m1: f32, x0, x1: f32, x: f32) -> f32 {
|
||||
h := x1 - x0
|
||||
t := (x - x0) / h
|
||||
|
||||
t2 := t * t
|
||||
t3 := t2 * t
|
||||
|
||||
h00 := 2 * t3 - 3 * t2 + 1
|
||||
h10 := t3 - 2 * t2 + t
|
||||
h01 := -2 * t3 + 3 * t2
|
||||
h11 := t3 - t2
|
||||
|
||||
return h00 * y0 + h10 * h * m0 + h01 * y1 + h11 * h * m1
|
||||
}
|
||||
|
||||
calc_slope :: proc(points: []RPMSample, i: int) -> f32 {
|
||||
if i == 0 {
|
||||
return(
|
||||
(points[1].torq - points[0].torq) /
|
||||
(points[1].rpm - points[0].rpm) \
|
||||
)
|
||||
}
|
||||
|
||||
if i == len(points) - 1 {
|
||||
return(
|
||||
(points[i].torq - points[i - 1].torq) /
|
||||
(points[i].rpm - points[i - 1].rpm) \
|
||||
)
|
||||
}
|
||||
|
||||
return(
|
||||
(points[i + 1].torq - points[i - 1].torq) /
|
||||
(points[i + 1].rpm - points[i - 1].rpm) \
|
||||
)
|
||||
}
|
||||
|
||||
torq_at_rpm :: proc(points: []RPMSample, rpm: f32) -> f32 {
|
||||
if rpm <= points[0].rpm {
|
||||
return points[0].torq
|
||||
}
|
||||
if rpm >= points[len(points) - 1].rpm {
|
||||
return points[len(points) - 1].torq
|
||||
}
|
||||
for i in 0 ..< len(points) - 1 {
|
||||
p0 := points[i]
|
||||
p1 := points[i + 1]
|
||||
|
||||
if rpm >= p0.rpm && rpm <= p1.rpm {
|
||||
m0 := calc_slope(points, i)
|
||||
m1 := calc_slope(points, i + 1)
|
||||
return hermite(p0.torq, p1.torq, m0, m1, p0.rpm, p1.rpm, rpm)
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
draw_gauges :: proc(car: ^Car) {
|
||||
base_pos := rl.Vector2{1050, 600}
|
||||
offset := rl.Vector2{40, 0}
|
||||
text_offset := rl.Vector2{10, 102}
|
||||
|
||||
current_pos := base_pos
|
||||
|
||||
gear_offset := rl.Vector2{0, 0}
|
||||
speed_offset := rl.Vector2{40, 0}
|
||||
throttle_offset := rl.Vector2{80, 0}
|
||||
rpm_offset := rl.Vector2{120, 0}
|
||||
torque_offset := rl.Vector2{160, 0}
|
||||
|
||||
draw_val(current_pos + text_offset, f32(car.drivetrain.gear))
|
||||
|
||||
current_pos += offset
|
||||
|
||||
draw_bar(current_pos, car.speed * 3.6 * 100 / 300, rl.GREEN)
|
||||
draw_val(current_pos + text_offset, car.speed * 3.6)
|
||||
|
||||
current_pos += offset
|
||||
|
||||
draw_bar(current_pos, car.engine_state.throttle * 100, rl.GREEN)
|
||||
draw_val(current_pos + text_offset, car.engine_state.throttle * 100)
|
||||
|
||||
current_pos += offset
|
||||
|
||||
draw_bar(current_pos, car.engine_state.rpm * 100 / 10000, rl.RED)
|
||||
draw_val(current_pos + text_offset, car.engine_state.rpm)
|
||||
|
||||
current_pos += offset
|
||||
|
||||
draw_bar(current_pos, car.engine_state.torque * 100 / 600, rl.BLUE)
|
||||
draw_val(current_pos + text_offset, car.engine_state.torque)
|
||||
}
|
||||
|
||||
draw_bar :: proc(pos: rl.Vector2, percentage: f32, color: rl.Color) {
|
||||
bar_size_x: i32 = 20
|
||||
bar_size_y: i32 = 100
|
||||
rl.DrawRectangle(i32(pos.x), i32(pos.y), bar_size_x, bar_size_y, rl.GRAY)
|
||||
|
||||
target_h := i32(percentage * f32(bar_size_y) / 100)
|
||||
|
||||
rl.DrawRectangle(
|
||||
i32(pos.x),
|
||||
i32(pos.y) + bar_size_y - target_h,
|
||||
bar_size_x,
|
||||
target_h,
|
||||
color,
|
||||
)
|
||||
|
||||
|
||||
rl.DrawRectangleLines(
|
||||
i32(pos.x),
|
||||
i32(pos.y),
|
||||
bar_size_x,
|
||||
bar_size_y,
|
||||
rl.BLACK,
|
||||
)
|
||||
}
|
||||
|
||||
draw_val :: proc(pos: rl.Vector2, val: f32) {
|
||||
str := fmt.ctprintf("%v", i32(val))
|
||||
str_len := rl.MeasureText(str, 15)
|
||||
rl.DrawText(str, i32(pos.x) - str_len / 2, i32(pos.y), 15, rl.WHITE)
|
||||
}
|
||||
|
||||
draw_stats :: proc(car: ^Car) {
|
||||
draw_graph(&car.engine_state)
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
package main
|
||||
|
||||
import math "core:math"
|
||||
import rl "vendor:raylib"
|
||||
|
||||
|
||||
Dyno_Point :: struct {
|
||||
rpm: f32,
|
||||
hp: f32,
|
||||
}
|
||||
|
||||
LUT_STEP :: 10
|
||||
MAX_RPM :: 8000
|
||||
LUT_SIZE :: (MAX_RPM / LUT_STEP) + 1
|
||||
|
||||
Engine :: struct {
|
||||
hp_lut: [LUT_SIZE]f32,
|
||||
}
|
||||
|
||||
EngineState :: struct {
|
||||
throttle: f32,
|
||||
rpm: f32,
|
||||
torque: f32,
|
||||
engine: Engine,
|
||||
}
|
||||
|
||||
draw_gauges :: proc(engine: ^EngineState) {
|
||||
base_pos := rl.Vector2{1150, 600}
|
||||
throttle_offset := rl.Vector2{}
|
||||
rpm_offset := rl.Vector2{40, 0}
|
||||
torque_offset := rl.Vector2{80, 0}
|
||||
draw_bar(base_pos + throttle_offset, engine.throttle, rl.GREEN)
|
||||
draw_bar(base_pos + rpm_offset, engine.rpm * 100 / 10000, rl.RED)
|
||||
draw_bar(base_pos + torque_offset, engine.torque * 100 / 10000, rl.BLUE)
|
||||
}
|
||||
|
||||
draw_bar :: proc(pos: rl.Vector2, percentage: f32, color: rl.Color) {
|
||||
bar_size_x: i32 = 20
|
||||
bar_size_y: i32 = 100
|
||||
rl.DrawRectangle(i32(pos.x), i32(pos.y), bar_size_x, bar_size_y, rl.GRAY)
|
||||
|
||||
target_h := i32(percentage * f32(bar_size_y) / 100)
|
||||
|
||||
rl.DrawRectangle(
|
||||
i32(pos.x),
|
||||
i32(pos.y) + bar_size_y - target_h,
|
||||
bar_size_x,
|
||||
target_h,
|
||||
color,
|
||||
)
|
||||
|
||||
|
||||
rl.DrawRectangleLines(
|
||||
i32(pos.x),
|
||||
i32(pos.y),
|
||||
bar_size_x,
|
||||
bar_size_y,
|
||||
rl.BLACK,
|
||||
)
|
||||
}
|
||||
|
||||
graph_data :: struct {
|
||||
min_val: f32,
|
||||
max_val: f32,
|
||||
steps: f32,
|
||||
}
|
||||
|
||||
graph_value :: distinct [2]f32
|
||||
|
||||
bake_engine_lut :: proc(points: []Dyno_Point) -> Engine {
|
||||
data: Engine
|
||||
|
||||
for step_idx in 0 ..< LUT_SIZE {
|
||||
current_rpm := f32(step_idx * LUT_STEP)
|
||||
|
||||
if current_rpm <= points[0].rpm {
|
||||
data.hp_lut[step_idx] = points[0].hp
|
||||
continue
|
||||
}
|
||||
if current_rpm >= points[len(points) - 1].rpm {
|
||||
data.hp_lut[step_idx] = points[len(points) - 1].hp
|
||||
continue
|
||||
}
|
||||
|
||||
for i in 0 ..< len(points) - 1 {
|
||||
p0 := points[i]
|
||||
p1 := points[i + 1]
|
||||
|
||||
if current_rpm >= p0.rpm && current_rpm <= p1.rpm {
|
||||
t := (current_rpm - p0.rpm) / (p1.rpm - p0.rpm)
|
||||
t_smooth := t * t * (3.0 - 2.0 * t)
|
||||
data.hp_lut[step_idx] = math.lerp(p0.hp, p1.hp, t_smooth)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
get_horsepower :: proc(engine: ^Engine, rpm: f32) -> f32 {
|
||||
clamped_rpm := math.clamp(rpm, 0, f32(MAX_RPM))
|
||||
|
||||
lut_index_f := clamped_rpm / f32(LUT_STEP)
|
||||
base_idx := int(lut_index_f)
|
||||
next_idx := min(base_idx + 1, LUT_SIZE - 1)
|
||||
|
||||
t := lut_index_f - f32(base_idx)
|
||||
|
||||
return math.lerp(engine.hp_lut[base_idx], engine.hp_lut[next_idx], t)
|
||||
}
|
||||
|
||||
|
||||
compute_graph_data :: proc(
|
||||
data: graph_data,
|
||||
engine: ^Engine,
|
||||
) -> [dynamic]graph_value {
|
||||
step := (data.max_val - data.min_val) / data.steps
|
||||
|
||||
values: [dynamic]graph_value
|
||||
|
||||
for i in 0 ..< data.steps {
|
||||
rpm := data.max_val + step * i
|
||||
append(&values, graph_value{rpm, get_horsepower(engine, rpm)})
|
||||
}
|
||||
|
||||
return values
|
||||
|
||||
}
|
||||
|
||||
draw_graph :: proc(pos: rl.Vector2, data: [dynamic]graph_value) {
|
||||
rl.DrawRectangle(i32(pos.x), i32(pos.y), 400, 200, rl.WHITE)
|
||||
|
||||
values := len(data)
|
||||
step := 380 / values
|
||||
|
||||
offset := rl.Vector2{20, 180}
|
||||
|
||||
for val in data {
|
||||
point_val := map_value(0, 600, 160, val[1])
|
||||
rl.DrawCircle(
|
||||
i32(pos.x + offset.x) + i32(step),
|
||||
i32(pos.y + offset.y) - i32(point_val),
|
||||
5,
|
||||
rl.BLUE,
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
map_value :: proc(min_val_in, max_val_in, max_val_out, val: f32) -> f32 {
|
||||
range_in := max_val_in - min_val_in
|
||||
rem := val - min_val_in
|
||||
scale := max_val_out / range_in
|
||||
return val * rem
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package main
|
||||
|
||||
import "core:math"
|
||||
import "core:math/rand"
|
||||
import rl "vendor:raylib"
|
||||
|
||||
SAMPLE_RATE :: 44100
|
||||
CYLINDERS :: 4
|
||||
GAIN :: 0.3
|
||||
|
||||
SUB_FRAMES :: 1024
|
||||
|
||||
EngineSound :: struct {
|
||||
stream: rl.AudioStream,
|
||||
phase: f32,
|
||||
exhaust_lp: f32,
|
||||
body_lp: f32,
|
||||
buf: [SUB_FRAMES]i16,
|
||||
}
|
||||
|
||||
engine_sound_init :: proc() -> EngineSound {
|
||||
es: EngineSound
|
||||
|
||||
rl.SetAudioStreamBufferSizeDefault(SUB_FRAMES)
|
||||
|
||||
es.stream = rl.LoadAudioStream(SAMPLE_RATE, 16, 1)
|
||||
rl.PlayAudioStream(es.stream)
|
||||
return es
|
||||
}
|
||||
|
||||
engine_sound_update :: proc(es: ^EngineSound, rpm: f32) {
|
||||
if !rl.IsAudioStreamProcessed(es.stream) do return
|
||||
f := rpm / 60.0 * (f32(CYLINDERS) / 2.0)
|
||||
phase_step := math.TAU * f / f32(SAMPLE_RATE)
|
||||
|
||||
for i in 0 ..< SUB_FRAMES {
|
||||
// Exhaust
|
||||
harm1 := math.sin_f32(es.phase) // cyllinder main
|
||||
harm2 := math.sin_f32(es.phase * 2.0) * 0.5 // exhaust
|
||||
harm_sub := math.sin_f32(es.phase * 0.5) * 0.3 // cyllinder pop
|
||||
|
||||
exhaust_raw := harm1 + harm2 + harm_sub
|
||||
exhaust_clipped := math.clamp(exhaust_raw * 1.4, -1.0, 1.0)
|
||||
|
||||
alpha_exhaust: f32 = 0.35
|
||||
es.exhaust_lp += alpha_exhaust * (exhaust_clipped - es.exhaust_lp)
|
||||
exhaust_sound := es.exhaust_lp * 0.6
|
||||
|
||||
// Body
|
||||
|
||||
mechanical_noise := (rand.float32() * 2.0 - 1.0) * 0.1
|
||||
body_raw := harm1 + mechanical_noise
|
||||
|
||||
alpha_body: f32 = 0.08
|
||||
es.body_lp += alpha_body * (body_raw - es.body_lp)
|
||||
body_sound := es.body_lp * 0.4
|
||||
|
||||
// Final
|
||||
final_sample := (exhaust_sound + body_sound) * GAIN
|
||||
final_clamped := math.clamp(final_sample, -1.0, 1.0)
|
||||
|
||||
es.buf[i] = i16(final_clamped * 32767.0)
|
||||
|
||||
es.phase += phase_step
|
||||
|
||||
if es.phase >= math.TAU {
|
||||
es.phase -= math.TAU
|
||||
}
|
||||
}
|
||||
|
||||
rl.UpdateAudioStream(es.stream, &es.buf[0], i32(SUB_FRAMES))
|
||||
}
|
||||
|
||||
engine_sound_unload :: proc(es: ^EngineSound) {
|
||||
rl.UnloadAudioStream(es.stream)
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package main
|
||||
|
||||
import rl "vendor:raylib"
|
||||
|
||||
import "core:fmt"
|
||||
|
||||
GameState :: struct {
|
||||
camera: rl.Camera3D,
|
||||
cam_offset: rl.Vector3,
|
||||
gfx_state: GfxState,
|
||||
car: Car,
|
||||
sound: EngineSound,
|
||||
}
|
||||
|
||||
|
||||
init_game_state :: proc() -> GameState {
|
||||
|
||||
state: GameState
|
||||
state.gfx_state = gfx_init()
|
||||
|
||||
state.cam_offset = rl.Vector3{0, 2, -6}
|
||||
|
||||
state.car = init_car()
|
||||
|
||||
state.sound = engine_sound_init()
|
||||
|
||||
state.camera = rl.Camera3D {
|
||||
position = state.car.pos + state.cam_offset,
|
||||
target = state.car.pos,
|
||||
up = {0, 1, 0},
|
||||
fovy = 45.0,
|
||||
projection = .PERSPECTIVE,
|
||||
}
|
||||
return state
|
||||
}
|
||||
|
||||
cleanup_game_state :: proc(state: ^GameState) {
|
||||
gfx_unload(&state.gfx_state)
|
||||
engine_sound_unload(&state.sound)
|
||||
}
|
||||
|
||||
update_game_state :: proc(state: ^GameState) {
|
||||
if rl.Vector3Length(state.car.vel) != 0 {
|
||||
state.camera.position =
|
||||
state.car.pos +
|
||||
rl.Vector3Normalize(state.car.vel) * -7 +
|
||||
rl.Vector3{0, 2, 0}
|
||||
// } else {
|
||||
// state.camera.position = state.car.pos + state.cam_offset
|
||||
}
|
||||
// state.camera.position = state.car.pos + state.cam_offset
|
||||
state.camera.target = state.car.pos + rl.Vector3{0, 1, 0}
|
||||
|
||||
update_car(&state.car)
|
||||
|
||||
engine_sound_update(&state.sound, state.car.engine_state.rpm)
|
||||
|
||||
cam_pos := state.camera.position
|
||||
gfx_update(&state.gfx_state, &cam_pos)
|
||||
}
|
||||
|
||||
draw_game_state :: proc(state: ^GameState) {
|
||||
rl.ClearBackground(rl.GetColor(0x181825FF)) // Dark background
|
||||
rl.BeginMode3D(state.camera)
|
||||
{
|
||||
draw_3d(state)
|
||||
}
|
||||
rl.EndMode3D()
|
||||
|
||||
draw_ui(state)
|
||||
|
||||
|
||||
}
|
||||
|
||||
draw_3d :: proc(state: ^GameState) {
|
||||
rl.DrawGrid(200, 2.0)
|
||||
gfx_draw_car(&state.gfx_state, state.car.pos, state.car.wheel_yaw)
|
||||
}
|
||||
|
||||
draw_ui :: proc(state: ^GameState) {
|
||||
draw_gauges(&state.car)
|
||||
draw_stats(&state.car)
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package main
|
||||
|
||||
import "core:math"
|
||||
import rl "vendor:raylib"
|
||||
|
||||
GfxState :: struct {
|
||||
shader: rl.Shader,
|
||||
shader_loc: i32,
|
||||
car: rl.Model,
|
||||
}
|
||||
|
||||
gfx_init :: proc() -> GfxState {
|
||||
|
||||
state: GfxState
|
||||
|
||||
state.car = rl.LoadModel("./assets/3rdparty/car.glb")
|
||||
|
||||
state.shader = rl.LoadShader(
|
||||
"src/shader/nfs_car.vs",
|
||||
"src/shader/nfs_car.fs",
|
||||
)
|
||||
|
||||
light_dir_loc := rl.GetShaderLocation(state.shader, "lightDir")
|
||||
light_col_loc := rl.GetShaderLocation(state.shader, "lightColor")
|
||||
ambient_col_loc := rl.GetShaderLocation(state.shader, "ambientColor")
|
||||
state.shader_loc = rl.GetShaderLocation(state.shader, "viewPos")
|
||||
|
||||
// Set Static Lighting Values (Night/Arcade Theme)
|
||||
light_dir := rl.Vector3{-0.5, -1.0, -0.5} // Angled overhead light
|
||||
light_color := rl.Vector3{1.2, 1.1, 0.9} // Slightly warm sunlight/streetlight
|
||||
ambient_col := rl.Vector3{0.15, 0.15, 0.25} // Cool dark blue ambient for shadows
|
||||
|
||||
rl.SetShaderValue(state.shader, light_dir_loc, &light_dir, .VEC3)
|
||||
rl.SetShaderValue(state.shader, light_col_loc, &light_color, .VEC3)
|
||||
rl.SetShaderValue(state.shader, ambient_col_loc, &ambient_col, .VEC3)
|
||||
|
||||
// Assign shader to model materials
|
||||
for i in 0 ..< state.car.materialCount {
|
||||
state.car.materials[i].shader = state.shader
|
||||
}
|
||||
|
||||
return state
|
||||
}
|
||||
|
||||
gfx_unload :: proc(state: ^GfxState) {
|
||||
rl.UnloadModel(state.car)
|
||||
rl.UnloadShader(state.shader)
|
||||
}
|
||||
|
||||
gfx_draw_car :: proc(state: ^GfxState, pos: rl.Vector3, yaw: f32) {
|
||||
angle_degrees := yaw * (180 / math.PI)
|
||||
rotation_axis := rl.Vector3{0, 1, 0}
|
||||
rl.DrawModelEx(state.car, pos, rotation_axis, angle_degrees, 1.0, rl.WHITE)
|
||||
}
|
||||
|
||||
gfx_update :: proc(state: ^GfxState, cam_pos: ^rl.Vector3) {
|
||||
rl.SetShaderValue(state.shader, state.shader_loc, cam_pos, .VEC3)
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package main
|
||||
|
||||
import "core:fmt"
|
||||
import math "core:math"
|
||||
import rl "vendor:raylib"
|
||||
|
||||
main :: proc() {
|
||||
rl.InitWindow(1280, 720, "Odin + Raylib - NFS Style Lighting")
|
||||
defer rl.CloseWindow()
|
||||
|
||||
rl.SetTargetFPS(140)
|
||||
rl.InitAudioDevice()
|
||||
defer rl.CloseAudioDevice()
|
||||
|
||||
state := init_game_state()
|
||||
defer cleanup_game_state(&state)
|
||||
|
||||
|
||||
for !rl.WindowShouldClose() {
|
||||
|
||||
update_game_state(&state)
|
||||
|
||||
rl.BeginDrawing()
|
||||
{
|
||||
draw_game_state(&state)
|
||||
}
|
||||
rl.EndDrawing()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
#version 330
|
||||
|
||||
in vec2 fragTexCoord;
|
||||
in vec3 fragNormal;
|
||||
in vec3 fragFragPos;
|
||||
in vec4 fragColor;
|
||||
|
||||
out vec4 finalColor;
|
||||
|
||||
uniform sampler2D texture0;
|
||||
uniform vec4 colDiffuse;
|
||||
|
||||
// Custom Uniforms for PS2-era arcade look
|
||||
uniform vec3 lightDir; // Direction light comes from (e.g. overhead sun)
|
||||
uniform vec3 lightColor; // Color of sun/streetlights (e.g. warm white/yellow)
|
||||
uniform vec3 ambientColor; // Shadow tint (e.g. dark blue/purple night sky)
|
||||
uniform vec3 viewPos; // Camera position for specular shine
|
||||
|
||||
void main() {
|
||||
// 1. Sample base texture and material tint
|
||||
vec4 texColor = texture(texture0, fragTexCoord) * colDiffuse * fragColor;
|
||||
|
||||
vec3 N = normalize(fragNormal);
|
||||
vec3 L = normalize(-lightDir);
|
||||
vec3 V = normalize(viewPos - fragFragPos);
|
||||
|
||||
// 2. Directional Diffuse (Lambert)
|
||||
float diff = max(dot(N, L), 0.0);
|
||||
vec3 diffuse = diff * lightColor;
|
||||
|
||||
// 3. Early-2000s Specular Highlight (Blinn-Phong)
|
||||
vec3 H = normalize(L + V);
|
||||
float spec = pow(max(dot(N, H), 0.0), 32.0); // 32.0 = medium glossiness
|
||||
vec3 specular = lightColor * spec * 0.6; // 0.6 = gloss strength
|
||||
|
||||
// 4. Combine Ambient + Diffuse + Specular
|
||||
vec3 lighting = ambientColor + diffuse + specular;
|
||||
|
||||
finalColor = vec4(texColor.rgb * lighting, texColor.a);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
#version 330
|
||||
|
||||
in vec3 vertexPosition;
|
||||
in vec2 vertexTexCoord;
|
||||
in vec3 vertexNormal;
|
||||
in vec4 vertexColor;
|
||||
|
||||
out vec2 fragTexCoord;
|
||||
out vec3 fragNormal;
|
||||
out vec3 fragFragPos;
|
||||
out vec4 fragColor;
|
||||
|
||||
uniform mat4 mvp;
|
||||
uniform mat4 matModel;
|
||||
uniform mat4 matNormal;
|
||||
|
||||
void main() {
|
||||
fragTexCoord = vertexTexCoord;
|
||||
fragColor = vertexColor;
|
||||
|
||||
// Transform normal and position to world space for lighting
|
||||
fragNormal = normalize(vec3(matNormal * vec4(vertexNormal, 0.0)));
|
||||
fragFragPos = vec3(matModel * vec4(vertexPosition, 1.0));
|
||||
|
||||
gl_Position = mvp * vec4(vertexPosition, 1.0);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
#version 330
|
||||
|
||||
in vec2 fragTexCoord;
|
||||
in vec4 fragColor;
|
||||
|
||||
out vec4 finalColor;
|
||||
|
||||
uniform sampler2D texture0;
|
||||
uniform vec4 colDiffuse;
|
||||
|
||||
void main() {
|
||||
vec4 texelColor = texture(texture0, fragTexCoord);
|
||||
finalColor = texelColor * colDiffuse * fragColor;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
#version 330
|
||||
|
||||
in vec3 vertexPosition;
|
||||
in vec2 vertexTexCoord;
|
||||
in vec4 vertexColor;
|
||||
|
||||
out vec2 fragTexCoord;
|
||||
out vec4 fragColor;
|
||||
|
||||
uniform mat4 mvp;
|
||||
|
||||
void main() {
|
||||
fragTexCoord = vertexTexCoord;
|
||||
fragColor = vertexColor;
|
||||
gl_Position = mvp * vec4(vertexPosition, 1.0);
|
||||
}
|
||||
Reference in New Issue
Block a user