Initial project import
This commit is contained in:
commit
23f2d29e32
11957 files changed
+1754577
No files matched your search
@@ -0,0 +1,278 @@
|
||||
// Demonstrates how raycasting works. The left side shows
|
||||
// the 2D layout of the walls and player. The green lines
|
||||
// represent the field of view of the player.
|
||||
//
|
||||
// The right side is a simple 3D projection of the field
|
||||
// of view.
|
||||
//
|
||||
// There is no collision detection so yes, you can walk
|
||||
// through walls.
|
||||
//
|
||||
// Watch https://www.youtube.com/watch?v=gYRrGTC7GtA to
|
||||
// learn more on how this code works. There are some silly
|
||||
// digressons in the video but the tech content is spot on.
|
||||
import gg
|
||||
import math
|
||||
|
||||
const player_size = 8
|
||||
const map_x_size = 8
|
||||
const map_y_size = 8
|
||||
const map_square = 64
|
||||
|
||||
struct App {
|
||||
mut:
|
||||
ctx &gg.Context = unsafe { nil }
|
||||
player_x f32
|
||||
player_y f32
|
||||
player_dx f32
|
||||
player_dy f32
|
||||
player_angle f32
|
||||
map []int
|
||||
}
|
||||
|
||||
fn main() {
|
||||
mut app := App{
|
||||
player_x: 230
|
||||
player_y: 320
|
||||
// each number represents an 8x8 square
|
||||
// 1 is a wall cube, 0 is empty space
|
||||
map: [
|
||||
// vfmt off
|
||||
1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 0, 0, 0, 0, 0, 0, 1,
|
||||
1, 0, 1, 1, 0, 0, 0, 1,
|
||||
1, 0, 1, 0, 0, 0, 0, 1,
|
||||
1, 0, 0, 0, 0, 0, 0, 1,
|
||||
1, 0, 0, 0, 0, 1, 0, 1,
|
||||
1, 0, 0, 0, 0, 0, 0, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1,
|
||||
// vfmt on
|
||||
]
|
||||
}
|
||||
|
||||
calc_deltas(mut app)
|
||||
|
||||
app.ctx = gg.new_context(
|
||||
user_data: &app
|
||||
window_title: 'Raycaster Demo'
|
||||
width: 1024
|
||||
height: 512
|
||||
bg_color: gg.gray
|
||||
frame_fn: draw
|
||||
event_fn: handle_events
|
||||
)
|
||||
|
||||
app.ctx.run()
|
||||
}
|
||||
|
||||
fn draw(mut app App) {
|
||||
app.ctx.begin()
|
||||
draw_map_2d(app)
|
||||
draw_player(app)
|
||||
draw_rays_and_walls(app)
|
||||
draw_instructions(app)
|
||||
app.ctx.end()
|
||||
}
|
||||
|
||||
fn draw_map_2d(app App) {
|
||||
for y := 0; y < map_y_size; y++ {
|
||||
for x := 0; x < map_x_size; x++ {
|
||||
color := if app.map[y * map_x_size + x] == 1 { gg.white } else { gg.black }
|
||||
app.ctx.draw_rect_filled(x * map_square, y * map_square, map_square - 1,
|
||||
map_square - 1, color)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_player(app App) {
|
||||
app.ctx.draw_rect_filled(app.player_x, app.player_y, player_size, player_size, gg.yellow)
|
||||
cx := app.player_x + player_size / 2
|
||||
cy := app.player_y + player_size / 2
|
||||
app.ctx.draw_line(cx, cy, cx + app.player_dx * 5, cy + app.player_dy * 5, gg.yellow)
|
||||
}
|
||||
|
||||
fn draw_rays_and_walls(app App) {
|
||||
pi2 := math.pi / 2
|
||||
pi3 := 3 * math.pi / 2
|
||||
degree_radian := f32(0.0174533)
|
||||
max_depth_of_field := 8
|
||||
field_of_view := 60 // 60 degrees
|
||||
|
||||
mut distance := f32(0)
|
||||
mut depth_of_field := 0
|
||||
mut ray_x := f32(0)
|
||||
mut ray_y := f32(0)
|
||||
mut offset_x := f32(0)
|
||||
mut offset_y := f32(0)
|
||||
mut map_x := 0
|
||||
mut map_y := 0
|
||||
mut map_pos := 0
|
||||
mut color := gg.red
|
||||
mut ray_angle := clamp_ray_angle(app.player_angle - degree_radian * field_of_view / 2)
|
||||
|
||||
// each step = 1/2 degree
|
||||
steps := field_of_view * 2
|
||||
|
||||
for step := 0; step < steps; step++ {
|
||||
// check horizontal lines
|
||||
mut hd := f32(max_int)
|
||||
mut hx := app.player_x
|
||||
mut hy := app.player_y
|
||||
depth_of_field = 0
|
||||
arc_tan := -1.0 / math.tanf(ray_angle)
|
||||
if ray_angle > math.pi { // looking up
|
||||
ray_y = f32(int(app.player_y) / map_square * map_square) - .0001
|
||||
ray_x = (app.player_y - ray_y) * arc_tan + app.player_x
|
||||
offset_y = -map_square
|
||||
offset_x = -offset_y * arc_tan
|
||||
} else if ray_angle < math.pi { // looking down
|
||||
ray_y = f32(int(app.player_y) / map_square * map_square + map_square)
|
||||
ray_x = (app.player_y - ray_y) * arc_tan + app.player_x
|
||||
offset_y = map_square
|
||||
offset_x = -offset_y * arc_tan
|
||||
} else if ray_angle == 0 || ray_angle == 2 * math.pi { // looking straight left/right
|
||||
ray_x = app.player_x
|
||||
ray_y = app.player_y
|
||||
depth_of_field = max_depth_of_field
|
||||
}
|
||||
for depth_of_field < max_depth_of_field {
|
||||
map_x = int(ray_x) / map_square
|
||||
map_y = int(ray_y) / map_square
|
||||
map_pos = map_y * map_x_size + map_x
|
||||
if app.map[map_pos] or { 0 } == 1 {
|
||||
// hit a wall
|
||||
hx = ray_x
|
||||
hy = ray_y
|
||||
hd = hypotenuse(app.player_x, app.player_y, hx, hy)
|
||||
depth_of_field = max_depth_of_field
|
||||
} else { // go to next line
|
||||
ray_x += offset_x
|
||||
ray_y += offset_y
|
||||
depth_of_field += 1
|
||||
}
|
||||
}
|
||||
// check vertical lines
|
||||
mut vd := f32(max_int)
|
||||
mut vx := app.player_x
|
||||
mut vy := app.player_y
|
||||
depth_of_field = 0
|
||||
neg_tan := -math.tanf(ray_angle)
|
||||
if ray_angle > pi2 && ray_angle < pi3 { // looking left
|
||||
ray_x = f32(int(app.player_x) / map_square * map_square) - .0001
|
||||
ray_y = (app.player_x - ray_x) * neg_tan + app.player_y
|
||||
offset_x = -map_square
|
||||
offset_y = -offset_x * neg_tan
|
||||
} else if ray_angle < pi2 || ray_angle > pi3 { // looking right
|
||||
ray_x = f32(int(app.player_x) / map_square * map_square + map_square)
|
||||
ray_y = (app.player_x - ray_x) * neg_tan + app.player_y
|
||||
offset_x = map_square
|
||||
offset_y = -offset_x * neg_tan
|
||||
} else if ray_angle == 0 || ray_angle == 2 * math.pi { // looking straight up/down
|
||||
ray_x = app.player_x
|
||||
ray_y = app.player_y
|
||||
depth_of_field = max_depth_of_field
|
||||
}
|
||||
for depth_of_field < max_depth_of_field {
|
||||
map_x = int(ray_x) / map_square
|
||||
map_y = int(ray_y) / map_square
|
||||
map_pos = map_y * map_x_size + map_x
|
||||
if app.map[map_pos] or { 0 } == 1 {
|
||||
// hit a wall
|
||||
vx = ray_x
|
||||
vy = ray_y
|
||||
vd = hypotenuse(app.player_x, app.player_y, vx, vy)
|
||||
depth_of_field = max_depth_of_field
|
||||
} else { // go to next line
|
||||
ray_x += offset_x
|
||||
ray_y += offset_y
|
||||
depth_of_field += 1
|
||||
}
|
||||
}
|
||||
// use the shorter of the horizontal and vertical distances to draw rays
|
||||
// use different colors for the two sides of the walls for lighting effect
|
||||
if vd < hd {
|
||||
ray_x = vx
|
||||
ray_y = vy
|
||||
distance = vd
|
||||
color = gg.rgb(0, 100, 0)
|
||||
} else if hd < vd {
|
||||
ray_x = hx
|
||||
ray_y = hy
|
||||
distance = hd
|
||||
color = gg.rgb(0, 120, 0)
|
||||
}
|
||||
// draw ray
|
||||
cx := app.player_x + player_size / 2
|
||||
cy := app.player_y + player_size / 2
|
||||
app.ctx.draw_line(cx, cy, ray_x, ray_y, gg.green)
|
||||
// draw wall section
|
||||
mut ca := clamp_ray_angle(app.player_angle - ray_angle)
|
||||
distance *= math.cosf(ca) // remove fish eye
|
||||
offset_3d_view := 530
|
||||
line_thickeness := 4
|
||||
max_wall_height := 320
|
||||
wall_height := math.min((map_square * max_wall_height) / distance, max_wall_height)
|
||||
wall_offset := max_wall_height / 2 - wall_height / 2
|
||||
app.ctx.draw_line_with_config(step * line_thickeness + offset_3d_view, wall_offset,
|
||||
|
||||
step * line_thickeness + offset_3d_view, wall_offset + wall_height, gg.PenConfig{
|
||||
color: color
|
||||
thickness: line_thickeness
|
||||
})
|
||||
// step to next ray angle
|
||||
ray_angle = clamp_ray_angle(ray_angle + degree_radian / 2)
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_events(event &gg.Event, mut app App) {
|
||||
if event.typ == .key_down {
|
||||
match event.key_code {
|
||||
.up {
|
||||
app.player_x += app.player_dx
|
||||
app.player_y += app.player_dy
|
||||
}
|
||||
.down {
|
||||
app.player_x -= app.player_dx
|
||||
app.player_y -= app.player_dy
|
||||
}
|
||||
.left {
|
||||
app.player_angle -= 0.1
|
||||
if app.player_angle < 0 {
|
||||
app.player_angle += 2 * math.pi
|
||||
}
|
||||
calc_deltas(mut app)
|
||||
}
|
||||
.right {
|
||||
app.player_angle += 0.1
|
||||
if app.player_angle > 2 * math.pi {
|
||||
app.player_angle -= 2 * math.pi
|
||||
}
|
||||
calc_deltas(mut app)
|
||||
}
|
||||
else {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn calc_deltas(mut app App) {
|
||||
app.player_dx = math.cosf(app.player_angle) * 5
|
||||
app.player_dy = math.sinf(app.player_angle) * 5
|
||||
}
|
||||
|
||||
fn hypotenuse(ax f32, ay f32, bx f32, by f32) f32 {
|
||||
a2 := math.square(bx - ax)
|
||||
b2 := math.square(by - ay)
|
||||
return math.sqrtf(a2 + b2)
|
||||
}
|
||||
|
||||
fn clamp_ray_angle(ra f32) f32 {
|
||||
return match true {
|
||||
ra < 0 { ra + 2 * math.pi }
|
||||
ra > 2 * math.pi { ra - 2 * math.pi }
|
||||
else { ra }
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_instructions(app App) {
|
||||
app.ctx.draw_text(700, app.ctx.height - 17, 'use arrow keys to move player')
|
||||
}
|
||||
Reference in new issue
Block a user