@cosmos.gl/graph 2.3.1-beta.1 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/.eslintrc +61 -0
  2. package/CHARTER.md +69 -0
  3. package/GOVERNANCE.md +21 -0
  4. package/dist/index.d.ts +46 -15
  5. package/dist/index.js +2986 -2701
  6. package/dist/index.js.map +1 -1
  7. package/dist/index.min.js +180 -62
  8. package/dist/index.min.js.map +1 -1
  9. package/dist/modules/GraphData/index.d.ts +18 -2
  10. package/dist/modules/Points/atlas-utils.d.ts +24 -0
  11. package/dist/modules/Points/index.d.ts +21 -2
  12. package/dist/modules/Store/index.d.ts +6 -1
  13. package/dist/stories/create-story.d.ts +5 -1
  14. package/dist/stories/shapes/image-example/index.d.ts +5 -0
  15. package/dist/stories/shapes.stories.d.ts +1 -0
  16. package/package.json +4 -4
  17. package/src/config.ts +1 -0
  18. package/src/declaration.d.ts +5 -0
  19. package/src/index.ts +119 -67
  20. package/src/modules/GraphData/index.ts +68 -6
  21. package/src/modules/Points/atlas-utils.ts +137 -0
  22. package/src/modules/Points/draw-highlighted.vert +3 -3
  23. package/src/modules/Points/draw-points.frag +106 -14
  24. package/src/modules/Points/draw-points.vert +51 -25
  25. package/src/modules/Points/find-points-on-area-selection.frag +6 -5
  26. package/src/modules/Points/index.ts +121 -13
  27. package/src/modules/Store/index.ts +11 -3
  28. package/src/stories/3. api-reference.mdx +48 -1
  29. package/src/stories/create-story.ts +32 -5
  30. package/src/stories/shapes/image-example/icons/box.png +0 -0
  31. package/src/stories/shapes/image-example/icons/lego.png +0 -0
  32. package/src/stories/shapes/image-example/icons/s.png +0 -0
  33. package/src/stories/shapes/image-example/icons/swift.png +0 -0
  34. package/src/stories/shapes/image-example/icons/toolbox.png +0 -0
  35. package/src/stories/shapes/image-example/index.ts +239 -0
  36. package/src/stories/shapes.stories.ts +12 -0
