11import { vec3 } from "gl-matrix" ;
22
3- import { Color } from "../../math/color" ;
3+ import { Color , ColorLike } from "../../math/color" ;
44import { RenderableObject } from "../../core/renderable_object" ;
55import { Texture2DArray } from "../textures/texture_2d_array" ;
66import { Geometry } from "../../core/geometry" ;
77
8+ type Marker = "circle" | "square" | "triangle" ;
9+
10+ const MARKER_INDEX : Record < Marker , number > = {
11+ circle : 0 ,
12+ square : 1 ,
13+ triangle : 2 ,
14+ } ;
15+
816// TODO: add a border (or "secondary") color to improve contrast against background
917type PointProperties = {
1018 position : vec3 ;
11- color : Color ;
19+ color : ColorLike ;
1220 size : number ;
13- markerIndex : number ;
21+ marker : Marker ;
1422} ;
1523
1624export class Points extends RenderableObject {
17- private atlas_ : Texture2DArray ;
18-
19- constructor ( points : PointProperties [ ] , markerAtlas : Texture2DArray ) {
25+ constructor ( points : PointProperties [ ] ) {
2026 super ( ) ;
2127 this . programName = "points" ;
22- this . atlas_ = markerAtlas ;
23-
24- points . forEach ( ( point ) => {
25- const marker = point . markerIndex ;
26- if ( marker < 0 || marker >= this . atlas_ . depth ) {
27- throw new Error (
28- `Markers must be in the range [0, ${ this . atlas_ . depth - 1 } ] (number of markers in atlas)`
29- ) ;
30- }
31- } ) ;
3228
33- const vertexData = points . flatMap ( ( point ) => [
34- point . position [ 0 ] ,
35- point . position [ 1 ] ,
36- point . position [ 2 ] ,
37- point . color . r ,
38- point . color . g ,
39- point . color . b ,
40- point . color . a ,
41- point . size ,
42- point . markerIndex ,
43- ] ) ;
29+ const vertexData = points . flatMap ( ( point ) => {
30+ const color = Color . from ( point . color ) ;
31+ return [
32+ point . position [ 0 ] ,
33+ point . position [ 1 ] ,
34+ point . position [ 2 ] ,
35+ color . r ,
36+ color . g ,
37+ color . b ,
38+ color . a ,
39+ point . size ,
40+ MARKER_INDEX [ point . marker ] ,
41+ ] ;
42+ } ) ;
4443 const geometry = new Geometry ( vertexData , [ ] , "points" ) ;
4544
4645 geometry . addAttribute ( {
@@ -65,10 +64,80 @@ export class Points extends RenderableObject {
6564 } ) ;
6665
6766 this . geometry = geometry ;
68- this . setTexture ( 0 , this . atlas_ ) ;
67+ this . setTexture ( 0 , getMarkerAtlas ( ) ) ;
6968 }
7069
7170 public get type ( ) {
7271 return "Points" ;
7372 }
7473}
74+
75+ let markerAtlas : Texture2DArray | undefined ;
76+
77+ // The marker atlas is a fixed set of sprites shared by all Points instances.
78+ // Each marker in `Marker` maps to a slice in the atlas (see `MARKER_INDEX`).
79+ function getMarkerAtlas ( ) : Texture2DArray {
80+ if ( ! markerAtlas ) {
81+ markerAtlas = createMarkerAtlas ( ) ;
82+ }
83+ return markerAtlas ;
84+ }
85+
86+ function createMarkerAtlas ( ) : Texture2DArray {
87+ const square = ( size : number ) => {
88+ const data = new Float32Array ( size * size ) ;
89+ data . fill ( 1.0 ) ;
90+ return data ;
91+ } ;
92+
93+ const circle = ( size : number ) => {
94+ const data = new Float32Array ( size * size ) ;
95+ for ( let i = 0 ; i < size ; i ++ ) {
96+ for ( let j = 0 ; j < size ; j ++ ) {
97+ if ( ( i - size / 2 ) ** 2 + ( j - size / 2 ) ** 2 < ( size / 2 ) ** 2 ) {
98+ data [ i * size + j ] = 1.0 ;
99+ }
100+ }
101+ }
102+ return data ;
103+ } ;
104+
105+ const triangle = ( size : number ) => {
106+ const data = new Float32Array ( size * size ) ;
107+ for ( let i = 0 ; i < size ; i ++ ) {
108+ for ( let j = 0 ; j < size ; j ++ ) {
109+ if ( j >= ( size - i ) / 2 && j <= ( size + i ) / 2 ) {
110+ data [ i * size + j ] = 1.0 ;
111+ }
112+ }
113+ }
114+ return data ;
115+ } ;
116+
117+ const SPRITE_SIZE = 256 ;
118+ const pixelsPerMarkerSprite = SPRITE_SIZE * SPRITE_SIZE ;
119+ const sprites : Record < Marker , Float32Array > = {
120+ circle : circle ( SPRITE_SIZE ) ,
121+ square : square ( SPRITE_SIZE ) ,
122+ triangle : triangle ( SPRITE_SIZE ) ,
123+ } ;
124+
125+ const numMarkers = Object . keys ( sprites ) . length ;
126+ const data = new Float32Array ( numMarkers * pixelsPerMarkerSprite ) ;
127+ for ( const [ marker , sprite ] of Object . entries ( sprites ) as [
128+ Marker ,
129+ Float32Array ,
130+ ] [ ] ) {
131+ data . set ( sprite , MARKER_INDEX [ marker ] * pixelsPerMarkerSprite ) ;
132+ }
133+
134+ // TODO: this uses f32 values, which are not (by default) filterable in WebGL2
135+ // to enable this, we can check/add OES_texture_float_linear.
136+ // we also don't need the precision of f32 for this so I'd like to use an R8
137+ // texture instead, but our Texture class does not yet support it.
138+ const texture = new Texture2DArray ( data , SPRITE_SIZE , SPRITE_SIZE ) ;
139+ texture . wrapR = "clamp_to_edge" ;
140+ texture . wrapS = "clamp_to_edge" ;
141+ texture . wrapT = "clamp_to_edge" ;
142+ return texture ;
143+ }
0 commit comments