Skip to content

Commit a9c9332

Browse files
committed
Move marker atlas into points renderable, remove Texture2DArray from public exports
1 parent 9adfda3 commit a9c9332

3 files changed

Lines changed: 101 additions & 103 deletions

File tree

examples/points/main.ts

Lines changed: 4 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import {
66
Points,
77
OrthographicCamera,
88
OmeZarrImageSource,
9-
Texture2DArray,
109
LayerState,
1110
ImageLayer,
1211
Color,
@@ -60,8 +59,7 @@ class Particles extends Layer {
6059
public readonly type = "Particles";
6160
private readonly points_: vec3[] = [];
6261
private readonly color_: Color;
63-
private readonly markerIndex_: number = 0;
64-
private static markerAtlas_: Texture2DArray;
62+
private readonly marker_: Marker;
6563
private needsUpdate_: boolean = true;
6664
private depth_: number = 0;
6765

@@ -70,7 +68,7 @@ class Particles extends Layer {
7068
this.setState("initialized");
7169
this.points_ = points;
7270
this.color_ = Color.from(color);
73-
this.markerIndex_ = marker === "circle" ? 0 : marker === "square" ? 1 : 2;
71+
this.marker_ = marker;
7472
this.refreshPointsRenderable();
7573
this.setState("ready");
7674
}
@@ -100,81 +98,13 @@ class Particles extends Layer {
10098
position: p,
10199
color: Color.from([r / zScale, g / zScale, b / zScale]),
102100
size: 20.0 / zScale,
103-
markerIndex: this.markerIndex_,
101+
marker: this.marker_,
104102
};
105103
});
106-
const pointsRenderable = new Points(scaledPoints, Particles.markerAtlas);
104+
const pointsRenderable = new Points(scaledPoints);
107105
this.objects.length = 0;
108106
this.addObject(pointsRenderable);
109107
}
110-
111-
private static get markerAtlas() {
112-
if (!Particles.markerAtlas_) {
113-
Particles.markerAtlas_ = Particles.createMarkerAtlas();
114-
}
115-
return Particles.markerAtlas_;
116-
}
117-
118-
private static createMarkerAtlas() {
119-
const square = (size: number) => {
120-
const w = size;
121-
const h = size;
122-
const data = new Float32Array(w * h);
123-
data.fill(1.0);
124-
return data;
125-
};
126-
127-
const circle = (size: number) => {
128-
const w = size;
129-
const h = size;
130-
const data = new Float32Array(w * h);
131-
for (let i = 0; i < h; i++) {
132-
for (let j = 0; j < w; j++) {
133-
if ((i - h / 2) ** 2 + (j - w / 2) ** 2 < (w / 2) ** 2) {
134-
data[i * w + j] = 1.0;
135-
}
136-
}
137-
}
138-
return data;
139-
};
140-
141-
const triangle = (size: number) => {
142-
const w = size;
143-
const h = size;
144-
const data = new Float32Array(w * h);
145-
for (let i = 0; i < h; i++) {
146-
for (let j = 0; j < w; j++) {
147-
if (j >= (w - i) / 2 && j <= (w + i) / 2) {
148-
data[i * w + j] = 1.0;
149-
}
150-
}
151-
}
152-
return data;
153-
};
154-
155-
const SPRITE_SIZE = 256;
156-
const numMarkers = 3;
157-
const pixelsPerMarkerSprite = SPRITE_SIZE * SPRITE_SIZE;
158-
const data = new Float32Array(numMarkers * pixelsPerMarkerSprite);
159-
const squareData = square(SPRITE_SIZE);
160-
const circleData = circle(SPRITE_SIZE);
161-
const triangleData = triangle(SPRITE_SIZE);
162-
for (let i = 0; i < pixelsPerMarkerSprite; i++) {
163-
data[i] = circleData[i];
164-
data[i + pixelsPerMarkerSprite] = squareData[i];
165-
data[i + 2 * pixelsPerMarkerSprite] = triangleData[i];
166-
}
167-
168-
// TODO: this uses f32 values, which are not (by defualt) filterable in WebGL2
169-
// to enable this, we can check/add OES_texture_float_linear.
170-
// we also don't need the precision of f32 for this so I'd like to use an R8
171-
// texture instead, but our Texture class does not yet support it.
172-
const texture = new Texture2DArray(data, SPRITE_SIZE, SPRITE_SIZE);
173-
texture.wrapR = "clamp_to_edge";
174-
texture.wrapS = "clamp_to_edge";
175-
texture.wrapT = "clamp_to_edge";
176-
return texture;
177-
}
178108
}
179109
const ribosomes = new Particles(ribosomeLocations, Color.RED, "circle");
180110
const ferritin = new Particles(ferritinLocations, Color.GREEN, "triangle");

src/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,5 @@ export type { LayerState } from "./core/layer";
4040
export type { Overlay } from "./idetik";
4141
export type { SliceCoordinates } from "./data/chunk";
4242

43-
export { Texture2DArray } from "./objects/textures/texture_2d_array";
4443
export { Color } from "./math/color";
4544
export type { ColorLike } from "./math/color";

src/objects/renderable/points.ts

Lines changed: 97 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,45 @@
11
import { vec3 } from "gl-matrix";
22

3-
import { Color } from "../../math/color";
3+
import { Color, ColorLike } from "../../math/color";
44
import { RenderableObject } from "../../core/renderable_object";
55
import { Texture2DArray } from "../textures/texture_2d_array";
66
import { 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
917
type PointProperties = {
1018
position: vec3;
11-
color: Color;
19+
color: ColorLike;
1220
size: number;
13-
markerIndex: number;
21+
marker: Marker;
1422
};
1523

1624
export 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

Comments
 (0)