Skip to content

Commit

Permalink
Merge cfa8c2b into 02a3488
Browse files Browse the repository at this point in the history
  • Loading branch information
1chandu committed Feb 12, 2020
2 parents 02a3488 + cfa8c2b commit d087a87
Show file tree
Hide file tree
Showing 16 changed files with 1,100 additions and 9 deletions.
14 changes: 9 additions & 5 deletions modules/engine/src/transform/texture-transform.js
Expand Up @@ -47,9 +47,11 @@ export default class TextureTransform {
}

getDrawOptions(opts = {}) {
const {sourceTextures, framebuffer, targetTexture} = this.bindings[this.currentIndex];
const {sourceTextures, framebuffer, targetTexture, sourceBuffers} = this.bindings[
this.currentIndex
];

const attributes = Object.assign({}, opts.attributes);
const attributes = Object.assign({}, sourceBuffers, opts.attributes);
const uniforms = Object.assign({}, opts.uniforms);
const parameters = Object.assign({}, opts.parameters);
let discard = opts.discard;
Expand Down Expand Up @@ -165,14 +167,14 @@ export default class TextureTransform {
}

_setupTextures(props = {}) {
const {_sourceTextures = {}, _targetTexture} = props;
const {_sourceTextures = {}, _targetTexture, sourceBuffers} = props;
const targetTexture = this._createTargetTexture({
sourceTextures: _sourceTextures,
textureOrReference: _targetTexture
});
this.hasSourceTextures =
this.hasSourceTextures || (_sourceTextures && Object.keys(_sourceTextures).length > 0);
this._updateBindings({sourceTextures: _sourceTextures, targetTexture});
this._updateBindings({sourceTextures: _sourceTextures, targetTexture, sourceBuffers});
if ('elementCount' in props) {
this._updateElementIDBuffer(props.elementCount);
}
Expand Down Expand Up @@ -211,14 +213,16 @@ export default class TextureTransform {
}

_updateBinding(binding, opts) {
const {sourceTextures, targetTexture} = opts;
const {sourceTextures, targetTexture, sourceBuffers} = opts;
if (!binding) {
binding = {
sourceBuffers: {},
sourceTextures: {},
targetTexture: null
};
}
Object.assign(binding.sourceTextures, sourceTextures);
Object.assign(binding.sourceBuffers, sourceBuffers);
if (targetTexture) {
binding.targetTexture = targetTexture;

Expand Down
2 changes: 1 addition & 1 deletion modules/engine/src/transform/transform.js
Expand Up @@ -153,8 +153,8 @@ export default class Transform {

function canCreateBufferTransform(props) {
if (
!isObjectEmpty(props.sourceBuffers) ||
!isObjectEmpty(props.feedbackBuffers) ||
!isObjectEmpty(props.feedbackMap) ||
(props.varyings && props.varyings.length > 0)
) {
return true;
Expand Down
3 changes: 2 additions & 1 deletion modules/experimental/package.json
Expand Up @@ -32,7 +32,8 @@
"dependencies": {
"@loaders.gl/images": "^2.0.0",
"@luma.gl/constants": "8.1.0-alpha.2",
"math.gl": "^3.1.2"
"math.gl": "^3.1.2",
"earcut": "^2.0.6"
},
"peerDependencies": {
"@loaders.gl/gltf": "^2.0.0",
Expand Down
@@ -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};
}
@@ -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.

0 comments on commit d087a87

Please sign in to comment.