Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- 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
189 changes: 189 additions & 0 deletions
189
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,189 @@ | ||
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; | ||
// WebGL2 only | ||
assert(isWebGL2(gl)); | ||
|
||
this.textureSize = opts.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, | ||
debug: true, | ||
sourceBuffers: { | ||
a_position: this.positionBuffer, | ||
a_polygonID: this.idBuffer, | ||
indices: this.indexBuffer // key doesn't matter | ||
} | ||
}); | ||
|
||
// transform to perform filtering | ||
this.filterTransform = new Transform(gl, { | ||
id: 'filter transform', | ||
vs: FILTER_VS, | ||
modules: [textureFilterModule], | ||
varyings: ['filterValueIndex'], | ||
debug: true | ||
}); | ||
} | ||
|
||
_updateResources(vertices, indices, ids, vertexCount) { | ||
const boundingBox = getBoundingBox(vertices, vertexCount); | ||
|
||
const width = boundingBox[2] - boundingBox[0]; | ||
const height = boundingBox[3] - boundingBox[1]; | ||
|
||
const whRatio = width / height; | ||
const {textureSize} = this; | ||
|
||
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 | ||
}); | ||
|
||
const [xMin, yMin, xMax, yMax] = boundingBox; | ||
this.polyTextureTransform.run({ | ||
uniforms: { | ||
boundingBoxOriginSize: [xMin, yMin, xMax - xMin, yMax - yMin] | ||
} | ||
}); | ||
} | ||
} | ||
|
||
// 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 curIds = new Float32Array(curVertices.length / SIZE).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 += curVertices.length / SIZE; | ||
indices.push(...curIndices); | ||
polygonId++; | ||
} | ||
|
||
const vertexCount = Polygon.getVertexCount(vertices, SIZE); | ||
|
||
return {vertices, indices, ids, vertexCount}; | ||
} |
78 changes: 78 additions & 0 deletions
78
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,78 @@ | ||
# 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, | ||
-0.25, -0.25, | ||
0.25, -0.25, | ||
0.25, 0.25, | ||
-0.25, 0.25, | ||
-0.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, that is used internally. 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. | ||
|
||
### 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.