Skip to content

Instantly share code, notes, and snippets.

@otmb
Created December 4, 2018 09:15
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save otmb/a27bf947b08dac1ed8179bf227073192 to your computer and use it in GitHub Desktop.
Save otmb/a27bf947b08dac1ed8179bf227073192 to your computer and use it in GitHub Desktop.

Web Pointcloud

Installation

Build zstd(wasm)

References

https://threejs.org/examples/?q=point#webgl_buffergeometry_points

Download

$ wget https://github.com/facebook/zstd/archive/v1.3.1.tar.gz
$ tar xvf v1.3.1.tar.gz
$ mv zstd-1.3.1{,-emscripten}
$ cd zstd-1.3.1-emscripten

Build

$ emmake make lib-release 
$ file lib/libzstd.dylib 
$ mv lib/libzstd.{dylib,bc}
$ echo 'module.exports = Module;' | cat > export_module.js

$ emcc -o zstd.js -O0 \
-s WASM=1 \
-s "MODULARIZE=1" \
-s EXPORTED_FUNCTIONS="['_ZSTD_isError', '_ZSTD_getFrameContentSize', '_ZSTD_decompress']" \
-s EXTRA_EXPORTED_RUNTIME_METHODS="['cwrap','ccall']" \
-s 'EXPORT_NAME="ZstdLib"' \
lib/libzstd.bc
"use strict";
importScripts('/static/zstd.min.js','/static/zstd-codec.js','/static/half.min.js');
var zstd;
ZstdLib().then(function(Module) {
zstd = new ZstdCodec(Module);
});
self.addEventListener('message', function(e) {
if (zstd == null){
return;
}
var data = e.data;
var head = new Uint8Array(data.slice(0,1));
if (head[0] != 11)
return;
var datLen = new Uint32Array(data.slice(2,6));
let compressed_bytes = new Uint8Array(data.slice(6,datLen + 6));
var buf = zstd.decompress(compressed_bytes);
if (!buf)
return;
var pLen = buf.length / 3 * 2;
var u16arr = new Uint16Array(buf.slice(0,pLen).buffer,0, pLen/2);
var vertices = new Float32Array(u16arr.length);
var colors = new Float32Array(u16arr.length);
for(var i=0; i < vertices.length; i++){
colors[i] = buf[pLen + i] / 255;
vertices[i] = fromHalf(u16arr[i]);
}
self.postMessage({'colors': colors,'vertices': vertices});
});
// from http://stackoverflow.com/questions/6162651/half-precision-floating-point-in-java/6162687#6162687
var toHalf = (function() {
var floatView = new Float32Array(1);
var int32View = new Int32Array(floatView.buffer);
return function toHalf(fval) {
floatView[0] = fval;
var fbits = int32View[0];
var sign = (fbits >> 16) & 0x8000; // sign only
var val = (fbits & 0x7fffffff) + 0x1000; // rounded value
if (val >= 0x47800000) { // might be or become NaN/Inf
if ((fbits & 0x7fffffff) >= 0x47800000) {
// is or must become NaN/Inf
if (val < 0x7f800000) { // was value but too large
return sign | 0x7c00; // make it +/-Inf
}
return sign | 0x7c00 | // remains +/-Inf or NaN
(fbits & 0x007fffff) >> 13; // keep NaN (and Inf) bits
}
return sign | 0x7bff; // unrounded not quite Inf
}
if (val >= 0x38800000) { // remains normalized value
return sign | val - 0x38000000 >> 13; // exp - 127 + 15
}
if (val < 0x33000000) { // too small for subnormal
return sign; // becomes +/-0
}
val = (fbits & 0x7fffffff) >> 23; // tmp exp for subnormal calc
return sign | ((fbits & 0x7fffff | 0x800000) // add subnormal bit
+ (0x800000 >>> val - 102) // round depending on cut off
>> 126 - val); // div by 2^(1-(exp-127+15)) and >> 13 | exp=0
};
}());
function FP32() {
var floatView = new Float32Array(1);
var uint32View = new Uint32Array(floatView.buffer);
return {
u: uint32View,
f: floatView,
};
}
// from https://gist.github.com/rygorous/2156668
var fromHalf = (function() {
const shifted_exp = 0x7c00 << 13; // exponent mask after shift
const magic = new FP32();
magic.u[0] = 113 << 23;
const o = new FP32();
return function(v) {
o.u[0] = (v & 0x7fff) << 13; // exponent/mantissa bits
const exp = shifted_exp & o.u[0]; // just the exponent
o.u[0] += (127 - 15) << 23; // exponent adjust
// handle exponent special cases
if (exp === shifted_exp) { // Inf/NaN?
o.u[0] += (128 - 16) << 23; // extra exp adjust
} else if (exp == 0) { // Zero/Denormal?
o.u[0] += 1 << 23; // extra exp adjust
o.f[0] -= magic.f[0]; // renormalize
}
o.u[0] |= (v & 0x8000) << 16; // sign bit
return o.f[0];
};
}());
<!doctype HTML>
<html lang="en">
<head>
<title>PointCloud Example</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<style>
body {
color: #cccccc;
font-family:Monospace;
font-size:13px;
text-align:center;
background-color: #050505;
margin: 0px;
overflow: hidden;
}
#info {
position: absolute;
top: 0px; width: 100%;
padding: 5px;
}
a {
color: #0080ff;
}
</style>
<script type="text/javascript">
var woker = new Worker('/static/doWork.js');
woker.addEventListener('message', function(e){
addPointCloud(e.data.vertices,e.data.colors);
},false);
window.onload = function () {
var conn;
var msg = document.getElementById("msg");
//var isUpdate = false;
if (window["WebSocket"]) {
conn = new WebSocket("ws://localhost:8080/ws");
conn.binaryType = 'arraybuffer';
conn.onclose = function (evt) {
console.log("Connection closed");
};
conn.onmessage = function (evt) {
var data = evt.data;
if (data.byteLength > 2000){
woker.postMessage(data);
}
};
} else {
var item = document.createElement("div");
item.innerHTML = "<b>Your browser does not support WebSockets.</b>";
appendLog(item);
}
};
</script>
</head>
<body>
<div id="container"></div>
<script src="/static/three.js"></script>
<script src="/static/js/Detector.js"></script>
<script src="/static/js/libs/stats.min.js"></script>
<script>
init();
animate();
if ( ! Detector.webgl ) Detector.addGetWebGLMessage();
var container, stats;
var camera, scene, renderer;
var points, material, geometry;
var animationID;
function init() {
isRun = true;
container = document.getElementById( 'container' );
//camera = new THREE.PerspectiveCamera( 27, window.innerWidth / window.innerHeight, 5, 1000 );
camera = new THREE.PerspectiveCamera( 40, window.innerWidth / window.innerHeight, 0.3, 1000 );
camera.position.z = -1;
camera.lookAt(new THREE.Vector3(0, 0, 0));
scene = new THREE.Scene();
scene.background = new THREE.Color( 0x050505 );
scene.fog = new THREE.Fog( 0x050505, 2000, 3500 );
renderer = new THREE.WebGLRenderer();
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.context.canvas.addEventListener("webglcontextlost", function(event) {
isRun = false;
event.preventDefault();
var cancelAnimationFrame =
window.cancelAnimationFrame ||
window.mozCancelAnimationFrame;
cancelAnimationFrame(animationID);
}, false);
renderer.context.canvas.addEventListener("webglcontextrestored", function(event) {
isRun = true;
}, false);
container.appendChild( renderer.domElement );
material = new THREE.PointsMaterial( { size: 0.010, vertexColors: THREE.VertexColors } );
stats = new Stats();
container.appendChild( stats.dom );
window.addEventListener( 'resize', onWindowResize, false );
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
}
function addPointCloud(vertices, colors){
if (!isRun) return;
if (geometry != null)
geometry.dispose();
geometry = new THREE.BufferGeometry();
geometry.addAttribute( 'position', new THREE.Float32BufferAttribute( vertices, 3 ) );
geometry.addAttribute( 'color', new THREE.Float32BufferAttribute( colors, 3 ) );
geometry.computeBoundingSphere();
scene.remove(points);
points = new THREE.Points( geometry, material );
scene.add(points);
}
function animate() {
animationID = requestAnimationFrame( animate );
render();
stats.update();
}
function render() {
renderer.render( scene, camera );
}
</script>
</body>
</html>
"use strict";
class ZstdCodec {
constructor(Module) {
this.zstd = Module;
this.ZSTD_isError = this.zstd.cwrap('ZSTD_isError', 'number', ['number']);
this.ZSTD_getFrameContentSize = this.zstd.cwrap('ZSTD_getFrameContentSize', 'number', ['array', 'number']);
this.ZSTD_decompress = this.zstd.cwrap('ZSTD_decompress', 'number', ['number', 'number', 'array', 'number']);
}
isError(zstd_rc) {
return this.ZSTD_isError(zstd_rc);
}
getFrameContentSize(zstd_bytes) {
const content_size = this.ZSTD_getFrameContentSize(zstd_bytes, zstd_bytes.length);
const content_size_limit = 10 * 1024 * 1024;
return content_size <= content_size_limit ? content_size : null;
}
decompress(zstd_bytes) {
const content_size = this.getFrameContentSize(zstd_bytes);
if (!content_size) return null;
const heap = this.zstd._malloc(content_size);
try {
const decompress_rc = this.ZSTD_decompress(heap, content_size, zstd_bytes, zstd_bytes.length);
if (this.isError(decompress_rc) || decompress_rc != content_size) return null;
return new Uint8Array(this.zstd.HEAPU8.buffer, heap, content_size);
} finally {
this.zstd._free(heap);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment