wren/val Public Public: anyone can read this; nobody can change it.

forked from benjivers/val

workbenchthis machine
Files 4990f87c0167ca7cfa9c9a4a67fa43bcfef26454
1
import { describe, expect, test } from "bun:test";
2
import {
3
  angleColumn,
4
  bandLight,
5
  castRay,
6
  columnAngle,
7
  columnOffset,
8
  FOV,
9
  hitResult,
10
  project,
11
  projection,
12
  projectResult,
13
  sliceHeight,
14
  smokeAlong,
15
  spriteColumnVisible,
16
  wallLight,
17
} from "../static/raycast.js";
18
19
const W = 900;
20
21
describe("columns and angles", () => {
22
  test("the middle column looks straight ahead", () => {
23
    expect(columnOffset(W / 2 - 0.5, W)).toBeCloseTo(0, 6);
24
    expect(columnAngle(W / 2 - 0.5, W, 1.2)).toBeCloseTo(1.2, 6);
25
  });
26
27
  test("the outer columns sit at the edges of the field of view", () => {
28
    expect(columnOffset(0, W)).toBeCloseTo(-FOV / 2, 2);
29
    expect(columnOffset(W - 1, W)).toBeCloseTo(FOV / 2, 2);
30
  });
31
32
  test("a column maps to an angle and back again", () => {
33
    for (const col of [0, 123, 450, 899]) {
34
      expect(angleColumn(columnOffset(col, W), W)).toBeCloseTo(col + 0.5, 4);
35
    }
36
  });
37
38
  test("columns are not spread evenly — that is what keeps walls straight", () => {
39
    const middle = columnOffset(451, W) - columnOffset(450, W);
40
    const edge = columnOffset(1, W) - columnOffset(0, W);
41
    expect(edge).toBeLessThan(middle);
42
  });
43
});
44
45
describe("wall slices", () => {
46
  test("a wall twice as far is half as tall", () => {
47
    const proj = projection(W);
48
    expect(sliceHeight(160, proj, 80)).toBeCloseTo(sliceHeight(80, proj, 80) / 2, 6);
49
  });
50
51
  test("a north–south face is darker than an east–west one at the same range", () => {
52
    expect(wallLight(200, 1, 80)).toBeLessThan(wallLight(200, 0, 80));
53
  });
54
55
  test("further is dimmer, and never fully black", () => {
56
    expect(wallLight(900, 0, 80)).toBeLessThan(wallLight(120, 0, 80));
57
    expect(wallLight(5000, 1, 80)).toBeGreaterThan(0);
58
  });
59
60
  test("the floor is dimmest at the horizon", () => {
61
    expect(bandLight(2, 200)).toBeLessThan(bandLight(180, 200));
62
  });
63
});
64
65
// A four-by-four room: solid border, open inside.
66
const room = (col: number, row: number): boolean => col <= 0 || row <= 0 || col >= 3 || row >= 3;
67
68
describe("castRay", () => {
69
  const out = hitResult();
70
71
  test("finds the wall straight ahead and calls it an east–west face", () => {
72
    castRay(room, 120, 120, 0, 80, 2000, out);
73
    expect(out.wall).toBe(true);
74
    expect(out.dist).toBeCloseTo(120, 6); // from x=120 to the face at x=240
75
    expect(out.side).toBe(0);
76
    expect(out.col).toBe(3);
77
  });
78
79
  test("a ray upward strikes a north–south face", () => {
80
    castRay(room, 120, 120, -Math.PI / 2, 80, 2000, out);
81
    expect(out.wall).toBe(true);
82
    expect(out.dist).toBeCloseTo(40, 6);
83
    expect(out.side).toBe(1);
84
    expect(out.row).toBe(0);
85
  });
86
87
  test("a ray that runs out of range reports no wall", () => {
88
    castRay(room, 120, 120, 0, 80, 50, out);
89
    expect(out.wall).toBe(false);
90
    expect(out.dist).toBe(50);
91
  });
92
93
  test("standing inside a wall is zero distance, not an infinite loop", () => {
94
    castRay(room, 10, 10, 0.3, 80, 2000, out);
95
    expect(out.dist).toBe(0);
96
    expect(out.wall).toBe(true);
97
  });
98
99
  test("a diagonal ray agrees with walking the same line by hand", () => {
100
    castRay(room, 120, 120, Math.PI / 4, 80, 2000, out);
101
    let step = 0;
102
    while (step < 2000 && !room(Math.floor((120 + Math.cos(Math.PI / 4) * step) / 80), Math.floor((120 + Math.sin(Math.PI / 4) * step) / 80)))
103
      step += 0.25;
104
    expect(out.dist).toBeCloseTo(step, 0);
105
  });
106
107
  test("it fills the caller's object and allocates nothing", () => {
108
    const same = castRay(room, 120, 120, 0, 80, 2000, out);
109
    expect(same).toBe(out);
110
  });
111
});
112
113
describe("projecting a body onto the screen", () => {
114
  const proj = projection(W);
115
  const out = projectResult();
116
117
  test("something dead ahead lands in the middle of the screen", () => {
118
    project(100, 100, 0, 500, 100, W, proj, 80, out);
119
    expect(out.behind).toBe(false);
120
    expect(out.column).toBeCloseTo(W / 2, 4);
121
    expect(out.perp).toBeCloseTo(400, 6);
122
  });
123
124
  test("something to the right lands right of centre", () => {
125
    project(100, 100, 0, 500, 260, W, proj, 80, out);
126
    expect(out.column).toBeGreaterThan(W / 2);
127
  });
128
129
  test("something behind the camera is not drawn at all", () => {
130
    project(100, 100, 0, -400, 100, W, proj, 80, out);
131
    expect(out.behind).toBe(true);
132
    expect(out.size).toBe(0);
133
  });
134
135
  test("the further body is the smaller one", () => {
136
    project(100, 100, 0, 300, 100, W, proj, 80, out);
137
    const near = out.size;
138
    project(100, 100, 0, 900, 100, W, proj, 80, out);
139
    expect(out.size).toBeLessThan(near);
140
  });
141
142
  test("perpendicular depth, not straight-line distance, so the edges do not bulge", () => {
143
    project(100, 100, 0, 500, 300, W, proj, 80, out);
144
    expect(out.perp).toBeLessThan(out.dist);
145
  });
146
});
147
148
describe("z-clip", () => {
149
  test("a body nearer than the wall behind it is drawn", () => {
150
    expect(spriteColumnVisible(300, 900)).toBe(true);
151
  });
152
153
  test("a body behind the corner is hidden", () => {
154
    expect(spriteColumnVisible(900, 300)).toBe(false);
155
  });
156
157
  test("a body flush against the wall does not bleed through it", () => {
158
    expect(spriteColumnVisible(299, 300)).toBe(false);
159
  });
160
});
161
162
describe("smoke as a volume", () => {
163
  test("a ray through the middle of a smoke is fully fogged", () => {
164
    expect(smokeAlong(0, 0, 1, 0, 400, 0, 110, 1000)).toBe(1);
165
  });
166
167
  test("a ray that misses it is not fogged at all", () => {
168
    expect(smokeAlong(0, 0, 1, 0, 400, 400, 110, 1000)).toBe(0);
169
  });
170
171
  test("a wall in front of the smoke is not fogged by it", () => {
172
    expect(smokeAlong(0, 0, 1, 0, 400, 0, 110, 200)).toBe(0);
173
  });
174
175
  test("clipping the edge fogs less than going through the middle", () => {
176
    const edge = smokeAlong(0, 0, 1, 0, 400, 100, 110, 1000);
177
    expect(edge).toBeGreaterThan(0);
178
    expect(edge).toBeLessThan(1);
179
  });
180
181
  test("standing in the middle of one is nearly opaque in every direction", () => {
182
    expect(smokeAlong(400, 0, 1, 0, 400, 0, 110, 1000)).toBeGreaterThan(0.9);
183
    expect(smokeAlong(400, 0, 0, -1, 400, 0, 110, 1000)).toBeGreaterThan(0.9);
184
  });
185
});
↑↓ navigate ↵ open esc close
↑↓ navigate ↵ open esc close

Keyboard shortcuts