@@ -0,0 +1,137 @@
1
+ /**
2
+ * Creates a texture atlas from an array of ImageData objects.
3
+ *
4
+ * A texture atlas is a single large texture that contains multiple smaller images.
5
+ * This allows efficient rendering by reducing the number of texture bindings needed.
6
+ *
7
+ * The atlas uses a grid layout where each image gets a square region sized to
8
+ * accommodate the largest image dimension. Images are placed left-to-right, top-to-bottom.
9
+ *
10
+ * @param imageDataArray - Array of ImageData objects to pack into the atlas
11
+ * @param webglMaxTextureSize - WebGL maximum texture size limit (default: 16384)
12
+ * @returns Atlas data object containing:
13
+ * - atlasData: RGBA pixel data as Uint8Array
14
+ * - atlasSize: Total atlas texture size in pixels
15
+ * - atlasCoords: UV coordinates for each image as Float32Array
16
+ * - atlasCoordsSize: Grid size (number of rows/columns)
17
+ * Returns null if creation fails or no valid images provided
18
+ */
19
+ export function createAtlasDataFromImageData (
20
+ imageDataArray: ImageData[],
21
+ webglMaxTextureSize = 16384
22
+ ): {
23
+ atlasData: Uint8Array;
24
+ atlasSize: number;
25
+ atlasCoords: Float32Array;
26
+ atlasCoordsSize: number;
27
+ } | null {
28
+ // Step 1: Validate input - ensure we have images to process
29
+ if (!imageDataArray?.length) {
30
+ return null
31
+ }
32
+
33
+ // Step 2: Find the maximum dimension across all images
34
+ // The max dimension determines the size of each grid cell in the atlas
35
+ let maxDimension = 0
36
+ for (const imageData of imageDataArray) {
37
+ const dimension = Math.max(imageData.width, imageData.height)
38
+ if (dimension > maxDimension) {
39
+ maxDimension = dimension
40
+ }
41
+ }
42
+
43
+ // Step 3: Validate that we found valid image dimensions
44
+ if (maxDimension === 0) {
45
+ console.warn('Invalid image dimensions: all images have zero width or height')
46
+ return null
47
+ }
48
+
49
+ const originalMaxDimension = maxDimension
50
+
51
+ // Step 4: Calculate optimal atlas grid size
52
+ const atlasCoordsSize = Math.ceil(Math.sqrt(imageDataArray.length))
53
+ let atlasSize = atlasCoordsSize * maxDimension
54
+
55
+ // Step 5: Apply WebGL size limit scaling if necessary
56
+ let scalingFactor = 1.0
57
+
58
+ if (atlasSize > webglMaxTextureSize) {
59
+ // Calculate required scale to fit within WebGL limits
60
+ scalingFactor = webglMaxTextureSize / atlasSize
61
+
62
+ // Apply scaling to both the individual image dimensions and atlas size
63
+ maxDimension = Math.max(1, Math.floor(maxDimension * scalingFactor))
64
+ atlasSize = Math.max(1, Math.floor(atlasSize * scalingFactor))
65
+
66
+ console.warn(
67
+ '🖼️ Atlas scaling required: Original size ' +
68
+ `${(originalMaxDimension * atlasCoordsSize).toLocaleString()}px exceeds WebGL limit ` +
69
+ `${webglMaxTextureSize.toLocaleString()}px. Scaling down to ${atlasSize.toLocaleString()}px ` +
70
+ `(${Math.round(scalingFactor * 100)}% of original quality)`
71
+ )
72
+ }
73
+
74
+ // Step 6: Create buffers for atlas data
75
+ const atlasData = new Uint8Array(atlasSize * atlasSize * 4).fill(0)
76
+ const atlasCoords = new Float32Array(atlasCoordsSize * atlasCoordsSize * 4).fill(-1)
77
+
78
+ // Step 7: Pack each image into the atlas grid
79
+ for (const [index, imageData] of imageDataArray.entries()) {
80
+ const originalWidth = imageData.width
81
+ const originalHeight = imageData.height
82
+ if (originalWidth === 0 || originalHeight === 0) {
83
+ // leave coords at -1 for this index and continue
84
+ continue
85
+ }
86
+
87
+ // Calculate individual scale for this image based on maxDimension
88
+ // This ensures each image fits optimally within its grid cell
89
+ const individualScale = Math.min(1.0, maxDimension / Math.max(originalWidth, originalHeight))
90
+
91
+ const scaledWidth = Math.floor(originalWidth * individualScale)
92
+ const scaledHeight = Math.floor(originalHeight * individualScale)
93
+
94
+ // Calculate grid position (row, column) for this image
95
+ const row = Math.floor(index / atlasCoordsSize)
96
+ const col = index % atlasCoordsSize
97
+
98
+ // Calculate pixel position in the atlas texture
99
+ const atlasX = col * maxDimension
100
+ const atlasY = row * maxDimension
101
+
102
+ // Calculate and store UV coordinates for this image
103
+ atlasCoords[index * 4] = atlasX / atlasSize // minU
104
+ atlasCoords[index * 4 + 1] = atlasY / atlasSize // minV
105
+ atlasCoords[index * 4 + 2] = (atlasX + scaledWidth) / atlasSize // maxU
106
+ atlasCoords[index * 4 + 3] = (atlasY + scaledHeight) / atlasSize // maxV
107
+
108
+ // Copy image pixel data into the atlas texture
109
+ for (let y = 0; y < scaledHeight; y++) {
110
+ for (let x = 0; x < scaledWidth; x++) {
111
+ // Calculate source pixel coordinates (with scaling)
112
+ const srcX = Math.floor(x * (originalWidth / scaledWidth))
113
+ const srcY = Math.floor(y * (originalHeight / scaledHeight))
114
+
115
+ // Calculate source pixel index in the original image
116
+ const srcIndex = (srcY * originalWidth + srcX) * 4
117
+
118
+ // Calculate target pixel index in the atlas texture
119
+ const atlasIndex = ((atlasY + y) * atlasSize + (atlasX + x)) * 4
120
+
121
+ // Copy RGBA values from source to atlas
122
+ atlasData[atlasIndex] = imageData.data[srcIndex] ?? 0 // Red channel
123
+ atlasData[atlasIndex + 1] = imageData.data[srcIndex + 1] ?? 0 // Green channel
124
+ atlasData[atlasIndex + 2] = imageData.data[srcIndex + 2] ?? 0 // Blue channel
125
+ atlasData[atlasIndex + 3] = imageData.data[srcIndex + 3] ?? 255 // Alpha channel
126
+ }
127
+ }
128
+ }
129
+
130
+ // Return the complete atlas data
131
+ return {
132
+ atlasData,
133
+ atlasSize,
134
+ atlasCoords,
135
+ atlasCoordsSize,
136
+ }
137
+ }
@@ -16,7 +16,7 @@ uniform float maxPointSize;
16
16
  uniform vec4 color;
