1- // Ported from lightgl.js and adapted for wgpu-matrix
2- // http://github.com/evanw/lightgl.js/
1+ // Ported from lightgl.js - http://github.com/evanw/lightgl.js/
32
43import { mat4 , vec3 } from 'wgpu-matrix' ;
54import type { Mat4 } from 'wgpu-matrix' ;
@@ -10,38 +9,42 @@ export class Vector {
109 y : number ;
1110 z : number ;
1211
13- constructor ( x ?: number , y ?: number , z ?: number ) {
14- this . x = x || 0 ;
15- this . y = y || 0 ;
16- this . z = z || 0 ;
12+ constructor ( x = 0 , y = 0 , z = 0 ) {
13+ this . x = x ;
14+ this . y = y ;
15+ this . z = z ;
1716 }
1817
1918 negative ( ) : Vector {
2019 return new Vector ( - this . x , - this . y , - this . z ) ;
2120 }
2221
2322 add ( v : Vector | number ) : Vector {
24- if ( v instanceof Vector ) return new Vector ( this . x + v . x , this . y + v . y , this . z + v . z ) ;
25- else return new Vector ( this . x + v , this . y + v , this . z + v ) ;
23+ if ( v instanceof Vector ) {
24+ return new Vector ( this . x + v . x , this . y + v . y , this . z + v . z ) ;
25+ }
26+ return new Vector ( this . x + v , this . y + v , this . z + v ) ;
2627 }
2728
2829 subtract ( v : Vector | number ) : Vector {
29- if ( v instanceof Vector ) return new Vector ( this . x - v . x , this . y - v . y , this . z - v . z ) ;
30- else return new Vector ( this . x - v , this . y - v , this . z - v ) ;
30+ if ( v instanceof Vector ) {
31+ return new Vector ( this . x - v . x , this . y - v . y , this . z - v . z ) ;
32+ }
33+ return new Vector ( this . x - v , this . y - v , this . z - v ) ;
3134 }
3235
3336 multiply ( v : Vector | number ) : Vector {
34- if ( v instanceof Vector ) return new Vector ( this . x * v . x , this . y * v . y , this . z * v . z ) ;
35- else return new Vector ( this . x * v , this . y * v , this . z * v ) ;
37+ if ( v instanceof Vector ) {
38+ return new Vector ( this . x * v . x , this . y * v . y , this . z * v . z ) ;
39+ }
40+ return new Vector ( this . x * v , this . y * v , this . z * v ) ;
3641 }
3742
3843 divide ( v : Vector | number ) : Vector {
39- if ( v instanceof Vector ) return new Vector ( this . x / v . x , this . y / v . y , this . z / v . z ) ;
40- else return new Vector ( this . x / v , this . y / v , this . z / v ) ;
41- }
42-
43- equals ( v : Vector ) : boolean {
44- return this . x == v . x && this . y == v . y && this . z == v . z ;
44+ if ( v instanceof Vector ) {
45+ return new Vector ( this . x / v . x , this . y / v . y , this . z / v . z ) ;
46+ }
47+ return new Vector ( this . x / v , this . y / v , this . z / v ) ;
4548 }
4649
4750 dot ( v : Vector ) : number {
@@ -65,39 +68,27 @@ export class Vector {
6568 }
6669
6770 min ( ) : number {
68- return Math . min ( Math . min ( this . x , this . y ) , this . z ) ;
71+ return Math . min ( this . x , this . y , this . z ) ;
6972 }
7073
7174 max ( ) : number {
72- return Math . max ( Math . max ( this . x , this . y ) , this . z ) ;
73- }
74-
75- toAngles ( ) : { theta : number ; phi : number } {
76- return {
77- theta : Math . atan2 ( this . z , this . x ) ,
78- phi : Math . asin ( this . y / this . length ( ) )
79- } ;
75+ return Math . max ( this . x , this . y , this . z ) ;
8076 }
8177
82- angleTo ( a : Vector ) : number {
83- return Math . acos ( this . dot ( a ) / ( this . length ( ) * a . length ( ) ) ) ;
84- }
85-
86- toArray ( n ?: number ) : number [ ] {
87- return [ this . x , this . y , this . z ] . slice ( 0 , n || 3 ) ;
78+ toArray ( ) : number [ ] {
79+ return [ this . x , this . y , this . z ] ;
8880 }
8981
9082 clone ( ) : Vector {
9183 return new Vector ( this . x , this . y , this . z ) ;
9284 }
9385
94- init ( x : number , y : number , z : number ) : Vector {
95- this . x = x ; this . y = y ; this . z = z ;
96- return this ;
97- }
98-
99- static fromArray ( a : number [ ] ) : Vector {
100- return new Vector ( a [ 0 ] , a [ 1 ] , a [ 2 ] ) ;
86+ static fromAngles ( theta : number , phi : number ) : Vector {
87+ return new Vector (
88+ Math . cos ( phi ) * Math . cos ( theta ) ,
89+ Math . sin ( phi ) ,
90+ Math . cos ( phi ) * Math . sin ( theta )
91+ ) ;
10192 }
10293
10394 static lerp ( a : Vector , b : Vector , t : number ) : Vector {
@@ -111,167 +102,83 @@ export class Vector {
111102 static max ( a : Vector , b : Vector ) : Vector {
112103 return new Vector ( Math . max ( a . x , b . x ) , Math . max ( a . y , b . y ) , Math . max ( a . z , b . z ) ) ;
113104 }
114-
115- static fromAngles ( theta : number , phi : number ) : Vector {
116- return new Vector ( Math . cos ( phi ) * Math . cos ( theta ) , Math . sin ( phi ) , Math . cos ( phi ) * Math . sin ( theta ) ) ;
117- }
118105}
119106
120107export class HitTest {
121108 t : number ;
122109 hit : Vector ;
123110 normal : Vector ;
124111
125- constructor ( t ?: number , hit ?: Vector , normal ?: Vector ) {
126- this . t = arguments . length ? ( t ?? Number . MAX_VALUE ) : Number . MAX_VALUE ;
127- this . hit = hit ! ;
128- this . normal = normal ! ;
129- }
130-
131- mergeWith ( other : HitTest ) : void {
132- if ( other . t > 0 && other . t < this . t ) {
133- this . t = other . t ;
134- this . hit = other . hit ;
135- this . normal = other . normal ;
136- }
112+ constructor ( t : number , hit : Vector , normal : Vector ) {
113+ this . t = t ;
114+ this . hit = hit ;
115+ this . normal = normal ;
137116 }
138117}
139118
140119export class Raytracer {
141- viewport : Viewport ;
142120 eye : Vector ;
143- invViewProj : Mat4 ;
144- ray00 : Vector ;
145- ray10 : Vector ;
146- ray01 : Vector ;
147- ray11 : Vector ;
121+ private viewport : Viewport ;
122+ private invViewProj : Mat4 ;
123+ private ray00 : Vector ;
124+ private ray10 : Vector ;
125+ private ray01 : Vector ;
126+ private ray11 : Vector ;
148127
149128 constructor ( viewMatrix : Mat4 , projectionMatrix : Mat4 , viewport : Viewport ) {
150- // viewMatrix and projectionMatrix are Float32Array (wgpu-matrix)
151129 this . viewport = viewport ;
152130
153- // Calculate Eye Position from View Matrix
154- // Eye is origin (0,0,0) in Camera space, transformed to World.
155- // World = View^-1 * Camera.
156- // Eye = View^-1 * [0,0,0,1].
131+ // Calculate eye position from inverse view matrix
157132 const invView = mat4 . invert ( viewMatrix ) ;
158133 const eyeVec = vec3 . transformMat4 ( [ 0 , 0 , 0 ] , invView ) ;
159134 this . eye = new Vector ( eyeVec [ 0 ] , eyeVec [ 1 ] , eyeVec [ 2 ] ) ;
160135
161- // Calculate View-Projection Inverse for unProject
136+ // Calculate view-projection inverse for unprojection
162137 this . invViewProj = mat4 . invert ( mat4 . multiply ( projectionMatrix , viewMatrix ) ) ;
163138
164- // Precalculate Corner Rays
165- const minX = viewport [ 0 ] , maxX = minX + viewport [ 2 ] ;
166- const minY = viewport [ 1 ] , maxY = minY + viewport [ 3 ] ;
167-
168- // unProject corners. Z=1 (Far).
169- // Note: WebGPU Clip Z is 0..1.
170- // For ray direction, we can unproject a point on Far plane.
171- this . ray00 = this . unProject ( minX , minY , 1 ) . subtract ( this . eye ) as Vector ;
172- this . ray10 = this . unProject ( maxX , minY , 1 ) . subtract ( this . eye ) as Vector ;
173- this . ray01 = this . unProject ( minX , maxY , 1 ) . subtract ( this . eye ) as Vector ;
174- this . ray11 = this . unProject ( maxX , maxY , 1 ) . subtract ( this . eye ) as Vector ;
139+ // Precalculate corner rays
140+ const [ minX , minY , width , height ] = viewport ;
141+ const maxX = minX + width ;
142+ const maxY = minY + height ;
143+
144+ this . ray00 = this . unProject ( minX , minY , 1 ) . subtract ( this . eye ) ;
145+ this . ray10 = this . unProject ( maxX , minY , 1 ) . subtract ( this . eye ) ;
146+ this . ray01 = this . unProject ( minX , maxY , 1 ) . subtract ( this . eye ) ;
147+ this . ray11 = this . unProject ( maxX , maxY , 1 ) . subtract ( this . eye ) ;
175148 }
176149
177- unProject ( winX : number , winY : number , winZ : number ) : Vector {
178- const v = this . viewport ;
179- // Map window (0..W, 0..H) to NDC (-1..1, -1..1)
180- // Note: wgpu-matrix assumes Y-down for some things?
181- // But standard projection logic usually maps:
182- // Bottom (-1) to Top (1) if Y-up.
183- // Top (-1) to Bottom (1) if Y-down.
184- // Let's assume standard GL behavior for now:
185- // x: -1 to 1
186- // y: -1 (bottom) to 1 (top)
187- // But winY 0 is top. So we flip Y.
188-
189- const x = ( winX - v [ 0 ] ) / v [ 2 ] * 2 - 1 ;
190- const y = ( 1 - ( winY - v [ 1 ] ) / v [ 3 ] ) * 2 - 1 ; // Flip Y: 0->1, H->-1.
191- const z = winZ ; // 0..1
192-
193- const ndc : [ number , number , number ] = [ x , y , z ] ;
194- const world = vec3 . transformMat4 ( ndc , this . invViewProj ) ;
150+ private unProject ( winX : number , winY : number , winZ : number ) : Vector {
151+ const [ vx , vy , vw , vh ] = this . viewport ;
152+ const x = ( ( winX - vx ) / vw ) * 2 - 1 ;
153+ const y = ( 1 - ( winY - vy ) / vh ) * 2 - 1 ;
154+
155+ const world = vec3 . transformMat4 ( [ x , y , winZ ] , this . invViewProj ) ;
195156 return new Vector ( world [ 0 ] , world [ 1 ] , world [ 2 ] ) ;
196157 }
197158
198159 getRayForPixel ( x : number , y : number ) : Vector {
199- // Interpolate precalculated rays
200- // x, y are window coordinates
201- const u = ( x - this . viewport [ 0 ] ) / this . viewport [ 2 ] ;
202- const v = ( y - this . viewport [ 1 ] ) / this . viewport [ 3 ] ; // 0 top, 1 bottom
203-
204- // ray00 is Top-Left?
205- // In constructor: minY (0) -> unProject(..., 0).
206- // unProject(..., 0) uses `y = (1 - 0) * 2 - 1 = 1` -> Top.
207- // So ray00 is Top-Left (minX, minY).
208- // ray01 is Bottom-Left (minX, maxY).
209- // ray10 is Top-Right.
210- // ray11 is Bottom-Right.
211-
212- // v goes 0 (Top) to 1 (Bottom).
213- // Lerp(rayTop, rayBottom, v).
160+ const [ vx , vy , vw , vh ] = this . viewport ;
161+ const u = ( x - vx ) / vw ;
162+ const v = ( y - vy ) / vh ;
163+
214164 const rayTop = Vector . lerp ( this . ray00 , this . ray10 , u ) ;
215165 const rayBottom = Vector . lerp ( this . ray01 , this . ray11 , u ) ;
216166
217167 return Vector . lerp ( rayTop , rayBottom , v ) . unit ( ) ;
218168 }
219169
220- static hitTestBox ( origin : Vector , ray : Vector , min : Vector , max : Vector ) : HitTest | null {
221- const tMin = min . subtract ( origin ) . divide ( ray ) as Vector ;
222- const tMax = max . subtract ( origin ) . divide ( ray ) as Vector ;
223- const t1 = Vector . min ( tMin , tMax ) ;
224- const t2 = Vector . max ( tMin , tMax ) ;
225- const tNear = t1 . max ( ) ;
226- const tFar = t2 . min ( ) ;
227-
228- if ( tNear > 0 && tNear < tFar ) {
229- const epsilon = 1.0e-6 , hit = origin . add ( ray . multiply ( tNear ) ) as Vector ;
230- min = min . add ( epsilon ) as Vector ;
231- max = max . subtract ( epsilon ) as Vector ;
232- return new HitTest ( tNear , hit , new Vector (
233- Number ( hit . x > max . x ) - Number ( hit . x < min . x ) ,
234- Number ( hit . y > max . y ) - Number ( hit . y < min . y ) ,
235- Number ( hit . z > max . z ) - Number ( hit . z < min . z )
236- ) ) ;
237- }
238-
239- return null ;
240- }
241-
242170 static hitTestSphere ( origin : Vector , ray : Vector , center : Vector , radius : number ) : HitTest | null {
243- const offset = origin . subtract ( center ) as Vector ;
171+ const offset = origin . subtract ( center ) ;
244172 const a = ray . dot ( ray ) ;
245173 const b = 2 * ray . dot ( offset ) ;
246174 const c = offset . dot ( offset ) - radius * radius ;
247175 const discriminant = b * b - 4 * a * c ;
248176
249177 if ( discriminant > 0 ) {
250- const t = ( - b - Math . sqrt ( discriminant ) ) / ( 2 * a ) , hit = origin . add ( ray . multiply ( t ) ) as Vector ;
251- return new HitTest ( t , hit , ( hit . subtract ( center ) as Vector ) . divide ( radius ) as Vector ) ;
252- }
253-
254- return null ;
255- }
256-
257- static hitTestTriangle ( origin : Vector , ray : Vector , a : Vector , b : Vector , c : Vector ) : HitTest | null {
258- const ab = b . subtract ( a ) as Vector ;
259- const ac = c . subtract ( a ) as Vector ;
260- const normal = ab . cross ( ac ) . unit ( ) ;
261- const t = normal . dot ( ( a . subtract ( origin ) as Vector ) ) / normal . dot ( ray ) ;
262-
263- if ( t > 0 ) {
264- const hit = origin . add ( ray . multiply ( t ) ) as Vector ;
265- const toHit = hit . subtract ( a ) as Vector ;
266- const dot00 = ac . dot ( ac ) ;
267- const dot01 = ac . dot ( ab ) ;
268- const dot02 = ac . dot ( toHit ) ;
269- const dot11 = ab . dot ( ab ) ;
270- const dot12 = ab . dot ( toHit ) ;
271- const divide = dot00 * dot11 - dot01 * dot01 ;
272- const u = ( dot11 * dot02 - dot01 * dot12 ) / divide ;
273- const v = ( dot00 * dot12 - dot01 * dot02 ) / divide ;
274- if ( u >= 0 && v >= 0 && u + v <= 1 ) return new HitTest ( t , hit , normal ) ;
178+ const t = ( - b - Math . sqrt ( discriminant ) ) / ( 2 * a ) ;
179+ const hit = origin . add ( ray . multiply ( t ) ) ;
180+ const normal = hit . subtract ( center ) . divide ( radius ) ;
181+ return new HitTest ( t , hit , normal ) ;
275182 }
276183
277184 return null ;
0 commit comments