Pivot to game jam game about an ice cream truck lol

This commit is contained in:
sergeypdev 2025-06-28 17:56:21 +04:00
parent 8378b943cb
commit 1094afac02
19 changed files with 3148283 additions and 25 deletions

24
assets/cone_convex.obj Normal file
View File

@ -0,0 +1,24 @@
# Blender 4.4.3
# www.blender.org
o Cone_Col
v 0.033251 2.766126 0.099102
v 0.592864 3.230694 0.103337
v 0.500712 3.913186 -0.130586
v -0.151054 4.131110 -0.368743
v -0.710666 3.666542 -0.372978
v -0.618514 2.984050 -0.139055
v 0.428131 2.878846 -1.989048
v -0.527696 4.062689 1.583188
s 0
f 1 7 2
f 2 7 3
f 3 7 4
f 4 7 5
f 5 7 6
f 6 7 1
f 8 3 4
f 8 2 3
f 8 1 2
f 8 6 1
f 8 5 6
f 8 4 5

BIN
assets/ice_cream_truck.glb (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -0,0 +1,21 @@
# Blender 4.4.3
# www.blender.org
o Body_Col
v 1.049841 0.369559 2.380744
v 1.049841 1.130518 2.380744
v 1.049841 2.734402 1.356388
v 1.049841 0.356804 -3.483506
v 1.049841 2.721647 -3.470500
v -1.049841 0.369559 2.380744
v -1.049841 1.130518 2.380744
v -1.049841 2.734402 1.356388
v -1.049841 0.356804 -3.483506
v -1.049841 2.721647 -3.470500
s 0
f 1 6 9 4
f 3 2 1 4 5
f 6 1 2 7
f 4 9 10 5
f 5 10 8 3
f 8 10 9 6 7
f 8 7 2 3

BIN
assets/ice_cream_truck_wheel.glb (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -0,0 +1,24 @@
# Blender 4.4.3
# www.blender.org
o Left_Fence
v 0.827589 2.728590 -0.605606
v 0.827589 3.290456 -0.605606
v 0.827589 2.728590 -2.598043
v 0.827589 3.290456 -2.598043
v 0.925107 2.728590 -0.605606
v 0.925107 3.290456 -0.605606
v 0.925107 2.728590 -2.598043
v 0.925107 3.290456 -2.598043
v 0.827589 3.432860 -0.744844
v 0.827589 3.432860 -2.458806
v 0.925107 3.432860 -0.744844
v 0.925107 3.432860 -2.458806
s 0
f 8 12 11 6 5 7
f 3 4 8 7
f 5 6 2 1
f 3 7 5 1
f 12 10 9 11
f 2 6 11 9
f 8 4 10 12
f 2 9 10 4 3 1

View File

@ -0,0 +1,24 @@
# Blender 4.4.3
# www.blender.org
o Right_Fence
v -0.925107 2.728590 -0.605606
v -0.925107 3.290456 -0.605606
v -0.925107 2.728590 -2.598043
v -0.925107 3.290456 -2.598043
v -0.827589 2.728590 -0.605606
v -0.827589 3.290456 -0.605606
v -0.827589 2.728590 -2.598043
v -0.827589 3.290456 -2.598043
v -0.925107 3.432860 -0.744844
v -0.925107 3.432860 -2.458806
v -0.827589 3.432860 -0.744844
v -0.827589 3.432860 -2.458806
s 0
f 8 12 11 6 5 7
f 3 4 8 7
f 5 6 2 1
f 3 7 5 1
f 12 10 9 11
f 2 6 11 9
f 8 4 10 12
f 2 9 10 4 3 1

View File

@ -0,0 +1,38 @@
#version 330
// Input vertex attributes (from vertex shader)
in vec2 fragTexCoord;
in vec4 fragColor;
// in vec3 worldPosition;
in vec3 worldNormal;
// Input uniform values
uniform sampler2D texture0;
uniform vec4 colDiffuse;
// Output fragment color
out vec4 finalColor;
uniform vec3 ambient;
uniform vec3 lightDir;
uniform vec3 lightColor;
// NOTE: Add your custom variables here
void main()
{
// Texel color fetching from texture sampler
vec4 texelColor = vec4(1); // texture(texture0, fragTexCoord);
// final color is the color from the texture
// times the tint color (colDiffuse)
// times the fragment color (interpolated vertex color)
float NDotL = max(dot(lightDir, -worldNormal), 0);
// float toon = 0.5 * smoothstep(0.5, 0.51, NDotL) + 0.5;
vec3 light = mix(vec3(ambient), lightColor, NDotL);
finalColor = texelColor * colDiffuse * vec4(light, 1);
}

View File

@ -0,0 +1,45 @@
#version 330
// Input vertex attributes
in vec3 vertexPosition;
in vec2 vertexTexCoord;
in vec3 vertexNormal;
in vec4 vertexColor;
// Input uniform values
uniform mat4 mvp;
uniform mat4 matModel;
uniform sampler2D texture0;
// Output vertex attributes (to fragment shader)
out vec2 fragTexCoord;
out vec4 fragColor;
// NOTE: Add your custom variables here
out vec3 worldPosition;
out vec3 worldNormal;
void main()
{
// Send vertex attributes to fragment shader
fragTexCoord = vertexTexCoord;
fragColor = vertexColor;
float l = textureOffset(texture0, vertexTexCoord, ivec2(-1, 0)).x;
float r = textureOffset(texture0, vertexTexCoord, ivec2(1, 0)).x;
float u = textureOffset(texture0, vertexTexCoord, ivec2(0, -1)).x;
float d = textureOffset(texture0, vertexTexCoord, ivec2(0, 1)).x;
vec3 lr = normalize(vec3(1, 0, r - l));
vec3 ud = normalize(vec3(0, 1, u - d));
vec3 normal = normalize(cross(lr, ud));
vec3 localPos = vertexPosition + vec3(0, texture2D(texture0, vertexTexCoord).x * 0.1, 0);
// Calculate final vertex position
gl_Position = mvp*vec4(localPos, 1.0);
worldPosition = (matModel * vec4(localPos, 1.0)).xyz;
worldNormal = mat3(matModel) * normal;
}

3147780
assets/subdiv_plane.obj Normal file

File diff suppressed because it is too large Load Diff

View File

@ -238,7 +238,7 @@ main :: proc() {
opts := Options {
tracy = true,
debug = true,
optimize = false,
optimize = true,
run = true,
}
flags.parse_or_exit(&opts, os.args, .Unix, context.temp_allocator)

View File

@ -79,6 +79,7 @@ Shader_Location :: enum {
Ambient,
LightDir,
LightColor,
Heightmap,
}
Shader_Location_Set :: bit_set[Shader_Location]
Shader_Location_Array :: [Shader_Location]i32
@ -87,6 +88,7 @@ SHADER_LOCATION_NAMES := [Shader_Location]cstring {
.Ambient = "ambient",
.LightDir = "lightDir",
.LightColor = "lightColor",
.Heightmap = "heightmap",
}
Loaded_Shader :: struct {

247
game/fluid.odin Normal file
View File

@ -0,0 +1,247 @@
package game
import "core:log"
import "core:math"
import lg "core:math/linalg"
import "game:assets"
import rl "libs:raylib"
// Some fluid simulation tests
Vec2i32 :: [2]i32
Fluid :: struct {
size: Vec2i32,
friction: f32,
gravity: f32,
terrain: []f32,
heights: []f32,
flow_x: []f32,
flow_y: []f32,
}
fluid_init :: proc(fluid: ^Fluid, size: Vec2i32, friction: f32, gravity: f32) {
fluid.size = size
fluid.friction = friction
fluid.gravity = gravity
fluid.terrain = make([]f32, size.x * size.y)
fluid.heights = make([]f32, size.x * size.y)
fluid.flow_x = make([]f32, (size.x + 1) * size.y)
fluid.flow_y = make([]f32, (size.y + 1) * size.x)
}
fluid_destroy :: proc(fluid: ^Fluid) {
delete(fluid.terrain)
delete(fluid.heights)
delete(fluid.flow_x)
delete(fluid.flow_y)
}
fluid_terrain :: #force_inline proc "contextless" (fluid: ^Fluid, x: i32, y: i32) -> ^f32 {
idx := fluid.size.x * y + x
return &fluid.terrain[idx]
}
fluid_height :: #force_inline proc "contextless" (fluid: ^Fluid, x: i32, y: i32) -> ^f32 {
idx := fluid.size.x * y + x
return &fluid.heights[idx]
}
fluid_flow_x :: #force_inline proc "contextless" (fluid: ^Fluid, x: i32, y: i32) -> ^f32 {
idx := (fluid.size.x + 1) * y + x
return &fluid.flow_x[idx]
}
fluid_flow_y :: #force_inline proc "contextless" (fluid: ^Fluid, x: i32, y: i32) -> ^f32 {
idx := fluid.size.x * y + x
return &fluid.flow_y[idx]
}
fluid_simulate :: proc "contextless" (fluid: ^Fluid, dt: f32) {
dt := dt
dt = 0.2
cell_size := f32(1)
dx := cell_size
dy := cell_size
area := f32(1)
mult := 10 * dt * area / cell_size
friction := math.pow(1.0 - 0.00, dt)
// Flow acceleration
{
for y in 0 ..< fluid.size.y {
for x in 1 ..< fluid.size.x {
fluid_flow_x(fluid, x, y)^ =
fluid_flow_x(fluid, x, y)^ * friction +
(fluid_height(fluid, x - 1, y)^ +
fluid_terrain(fluid, x - 1, y)^ -
fluid_height(fluid, x, y)^ -
fluid_terrain(fluid, x, y)^) *
mult
}
}
for y in 1 ..< fluid.size.y {
for x in 0 ..< fluid.size.x {
fluid_flow_y(fluid, x, y)^ =
fluid_flow_y(fluid, x, y)^ * friction +
(fluid_height(fluid, x, y - 1)^ +
fluid_terrain(fluid, x, y - 1)^ -
fluid_height(fluid, x, y)^ -
fluid_terrain(fluid, x, y)^) *
mult
}
}
}
// Flow Scaling to prevent water height going below 0
{
for y in 0 ..< fluid.size.y {
for x in 0 ..< fluid.size.x {
total_outflow := f32(0)
total_outflow -= fluid_flow_x(fluid, x, y)^
total_outflow -= fluid_flow_y(fluid, x, y)^
total_outflow += fluid_flow_x(fluid, x + 1, y)^
total_outflow += fluid_flow_y(fluid, x, y + 1)^
max_outflow := fluid_height(fluid, x, y)^ * dx * dy / dt
EPS :: f32(0.0001)
if total_outflow > 0 {
scale := min(1, max_outflow / total_outflow)
if fluid_flow_x(fluid, x, y)^ < 0 {
fluid_flow_x(fluid, x, y)^ *= scale
}
if fluid_flow_y(fluid, x, y)^ < 0 {
fluid_flow_y(fluid, x, y)^ *= scale
}
if fluid_flow_x(fluid, x + 1, y)^ > 0 {
fluid_flow_x(fluid, x + 1, y)^ *= scale
}
if fluid_flow_y(fluid, x, y + 1)^ > 0 {
fluid_flow_y(fluid, x, y + 1)^ *= scale
}
}
}
}
}
// Height update
{
factor := dt / dx / dy
for y in 0 ..< fluid.size.y {
for x in 0 ..< fluid.size.x {
fluid_height(fluid, x, y)^ +=
(fluid_flow_x(fluid, x, y)^ +
fluid_flow_y(fluid, x, y)^ -
fluid_flow_x(fluid, x + 1, y)^ -
fluid_flow_y(fluid, x, y + 1)^) *
factor
}
}
}
}
Fluid_Scene :: struct {
fluid: Fluid,
texture: rl.Texture,
}
fluid_scene_init :: proc(scene: ^Fluid_Scene) {
fluid_init(&scene.fluid, {256, 256}, 0.1, 9.8)
}
fluid_scene_destroy :: proc(scene: ^Fluid_Scene) {
fluid_destroy(&scene.fluid)
rl.UnloadTexture(scene.texture)
}
fluid_scene_update :: proc(scene: ^Fluid_Scene, dt: f32) {
if rl.IsMouseButtonDown(rl.MouseButton.LEFT) || rl.IsMouseButtonDown(rl.MouseButton.RIGHT) {
sim_size := scene.fluid.size
mouse_pos := rl.GetMousePosition()
center := Vec2i32{i32(mouse_pos.x), i32(mouse_pos.y)}
for y in max(center.y - 100, 0) ..< min(center.y + 100, sim_size.y - 1) {
for x in max(center.x - 100, 0) ..< max(center.x + 100, sim_size.x - 1) {
offset_from_center := Vec2i32{x, y} - center
if lg.length2([2]f32{f32(offset_from_center.x), f32(offset_from_center.y)}) <
(100 * 100) {
if rl.IsMouseButtonDown(.LEFT) {
fluid_height(&scene.fluid, x, y)^ = 20
}
// if rl.IsMouseButtonDown(.RIGHT) {
// fluid_terrain(&scene.fluid, x, y)^ += 10
// }
}
}
}
}
fluid_simulate(&scene.fluid, dt)
}
fluid_scene_draw_3d :: proc(assetman: ^assets.Asset_Manager, scene: ^Fluid_Scene) {
sim_size := scene.fluid.size
image: rl.Image
image.format = rl.PixelFormat.UNCOMPRESSED_R32
image.width = sim_size.x
image.height = sim_size.y
image.mipmaps = 1
image_data := make([]f32, sim_size.x * sim_size.y, context.temp_allocator)
image.data = rawptr(&image_data[0])
for y in 0 ..< sim_size.y {
for x in 0 ..< sim_size.x {
image_data[y * sim_size.x + x] = fluid_height(&scene.fluid, x, y)^ * 0.01
}
}
{
if !rl.IsTextureValid(scene.texture) ||
scene.texture.width != sim_size.x ||
scene.texture.height != sim_size.y {
rl.UnloadTexture(scene.texture)
scene.texture = rl.LoadTextureFromImage(image)
} else {
rl.UpdateTexture(scene.texture, image.data)
}
}
if !rl.IsTextureValid(scene.texture) || !rl.IsTextureReady(scene.texture) {
log.warnf("texture is not valid")
}
water_shader := assets.get_shader(
assetman,
"assets/shaders/water_vs.glsl",
"assets/shaders/water_ps.glsl",
assets.Shader_Location_Set{.LightDir, .LightColor, .Ambient},
)
light_dir := lg.normalize(rl.Vector3{1, -1, 0})
ambient := rl.Vector3{0.1, 0.1, 0.1}
light_color := rl.Vector3{0.816, 0.855, 0.89}
rl.SetShaderValue(water_shader.shader, water_shader.locations[.LightDir], &light_dir, .VEC3)
rl.SetShaderValue(water_shader.shader, water_shader.locations[.Ambient], &ambient, .VEC3)
rl.SetShaderValue(
water_shader.shader,
water_shader.locations[.LightColor],
&light_color,
.VEC3,
)
model := assets.get_model(assetman, "assets/subdiv_plane.obj")
model.materials[0].shader = water_shader.shader
model.materials[0].maps[rl.MaterialMapIndex.ALBEDO].texture = scene.texture
rl.DrawModel(model, {0, 1, 0}, 1, rl.WHITE)
}
fluid_scene_draw_2d :: proc(assetman: ^assets.Asset_Manager, scene: ^Fluid_Scene) {
rl.DrawTexture(scene.texture, 0, 0, rl.WHITE)
}

