@sohale/implisolid
Version:
A Solid Modelling Kernel in Javascript based on Implicit Surfaces, for MP5 file format
539 lines (418 loc) • 21.1 kB
JavaScript
'use strict';
function init(service) {
'use strict';
//main = Module.cwrap('main', 'number', []);
//var service={}; //= newProducer //is an interface
service.build_geometry = Module.cwrap('build_geometry', null, [ 'string', 'string']);
service.get_v_size = Module.cwrap('get_v_size', 'number', []);
service.get_f_size = Module.cwrap('get_f_size', 'number', []);
service.get_v = Module.cwrap('get_v', null, ['number']);
service.get_f = Module.cwrap('get_f', null, ['number']);
service.get_v_ptr = Module.cwrap('get_v_ptr', 'number', []);
service.get_f_ptr = Module.cwrap('get_f_ptr', 'number', []);
service.finish_geometry = Module.cwrap('finish_geometry', null, []);
service.set_object = Module.cwrap('set_object', 'number', ['string', 'number']);
service.unset_object = Module.cwrap('unset_object', 'number', ['number']);
service.set_x = Module.cwrap('set_x', 'number', ['number', 'number']); // sends the x points for evaluation of implicit or gradient
service.unset_x = Module.cwrap('unset_x', null, []);
service.calculate_implicit_values = Module.cwrap('calculate_implicit_values', null, []);
service.get_values_ptr = Module.cwrap('get_values_ptr', 'number', []);
service.get_values_size = Module.cwrap('get_values_size', 'number', []);
service.calculate_implicit_gradients = Module.cwrap('calculate_implicit_gradients', null, ['number']); // boolean argument to normalize and reverse the vevtors, suitable for rendering.
service.get_gradients_ptr = Module.cwrap('get_gradients_ptr', 'number', []);
service.get_gradients_size = Module.cwrap('get_gradients_size', 'number', []);
service.get_pointset_ptr = Module.cwrap('get_pointset_ptr', 'number', ['string']);
service.get_pointset_size = Module.cwrap('get_pointset_size', 'number', ['string']);
if (Module["_about"]) {
service.about = Module.cwrap('about', null, []);
} else {
service.about = "C++ method problem: no about()";
console.error("C++ method problem: a correct version not found");
}
/*
try {
service.about = Module.cwrap('about', null, []);
} catch(err) {
service.about = "C++ method problem";
console.log("C++ method problem: not found");
}
*/
service.init = function(){
service.needs_deallocation = false;
this.use_II = true;
this.use_II_qem = true;
}
service.finish_with = function (){
//after the last round.
if(!this.needs_deallocation){
console.error("cannot `finish_geometry()`. Geometry not produced.");
}
service.finish_geometry();
service.needs_deallocation = false;
}
service.set_vect = function (float32Array) {
// Accesses module
const _FLOAT_SIZE = Float32Array.BYTES_PER_ELEMENT;
if (float32Array.length % 3 != 0) {console.error("bad input array");};
var nverts = float32Array.length / 3;
var verts_space = Module._malloc(_FLOAT_SIZE*3*nverts);
Module.HEAPF32.subarray(verts_space/_FLOAT_SIZE, verts_space/_FLOAT_SIZE + 3*nverts).set(float32Array);
var result = this.set_x(verts_space, nverts);
console.log("result: "+result);
if (!result) {
console.error("Something went wrong: ", result);
}
Module._free( verts_space );
};
service.update_geometry = function(geometry, ignoreNormals) {
var implicit_service = this;
const _FLOAT_SIZE = Float32Array.BYTES_PER_ELEMENT;
const _INT_SIZE = Uint32Array.BYTES_PER_ELEMENT;
const POINTS_PER_FACE = 3;
var nverts = implicit_service.get_v_size();
var nfaces = implicit_service.get_f_size();
if(nfaces > 0){
var verts_address = implicit_service.get_v_ptr();
var faces_address = implicit_service.get_f_ptr();
var verts = Module.HEAPF32.subarray(
verts_address/_FLOAT_SIZE,
verts_address/_FLOAT_SIZE + 3*nverts);
var faces = Module.HEAPU32.subarray(
faces_address/_INT_SIZE,
faces_address/_INT_SIZE + nfaces * POINTS_PER_FACE);
}
else{
console.log("empty implicit");
var verts = new Float32Array([0,0,0, 1,0,0, 1,1,0, 0,1,0, 0,0,1, 1,0,1, 1,1,1, 0,1,1 ]);
var faces = new Uint32Array([0,1,2, 0,2,3, 0,4,5, 0,5,1, 1,5,6, 1,6,2, 2,6,3, 3,6,7, 4,5,6, 5,6,7]);
}
return geometry.update_geometry1(verts, faces, ignoreNormals, false);
};
//was: geom. update_normals (service ..)
service.make_normals_into_geometry = function(geom, mp5_str, x, ignore_root_matrix) {
geom.removeAttribute('normal');
geom.computeVertexNormals();
geom.__set_needsUpdate_flag(false);
return;
const _FLOAT_SIZE = Float32Array.BYTES_PER_ELEMENT;
// console.error(x);
/*
for( var i = 0 ; i < x.length; i++) {
x[i] += Math.random() * 0.9;
}
*/
service.set_object(mp5_str, ignore_root_matrix);
service.set_vect(x); // overhead
service.calculate_implicit_gradients(true); // Why TRUE doe snot have any effect?
var ptr = service.get_gradients_ptr();
var ptr_len = service.get_gradients_size();
var gradients = Module.HEAPF32.subarray(ptr/_FLOAT_SIZE, ptr/_FLOAT_SIZE + ptr_len);
//console.log("grad len = " + ptr_len+ " grad = " + gradients); // x 4
//for( var i = 0 ; i < ptr_len; i++) {
// gradients[i] += Math.random() * 0.2;
//}
geom.update_normals_from_array(gradients);
service.unset_x();
service.unset_object();
};
service.init();
return service;
}
/*
Function: query_implicit_values(shape_str, points, reduce_callback)
Evaluates the points **points** in the implicit function. The function is specified as a mp5-fragment (json) string **shape_str**.
The values are not directly returned. Instead, a callback is used to prepare a value.
The reason a callback is used is that the resources need to be freed.
Returned value:
The value returned us the value returned by the callback reduce_callback.
In examples below, the return value is:
- Example1: returns Float32Array of implicit values in the following usage.
- Example2: returns true for collision, false for no collision,
Example 1:
var my_shape = mainModel.root.sons[#nr]
var verts_array = new Float32Array([x1,y1,z1, x2,y2,z2, ..., xn,yn,zn])
var implicit_vals = query_implicit_values(my_shape, points, // false
reduce_callback = function (values_tarray) {
// here we create a view on the HEAP from position
// ( ptr / _FLOAT_SIZE ) to position (ptr / _FLOAT_SIZE + ptr_len)
var result = new Float32Array(values_tarray); // clones (makes a copy of) data
return result;
});
Example 2:
var my_shape = mainModel.root.sons[#nr]
var verts_array = new Float32Array([x1,y1,z1, x2,y2,z2, ..., xn,yn,zn])
var has_positive_vals = query_implicit_values(my_shape, verts_array, // true
function (values_tarray) {
var found_positive_val = false;
for (var i=0; i< values_tarray.length; i++)
{
if (values_tarray[i] >= - CONFIG.collision_detection.epsilon)
{
found_positive_val = true;
break;
}
}
var result = found_positive_val;
return result;
});
Implementation Details:
1. We do not have direct access to C++ objects from Javascript, so we call the next functions
set_object: sets the object specified by mp5_str as the current object for any calculation to follow until it gets freed by unset_object
set_x: does something similar but for the input x .
2. There is a repeating pattern taking place:
JS asks for C++ pointers, and the pointers' data is accessed using HEAPF32(start, end)
3. Don't forget to Module._free() andter Module._alloc().
*/
function _query_implicit_values(mp5_str, points, reduce_callback)
{
assert(points instanceof Float32Array); /* simple check */
assert(typeof reduce_callback === 'function'); /* the callback that produces the return value of the function */
/* Declarations */
var _FLOAT_SIZE = Float32Array.BYTES_PER_ELEMENT; /* We ll need the _FLOAT_SIZE in bytes, when we deal with allocations and HEAPF32*/
var nverts = points.length / 3; /* the number of vertices for which we want to calculate the implicit value */
var verts_space_address = Module._malloc(_FLOAT_SIZE * 3 * nverts); /* This allocates space in the C++ side, in order to create the array of vertices. */
var ignore_root_matrix = false;
/* body */
Module.HEAPF32.subarray(verts_space_address / _FLOAT_SIZE, verts_space_address / _FLOAT_SIZE + 3 * nverts
).set(points);
var obj_id = IMPLICIT.set_object(mp5_str, ignore_root_matrix); /* Allocations on the C++ side */
var success = IMPLICIT.set_x(verts_space_address, nverts);
//todo: rename "success"
if (!success){
console.log("set_x returned false . Probably an error in memory allocation. nverts was: " + nverts);
IMPLICIT.unset_object(obj_id);
return false;
}
// IMPLICIT.set_x_with_matrix(verts_space_address, nverts, matrix); /* can create a function like this so we dont have the matrix issue */
IMPLICIT.calculate_implicit_values(); /* The actual implicit value calculation*/
var ptr = IMPLICIT.get_values_ptr(); /* retrieve a pointer to the position in memory of the calculated array */
var ptr_len = IMPLICIT.get_values_size()
var values_tarray = Module.HEAPF32.subarray(ptr / _FLOAT_SIZE , ptr / _FLOAT_SIZE + ptr_len );
/*
var found_positive_val = false;
if (typeof return_boolean === 'boolean' && return_boolean === true)
{
for (var i=0; i< values_tarray.length; i++)
{
if (values_tarray[i] >= - CONFIG.collision_detection.epsilon)
{
found_positive_val = true;
break;
}
}
result = found_positive_val;
} else if (typeof return_boolean === 'boolean' && return_boolean === false)
{
var result = new Float32Array(values_tarray); // here we create a view on the HEAP from position
// ( ptr / _FLOAT_SIZE ) to position (ptr / _FLOAT_SIZE + ptr_len)
} else if (typeof return_boolean === 'function') {
var reduce_callback = return_boolean;
var result = reduce_callback(values_tarray);
}
*/
var result = reduce_callback(values_tarray);
//Bug! Forgot to FREE!!!
Module._free( verts_space_address );
IMPLICIT.unset_object(obj_id); /* Free allocated C++ memory */
IMPLICIT.unset_x();
return result;
}
var ImplicitService = function(){
init(this);
this.make_geometry = function (shape_params, mc_params) {
var startTime = new Date();
const _FLOAT_SIZE = Float32Array.BYTES_PER_ELEMENT;
const _INT_SIZE = Uint32Array.BYTES_PER_ELEMENT
if(this.needs_deallocation) {
this.finish_geometry();
this.needs_deallocation = false;
}
//console.log("mc_params.resolution " + mc_params.resolution);
//mc_params.resolution = 40;
var mp5_str = JSON.stringify(shape_params);
this.build_geometry(mp5_str, JSON.stringify(mc_params));
this.needs_deallocation = true;
var nverts = this.get_v_size();
var nfaces = this.get_f_size();
var verts_address = this.get_v_ptr();
var faces_address = this.get_f_ptr();
var verts = Module.HEAPF32.subarray(verts_address/_FLOAT_SIZE, verts_address/_FLOAT_SIZE + 3*nverts);
var faces = Module.HEAPU32.subarray(faces_address/_INT_SIZE, faces_address/_INT_SIZE + 3*nfaces);
var allocate_buffers = true;
var geom = new LiveBufferGeometry79(verts, faces, allocate_buffers);
// Set the normals
var ignore_root_matrix = mc_params.ignore_root_matrix; // Does not need other (MC-related) arguments.
//geom.update_normals(this, verts, mp5_str, ignore_root_matrix); // Evaluates the implicit function and sets the goemetry's normals based on it.
this.make_normals_into_geometry(geom, mp5_str, verts, ignore_root_matrix); // Evaluates the implicit function and sets the goemetry's normals based on it.
//this.aaaaaaaaaA(verts);
var endTime = new Date();
var timeDiff = endTime - startTime;
//report_time(timeDiff, function(){hist();});
return geom;
};
/*
this.aaaaaaaaaA(x, mp5_str, ignore_root_matrix) {
// var x = new Float32Array(nverts);
this.set_object(mp5_str, ignore_root_matrix);
this.set_vect(x); // overhead
this.calculate_implicit_gradients();
var ptr = this.get_gradients_ptr();
var ptr_len = this.get_gradients_size();
var gradients = Module.HEAPF32.subarray(ptr/_FLOAT_SIZE, ptr/_FLOAT_SIZE + ptr_len);
//console.log("grad len = " + ptr_len+ " grad = " + gradients); // x 4
geom.update_normals(gradients);
this.unset_x();
this.unset_object();
}
*/
//This method is called by the designer to obtain the geometry from the ImplicitService
this.getLiveGeometry = function(dict, bbox, ignore_root_matrix) {
//var mc_properties = {resolution: 28, box: {xmin: -1, xmax: 1, ymin: -1, ymax: 1, zmin: -1, zmax: 1}};
//var shape_properties = {type:"sphere",displayColor:{x:0.38015037447759337,y:0.6015094592616681,z:0.9774198226067741},matrix:[10,0,0,92.9405888205127,0,10,0,101.93969389296757,0,0,10,8.59828143220919,0,0,0,1],index:7935813}
//var shape_properties = {type:"sphere",displayColor:{x:0.38015037447759337,y:0.6015094592616681,z:0.9774198226067741},matrix:[10,0,0,92.9405888205127,0,10,0,101.93969389296757,0,0,10,8.59828143220919,0,0,0,1],index:7935813}
/*{subjective_time: 0.0, implicit_obj_name: "sphere"*/
//var shape_properties = {type:"meta_balls",time: 0.0};
var shape_properties = dict;
//var radius = 3.0;
//var mc_properties = {resolution: 28, box: {xmin: -radius+1, xmax: radius, ymin: -radius, ymax: radius, zmin: -radius, zmax: radius}};
//var shape_properties = {type:"simple_sphere", radius: radius};
// var shape_properties = {type:"egg",displayColor:{x:0.38015037447759337,y:0.6015094592616681,z:0.9774198226067741},matrix:[10,0,0,92.9405888205127,0,10,0,101.93969389296757,0,0,10,8.59828143220919,0,0,0,1],index:7935813}
// var s = 10;
// var mc_properties = {resolution: 28, box: {xmin: 100-s, xmax: 100+s, ymin: 100-s, ymax: 100+s, zmin: 5-s, zmax: 5+s}};
//var shape_properties = {type:"egg",displayColor:{x:0.38015037447759337,y:0.6015094592616681,z:0.9774198226067741},matrix:[1,0,0,92.9405888205127-100,0,1,0,101.93969389296757-100,0,0,1,8.59828143220919-5,0,0,0,1],index:7935813}
var s = 1;
//var mc_properties = {resolution: 28, box: {xmin: -s, xmax: s, ymin: -s, ymax: s, zmin: -s, zmax: s}};
//var mc_properties = {resolution: 28, box: {xmin: 92.9405888205127-100-s, xmax: 92.9405888205127-100+s, ymin: 101.93969389296757-100-s, ymax: 101.93969389296757-100+s, zmin: 8.59828143220919-5-s, zmax: 8.59828143220919-5+s}};
//implicit_double_mushroom center will be zero.
/*
//shape_properties.type = "egg";
var m = shape_properties.matrix;
var bb ={};
var dd = 0.0;
var wx = Math.sqrt(m[0]*m[0] + m[1]*m[1] + m[2]*m[2]);
var wy = Math.sqrt(m[4]*m[4] + m[5]*m[5] + m[6]*m[6]);
var wz = Math.sqrt(m[8]*m[8] + m[9]*m[9] + m[10]*m[10]);
bb["xmin"] = m[3] - wx/2 +dd;
bb["xmax"] = m[3] + wx/2-dd;
bb["ymin"] = m[7] - wy/2 +dd;
bb["ymax"] = m[7] + wy/2-dd;
bb["zmin"] = m[11] - wz/2 +dd;
bb["zmax"] = m[11] + wz/2-dd;
*/
assert(bbox, "yOU need to specify the bounding box");
var bb ={};
var sc = 1.0;
var test = 0.;
bb["xmin"] = bbox.min.x * sc + test;
bb["xmax"] = bbox.max.x * sc - test;
bb["ymin"] = bbox.min.y * sc + test;
bb["ymax"] = bbox.max.y * sc - test;
bb["zmin"] = bbox.min.z * sc + test;
bb["zmax"] = bbox.max.z * sc - test;
_expect(bb["xmin"], "boundingbox has null");
_expect(bb["xmax"], "boundingbox has null");
_expect(bb["ymin"], "boundingbox has null");
_expect(bb["ymax"], "boundingbox has null");
_expect(bb["zmin"], "boundingbox has null");
_expect(bb["zmax"], "boundingbox has null");
var mc_res = CONFIG.implisolid.default_mc_resolution;
var mc_properties = {
resolution: getResolution(bb),
box: bb,
ignore_root_matrix: ignore_root_matrix,
vresampl: {iters: this.use_II?1:0, c: 1.0},
projection: {enabled: this.use_II?1:0},
qem: {enabled: (this.use_II && this.use_II_qem)?1:0},
subdiv: {enabled: 0},
overall_repeats : 1,
debug: {
enabled_pointsets: 0,
post_subdiv_noise: 0.0
}
};
console.log (" mc properties : " + JSON.stringify(mc_properties));
var geom = this.make_geometry(shape_properties, mc_properties);
return geom;
};
this.query_implicit_values = _query_implicit_values;
};
var IMPLICIT = null;
function _on_cpp_loaded() {
console.log("C++ ready.");
//IMPLISOLID.
IMPLICIT = new ImplicitService();
assert = _assert_000;
};
function getResolution(bb){
return CONFIG.implisolid.default_mc_resolution;
const max_value = 40;
const min_value = 14;
const factor = CONFIG.implisolid.default_mc_resolution;
var max_length = Math.max(bb["xmax"] - bb["xmin"], bb["ymax"] - bb["ymin"], bb["zmax"] - bb["zmin"]);
var tmp = Math.min(max_value,max_length*factor);
return Math.floor(Math.max(tmp,min_value));
// 1 -> 28
// 2 -> 48
}
/* Put the following in the HTML
<script>
Module={preRun:[],
onRuntimeInitialized: _on_cpp_loaded,
};
</script>
<script type="text/javascript" src="mcc2.cpp.js"></script>
*/
//function test_update1(t, mesh){
function test_update1(t, mesh, dict){
var g = mesh.geometry;
IMPLICIT.finish_geometry();
IMPLICIT.needs_deallocation = false;
var s = Math.sin(t)*3+3;
console.log("s="+s);
var mc_properties = {resolution: 28, box: {xmin: (-1-s)*0, xmax: 1+s, ymin: (-1-s)*0 , ymax: 1+s, zmin: (-1-s)*0, zmax: 1+s}};
// Aliasing test:
//var res = Math.floor(28*(s+1)/3)-4; var sz = (res) * 0.14/2;
//var mc_properties = {resolution: res, box: {xmin: 0, xmax: sz, ymin: 0 , ymax: sz, zmin: 0, zmax: sz}};
//var mc_properties = {resolution: 28, box: {xmin: -1, xmax: 1, ymin: -1, ymax: 1, zmin: -1, zmax: 1}};
//var new_geometry = IMPLICIT.build_geometry(28, mc_properties, "sphere", 0);
//var shape_properties = {type: "sphere",matrix:[10,0,0,92.9405888205127,0,10,0,101.93969389296757,0,0,10,8.59828143220919,0,0,0,1]};
//var shape_properties = mesh.parentShape.getDict1();
console.log()
if(!dict){
//var shape_properties = {type:"meta_balls",time: t };
var shape_properties = {type:"simple_sphere", radius: 3.0};
}
else
{
var shape_properties = dict;
}
var new_geometry = IMPLICIT.build_geometry(
JSON.stringify(shape_properties) ,
JSON.stringify(mc_properties));
IMPLICIT.needs_deallocation = true;
if(new_geometry){
mesh.geometry = new_geometry;
g = new_geometry;
}
// g.update_geometry(IMPLICIT, true);
IMPLICIT.update_geometry(g, true);
//??????? g.update_normals(IMPLICIT);
var x = "?";
IMPLICIT.make_normals_into_geometry(g, JSON.stringify(shape_properties), x, false);
}
function test_update2(t){
var g = currentMeshes[0].geometry;
// var new_geometry = g.update_geometry(IMPLICIT, false);
var new_geometry = IMPLICIT.update_geometry(g, false);
// g.update_normals(IMPLICIT);
var x = "?";
var mp5_string = "?";
IMPLICIT.make_normals_into_geometry(g, mp5_String, x, false);
if(new_geometry){
currentMeshes[0].geometry = new_geometry;
}
}
/*
var t=0;m=currentMeshes[0];test_update1(t, m);var iid=setInterval(function(){test_update1(t+=0.02, m);},6+9);
var t=0;m=currentMeshes[0]; d=m.parentShape.getDict1();test_update1(t, m);var iid=setInterval(function(){test_update1(t+=0.02, m,d);}, 66);
*/