mirror of
https://github.com/vlang/v.git
synced 2025-09-13 14:32:26 +03:00

Some checks failed
Graphics CI / gg-regressions (push) Waiting to run
vlib modules CI / build-module-docs (push) Waiting to run
native backend CI / native-backend-ubuntu (push) Waiting to run
vab CI / v-compiles-os-android (push) Waiting to run
native backend CI / native-backend-windows (push) Waiting to run
Shy and PV CI / v-compiles-puzzle-vibes (push) Waiting to run
Sanitized CI / sanitize-undefined-clang (push) Waiting to run
Sanitized CI / sanitize-undefined-gcc (push) Waiting to run
Sanitized CI / tests-sanitize-address-clang (push) Waiting to run
Sanitized CI / sanitize-address-msvc (push) Waiting to run
Sanitized CI / sanitize-address-gcc (push) Waiting to run
Sanitized CI / sanitize-memory-clang (push) Waiting to run
sdl CI / v-compiles-sdl-examples (push) Waiting to run
Time CI / time-linux (push) Waiting to run
Time CI / time-macos (push) Waiting to run
Time CI / time-windows (push) Waiting to run
toml CI / toml-module-pass-external-test-suites (push) Waiting to run
Tools CI / tools-linux (clang) (push) Waiting to run
Tools CI / tools-linux (gcc) (push) Waiting to run
Tools CI / tools-linux (tcc) (push) Waiting to run
Tools CI / tools-macos (clang) (push) Waiting to run
Tools CI / tools-windows (gcc) (push) Waiting to run
Tools CI / tools-windows (msvc) (push) Waiting to run
Tools CI / tools-windows (tcc) (push) Waiting to run
Tools CI / tools-docker-ubuntu-musl (push) Waiting to run
vab CI / vab-compiles-v-examples (push) Waiting to run
wasm backend CI / wasm-backend (ubuntu-22.04) (push) Waiting to run
wasm backend CI / wasm-backend (windows-2022) (push) Waiting to run
Workflow Lint / lint-yml-workflows (push) Has been cancelled
278 lines
7.3 KiB
V
278 lines
7.3 KiB
V
// 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 player_move_delta = 10
|
|
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')
|
|
}
|