View File

@ -126,6 +126,7 @@ Game_Memory :: struct {
name_container: name.Container,
assetman: assets.Asset_Manager,
runtime_world: Runtime_World,
fluid_scene: Fluid_Scene,
es: Editor_State,
ui_context: ui.Context,
default_font: rl.Font,
@ -362,7 +363,7 @@ World_Update_Config :: struct {
update_world :: proc(world: ^World, dt: f32, config: World_Update_Config) {
if !world.pause {
car_model := assets.get_model(&g_mem.assetman, "assets/toyota_corolla_ae86_trueno.glb")
car_model := assets.get_model(&g_mem.assetman, "assets/ice_cream_truck.glb")
car_bounds := rl.GetModelBoundingBox(car_model)
world.car_com = (car_bounds.min + car_bounds.max) / 2
@ -400,7 +401,10 @@ update_world :: proc(world: ^World, dt: f32, config: World_Update_Config) {
)
}
car_convex := assets.get_convex(&g_mem.assetman, "assets/car_convex.obj")
car_convex := assets.get_convex(&g_mem.assetman, "assets/ice_cream_truck_convex.obj")
cone_convex := assets.get_convex(&g_mem.assetman, "assets/cone_convex.obj")
left_fence_convex := assets.get_convex(&g_mem.assetman, "assets/left_fence_convex.obj")
right_fence_convex := assets.get_convex(&g_mem.assetman, "assets/right_fence_convex.obj")
world.car_handle = physics.immediate_body(
&world.physics_scene,
@ -426,13 +430,38 @@ update_world :: proc(world: ^World, dt: f32, config: World_Update_Config) {
},
},
{
rel_x = {0, 1, 0},
rel_q = linalg.QUATERNIONF32_IDENTITY,
density_minus_one = 0.1 - 1,
inner_shape = physics.Shape_Box{size = 1},
density_minus_one = 0.2 - 1,
inner_shape = physics.Shape_Convex {
mesh = cone_convex.mesh,
center_of_mass = cone_convex.center_of_mass,
inertia_tensor = auto_cast cone_convex.inertia_tensor,
total_volume = cone_convex.total_volume,
},
},
{
rel_q = linalg.QUATERNIONF32_IDENTITY,
density_minus_one = 0.2 - 1,
inner_shape = physics.Shape_Convex {
mesh = left_fence_convex.mesh,
center_of_mass = left_fence_convex.center_of_mass,
inertia_tensor = auto_cast left_fence_convex.inertia_tensor,
total_volume = left_fence_convex.total_volume,
},
},
{
rel_q = linalg.QUATERNIONF32_IDENTITY,
density_minus_one = 0.2 - 1,
inner_shape = physics.Shape_Convex {
mesh = right_fence_convex.mesh,
center_of_mass = right_fence_convex.center_of_mass,
inertia_tensor = auto_cast right_fence_convex.inertia_tensor,
total_volume = right_fence_convex.total_volume,
},
},
},
mass = 1000,
com_shift = physics.Vec3{0, 0, -0.5},
},
)
@ -480,15 +509,15 @@ update_world :: proc(world: ^World, dt: f32, config: World_Update_Config) {
sim_state := physics.get_sim_state(&world.physics_scene)
wheel_extent_x_front := f32(1.355) / 2
wheel_extent_x_back := f32(1.345) / 2
wheel_y := f32(-0.4)
rest := f32(0.2)
natural_frequency := f32(1.2)
damping := f32(0.15)
radius := f32(0.2888)
wheel_front_z := f32(1.35)
wheel_back_z := f32(-1.05)
wheel_extent_x_front := f32(2) / 2
wheel_extent_x_back := f32(2) / 2
wheel_y := f32(0.5)
rest := f32(0.7)
natural_frequency := f32(0.4)
damping := f32(0.06)
radius := f32(0.737649) / 2
wheel_front_z := f32(1.6)
wheel_back_z := f32(-1.63)
wheel_mass := f32(14)
pacejka_long := physics.slice_to_pacejka94_long(
@ -629,7 +658,7 @@ update_world :: proc(world: ^World, dt: f32, config: World_Update_Config) {
}
car_body := physics.get_body(sim_state, world.car_handle)
turn_vel_correction := clamp(10.0 / linalg.length(car_body.v), 0, 1)
turn_vel_correction := clamp(4.0 / linalg.length(car_body.v), 0, 1)
turn_input := rl.GetGamepadAxisMovement(0, .LEFT_X)
if abs(turn_input) < GAMEPAD_DEADZONE {
@ -987,7 +1016,7 @@ draw_world :: proc(world: ^World) {
sim_state := physics.get_sim_state(&world.physics_scene)
car_body := physics.get_body(sim_state, world.car_handle)
car_model := assets.get_model(&g_mem.assetman, "assets/toyota_corolla_ae86_trueno.glb")
car_model := assets.get_model(&g_mem.assetman, "assets/ice_cream_truck.glb")
engine := physics.get_engine(sim_state, world.engine_handle)
{
@ -1327,6 +1356,8 @@ game_init :: proc() {
init_physifs_raylib_callbacks()
assets.assetman_init(&g_mem.assetman)
fluid_scene_init(&g_mem.fluid_scene)
editor_state_init(&g_mem.es, 100)
runtime_world_init(&g_mem.runtime_world, DEV_BUILD ? 100 : 2)
@ -1348,6 +1379,7 @@ game_shutdown :: proc() {
editor_state_destroy(&g_mem.es)
delete(g_mem.es.point_selection)
runtime_world_destroy(&g_mem.runtime_world)
fluid_scene_destroy(&g_mem.fluid_scene)
free(g_mem)
}
@ -1377,7 +1409,7 @@ game_hot_reloaded :: proc(mem: rawptr) {
render.init(&g_mem.assetman)
ui.rl_init()
g_mem.runtime_world.orbit_camera.distance = 4
g_mem.runtime_world.orbit_camera.distance = 6
log.debugf("hot reloaded")
}

View File

@ -121,7 +121,11 @@ draw_debug_scene :: proc(scene: ^Scene) {
pos := body.x
rot := body.q
pos += lg.quaternion_mul_vector3(rot, wheel.rel_pos)
pos += lg.quaternion_mul_vector3(rot, wheel.rel_pos + body.shape_offset)
wheel_world_pos := body_local_to_world(body, wheel.rel_pos + body.shape_offset)
dir := body_local_to_world_vec(body, wheel.rel_dir)
rl.DrawLine3D(wheel_world_pos, wheel_world_pos + dir, rl.BLUE)
rel_wheel_pos := wheel_get_rel_wheel_pos(body, wheel)
wheel_pos := body_local_to_world(body, rel_wheel_pos)

View File

@ -127,7 +127,7 @@ wheel_get_rel_wheel_pos :: #force_inline proc(
wheel: Suspension_Constraint_Ptr,
) -> Vec3 {
t := wheel.hit_t > 0 ? wheel.hit_t : wheel.rest
return wheel.rel_pos + wheel.rel_dir * (t - wheel.radius)
return wheel.rel_pos + body.shape_offset + wheel.rel_dir * (t - wheel.radius)
}
wheel_get_right_vec :: #force_inline proc(

View File

@ -529,6 +529,8 @@ Body_Config :: struct {
inertia_mode: Body_Config_Inertia_Mode,
// Unit inertia tensor
inertia_tensor: Matrix3,
// Allows shifting the autocalculated center of mass
com_shift: Vec3,
}
// TODO: rename to wheel
@ -664,7 +666,7 @@ initialize_body_from_config :: proc(sim_state: ^Sim_State, body: ^Body, config:
body.w = config.initial_ang_vel
body.shape = add_shape(sim_state, config.shapes)
body.shape_offset = -combined_center_of_mass(config.shapes)
body.shape_offset = -combined_center_of_mass(config.shapes) + config.com_shift
body.shape_aabb = input_shape_get_aabb(config.shapes)
body.name = config.name
@ -675,7 +677,7 @@ update_body_from_config :: proc(sim_state: ^Sim_State, body: Body_Ptr, config: B
// Inefficient, but eh
remove_shape(sim_state, body.shape)
body.shape = add_shape(sim_state, config.shapes)
body.shape_offset = -combined_center_of_mass(config.shapes)
body.shape_offset = -combined_center_of_mass(config.shapes) + config.com_shift
body.shape_aabb = input_shape_get_aabb(config.shapes)
body.name = config.name

View File

@ -1158,7 +1158,7 @@ pgs_solve_suspension :: proc(
body := get_body(sim_state, v.body)
if body.alive {
wheel_world_pos := body_local_to_world(body, v.rel_pos)
wheel_world_pos := body_local_to_world(body, v.rel_pos + body.shape_offset)
dir := body_local_to_world_vec(body, v.rel_dir)
v.hit_t, v.hit_normal, v.hit = raycast(
sim_state,
@ -1442,8 +1442,11 @@ pgs_substep :: proc(
if s.hit {
body := get_body(sim_state, s.body)
p := body_local_to_world(body, s.rel_pos)
hit_p := body_local_to_world(body, s.rel_pos + s.rel_dir * s.hit_t)
p := body_local_to_world(body, s.rel_pos + body.shape_offset)
hit_p := body_local_to_world(
body,
s.rel_pos + body.shape_offset + s.rel_dir * s.hit_t,
)
forward := wheel_get_forward_vec(body, s)
right := wheel_get_right_vec(body, s)

BIN
src_assets/ice_cream_truck.blend (Stored with Git LFS) Normal file

Binary file not shown.

BIN
src_assets/ice_cream_truck.blend1 (Stored with Git LFS) Normal file

Binary file not shown.