import { adaptDPI } from "@thi.ng/adapt-dpi" ; import type { GroupInfo , GroupTuple } from "@thi.ng/ecs" ; import { ECS } from "@thi.ng/ecs/ecs" ; import { canvasWebGL } from "@thi.ng/hdom-components/canvas" ; import { start } from "@thi.ng/hdom/start" ; import { fract } from "@thi.ng/math/prec" ; import { ortho } from "@thi.ng/matrices/ortho" ; import type { Mat4Sym , Vec2Sym } from "@thi.ng/shader-ast" ; import { assign } from "@thi.ng/shader-ast/ast/assign" ; import { defMain } from "@thi.ng/shader-ast/ast/function" ; import { float , vec4 } from "@thi.ng/shader-ast/ast/lit" ; import { mul } from "@thi.ng/shader-ast/ast/ops" ; import { $y } from "@thi.ng/shader-ast/ast/swizzle" ; import { mix } from "@thi.ng/shader-ast/builtin/math" ; import { add2 } from "@thi.ng/vectors/add" ; import { addS2 } from "@thi.ng/vectors/adds" ; import { magSq2 } from "@thi.ng/vectors/magsq" ; import { magSqS2 } from "@thi.ng/vectors/magsqs" ; import { mixN2 } from "@thi.ng/vectors/mixn" ; import { mixNS2 } from "@thi.ng/vectors/mixns" ; import { mulN2 } from "@thi.ng/vectors/muln" ; import { mulNS2 } from "@thi.ng/vectors/mulns" ; import { normalize } from "@thi.ng/vectors/normalize" ; import { normalizeS2 } from "@thi.ng/vectors/normalizes" ; import { randNorm } from "@thi.ng/vectors/random" ; import { randNormS2 } from "@thi.ng/vectors/randoms" ; import { rotate } from "@thi.ng/vectors/rotate" ; import { rotateS2 } from "@thi.ng/vectors/rotates" ; import { setVN4 } from "@thi.ng/vectors/setvn" ; import type { GLMat4 , GLVec4 , ModelSpec , ShaderSpec } from "@thi.ng/webgl" ; import { BLEND_ADD } from "@thi.ng/webgl/api/blend" ; import { DrawMode } from "@thi.ng/webgl/api/model" ; import { compileModel } from "@thi.ng/webgl/buffer" ; import { draw } from "@thi.ng/webgl/draw" ; import { defShader } from "@thi.ng/webgl/shader" ; const BATCH_UPDATE = true ; const NUM = 100000 ; const W = 480 ; const W2 = W / 2 ; const ALPHA = 0.3 ; const COLOR = [ 1 , 0.7 , 0.1 , 0.001 ] ; const COLOR2 = [ 0.1 , 0.9 , 1 , 0.001 ] ; interface CompSpecs { pos : Float32Array ; vel : Float32Array ; } const ecs = new ECS < CompSpecs > ( { capacity : NUM } ) ; const pos = ecs . defComponent ( { id : "pos" , type : "f32" , size : 2 , } ) ! ; const vel = ecs . defComponent ( { id : "vel" , type : "f32" , size : 2 , default : ( ) => randNormS2 ( [ 0 , 0 ] ) , } ) ! ; const group = ecs . defGroup ( [ pos , vel ] ) ; for ( let i = 0 ; i < NUM ; i ++ ) { ecs . defEntity ( [ pos , vel ] ) ; } const dir = [ 0 , 0 ] ; const updateBatch = ( info , num , t , amp ) => { const { values : pos , stride : ps } = info . pos ; const { values : vel , stride : vs } = info . vel ; const invNum = 1 / num ; for ( let i = 0 ; i < num ; i ++ ) { const ip = i * ps ; const iv = i * vs ; const m = magSqS2 ( pos , ip ) ; rotateS2 ( pos , pos , m * amp , ip , ip ) ; if ( m < 4e4 ) { mixNS2 ( vel , vel , dir , 0.01 + 0.2 * fract ( ( i + t ) * invNum ) , iv , iv ) ; normalizeS2 ( vel , vel , 1 , iv , iv ) ; } else { mulNS2 ( pos , pos , 0.98 , ip , ip ) ; randNormS2 ( vel , 1 , undefined , iv ) ; } addS2 ( pos , pos , vel , ip , ip , iv ) ; } } ; const updateSingle = ( { pos , vel } , i , t , amp ) => { const m = magSq2 ( pos ) ; rotate ( pos , pos , m * amp ) ; if ( m < 4e4 ) { normalize ( vel , mixN2 ( vel , vel , dir , 0.01 + 0.2 * fract ( ( i + t ) / NUM ) ) ) ; } else { mulN2 ( pos , pos , 0.98 ) ; randNorm ( vel ) ; } add2 ( pos , pos , vel ) ; } ; const pointShader = { vs : ( gl , unis , ins , outs ) => [ defMain ( ( ) => [ assign ( outs . vcol , mix ( unis . color , unis . color2 , mul ( 0.006 , $y ( < Vec2Sym > ins . position ) ) ) ) , assign ( gl . gl_Position , mul ( < Mat4Sym > unis . proj , vec4 ( < Vec2Sym > ins . position , 0 , 1 ) ) ) , assign ( gl . gl_PointSize , float ( 2 ) ) , ] ) , ] , fs : ( gl , unis , ins , outs ) => [ defMain ( ( ) => [ assign ( outs . fragColor , ins . vcol ) ] ) , ] , attribs : { position : "vec2" , } , varying : { vcol : "vec4" , } , uniforms : { proj : "mat4" , color : "vec4" , color2 : "vec4" , } , state : { depth : false , blend : true , blendFn : BLEND_ADD , } , } ; const app = ( ) => { let model ; let targetDir = [ 0 , 0 ] ; const canvas = canvasWebGL ( { init : ( _ , gl ) => { model = compileModel ( gl , { attribs : { position : { data : < Float32Array > pos . packedValues ( ) , size : 2 , } , } , shader : defShader ( gl , pointShader ) , uniforms : { proj : < GLMat4 > ortho ( [ ] , - W2 , W2 , - W2 , W2 , 0 , 1 ) , color : COLOR , color2 : COLOR2 , } , num : NUM , mode : DrawMode . POINTS , } ) ; } , update : ( el , gl , __ , time ) => { if ( ! model ) { adaptDPI ( el , W , W ) ; return ; } time *= 0.001 ; mixN2 ( dir , dir , randNormS2 ( targetDir ) , 0.1 ) ; BATCH_UPDATE ? group . run ( updateBatch , time , Math . sin ( time / 8 ) * 4e-7 ) : group . forEach ( updateSingle , time , Math . sin ( time / 8 ) * 4e-7 ) ; model . attribs . position . buffer ! . set ( model . attribs . position . data ! ) ; const alpha = Math . pow ( Math . min ( time / 5 , 1 ) , 3 ) * ALPHA ; if ( alpha < 1 ) { const col1 = < GLVec4 > model . uniforms ! . color ; const col2 = < GLVec4 > model . uniforms ! . color2 ; setVN4 ( col1 , col1 , alpha ) ; setVN4 ( col2 , col2 , alpha ) ; } gl . clearColor ( 0 , 0 , 0 , 1 ) ; gl . clear ( gl . COLOR_BUFFER_BIT | gl . DEPTH_BUFFER_BIT ) ; draw ( model ) ; } , } ) ; return ( ) => [ "div.bg-black.vh-100.flex.flex-column.items-center.justify-center" , [ canvas , { width : W , height : W } ] , ] ; } ; start ( app ( ) ) ;