1- import * as twgl from "twgl.js"
1+ import * as twgl from "twgl.js" ;
2+
3+ const vertShaderSrc = `#version 300 es
4+ in vec3 position;
5+ in vec3 instanceCenter;
6+ in vec3 instanceColor;
7+ in float instanceSize;
8+
9+ uniform mat4 u_view;
10+ uniform mat4 u_proj;
11+
12+ out vec3 v_color;
13+
14+ // TODO: test more math out in here using gl_InstanceID, see if that gets
15+ // more performance.
16+
17+ void main() {
18+ vec3 scaledPos = position * instanceSize + instanceCenter;
19+ gl_Position = u_proj * u_view * vec4(scaledPos, 1.0);
20+ v_color = instanceColor;
21+ }` ;
22+
23+ const fragShaderSrc = `#version 300 es
24+ precision highp float;
25+
26+ in vec3 v_color;
27+ out vec4 outColor;
28+
29+ void main() {
30+ outColor = vec4(v_color, 1.0);
31+ }` ;
232
333const CENTER = twgl . v3 . create ( 0 , 0 , 0 ) ;
434const EPSILON = 1e-6 ; // from raymath
@@ -56,7 +86,7 @@ const Direction = {
5686
5787type DirVals = typeof Direction [ keyof typeof Direction ] ;
5888
59- type Freelist = { head : number , tail : number }
89+ type Freelist = [ head : number , tail : number ] ;
6090
6191// ------------------------------------------
6292
@@ -86,16 +116,16 @@ const initFreelist = (frie: Freelist, bullets: PointBufferArray) => {
86116 for ( let i = 0 ; i < BULLET_COUNT - 1 ; i ++ )
87117 bullets . nextFreeOrSpawned [ i ] = i + 1 ;
88118 bullets . nextFreeOrSpawned [ BULLET_COUNT - 1 ] = LIST_END ;
89- frie . head = 0 ;
90- frie . tail = BULLET_COUNT - 1 ;
119+ frie [ 0 ] = 0 ;
120+ frie [ 1 ] = BULLET_COUNT - 1 ;
91121} ;
92122
93123const spawnPoint = ( freelist : Freelist , points : PointBufferArray ) => {
94- if ( freelist . head == LIST_END ) return LIST_END ;
95- const idx = freelist . head ;
96- freelist . head = points . nextFreeOrSpawned [ idx ] ;
97- if ( freelist . head == LIST_END )
98- freelist . tail = LIST_END ;
124+ if ( freelist [ 0 ] == LIST_END ) return LIST_END ;
125+ const idx = freelist [ 0 ] ;
126+ freelist [ 0 ] = points . nextFreeOrSpawned [ idx ] ;
127+ if ( freelist [ 0 ] == LIST_END )
128+ freelist [ 1 ] = LIST_END ;
99129 points . nextFreeOrSpawned [ idx ] = IS_SPAWNED ;
100130
101131 const [ bx , by , bz ] = getVec3Idx ( idx ) ;
@@ -105,7 +135,7 @@ const spawnPoint = (freelist: Freelist, points: PointBufferArray) => {
105135 points . colors [ by ] = ( 0x51 / 255 ) * lerpA + ( 0x0C / 255 ) * lerpB ;
106136 points . colors [ bz ] = ( 0x08 / 255 ) * lerpA + ( 0xCF / 255 ) * lerpB ;
107137
108- points . directions [ idx ] = ( 1 << ( Math . floor ( Math . random ( ) * Direction . LEN ) ) ) as DirVals ;
138+ points . directions [ idx ] = 1 << ( Math . floor ( Math . random ( ) * Direction . LEN ) ) as DirVals ;
109139 points . signs [ idx ] = points . directions [ idx ] & ( Direction . PX | Direction . PY | Direction . PZ ) ? 1 : - 1 ;
110140 points . activeIdx [ idx ] = points . directions [ idx ] & ( Direction . PX | Direction . NX ) ? bx
111141 : points . directions [ idx ] & ( Direction . PY | Direction . NY ) ? by : bz ;
@@ -130,18 +160,19 @@ const freeBullet = (freelist: Freelist, points: PointBufferArray, idx: number) =
130160 points . positions [ vy ] = Number . MAX_VALUE ;
131161 points . positions [ vz ] = Number . MAX_VALUE ;
132162 points . nextFreeOrSpawned [ idx ] = LIST_END ;
133- if ( freelist . tail != LIST_END )
134- points . nextFreeOrSpawned [ freelist . tail ] = idx ;
163+ if ( freelist [ 1 ] != LIST_END )
164+ points . nextFreeOrSpawned [ freelist [ 1 ] ] = idx ;
135165 else
136- freelist . head = idx ;
137- freelist . tail = idx ;
166+ freelist [ 0 ] = idx ;
167+ freelist [ 1 ] = idx ;
138168} ;
139169
140170const worldToIndex = ( coord : number , basePos : number , maxIdx : number ) => {
141171 // NOTE: idk why ceilf had to be used here but it fixed an off by -1 offset
142172 // issue.
143173 const ret = Math . ceil ( ( coord - basePos ) / ( CUBE_SIZE + CUBE_PADDING ) ) ;
144174 return ret > maxIdx ? maxIdx : ret < 0 ? 0 : ret ;
175+ // todo: revisit this to see what's up, why it broke, so it just naturally gives the correct val at correct range without clamping
145176}
146177
147178const getBulletBoundingBox = ( points : PointBufferArray , idx : number ) => {
@@ -176,7 +207,7 @@ const isOutOfBounds = (bullets: PointBufferArray, idx: number) => {
176207 }
177208} ;
178209
179- // ------------ Raylib ports ---------------
210+ // ------------ rendering fns ---------------
180211
181212type Vector3 = { x : number , y : number , z : number } ;
182213type Camera = {
@@ -193,12 +224,66 @@ const UpdateCamera = (camera: Camera) => {
193224
194225// -----------------------------------------
195226
196- const main = ( ) => {
227+ export const main = async ( ) => {
197228 const gl = ( document . getElementById ( "bg-canvas" ) as HTMLCanvasElement ) . getContext ( "webgl2" ) ;
198229 if ( ! gl ) return ;
230+ twgl
231+ // todo: upload cubePositions to VAO.
232+
233+ // todo: constants
234+ let spawnTimer = PRNG . nextRange ( 0 , 1 ) ;
235+
236+ const maxInstances = BULLET_COUNT * 100 ; // safe upper bound
237+
238+ // ------ gl setup ------
239+ const progInfo = twgl . createProgramInfo ( gl , [ vertShaderSrc , fragShaderSrc ] ) ;
240+ const instanceCenters = new Float32Array ( maxInstances * 3 ) ;
241+ const instanceColors = new Float32Array ( maxInstances * 3 ) ;
242+ const instanceSizes = new Float32Array ( maxInstances ) ;
243+ const cubeBufferInfo = twgl . createBufferInfoFromArrays ( gl , {
244+ position : {
245+ numComponents : 3 ,
246+ data : [
247+ // 8 corners
248+ - 0.5 , - 0.5 , - 0.5 , // 0
249+ 0.5 , - 0.5 , - 0.5 , // 1
250+ 0.5 , 0.5 , - 0.5 , // 2
251+ - 0.5 , 0.5 , - 0.5 , // 3
252+ - 0.5 , - 0.5 , 0.5 , // 4
253+ 0.5 , - 0.5 , 0.5 , // 5
254+ 0.5 , 0.5 , 0.5 , // 6
255+ - 0.5 , 0.5 , 0.5 , // 7
256+ ]
257+ } ,
258+ indices : [
259+ // bottom face
260+ 0 , 1 , 1 , 2 , 2 , 3 , 3 , 0 ,
261+ // top face
262+ 4 , 5 , 5 , 6 , 6 , 7 , 7 , 4 ,
263+ // vertical edges
264+ 0 , 4 , 1 , 5 , 2 , 6 , 3 , 7 ,
265+ ] ,
266+ instanceCenter : {
267+ numComponents : 3 ,
268+ data : instanceCenters ,
269+ divisor : 1 ,
270+ } ,
271+ instanceColor : {
272+ numComponents : 3 ,
273+ data : instanceColors ,
274+ divisor : 1 ,
275+ } ,
276+ instanceSize : {
277+ numComponents : 1 ,
278+ data : instanceSizes ,
279+ divisor : 1 ,
280+ }
281+ } ) ;
282+
283+ const vao = twgl . createVAOAndSetAttributes ( gl , progInfo . attribSetters , cubeBufferInfo . attribs ! , cubeBufferInfo . indices ) ;
199284
200285 const points = new PointBufferArray ( BULLET_COUNT ) ;
201- const freelist : Freelist = { head : LIST_END , tail : LIST_END } ;
286+ const freelist : Freelist = [ LIST_END , LIST_END ] ;
202287 initFreelist ( freelist , points ) ;
203288
204289 const cubePositions = new Float32Array ( CUBES_COUNT * 3 ) ;
@@ -218,29 +303,21 @@ const main = () => {
218303 }
219304 refPos [ 2 ] += CUBE_SIZE + CUBE_PADDING ;
220305 }
221- // todo: upload cubePositions to VAO.
222-
223- // todo: constants
224- let spawnTimer = PRNG . nextRange ( 0 , 1 ) ;
225-
226- // ------ gl setup ------
227- const progInfo = twgl . createProgramInfo ( gl , [ "shader.vs" , "shader.fs" ] ) ;
228306
229- // todo/architecture: pass around "base index" (pointing to x component) instead of doing getVec3Indices everywhere
230307 const render = ( dt : number ) => {
231308 if ( ( spawnTimer -= dt ) <= 0 ) {
232309 spawnPoint ( freelist , points ) ;
233310 spawnTimer = PRNG . nextRange ( 0 , 1 ) ; // todo: constants
234311 }
235312
313+ const cubeData : { centerIdx : number , colorIdx : number , size : number } [ ] = [ ] ;
236314 // todo: UpdateCamera (orbital)
237315 for ( let i = 0 ; i < BULLET_COUNT ; i ++ ) {
238316 if ( points . nextFreeOrSpawned [ i ] != IS_SPAWNED )
239317 continue ;
240318 // debug: keep track of bullet count
241319 // bullet_count++;
242320
243- const dir = points . directions [ i ] as DirVals ;
244321 points . positions [ points . activeIdx [ i ] ] += dt * points . signs [ i ] * points . speeds [ i ] ;
245322 if ( isOutOfBounds ( points , i ) ) {
246323 freeBullet ( freelist , points , i ) ;
@@ -250,8 +327,6 @@ const main = () => {
250327 // DrawSphereEx(points.positions[i], CUBE_SIZE / 8.0f, 4, 4,
251328 // ColorFromNormalized(points.colors[i]));
252329
253- const cubeData : { centerIdx : number , colorIdx : number , size : number } [ ] = [ ] ;
254-
255330 const bbox = getBulletBoundingBox ( points , i ) ;
256331 const [ vx , vy , vz ] = getVec3Idx ( i ) ;
257332 for ( let z = bbox . min_z ; z < bbox . max_z ; z ++ ) {
@@ -272,19 +347,52 @@ const main = () => {
272347 }
273348 }
274349 }
350+
351+ // --- Fill instance arrays ---
352+ for ( let i = 0 ; i < cubeData . length ; i ++ ) {
353+ if ( i >= maxInstances ) break ;
354+ const c = cubeData [ i ] ;
355+ const offset = i * 3 ;
356+ cubeBufferInfo . attribs ! [ "instanceCenter" ] ;
357+ instanceCenters . set ( [
358+ cubePositions [ c . centerIdx ] ,
359+ cubePositions [ c . centerIdx + 1 ] ,
360+ cubePositions [ c . centerIdx + 2 ]
361+ ] , offset ) ;
362+ instanceColors . set ( [
363+ points . colors [ c . colorIdx ] ,
364+ points . colors [ c . colorIdx + 1 ] ,
365+ points . colors [ c . colorIdx + 2 ]
366+ ] , offset ) ;
367+ instanceSizes [ i ] = c . size ;
368+ }
369+ // --- Upload updated instance data ---
370+ twgl . setAttribInfoBufferFromArray ( gl , cubeBufferInfo . attribs ! . instanceCenter , instanceCenters ) ;
371+ twgl . setAttribInfoBufferFromArray ( gl , cubeBufferInfo . attribs ! . instanceColor , instanceColors ) ;
372+ twgl . setAttribInfoBufferFromArray ( gl , cubeBufferInfo . attribs ! . instanceSize , instanceSizes ) ;
373+
374+ // --- Draw ---
275375 twgl . resizeCanvasToDisplaySize ( gl . canvas as HTMLCanvasElement ) ;
276- gl . viewport ( 0 , 0 , gl . canvas . width , gl . canvas . height ) ;
376+ gl . viewport ( 0 , 0 , gl . drawingBufferWidth , gl . drawingBufferHeight ) ;
377+ gl . useProgram ( progInfo . program ) ;
378+ gl . clearColor ( 0.1 , 0.1 , 0.1 , 1.0 ) ;
379+ gl . clear ( gl . COLOR_BUFFER_BIT | gl . DEPTH_BUFFER_BIT ) ;
380+ gl . enable ( gl . DEPTH_TEST ) ;
381+
382+ gl . bindVertexArray ( vao ) ;
383+ twgl . setUniforms ( progInfo , {
384+ u_view : twgl . m4 . lookAt ( [ 10 , 10 , 10 ] , [ 0 , 0 , 0 ] , [ 0 , 1 , 0 ] ) ,
385+ u_proj : twgl . m4 . perspective ( Math . PI / 4 , gl . canvas . width / gl . canvas . height , 0.1 , 1000 ) ,
386+ } ) ;
387+ twgl . drawBufferInfo ( gl , cubeBufferInfo , gl . TRIANGLES , cubeBufferInfo . numElements , 0 , cubeData . length ) ;
388+ gl . bindVertexArray ( null ) ;
277389
278390 requestAnimationFrame ( render ) ;
279391 } ;
280392
281393 requestAnimationFrame ( render ) ;
282-
283394} ;
284395
285- main ( ) ;
286-
287-
288396class PointBufferArray {
289397 private data : ArrayBuffer ;
290398 // data layout: positions(vec3float), colors(vec3float), scales(vec3float), speeds(int32), nextFreeOrSpawned(int32), directions(uint8)
0 commit comments