17
17
  uniform float universalPointOpacity;
18
18
  uniform float greyoutOpacity;
19
- uniform bool darkenGreyout;
19
+ uniform bool isDarkenGreyout;
20
20
  uniform vec4 backgroundColor;
21
21
  uniform vec4 greyoutColor;
22
22
  varying vec2 vertexPosition;
@@ -49,10 +49,10 @@ void main () {
49
49
  rgbColor = greyoutColor.rgb;
50
50
  pointOpacity = greyoutColor.a;
51
51
  } else {
52
- // If greyoutColor is not set, make color lighter or darker based on darkenGreyout
52
+ // If greyoutColor is not set, make color lighter or darker based on isDarkenGreyout
53
53
  float blendFactor = 0.65; // Controls how much to modify (0.0 = original, 1.0 = target color)
54
54
 
55
- if (darkenGreyout) {
55
+ if (isDarkenGreyout) {
56
56
  // Darken the color
57
57
  rgbColor = mix(rgbColor, vec3(0.2), blendFactor);
58
58
  } else {
@@ -4,10 +4,20 @@
4
4
  precision highp float;
5
5
  #endif
6
6
 
7
- // The color and alpha values from the vertex shader
8
- varying vec3 rgbColor;
9
- varying float alpha;
7
+ uniform float greyoutOpacity;
8
+ uniform float pointOpacity;
9
+ uniform sampler2D imageAtlasTexture;
10
+ uniform bool isDarkenGreyout;
11
+ uniform vec4 backgroundColor;
12
+
13
+
10
14
  varying float pointShape;
15
+ varying float isGreyedOut;
16
+ varying vec4 shapeColor;
17
+ varying vec4 imageAtlasUV;
18
+ varying float shapeSize;
19
+ varying float imageSizeVarying;
20
+ varying float overallSize;
11
21
 
12
22
  // Smoothing controls the smoothness of the point's edge
13
23
  const float smoothing = 0.9;
@@ -21,12 +31,31 @@ const float PENTAGON = 4.0;
21
31
  const float HEXAGON = 5.0;
22
32
  const float STAR = 6.0;
23
33
  const float CROSS = 7.0;
34
+ const float NONE = 8.0;
24
35
 
25
36
  // Distance functions for different shapes
26
37
  float circleDistance(vec2 p) {
27
38
  return dot(p, p);
28
39
  }
29
40
 
41
+ // Function to apply greyout logic to image colors
42
+ vec4 applyGreyoutToImage(vec4 imageColor) {
43
+ vec3 finalColor = imageColor.rgb;
44
+ float finalAlpha = imageColor.a;
45
+
46
+ if (isGreyedOut > 0.0) {
47
+ float blendFactor = 0.65; // Controls how much to modify (0.0 = original, 1.0 = target color)
48
+
49
+ if (isDarkenGreyout) {
50
+ finalColor = mix(finalColor, vec3(0.2), blendFactor);
51
+ } else {
52
+ finalColor = mix(finalColor, max(backgroundColor.rgb, vec3(0.8)), blendFactor);
53
+ }
54
+ }
55
+
56
+ return vec4(finalColor, finalAlpha);
57
+ }
58
+
30
59
  float squareDistance(vec2 p) {
31
60
  vec2 d = abs(p) - vec2(0.8);
32
61
  return length(max(d, 0.0)) + min(max(d.x, d.y), 0.0);
@@ -130,22 +159,85 @@ float getShapeDistance(vec2 p, float shape) {
130
159
  }
131
160
 
132
161
  void main() {
133
- // Discard the fragment if the point is fully transparent
134
- if (alpha == 0.0) { discard; }
162
+ // Discard the fragment if the point is fully transparent and has no image
163
+ if (shapeColor.a == 0.0 && imageAtlasUV.x == -1.0) {
164
+ discard;
165
+ }
166
+
167
+ // Discard the fragment if the point has no shape and no image
168
+ if (pointShape == NONE && imageAtlasUV.x == -1.0) {
169
+ discard;
170
+ }
135
171
 
136
172
  // Calculate coordinates within the point
137
173
  vec2 pointCoord = 2.0 * gl_PointCoord - 1.0;
174
+
175
+ vec4 finalShapeColor = vec4(0.0);
176
+ vec4 finalImageColor = vec4(0.0);
138
177
 
139
- float opacity;
140
- if (pointShape == CIRCLE) {
141
- // For circles, use the original distance calculation
142
- float pointCenterDistance = dot(pointCoord, pointCoord);
143
- opacity = alpha * (1.0 - smoothstep(smoothing, 1.0, pointCenterDistance));
178
+ // Handle shape rendering with centering logic
179
+ if (pointShape != NONE) {
180
+ // Calculate shape coordinates with centering
181
+ vec2 shapeCoord = pointCoord;
182
+ if (overallSize > shapeSize && shapeSize > 0.0) {
183
+ // Shape is smaller than overall size, center it
184
+ float scale = shapeSize / overallSize;
185
+ shapeCoord = pointCoord / scale;
186
+ }
187
+
188
+ float opacity;
189
+ if (pointShape == CIRCLE) {
190
+ // For circles, use the original distance calculation
191
+ float pointCenterDistance = dot(shapeCoord, shapeCoord);
192
+ opacity = 1.0 - smoothstep(smoothing, 1.0, pointCenterDistance);
193
+ } else {
194
+ // For other shapes, use the shape distance function
195
+ float shapeDistance = getShapeDistance(shapeCoord, pointShape);
196
+ opacity = 1.0 - smoothstep(-0.01, 0.01, shapeDistance);
197
+ }
198
+ opacity *= shapeColor.a;
199
+
200
+ finalShapeColor = vec4(shapeColor.rgb, opacity);
201
+ }
202
+
203
+ // Handle image rendering with centering logic
204
+ if (imageAtlasUV.x != -1.0) {
205
+ // Calculate image coordinates with centering
206
+ vec2 imageCoord = pointCoord;
207
+ if (overallSize > imageSizeVarying && imageSizeVarying > 0.0) {
208
+ // Image is smaller than overall size, center it
209
+ float scale = imageSizeVarying / overallSize;
210
+ imageCoord = pointCoord / scale;
211
+
212
+ // Check if we're outside the valid image area
213
+ if (abs(imageCoord.x) > 1.0 || abs(imageCoord.y) > 1.0) {
214
+ // We're outside the image bounds, don't render the image
215
+ finalImageColor = vec4(0.0);
216
+ } else {
217
+ // Sample from texture atlas
218
+ vec2 atlasUV = mix(imageAtlasUV.xy, imageAtlasUV.zw, (imageCoord + 1.0) * 0.5);
219
+ vec4 imageColor = texture2D(imageAtlasTexture, atlasUV);
220
+ finalImageColor = applyGreyoutToImage(imageColor);
221
+ }
222
+ } else {
223
+ // Image is same size or larger than overall size, no scaling needed
224
+ // Sample from texture atlas
225
+ vec2 atlasUV = mix(imageAtlasUV.xy, imageAtlasUV.zw, (imageCoord + 1.0) * 0.5);
226
+ vec4 imageColor = texture2D(imageAtlasTexture, atlasUV);
227
+ finalImageColor = applyGreyoutToImage(imageColor);
228
+ }
229
+ }
230
+
231
+ float finalPointAlpha = max(finalShapeColor.a, finalImageColor.a);
232
+ if (isGreyedOut > 0.0 && greyoutOpacity != -1.0) {
233
+ finalPointAlpha *= greyoutOpacity;
144
234
  } else {
145
- // For other shapes, use the shape distance function
146
- float shapeDistance = getShapeDistance(pointCoord, pointShape);
147
- opacity = alpha * (1.0 - smoothstep(-0.01, 0.01, shapeDistance));
235
+ finalPointAlpha *= pointOpacity;
148
236
  }
149
237
 
150
- gl_FragColor = vec4(rgbColor, opacity);
238
+ // Blend image color above point color
239
+ gl_FragColor = vec4(
240
+ mix(finalShapeColor.rgb, finalImageColor.rgb, finalImageColor.a),
241
+ finalPointAlpha
242
+ );
151
243
  }
@@ -6,29 +6,37 @@ attribute vec2 pointIndices;
6
6
  attribute float size;
7
7
  attribute vec4 color;
8
8
  attribute float shape;
9
+ attribute float imageIndex;
10
+ attribute float imageSize;
9
11
 
10
12
  uniform sampler2D positionsTexture;
11
13
  uniform sampler2D pointGreyoutStatus;
14
+ uniform sampler2D imageAtlasCoords;
12
15
  uniform float ratio;
13
16
  uniform mat3 transformationMatrix;
14
17
  uniform float pointsTextureSize;
15
18
  uniform float sizeScale;
16
19
  uniform float spaceSize;
17
20
  uniform vec2 screenSize;
18
- uniform float greyoutOpacity;
19
- uniform float pointOpacity;
21
+
20
22
  uniform vec4 greyoutColor;
21
23
  uniform vec4 backgroundColor;
22
24
  uniform bool scalePointsOnZoom;
23
25
  uniform float maxPointSize;
24
- uniform bool darkenGreyout;
26
+ uniform bool isDarkenGreyout;
25
27
  uniform bool skipSelected;
26
28
  uniform bool skipUnselected;
29
+ uniform bool hasImages;
30
+ uniform float imageCount;
31
+ uniform float imageAtlasCoordsTextureSize;
27
32
 
28
- varying vec2 textureCoords;
29
- varying vec3 rgbColor;
30
- varying float alpha;
31
33
  varying float pointShape;
34
+ varying float isGreyedOut;
35
+ varying vec4 shapeColor;
36
+ varying vec4 imageAtlasUV;
37
+ varying float shapeSize;
38
+ varying float imageSizeVarying;
39
+ varying float overallSize;
32
40
 
33
41
  float calculatePointSize(float size) {
34
42
  float pSize;
@@ -41,11 +49,10 @@ float calculatePointSize(float size) {
41
49
  return min(pSize, maxPointSize * ratio);
42
50
  }
43
51
 
44
- void main() {
45
- textureCoords = pointIndices;
46
-
52
+ void main() {
47
53
  // Check greyout status for selective rendering
48
- vec4 greyoutStatus = texture2D(pointGreyoutStatus, (textureCoords + 0.5) / pointsTextureSize);
54
+ vec4 greyoutStatus = texture2D(pointGreyoutStatus, (pointIndices + 0.5) / pointsTextureSize);
55
+ isGreyedOut = greyoutStatus.r;
49
56
  bool isSelected = greyoutStatus.r == 0.0;
50
57
 
51
58
  // Discard point based on rendering mode
@@ -61,7 +68,7 @@ void main() {
61
68
  }
62
69
 
63
70
  // Position
64
- vec4 pointPosition = texture2D(positionsTexture, (textureCoords + 0.5) / pointsTextureSize);
71
+ vec4 pointPosition = texture2D(positionsTexture, (pointIndices + 0.5) / pointsTextureSize);
65
72
  vec2 point = pointPosition.rg;
66
73
 
67
74
  // Transform point position to normalized device coordinates
@@ -70,32 +77,51 @@ void main() {
70
77
  vec3 finalPosition = transformationMatrix * vec3(normalizedPosition, 1);
71
78
  gl_Position = vec4(finalPosition.rg, 0, 1);
72
79
 
73
- gl_PointSize = calculatePointSize(size * sizeScale);
80
+ // Calculate sizes for shape and image
81
+ float shapeSizeValue = calculatePointSize(size * sizeScale);
82
+ float imageSizeValue = calculatePointSize(imageSize * sizeScale);
83
+
84
+ // Use the larger of the two sizes for the overall point size
85
+ float overallSizeValue = max(shapeSizeValue, imageSizeValue);
86
+ gl_PointSize = overallSizeValue;
87
+
88
+ // Pass size information to fragment shader
89
+ shapeSize = shapeSizeValue;
90
+ imageSizeVarying = imageSizeValue;
91
+ overallSize = overallSizeValue;
74
92
 
75
- rgbColor = color.rgb;
76
- alpha = color.a * pointOpacity;
93
+ shapeColor = color;
77
94
  pointShape = shape;
78
95
 
79
96
  // Adjust alpha of selected points
80
- if (greyoutStatus.r > 0.0) {
97
+ if (isGreyedOut > 0.0) {
81
98
  if (greyoutColor[0] != -1.0) {
82
- rgbColor = greyoutColor.rgb;
83
- alpha = greyoutColor.a;
99
+ shapeColor = greyoutColor;
84
100
  } else {
85
- // If greyoutColor is not set, make color lighter or darker based on darkenGreyout
101
+ // If greyoutColor is not set, make color lighter or darker based on isDarkenGreyout
86
102
  float blendFactor = 0.65; // Controls how much to modify (0.0 = original, 1.0 = target color)
87
103
 
88
- if (darkenGreyout) {
104
+ if (isDarkenGreyout) {
89
105
  // Darken the color
90
- rgbColor = mix(rgbColor, vec3(0.2), blendFactor);
106
+ shapeColor.rgb = mix(shapeColor.rgb, vec3(0.2), blendFactor);
91
107
  } else {
92
108
  // Lighten the color
93
- rgbColor = mix(rgbColor, max(backgroundColor.rgb, vec3(0.8)), blendFactor);
109
+ shapeColor.rgb = mix(shapeColor.rgb, max(backgroundColor.rgb, vec3(0.8)), blendFactor);
94
110
  }
95
111
  }
112
+ }
96
113
 
97
- if (greyoutOpacity != -1.0) {
98
- alpha *= greyoutOpacity;
99
- }
114
+ if (!hasImages || imageIndex < 0.0 || imageIndex >= imageCount) {
115
+ imageAtlasUV = vec4(-1.0);
116
+ return;
100
117
  }
101
- }
118
+ // Calculate image atlas UV coordinates based on imageIndex
119
+ float atlasCoordIndex = imageIndex;
120
+ // Calculate the position in the texture grid
121
+ float texX = mod(atlasCoordIndex, imageAtlasCoordsTextureSize);
122
+ float texY = floor(atlasCoordIndex / imageAtlasCoordsTextureSize);
123
+ // Convert to texture coordinates (0.0 to 1.0)
124
+ vec2 atlasCoordTexCoord = (vec2(texX, texY) + 0.5) / imageAtlasCoordsTextureSize;
125
+ vec4 atlasCoords = texture2D(imageAtlasCoords, atlasCoordTexCoord);
126
+ imageAtlasUV = atlasCoords;
127
+ }
@@ -9,7 +9,8 @@ uniform float spaceSize;
9
9
  uniform vec2 screenSize;
10
10
  uniform float ratio;
11
11
  uniform mat3 transformationMatrix;
12
- uniform vec2 selection[2];
12
+ uniform vec2 selection0;
13
+ uniform vec2 selection1;
13
14
  uniform bool scalePointsOnZoom;
14
15
  uniform float maxPointSize;
15
16
 
@@ -34,10 +35,10 @@ void main() {
34
35
  vec4 pSize = texture2D(pointSize, textureCoords);
35
36
  float size = pSize.r * sizeScale;
36
37
 
37
- float left = 2.0 * (selection[0].x - 0.5 * pointSizeF(size)) / screenSize.x - 1.0;
38
- float right = 2.0 * (selection[1].x + 0.5 * pointSizeF(size)) / screenSize.x - 1.0;
39
- float top = 2.0 * (selection[0].y - 0.5 * pointSizeF(size)) / screenSize.y - 1.0;
40
- float bottom = 2.0 * (selection[1].y + 0.5 * pointSizeF(size)) / screenSize.y - 1.0;
38
+ float left = 2.0 * (selection0.x - 0.5 * pointSizeF(size)) / screenSize.x - 1.0;
39
+ float right = 2.0 * (selection1.x + 0.5 * pointSizeF(size)) / screenSize.x - 1.0;
40
+ float top = 2.0 * (selection0.y - 0.5 * pointSizeF(size)) / screenSize.y - 1.0;
41
+ float bottom = 2.0 * (selection1.y + 0.5 * pointSizeF(size)) / screenSize.y - 1.0;
41
42
 
42
43
  gl_FragColor = vec4(0.0, 0.0, pointPosition.rg);
43
44
  if (final.x >= left && final.x <= right && final.y >= top && final.y <= bottom) {