UNPKG

@sohale/implisolid

Version:

A Solid Modelling Kernel in Javascript based on Implicit Surfaces, for MP5 file format

604 lines (482 loc) 21.9 kB
/* Worker version of implisolid.js Also the Node version. The new callback-based structure is created, which is much more complicated, but allows separated ThreeJS-related code from the rest (core) of the code. The code has three levels, each one has a different service: Level I: IMPLICIT_LOW 1-low level MCC2 API, which directly calls C++ functions. Can run in NodeJS and as a Worker. Fully incode the Worker. Level II: 2- ImpliSolid API functions: which call the MCC2 API functions. They also receive callbacks to receive front-end logic code (ThreeJS). Can also run in Node or as a Worker. This part however most likrely will remain on client side. This mediaes calls to level I. e.g. ideally, no direct call to Level I should be necessary. Between the worker and front-end. NodeJS compatible. IMPLISOLID Level III: 3- The ThreeJS-related code, which runs only on browser. The methods for manipulating ThreeJS objects (LiveGeometry, setting of the normals in the relevant ThreeJS attributes, etc). This logic is defines are a few functions as well and some callbacks defined within them, which are sent to level II functions. */ 'use strict'; var ImplicitService = (function () { 'use strict'; function init(service) { 'use strict'; // Low level API //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; } service.finish_with = function (){ //after the last round. if(!service.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 = service.set_x(verts_space, nverts); console.log("result: "+result); if (!result) { console.error("Something went wrong: ", result); } Module._free( verts_space ); }; service.init_(); return service; } /** * Service 2: * Technical: * Wraps all accesses to Module. Suitable for weparating Worker from the main one. */ function init2(impli2, impli1) { /** * Callback receives the outcome of the normals. Instead of returning the normals, they are used in the callback and then the resources are freed. */ impli2.query_normals = function(x, callback) { impli1.set_object(mp5_str, ignore_root_matrix); impli2.set_vect(x); // overhead impli1.calculate_implicit_gradients(true); // Why TRUE doe snot have any effect? var ptr = impli1.get_gradients_ptr(); var ptr_len = impli1.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); callback(gradients); impli1.unset_x(); impli1.unset_object(); }; // High-level API impli2.make_geometry = function (mp5_str, mc_params, callback) { assert(typeof callback !== 'undefined'); assert(callback); var startTime = new Date(); const _FLOAT_SIZE = Float32Array.BYTES_PER_ELEMENT; const _INT_SIZE = Uint32Array.BYTES_PER_ELEMENT if(impli1.needs_deallocation) { impli1.finish_geometry(); impli1.needs_deallocation = false; } //console.log("mc_params.resolution " + mc_params.resolution); //mc_params.resolution = 40; //var mp5_str = JSON.stringify(shape_params); //var mp5_str = JSON.stringify(shape_params); impli1.build_geometry(mp5_str, JSON.stringify(mc_params)); impli1.needs_deallocation = true; var nverts = impli1.get_v_size(); var nfaces = impli1.get_f_size(); var verts_address = impli1.get_v_ptr(); var faces_address = impli1.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); // first iteration: the callback var geom = callback(verts, faces); var endTime = new Date(); var timeDiff = endTime - startTime; //report_time(timeDiff, function(){hist();}); return geom; }; /* 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; }); */ impli2.query_implicit_values = function(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 = impli1.set_object(mp5_str, ignore_root_matrix); /* Allocations on the C++ side */ var success = impli1.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); impli1.unset_object(obj_id); return false; } // impli1.set_x_with_matrix(verts_space_address, nverts, matrix); /* can create a function like this so we dont have the matrix issue */ impli1.calculate_implicit_values(); /* The actual implicit value calculation*/ var ptr = impli1.get_values_ptr(); /* retrieve a pointer to the position in memory of the calculated array */ var ptr_len = impli1.get_values_size() var values_tarray = Module.HEAPF32.subarray(ptr / _FLOAT_SIZE , ptr / _FLOAT_SIZE + ptr_len ); var result = reduce_callback(values_tarray); //Bug! Forgot to FREE!!! Module._free( verts_space_address ); impli1.unset_object(obj_id); /* Free allocated C++ memory */ impli1.unset_x(); return result; } /* * The output is written into vf_dict, which is a dictionary with keys 'verts' and 'faces'. * Returns true on success, i.e. if the shape is not empty. Otherwise, returns false; */ impli2.get_latest_vf = function (vf_dict) { const _FLOAT_SIZE = Float32Array.BYTES_PER_ELEMENT; const _INT_SIZE = Uint32Array.BYTES_PER_ELEMENT; const POINTS_PER_FACE = 3; var nverts = implicit_service1.get_v_size(); var nfaces = implicit_service1.get_f_size(); if(nfaces > 0){ var verts_address = implicit_service1.get_v_ptr(); var faces_address = implicit_service1.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); // return {faces: faces, verts: verts}; vf['faces'] = faces; vf['verts'] = verts; return true; } else { // empty vf['faces'] = null; vf['verts'] = null; return false; } } } /** * High level API. Works with ThreeJS objects (THREE.Geometry) * Dependency: ThreeJS (not-explicit) * This may need to go inside liveGeomtry or a subclass. * move to front-end: implisolid_front.js * This is part of WeDesign, not ImpliSolid back-end. * There is no mention of 'Module' here. * No access to impli1. */ function init3(service3, service2) { service3.use_II = true; service3.use_II_qem = true; service3.update_geometry = function(geometry, ignoreNormals) { //var implicit_service1 = impli1; vf = {faces: null, verts: null}; var nonempty = service2.get_latest_vf(vf); if (nonempty) { // vf is updated; } else{ console.log("empty implicit"); vf['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 ]); vf['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]); } assert(vf['verts']); assert(vf['faces']); return geometry.update_geometry1(vf['verts'], vf['faces'], ignoreNormals, false); }; //was: geom. update_normals (service ..) // todo: move the callback into LiveGeometry service3.make_normals_into_geometry = function(geom, mp5_str, x, ignore_root_matrix) { geom.removeAttribute('normal'); geom.computeVertexNormals(); geom.__set_needsUpdate_flag(false); return; // console.error(x); /* for( var i = 0 ; i < x.length; i++) { x[i] += Math.random() * 0.9; } */ service2.query_normals(x, function(gradients) { //for( var i = 0 ; i < ptr_len; i++) { // gradients[i] += Math.random() * 0.2; //} geom.update_normals_from_array(gradients); }); /* const _FLOAT_SIZE = Float32Array.BYTES_PER_ELEMENT; service1.set_object(mp5_str, ignore_root_matrix); service2.set_vect(x); // overhead service1.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(); */ }; // Example usage of implisolid // This method is called by the designer to obtain the geometry from the ImplicitService // service.getLiveGeometry = function(dict, bbox, ignore_root_matrix) service3.getLiveGeometry = function(dict, bbox, ignore_root_matrix) { var shape_properties = dict; var s = 1; assert(bbox, "You need to specify the bounding box"); assert("min" in bbox, "You need to specify *.min.x"); assert("max" in bbox, "You need to specify *.max.x"); assert("x" in bbox["min"], "You need to specify *.max.x"); 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 is null"); _expect(bb["xmax"], "boundingbox is null"); _expect(bb["ymin"], "boundingbox is null"); _expect(bb["ymax"], "boundingbox is null"); _expect(bb["zmin"], "boundingbox is null"); _expect(bb["zmax"], "boundingbox is null"); // Designer-specific var getResolution = function(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 }; var mc_res = CONFIG.implisolid.default_mc_resolution; /* var mc_properties = { resolution: getResolution(bb), box: bb, ignore_root_matrix: ignore_root_matrix, vresampl: {iters: this3_.use_II? 1:0, c: 1.0}, projection: {enabled: this_.use_II? 1:0}, qem: {enabled: (this3_.use_II && this3_.use_II_qem)?1:0}, subdiv: {enabled: 1}, overall_repeats: 1, debug: { post_subdiv_noise: 0.01, } }; */ var mc_properties = { resolution: getResolution(bb), box: bb, ignore_root_matrix: ignore_root_matrix, vresampl: {iters: 1, c: 0.4}, projection: {enabled: 1}, qem: {enabled: 1}, //subdiv: {enabled: 1}, //overall_repeats: 2, subdiv: {enabled: 0}, overall_repeats: 1, debug: { enabled_pointsets: 0, post_subdiv_noise: 0.01, }, }; console.log (" mc properties : " + JSON.stringify(mc_properties)); var mp5_str = JSON.stringify(shape_properties); var geom = service2.make_geometry(mp5_str, mc_properties, function (verts, faces) { // ThreeJS-specific code // var ignore_root_matrix: Does not need other (MC-related) arguments. 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. // if (!mp5_str) console.error(mp5_str); service3.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); return geom; } ); return geom; }; service3.query_implicit_values = service2.query_implicit_values; } var ImplicitService = function() { var impli1 = {}; // adds the low-level API to 'this' init(impli1); var impli2 = {}; // adds the mid-level API to 'this' init2(impli2, impli1); var impli3 = this; // adds the high-level API to 'this' init3(impli3, impli2); console.log("impli1 ---------------"); for(var a in impli1) console.log(a); console.log("impli2 ---------------"); for(var a in impli2) console.log(a); console.log("impli3 ---------------"); for(var a in impli3) console.log(a); }; // return IMPLICIT; // is null! //return _on_cpp_loaded; // return null; return ImplicitService; }()); var IMPLICIT = null; // is assigned to at _on_cpp_loaded(); function _on_cpp_loaded() { console.log("C++ ready."); IMPLICIT = new ImplicitService(); // IMPLICIT = new ImplicitWorkerService(); // combines the IMPLICIT as a Worker/Node/npm library with the ThreeJS part. Not good! Solution: divide into two classes. // ImpliSolid.js, ...ImpliSolid3js.js (frontend) // Alternative: Globally: Module._on_cpp_loaded = ...; assert = _assert_000; }; /** * Confusion: * IMPLICIT is an object * of type ImplicitService * ImplicitService is global * IMPLICIT is global too. * Do we really need an instancing a class for IMPLICIT ? i.e. is there really a need for separation of class and object? * Instantiation is done in _on_cpp_loaded(). It cannot have any parameter. It is called automatically as a callback by Emscripten. In which, a global variable IMPLICIT is set. * No code should/can be executed prior to _on_cpp_loaded. todo: make ((function () {..}());) a constructor. * Rename? IMPLISOLID. */ /* impli1: Low level API: ===================== build_geometry get_v_size get_f_size get_v get_f get_v_ptr get_f_ptr finish_geometry set_object unset_object set_x unset_x calculate_implicit_values get_values_ptr get_values_size calculate_implicit_gradients get_gradients_ptr get_gradients_size get_pointset_ptr get_pointset_size about init_ finish_with set_vect needs_deallocation impli2: Mid level API: ===================== query_implicit_values (string, array, function) query_normals (array, function) make_geometry (string, dict, function) get_latest_vf ({array,array}) impli3: Mid level API: ===================== use_II use_II_qem update_geometry make_normals_into_geometry getLiveGeometry query_implicit_values */