-
Notifications
You must be signed in to change notification settings - Fork 208
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
GPU Accelerated Point In Polygon Test (#1360)
* GPU Accelerated point in polygon test.
- Loading branch information
Showing
16 changed files
with
1,100 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
186 changes: 186 additions & 0 deletions
186
modules/experimental/src/gpgpu/point-in-polygon/gpu-point-in-polygon.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,186 @@ | ||
import GL from '@luma.gl/constants'; | ||
import {Buffer, Texture2D, assert} from '@luma.gl/webgl'; | ||
import {isWebGL2} from '@luma.gl/gltools'; | ||
import {Transform} from '@luma.gl/engine'; | ||
import {default as textureFilterModule} from './texture-filter'; | ||
import {POLY_TEX_VS, FILTER_VS} from './shaders'; | ||
const TEXTURE_SIZE = 512; | ||
import * as Polygon from './polygon'; | ||
|
||
export default class GPUPointInPolygon { | ||
constructor(gl, opts = {}) { | ||
this.gl = gl; | ||
assert(isWebGL2(gl)); // supports WebGL2 only | ||
this.textureSize = TEXTURE_SIZE; | ||
this._setupResources(); | ||
this.update(opts); | ||
} | ||
|
||
update({polygons, textureSize} = {}) { | ||
if (textureSize) { | ||
this.textureSize = textureSize; | ||
} | ||
if (!polygons || polygons.length === 0) { | ||
return; | ||
} | ||
|
||
const {vertices, indices, vertexCount, ids} = triangulatePolygons(polygons); | ||
this._updateResources(vertices, indices, ids, vertexCount); | ||
} | ||
|
||
filter({positionBuffer, filterValueIndexBuffer, count}) { | ||
this.filterTransform.update({ | ||
sourceBuffers: { | ||
a_position: positionBuffer | ||
}, | ||
feedbackBuffers: { | ||
filterValueIndex: filterValueIndexBuffer | ||
}, | ||
elementCount: count | ||
}); | ||
const {polygonTexture, boundingBox} = this; | ||
|
||
this.filterTransform.run({ | ||
moduleSettings: {boundingBox, texture: polygonTexture} | ||
}); | ||
} | ||
|
||
// PRIVATE | ||
|
||
_setupResources() { | ||
const {gl} = this; | ||
|
||
// texture to render polygons to | ||
this.polygonTexture = new Texture2D(gl, { | ||
format: GL.RGB, | ||
type: GL.UNSIGNED_BYTE, | ||
dataFormat: GL.RGB, | ||
border: 0, | ||
mipmaps: false, | ||
parameters: { | ||
[GL.TEXTURE_MAG_FILTER]: GL.NEAREST, | ||
[GL.TEXTURE_MIN_FILTER]: GL.NEAREST, | ||
[GL.TEXTURE_WRAP_S]: gl.CLAMP_TO_EDGE, | ||
[GL.TEXTURE_WRAP_T]: gl.CLAMP_TO_EDGE | ||
} | ||
}); | ||
this.positionBuffer = new Buffer(gl, {accessor: {type: GL.FLOAT, size: 2}}); | ||
this.idBuffer = new Buffer(gl, {accessor: {type: GL.FLOAT, size: 1}}); | ||
this.indexBuffer = new Buffer(gl, { | ||
target: GL.ELEMENT_ARRAY_BUFFER, | ||
accessor: {type: GL.UNSIGNED_SHORT} | ||
}); | ||
|
||
// transform to generate polygon texture | ||
this.polyTextureTransform = new Transform(gl, { | ||
id: `polygon-texture-creation-transform`, | ||
elementCount: 0, | ||
_targetTexture: this.polygonTexture, | ||
_targetTextureVarying: 'v_polygonColor', | ||
vs: POLY_TEX_VS, | ||
drawMode: GL.TRIANGLES, | ||
isIndexed: true, | ||
sourceBuffers: { | ||
a_position: this.positionBuffer, | ||
a_polygonID: this.idBuffer, | ||
indices: this.indexBuffer | ||
} | ||
}); | ||
|
||
// transform to perform filtering | ||
this.filterTransform = new Transform(gl, { | ||
id: 'filter transform', | ||
vs: FILTER_VS, | ||
modules: [textureFilterModule], | ||
varyings: ['filterValueIndex'] | ||
}); | ||
} | ||
|
||
_updateResources(vertices, indices, ids, vertexCount) { | ||
const boundingBox = getBoundingBox(vertices, vertexCount); | ||
const [xMin, yMin, xMax, yMax] = boundingBox; | ||
const width = xMax - xMin; | ||
const height = yMax - yMin; | ||
const whRatio = width / height; | ||
const {textureSize} = this; | ||
|
||
// calculate max texture size with same aspect ratio | ||
let texWidth = textureSize; | ||
let texHeight = textureSize; | ||
if (whRatio > 1) { | ||
texHeight = texWidth / whRatio; | ||
} else { | ||
texWidth = texHeight * whRatio; | ||
} | ||
|
||
this.boundingBox = boundingBox; | ||
this.polygonTexture.resize({width: texWidth, height: texHeight, mipmaps: false}); | ||
this.positionBuffer.setData(new Float32Array(vertices)); | ||
this.idBuffer.setData(new Float32Array(ids)); | ||
this.indexBuffer.setData(new Uint16Array(indices)); | ||
this.polyTextureTransform.update({ | ||
elementCount: indices.length, | ||
_targetTexture: this.polygonTexture | ||
}); | ||
|
||
this.polyTextureTransform.run({ | ||
uniforms: { | ||
boundingBoxOriginSize: [xMin, yMin, width, height] | ||
} | ||
}); | ||
} | ||
} | ||
|
||
// Helper methods | ||
|
||
function getBoundingBox(positions, vertexCount) { | ||
let yMin = Infinity; | ||
let yMax = -Infinity; | ||
let xMin = Infinity; | ||
let xMax = -Infinity; | ||
let y; | ||
let x; | ||
|
||
for (let i = 0; i < vertexCount; i++) { | ||
x = positions[i * 2]; | ||
y = positions[i * 2 + 1]; | ||
yMin = y < yMin ? y : yMin; | ||
yMax = y > yMax ? y : yMax; | ||
xMin = x < xMin ? x : xMin; | ||
xMax = x > xMax ? x : xMax; | ||
} | ||
|
||
return [xMin, yMin, xMax, yMax]; | ||
} | ||
|
||
function triangulatePolygons(polygons) { | ||
const SIZE = 2; | ||
const vertices = []; | ||
const indices = []; | ||
const ids = []; | ||
let count = 0; | ||
let polygonId = 0; | ||
for (let i = 0; i < polygons.length; i++) { | ||
const normalized = Polygon.normalize(polygons[i], SIZE); | ||
const curVertices = normalized.positions || normalized; | ||
const curCount = curVertices.length / SIZE; | ||
const curIds = new Array(curCount).fill(polygonId); | ||
vertices.push(...curVertices); | ||
ids.push(...curIds); | ||
const curIndices = Polygon.getSurfaceIndices(normalized, SIZE); | ||
const indexCount = curIndices.length; | ||
for (let j = 0; j < indexCount; j++) { | ||
curIndices[j] += count; | ||
} | ||
count += curCount; | ||
indices.push(...curIndices); | ||
polygonId++; | ||
} | ||
|
||
// UInt16 (UNSIGNED_SHORT) buffer is used for indices | ||
assert(count < 65536); // 0xFFFF | ||
|
||
const vertexCount = Polygon.getVertexCount(vertices, SIZE); | ||
|
||
return {vertices, indices, ids, vertexCount}; | ||
} |
81 changes: 81 additions & 0 deletions
81
modules/experimental/src/gpgpu/point-in-polygon/gpu-point-in-polygon.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
# GPUPointInPolygon (WebGL2) | ||
|
||
`GPUPointInPolygon` provides GPU accelerated PIP (Point-In-Polygon) testing functionality. A given set of 2D points and one or more 2D polygons, it computes, whether each point is inside or outside of any polygon. | ||
|
||
## Sample Usage | ||
```js | ||
|
||
// construct data into required formats | ||
const polygons = | ||
[[-0.5, -0.5], [0.5, -0.5], [0.5, 0.5], [-0.5, 0.5], [-0.5, -0.5]] // polygon vertices | ||
]; | ||
|
||
// XY locations of 6 points | ||
const points = [ | ||
0, 0, | ||
5.0, -0.25, | ||
0.25, -0.25, | ||
0.25, 8.25, | ||
-0.25, 0.25, | ||
-3.45, 0.45 | ||
]; | ||
|
||
const positionBuffer = new Buffer(gl2, new Float32Array(points)); | ||
const count = 6; | ||
// Allocate result buffer with enough space (2 floats for each point) | ||
const filterValueIndexBuffer = new Buffer(gl2, count * 2 * 4); | ||
|
||
const gpuPointInPolygon = new GPUPointInPolygon(gl2); | ||
gpuPointInPolygon.update({polygons}); | ||
gpuPointInPolygon.filter({positionBuffer, filterValueIndexBuffer, count}); | ||
|
||
const results = filterValueIndexBuffer.getData(); | ||
|
||
// results array contains 2 elements (filterValue, index) for each point, where | ||
// `filterValue` is '-1' if point in outside of polygons, otherwise index of the polygon in which it lies | ||
// `index` is the point index in `positionBuffer` | ||
|
||
``` | ||
|
||
## Constructor | ||
|
||
### GPUPointInPolygon(gl: WebGL2RenderingContext, props: Object) | ||
|
||
Creates a new `GPUPointInPolygon` object. | ||
|
||
* `gl` - (WebGL2RenderingContext) - WebGL2 context. | ||
* `opts.polygons` (`Array`, Optional) - Array of polygons, where each polygon is in following format: | ||
* `Simple polygon` : [[x1, y1], [x2, y2], ...] | ||
* `Polygon with holes` : [ | ||
[[x1, y1], [x2, y2], ...], // outer ring | ||
[[a1, b1], [a2, b2], ...], // hole - 1 | ||
[[s1, t1], [s2, t2], ...], // hole - 2 | ||
... | ||
] | ||
* `opts.textureSize` (`Number`, Optional) - Size of the texture to be used to create a polygon texture. Default value is 512. | ||
|
||
|
||
## Methods | ||
|
||
### update(opts) | ||
|
||
* `opts.polygons` (`Array`, Optional) - Array of polygons, where each polygon is in following format: | ||
* `Simple polygon` : [[x1, y1], [x2, y2], ...] | ||
* `Polygon with holes` : [ | ||
[[x1, y1], [x2, y2], ...] // outer ring | ||
[[a1, b1], [a2, b2], ...] // hole - 1 | ||
[[s1, t1], [s2, t2], ...] // hole - 2 | ||
] | ||
* `opts.textureSize` (`Number`, Optional) - Size of the texture to be used to create a polygon texture, that is used internally. Default value is 512. | ||
|
||
NOTE: Index of a polygon in `opts.polygons` array is its id, and it is used to identify which polygon a point lies in the result buffer (check `filterValueIndexBuffer below`). A maximum of 256 values are supported for polygon id, i.e its valid range is [0, 255], if `opts.polygons` size is more than 256, polygon id will be clamped to 255. | ||
|
||
### filter(opts) | ||
|
||
* `opts.positionBuffer` (`Buffer`) - Buffer object containing X, Y position of input points. | ||
* `opts.count` (`Number`) - Number of points to be processed. | ||
* `opts.filterValueIndexBuffer` (`Buffer`) - Buffer object to hold results for each input point. After the method is executed, this buffer contains two floats `filterValue` and `index` for each input point, where : | ||
* `filterValue` is '-1' if point in outside of polygons, else index of the polygon in which it lies | ||
* `index` is the point index in `positionBuffer` | ||
|
||
NOTE: If a point lies in the region that is overlapped by 2 or more polygons, `filterValue` will be index of one of the polygons. |
Oops, something went wrong.