UNPKG

@sohale/implisolid

Version:

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

609 lines (492 loc) 24.5 kB
'use strict'; function __check_TypedArray_type(src, _type){ //if(typeof src !== type_name) if(!(src instanceof _type)) { console.error(); throw "Developer's error; " + (typeof src) + (Object.prototype.toString.call(src.buffer)); } } /** * @param {Number | String} initial_value: if "random" then randomize color else if it's a number color set to this value */ function copy_Float32Array_preallocated(src, min_prealloc_size, initial_value) { __check_TypedArray_type(src, Float32Array); var TYPE_SIZE = 4; if(min_prealloc_size % 1 !== 0) console.error("min_prealloc_size must be integer: " + min_prealloc_size); //if(min_prealloc_size !== int(min_prealloc_size)) console.error("min_prealloc_size must be integer: " + min_prealloc_size); var len_bytes = Math.max(min_prealloc_size*TYPE_SIZE, src.byteLength); var dst = new ArrayBuffer(len_bytes); var r = new Float32Array(dst); r.set(new Float32Array(src)); if(initial_value === "random"){ for(var i=0*src.byteLength/TYPE_SIZE;i<r.length;i++){ r[i] = (Math.random()-0.5); } }else if (Number(initial_value) === initial_value){ console.log("color number :" + initial_value + " " + len_bytes); for(var i=0*src.byteLength/TYPE_SIZE;i<r.length;i++){ r[i] = initial_value; } } return r; } function copy_Uint32Array_preallocated(src, min_prealloc_size) { __check_TypedArray_type(src, Uint32Array); var TYPE_SIZE = 4; //if(min_prealloc_size !== int(min_prealloc_size)) console.error("min_prealloc_size must be integer: " + min_prealloc_size); if(min_prealloc_size % 1 !== 0) console.error("min_prealloc_size must be integer: " + min_prealloc_size); var len_bytes = Math.max(min_prealloc_size*TYPE_SIZE, src.byteLength); var dst = new ArrayBuffer(len_bytes); // console.log("dst[0] : " + dst[0]); // output: undefined var r = new Uint32Array(dst); r.set(new Uint32Array(src)); return r; } function copy_Uint16Array_preallocated(src, min_prealloc_size) { __check_TypedArray_type(src, Uint16Array); var TYPE_SIZE = 2; if(min_prealloc_size !== int(min_prealloc_size)) console.error("min_prealloc_size must be integer: " + min_prealloc_size); var len_bytes = Math.max(min_prealloc_size*TYPE_SIZE, src.byteLength);; var dst = new ArrayBuffer(len_bytes); var r = new Uint16Array(dst); r.set(new Uint16Array(src)); return r; } const threejs_r71 = false; const threejs_r79 = true; assert( threejs_r71 || threejs_r79 ); /** Simply creates a geometry . This is static and cannot be modified when displayed. Instantiate a new one and make a new THREE.Mesh() */ function LiveBufferGeometry79( verts_, faces_, pre_allocate_, min_faces_capacity_, min_verts_capacity_) { // Alternative design: another argument: given_normals THREE.BufferGeometry.call( this ); this.type = 'LiveBufferGeometry79'; // Use THREE.InstancedBufferGeometry for geometry with multiple instances. this.parameters = { }; if(min_faces_capacity_ === undefined) min_faces_capacity_ = 9000*0; if(min_verts_capacity_ === undefined) min_verts_capacity_ = 8000*0; const GROWTH_FACTOR = 1.5; const GROWTH_ADDITIONAL = 1; if (pre_allocate_ == "Deliberately Empty") { // todo => set some flags, dont create this box, etc. // for now, it creates the box, which will be replaced with something else => it's fine. // But if I dont create anything, things might go wrong. // Also we should not allow update() to zero faces. i.e. this.deliberately_empty = true; } else { this.deliberately_empty = false; } pre_allocate_ = !!pre_allocate_; //_expect(min_faces_capacity_); //_expect(min_verts_capacity_); //console.log("verts : "+ min_verts_capacity_ + " faces : "+ min_faces_capacity_); this.allocate_buffers = function(verts, faces, pre_allocate, faces_capacity, min_verts_capacity) { /* Allocates buffers, also copies. Can be used to update the geometry object. */ if(faces.length == 0) { if (!this.deliberately_empty) { console.error("emptyimplicit"); } 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]); } /*if (faces_capacity == 0 || faces.length == 0 || verts.length == 0 ) { console.warn("faces_capacity == 0"); } */ if (faces.length == 0 || verts.length == 0 ) { if (this.deliberately_empty) { // ok } else { console.error("faces.length, verts.length == 0"); } } if (this.deliberately_empty) { // assert certain things } var POINTS_PER_FACE = 3; // console.log("min capacity for verts : "+ min_verts_capacity + " faces : "+ faces_capacity); var padded_faces, padded_verts; //if(pre_allocate){ // console.log("Allocating separate space for verts,faces."); padded_faces = copy_Uint32Array_preallocated(faces, faces_capacity * POINTS_PER_FACE); padded_verts = copy_Float32Array_preallocated(verts, min_verts_capacity*3); //} /* else { // Warning: not cloned //padded_faces = faces; //padded_verts = verts; console.error("padded_faces = ?"); } */ assert(pre_allocate); // Will be wrong if pre_allocate is false // build geometry /*********************************** * Adding the attributes: position ***********************************/ // todo: // if exists (i.e. it's an update) then: geometry.removeAttribute('position') assert(padded_verts instanceof Float32Array); this.addAttribute( 'position', new THREE.BufferAttribute( padded_verts, 3 ) ); // does this replace the old one? /*********************************** * Adding the attributes: index (i.e. faces) ***********************************/ assert(padded_faces instanceof Uint32Array); _expect(padded_faces.length < 65535); if(threejs_r71) { if (POINTS_PER_FACE !== 3) { console.error("In ThreeJS r71 POINTS_PER_FACE should be (& can be) 3."); } this.addAttribute( 'index', new THREE.BufferAttribute( padded_faces, POINTS_PER_FACE ) ); } if(threejs_r79) { // If you choose 3, it will not choose them in .setDrawRange() correctly. this.setIndex( new THREE.BufferAttribute( padded_faces, 1 ) ); // Why 1?? } //this.setIndex( new THREE.BufferAttribute( padded_faces, POINTS_PER_FACE ) ); //new Uint32Array(padded_faces) ?? for(var j=0;j<faces.length;j++) //if(this.attributes.index.array[j] !== faces[j]){ //threejs_r71 if(this.index.array[j] !== faces[j]){ //r77 console.error(j);break; } //assert(this.attributes.index.array === padded_faces); /*********************************** * Adding the attributes: normal ***********************************/ //if(!ignoreNormals) { var padded_normals; if(pre_allocate){ console.log("Allocating separate space for norms, colors."); padded_normals = copy_Float32Array_preallocated(verts, min_verts_capacity*3, "random"); // if !ignoreNormals ... //is this correct? : padded_verts padded_normals.set(padded_verts); /* if (given_normals) { console.log("given_normals = ", given_normals); console.log(padded_normals.length); console.log(given_normals.length); padded_normals.set(given_normals); } else if (given_normals == null ) { ; //it's fine. } else { console.error(" no given_normals", given_normals); } */ //var padded_colors = copy_Float32Array_preallocated(verts, min_verts_capacity*3, "random"); //var uvs = copy_Float32Array_preallocated(new Float32Array([]), min_verts_capacity*3 * 0, "random"); //padded_colors.set(padded_verts); } else{ console.error("padded_normals = ?"); } // We always need to allocate normals this.addAttribute( 'normal', new THREE.BufferAttribute( padded_normals, 3, true ) ); //} /*********************************** * Color ***********************************/ var padded_color = copy_Float32Array_preallocated(verts, min_verts_capacity*3, "random"); padded_color.set(padded_verts); this.addAttribute( 'color', new THREE.BufferAttribute( padded_color, 3, true ) ); /*********************************** * Other attributes ***********************************/ //Other attributes: // Specific to InstancedBufferGeometry // values added to each coordinate: // var offsets = new THREE.InstancedBufferAttribute( new Float32Array( instances * 3 ), 3, 1 ); // or offsets = new THREE.InstancedBufferAttribute( DELTA_XYZ, 3,1 )?? // * this.addAttribute( 'offset', offsets ); // * this.addAttribute( 'orientation', new THREE.InstancedBufferAttribute( DELTA_XYZW, 4,1 ) ); //this.addAttribute( 'uv', new THREE.BufferAttribute( new Float32Array( [..]), 2 ); //geometry.faces //this.addAttribute( 'color', new THREE.BufferAttribute( padded_colors, 3, true ) ); //color is overidden //this.addAttribute( 'uv', new THREE.BufferAttribute( uvs, 2 ) ); /*********************************** * Setting the sizes (number of faces) ***********************************/ // var nf3 = faces.length; var ntriangles = faces.length / POINTS_PER_FACE; this.__set_range_of_used_faces(ntriangles, POINTS_PER_FACE); //var materialIndex = 0; //this.addGroup( 0, faces.length*1-10, materialIndex ); //scope.addGroup( groupStart, groupCount, materialIndex ); /*********************************** * Other settings of the attributes ***********************************/ //this.index has a .count attribute this.index.count this.index.dynamic=true; this.attributes.position.dynamic=true; this.attributes.normal.dynamic=true; // this.index.needsUpdate = true; }; // ThreeJS does not use prototype-based OOP. /* This function may return new LiveBufferGeometry79 object, in this case the old geometry has to be replaced (by the new returned object) in the caller function. */ /* deprecaed. Use ImpliSolid.update_geometry() instead. i.e. swap geometry & implicit_service: geom.update_geometry(IMPLISOLID, true) -> IMPLISOLID.update_geometry(geom, true) */ this.update_geometry_ = function(implicit_service, ignoreDefaultNormals) { const _FLOAT_SIZE = Float32Array.BYTES_PER_ELEMENT; const _INT_SIZE = Uint32Array.BYTES_PER_ELEMENT; const POINTS_PER_FACE = 3; const geometry = this; 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 this.update_geometry1(verts, faces, ignoreDefaultNormals, false); }; this.__set_range_of_used_faces = function (ntriangles, POINTS_PER_FACE) { /* Sets which part of the faces array is actually used. This is necessary when we preallocate an extra capacity for updatable geometryies to avoid need for dynamically growing/changing the size at each update. */ if(threejs_r71) { var gl_chunkSize=21845; // var ii = 0;this.offsets.push({start:ii, index: ii , count: Math.min(faces.length - ii, gl_chunkSize*3)}); // if(threejs_r71) {...} this.offsets = []; var offsets = ntriangles / gl_chunkSize; for ( var i = 0; i < offsets; i ++ ) { var offset = { start: i * gl_chunkSize * POINTS_PER_FACE, index: i * gl_chunkSize * POINTS_PER_FACE, count: Math.min( ntriangles - ( i * gl_chunkSize ), gl_chunkSize ) * POINTS_PER_FACE }; this.offsets.push( offset ); } this.used_faces_range = ntriangles * POINTS_PER_FACE; //this.computeBoundingSphere(); //this.clearGroups();this.addGroup( 0, ntriangles * POINTS_PER_FACE, 0 ); } else if(threejs_r79) { // assert(ntriangles); if (!ntriangles) { console.error("Warning: ntriangles==0 : " + ntriangles); } //vertices or faces? faces. this.setDrawRange( 0, ntriangles * POINTS_PER_FACE ); this.used_faces_range = ntriangles * POINTS_PER_FACE; // array size } }; this.__set_needsUpdate_flag = function (ignoreNormals) { /* Sets the flags so that WebGL is notified of change/update in the geometry's faces or verts. */ var geometry = this; //geometry.computeOffsets(); geometry.attributes.position.needsUpdate = true; geometry.attributes.position.dynamic = true; if(threejs_r71) { geometry.attributes.index.needsUpdate = true; } else if (threejs_r79) { geometry.index.needsUpdate = true; geometry.index.dynamic = true; } if(!ignoreNormals) { geometry.attributes.normal.needsUpdate = true; geometry.attributes.normal.dynamic = true; } //geometry.attributes.color.needsUpdate = true; }; /** Known bug: The "Wireframe update" bug: When the BufferGrometry is shown using a wirefram material, it does not update the lines correctly. But the non-wirefram solid works perfectly. @param ignoreDefaultNormals=true; if false, uses the vertex's vector as the "default normal" (which is incorrect), i.e. the vector from O=(0,0,0) to each vector's position. So it is always suggested to be `true`. Note that we use a vertex-based normal here (i.e. a normal vector for each vertex, not for each face). @param use_wireframe=false should always be false. This is an attempt to fix the "wireframe update" bug. */ this.update_geometry1 = function(verts, faces, ignoreDefaultNormals, use_wireframe) { use_wireframe = false; // experimental. should be always false; if (faces.length == 0) { console.warn("Warning: faces.length == 0"); } /* A wireframe BufferGeometry may have "2" vertex "indices" per line. A non-wireframe one uses "3" vertex "indices" per face. */ if (use_wireframe) var POINTS_PER_FACE = 2; else var POINTS_PER_FACE = 3; // nverts actually not used // var nverts = verts.length / 3; // todo: double check // var nfaces = faces.length / POINTS_PER_FACE; // todo: double check // A BUG DETECTED HERE! IT SHOULD BE LIKE THIS INSTEAD: // var nfaces = faces.length / POINTS_PER_FACE; // todo: double check const geometry = this; if(threejs_r71) { var old_nverts = geometry.attributes.position.array.length/3; // Physical space size. var old_nfaces = geometry.attributes.index.array.length/POINTS_PER_FACE; } else if (threejs_r79) { var old_nverts = geometry.attributes.position.array.length/3; var old_nfaces = geometry.index.array.length/POINTS_PER_FACE; } // var nv3 = Math.min(nverts, old_nverts) * 3; // var nf3 = Math.min(nfaces, old_nfaces) * POINTS_PER_FACE; //assert(nv3 === verts.length); //assert(nf3 === faces.length); // ************************************* // * The following code works only when we use a single instance of the geometry, i.e. used in one Mesh. // * It will not work well if the same BufferGeometry is used in more than one Mesh // * So the object will look fine if we disable the wireframe mesh. // ************************************* var growth_needed = false; if(threejs_r71) { var availableVertsSize = geometry.attributes.position.array.length; } else if (threejs_r79) { var availableVertsSize = geometry.attributes.position.array.length; } if(verts.length > availableVertsSize){ growth_needed = true; }else{ // copy now geometry.attributes.position.array.set(verts); // .set() can copy if the number of soure (verts) is smaller than target if(!ignoreDefaultNormals) { geometry.attributes.normal.array.set(verts); } } if(threejs_r71) { var availableFacesSize = geometry.attributes.index.array.length; } else if (threejs_r79) { var availableFacesSize = geometry.index.array.length; } if(faces.length > availableFacesSize){ //geometry.attributes.index.array.set(faces.subarray(0, availableFacesSize)); growth_needed = true; }else{ // copy now if(threejs_r71) { geometry.attributes.index.array.set(faces); } else if (threejs_r79) { // .set() can copy if the number of soure (verts) is smaller than target geometry.index.array.set(faces); } } if(growth_needed){ // Why are we using faces_ here ?? console.log("increasing capacity : availableFacesSize : " + availableFacesSize + " facesLength : " + faces.length); //this.dispose(); var min_faces_capacity = Math.floor(Math.max(availableFacesSize/POINTS_PER_FACE, faces.length/POINTS_PER_FACE) * GROWTH_FACTOR + GROWTH_ADDITIONAL); var min_verts_capacity = Math.floor(Math.max(availableVertsSize/3, verts.length/3) * GROWTH_FACTOR + GROWTH_ADDITIONAL); var min_faces_capacity = faces.length/POINTS_PER_FACE; var min_verts_capacity = verts.length/3; console.error("capacity grow: "+min_faces_capacity+" "+min_verts_capacity); //also copies this.allocate_buffers(verts, faces, true, min_faces_capacity, min_verts_capacity); // It seems this is needed: // faces_ = faces; // verts_ = verts; //old solution was: create a new object // var new_geometry= new LiveBufferGeometry79( verts, faces, true, Math.max(availableFacesSize/POINTS_PER_FACE, faces.length/POINTS_PER_FACE) * 1.5 + 1, Math.max(availableVertsSize/3, verts.length/3) * 1.5 +1); geometry.__set_needsUpdate_flag(ignoreDefaultNormals); return false; //new_geometry; } else { assert(!growth_needed); var copied_faces = Math.min(faces.length, availableFacesSize); var ntriangles = copied_faces/POINTS_PER_FACE; this.__set_range_of_used_faces(ntriangles, POINTS_PER_FACE); assert(!growth_needed); //if(growth_needed){ // Notify the changes geometry.__set_needsUpdate_flag(ignoreDefaultNormals); /* g =currentMeshes[0].geometry IMPLICIT.finish_geometry(); var mc_properties_json = JSON.stringify({resolution: CONFIG.implisolid.default_mc_resolution, box: {xmin: -1, xmax: 1, ymin: -1 , ymax: 1, zmin: -1, zmax: 1}}); IMPLICIT.build_geometry(......., mc_properties_json,); //g.update_geometry(IMPLICIT, false) IMPLICIT.update_geometry(g, false) for(var i=0;i<1000;i++){ IMPLICIT.finish_geometry();IMPLICIT.build_geometry(..........28, mc_properties_json, "sphere", i*0.1); g.update_geometry(IMPLICIT, false);} */ return false; } }; this.update_normals_from_array = function(normals) { this.computeVertexNormals(); this.__set_needsUpdate_flag(false); return; //i.e. setNormals() // Only called after updateGeometry(), or after LiveGeometry() var geometry = this; // assert ensure length geometry.attributes.normal.array.set(normals); geometry.computeVertexNormals(); geometry.attributes.normal.needsUpdate = true; }; // Separation of concern: Should not use implicit_service. Only provide .update_normals_from_array() this.update_normals__ = function(implicit_service, mp5_str, x, ignore_root_matrix) { // Only called after updateGeometry(), or after LiveGeometry() //this.aaaaaaaaaA(x, mp5_str, ignore_root_matrix) { // var x = new Float32Array(nverts); console.error("geometry.update_normals() deprecated"); // throw new Error("Deprecated method"); var geom = this; //var gradients = implicit_service.make_normals_into_geometry(geom, mp5_str, x, ignore_root_matrix); implicit_service.make_normals_into_geometry(geom, mp5_str, x, ignore_root_matrix); /* const _FLOAT_SIZE = Float32Array.BYTES_PER_ELEMENT; implicit_service.set_object(mp5_str, ignore_root_matrix); implicit_service.set_vect(x); // overhead implicit_service.calculate_implicit_gradients(true); // Why TRUE doe snot have any effect? var ptr = implicit_service.get_gradients_ptr(); var ptr_len = implicit_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); implicit_service.unset_x(); implicit_service.unset_object(); */ }; this.use_default_normals_from_vertices = function () { // use default normals // candidate name: remove_normals() this.removeAttribute('normal'); this.computeVertexNormals(); this.__set_needsUpdate_flag(false); } // rename: get_min_z_from_mesh() this.get_minz = function(matrix, shape) { // shape is not needed really. return +1; } this.getVerticesArray = function (){ return this.attributes.position.array; }; this.getFacesIndexArray = function (){ return this.index.array; }; this.getNumVerts = function (){ return this.attributes.position.count; }; this.getNumFaces = function (){ return this.index.count; }; // this.computeBoundingBox = function...; // not needed. // this.computeBoundingSphere = function...; // not needed. this.allocate_buffers(verts_, faces_, pre_allocate_, min_faces_capacity_, min_verts_capacity_); }; LiveBufferGeometry79.prototype = Object.create( THREE.BufferGeometry.prototype ); LiveBufferGeometry79.prototype.constructor = LiveBufferGeometry79;