diff --git a/README.md b/README.md index 342e5a3..4738d5c 100644 --- a/README.md +++ b/README.md @@ -1,105 +1,18 @@ # Project 6: Implicit surfaces - Marching cubes -**Goal:** Implement an isosurface created from metaballs using the marching cubes algorithm. +**Goal:** Implement an isosurface created from metaballs using the marching cubes algorithm. -Metaballs are organic-looking n-dimensional objects. We will be implementing a 3-dimensional metaballs. They are great to make bloppy shapes. An isosurface is created whenever the metaball function crosses a certain threshold, called isolevel. The metaball function describes the total influences of each metaball to a given points. A metaball influence is a function between its radius and distance to the point: +Name: Meghana Seshadri +PennKey: msesh -`f(point) = (radius * radius) / (distance * distance)` +## Renders -By summing up all these influences, you effectively describes all the points that are greater than the isolevel as inside, and less than the isolevel as outside (or vice versa). As an observation, the bigger the metaball's radius is, the bigger its influence is. +These following renders show metaballs with iridescent shading! -Marching cubes essentially voxelize the space, then generate triangles based on the density function distribution at the corners of each voxel. By increasing the voxelized grid's resolution, the surface eventually gets that blobby, organic look of the metaballs. Marching cubes can achieve a similar effect to ray marching for rendering implicit surfaces, but in addition to the rendered image, you also retain actual geometries. +1. ![alt text](https://github.com/MegSesh/Project6-MarchingCubes-Implicit-Surfaces/blob/master/images/1.png "Image 1") -Marching cubes are commonly used in MRI scanning, where you can generate geometries for the scans. Marching cubes are also used to generate complex terrains with caves in games. The additional geometry information can handily support collision and other physical calculation for game engines. For example, their bounding boxes can then be computed to construct the acceleration data structure for collisions. +2. ![alt text](https://github.com/MegSesh/Project6-MarchingCubes-Implicit-Surfaces/blob/master/images/2.png "Image 1") -**Warning**: this assignment option requires more effort than the ray marching option. The two base codes diverge significantly, so switching options midway can be costly for your time and effort. +3. ![alt text](https://github.com/MegSesh/Project6-MarchingCubes-Implicit-Surfaces/blob/master/images/3.png "Image 1") -## Resources -We suggest reading the following resources before starting your assignment: - -- [Generating complex terrain](https://developer.nvidia.com/gpugems/GPUGems3/gpugems3_ch01.html) from [GPU Gems 3](https://developer.nvidia.com/gpugems/GPUGems3/gpugems3_pref01.html). -- [Polygonising a scalar field](http://paulbourke.net/geometry/polygonise/) by Paul Bourke. -- [Marching squares](http://jamie-wong.com/2014/08/19/metaballs-and-marching-squares/) by Jamie Wong. - -## Base code framework - -We have provided a basecode as a reference. You are welcome to modify the framework for your project. The basecode implements metaballs on the CPU. - -_main.js_: - - - `App`: - -This is a global configuration object. All information for the marching cubes are stored here. - -**Note**: `App.visualDebug` is a global control of all the visual debugging components. Even though it is helpful for development, it could be memory intensive. Toggle this flag off for better perforamance at high resolution. - -_marching_cubes.js_: - - - `class MarchingCubes`: - This class encapsulates everything about the metaballs, grid, voxels, and sampling information. - - - `class Voxel`: - This class contains information about a single voxel, and its sample points. Polygonization happens here. - -_inspect_point.js_: - - - `class InspectPoint`: - This class simply contains a single sample point that can output its value on the screen at its pixel location. - -_metaball.js_: - - - `class Metaball`: - This class represents a single metaball. - -_marching_cube_LUT.js_: - -This file contains the edge table and the triangle table for the marching cubes. - -## Animate metaballs (5 points) -Implement the `update` for metaballs to move its position based velocity. Reverse the velocity whenever the metaball goes out of bounds. Since the metaball function is not well defined at the boundaries, maintain an additional small margin so that the metaball can reverse its moving direction before reaching the bounds. - -## Metaball function (2 points) -Implement the metaball function inside `sample` of `MarchingCubes`. This function should return the total influences of all moving metaballs with respect to a given point. - -## Sampling at corners (15 points) -In order to polygonize a voxel, generate new samples at each corner of the voxel. Their isovalues must be updated as the metaball function changes due of metaballs moving. - -## Polygonization (50 points) -Implement `polygonize` inside `Cell` class. This function should return the list of **vertices** and **normals** of the triangles polygonized in the voxel. - -### Vertices (30 points out of 50) -To compute the vertices, we have provided the look-up tables from Paul Bourke's. The table assumes the following indexing scheme: -![](./ref_voxel_indexing.png) - -- _The eight corners can be represented as an 8-bit number, where 1 means the isovalue is above or below the isolevel based on your implementation._ -- _The twelve edges can be represented as a 12-bit number, where 1 means that the isosurface intersects with this edge._ - -- **EDGE_TABLE**: This table returns a 12-bit number that represents the edges intersected by the isosurface. For each intersected edge, compute the linearly interpolated vertex position on the edge according to the isovalue at each end corner of the edge. - -- **TRI_TABLE**: This table acts as the triangle indices. Every 16 elements in the table represents a possible polygonizing configuration. Within each configuration, every three consecutive elements represents the indices of a triangle that should be created from the edges above. - -### Normals (20 points out of 50) -Compute the normals using the gradient of the vertex with respect to the x, y, and z. The normals are then used for shading different materials. - -## Meshing (18 points) -The mesh for the metaball's isosurface should be created once. At each frame, using the list of **vertices** and **normals** polygonized from the voxels, update the mesh's geometry for the isosurface. Notice that the total volume of the mesh does change. - -## Materials and post-processing (10 points) -Interesting shader materials beyond just the provided threejs materials. We encourage using your previous shaders assignment for this part. - -## Extra credits (Up to 30 points) -- Metaball can be positive or negative. A negative metaball will substract from the surface, which pushed the surface inward. **Implement a scene with both positive and negative metaballs. (10 points)** -- **More implicit surfaces!** For example: planes, mesh, etc.). Some very interesting ideas are to blend your metaballs into those surfaces. **(5 points for each)** - -## Submission - -- Update `README.md` to contain a solid description of your project -- Publish your project to gh-pages. `npm run deploy`. It should now be visible at http://username.github.io/repo-name -- Create a [pull request](https://help.github.com/articles/creating-a-pull-request/) to this repository, and in the comment, include a link to your published project. -- Submit the link to your pull request on Canvas. - -## Deploy -- `npm run build` -- Add and commit all changes -- `npm run deploy` -- If you're having problems with assets not linking correctly, make sure you wrap you're filepaths in `require()`. This will make the bundler package and your static assets as well. So, instead of `loadTexture('./images/thing.bmp')`, do `loadTexture(require('./images/thing.bmp'))`. \ No newline at end of file +4. ![alt text](https://github.com/MegSesh/Project6-MarchingCubes-Implicit-Surfaces/blob/master/images/4.png "Image 1") diff --git a/images/1.png b/images/1.png new file mode 100644 index 0000000..6dc536b Binary files /dev/null and b/images/1.png differ diff --git a/images/2.png b/images/2.png new file mode 100644 index 0000000..cb4ca35 Binary files /dev/null and b/images/2.png differ diff --git a/images/3.png b/images/3.png new file mode 100644 index 0000000..f59ee94 Binary files /dev/null and b/images/3.png differ diff --git a/images/4.png b/images/4.png new file mode 100644 index 0000000..54d94d1 Binary files /dev/null and b/images/4.png differ diff --git a/src/glsl/iridescent-frag.glsl b/src/glsl/iridescent-frag.glsl new file mode 100644 index 0000000..f1d5775 --- /dev/null +++ b/src/glsl/iridescent-frag.glsl @@ -0,0 +1,31 @@ +uniform sampler2D texture; +uniform vec3 u_albedo; + +varying vec3 f_position; +varying vec3 f_normal; +varying vec2 f_uv; + +#define PI 3.1415926535897932384626433832795 + +void main() { + vec4 color = vec4(u_albedo, 1.0); + //color = texture2D(texture, f_uv); + + //iridescent is view dependent + float d = clamp(dot(f_normal, normalize(cameraPosition - f_position)), 0.0, 1.0); + + //plug in cosine palette function + vec3 _a = vec3(0.5, 0.5, 0.5); + vec3 _b = vec3(0.5, 0.5, 0.5); + vec3 _c = vec3(1.0, 1.0, 1.0); + vec3 _d = vec3(0.0, 0.33, 0.67); + vec3 result = _a + _b * cos(2.0 * PI * (_c * d + _d)); + + //lerp with albedo + float lerp_x = ((1.0 - d) * result[0]) + (u_albedo[0] * d); + float lerp_y = ((1.0 - d) * result[1]) + (u_albedo[1] * d); + float lerp_z = ((1.0 - d) * result[2]) + (u_albedo[2] * d); + color = vec4(lerp_x, lerp_y, lerp_z, 1.0); + + gl_FragColor = vec4(result.xyz, 1.0); +} diff --git a/src/glsl/iridescent-vert.glsl b/src/glsl/iridescent-vert.glsl new file mode 100644 index 0000000..7556051 --- /dev/null +++ b/src/glsl/iridescent-vert.glsl @@ -0,0 +1,12 @@ + +varying vec2 f_uv; +varying vec3 f_normal; +varying vec3 f_position; + +// uv, position, projectionMatrix, modelViewMatrix, normal +void main() { + f_uv = uv; + f_normal = normal; + f_position = position; + gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); +} diff --git a/src/main.js b/src/main.js index 29f2f30..a75849a 100644 --- a/src/main.js +++ b/src/main.js @@ -12,7 +12,7 @@ import MarchingCubes from './marching_cubes.js' const DEFAULT_VISUAL_DEBUG = true; const DEFAULT_ISO_LEVEL = 1.0; -const DEFAULT_GRID_RES = 4; +const DEFAULT_GRID_RES = 12;//4; const DEFAULT_GRID_WIDTH = 10; const DEFAULT_NUM_METABALLS = 10; const DEFAULT_MIN_RADIUS = 0.5; @@ -20,11 +20,11 @@ const DEFAULT_MAX_RADIUS = 1; const DEFAULT_MAX_SPEED = 0.01; var App = { - // + // marchingCubes: undefined, config: { - // Global control of all visual debugging. - // This can be set to false to disallow any memory allocation of visual debugging components. + // Global control of all visual debugging. + // This can be set to false to disallow any memory allocation of visual debugging components. // **Note**: If your application experiences performance drop, disable this flag. visualDebug: DEFAULT_VISUAL_DEBUG, @@ -113,7 +113,7 @@ function setupScene(scene) { function setupGUI(gui) { // more information here: https://workshop.chromeexperiments.com/examples/gui/#1--Basic-Usage - + // --- CONFIG --- gui.add(App, 'isPaused').onChange(function(value) { App.isPaused = value; @@ -153,7 +153,7 @@ function setupGUI(gui) { } } }); - debugFolder.open(); + debugFolder.open(); } // when the scene is done initializing, it will call onLoad, then on frame updates, call onUpdate diff --git a/src/marching_cubes.js b/src/marching_cubes.js index 505eb24..8989d92 100644 --- a/src/marching_cubes.js +++ b/src/marching_cubes.js @@ -9,15 +9,34 @@ const LAMBERT_WHITE = new THREE.MeshLambertMaterial({ color: 0xeeeeee }); const LAMBERT_GREEN = new THREE.MeshBasicMaterial( { color: 0x00ee00, transparent: true, opacity: 0.5 }); const WIREFRAME_MAT = new THREE.LineBasicMaterial( { color: 0xffffff, linewidth: 10 } ); +var options = { + albedo: '#dddddd' +} + +const IRIDESCENCE = new THREE.ShaderMaterial({ + uniforms: { + texture: { + type: "t", + value: null + }, + u_albedo: { + type: 'v3', + value: new THREE.Color(options.albedo) + } + }, + + vertexShader: require('./glsl/iridescent-vert.glsl'), + fragmentShader: require('./glsl/iridescent-frag.glsl') +}); export default class MarchingCubes { - constructor(App) { + constructor(App) { this.init(App); } init(App) { - this.isPaused = false; + this.isPaused = false; VISUAL_DEBUG = App.config.visualDebug; // Initializing member variables. @@ -46,6 +65,15 @@ export default class MarchingCubes { this.labels = []; this.balls = []; + + //CREATING MESH OBJECT + //var geom = new THREE.Geometry(); + //var object = new THREE.Mesh( new THREE.Geometry(), IRIDESCENCE);//new THREE.MeshNormalMaterial() ); + //this.meshGeom = object; + this.meshGeom = new THREE.Mesh( new THREE.Geometry(), IRIDESCENCE); + + + this.showSpheres = true; this.showGrid = true; @@ -105,7 +133,7 @@ export default class MarchingCubes { this.scene.add(voxel.wireframe); this.scene.add(voxel.mesh); } - } + } } setupMetaballs() { @@ -119,21 +147,21 @@ export default class MarchingCubes { // Randomly generate metaballs with different sizes and velocities for (var i = 0; i < this.numMetaballs; i++) { - x = this.gridWidth / 2; - y = this.gridWidth / 2; - z = this.gridWidth / 2; + x = this.gridWidth / 2; + y = this.gridWidth / 2; + z = this.gridWidth / 2; pos = new THREE.Vector3(x, y, z); - + vx = (Math.random() * 2 - 1) * this.maxSpeed; vy = (Math.random() * 2 - 1) * this.maxSpeed; vz = (Math.random() * 2 - 1) * this.maxSpeed; vel = new THREE.Vector3(vx, vy, vz); - + radius = Math.random() * (this.maxRadius - this.minRadius) + this.minRadius; - + var ball = new Metaball(pos, radius, vel, this.gridWidth, VISUAL_DEBUG); this.balls.push(ball); - + if (VISUAL_DEBUG) { this.scene.add(ball.mesh); } @@ -145,9 +173,31 @@ export default class MarchingCubes { // Please follow the resources given in the write-up for details. sample(point) { // @TODO - var isovalue = 1.1; + /* METABALL FUNCTION (2 POINTS) + Metaballs are organic-looking n-dimensional objects. We will be implementing a 3-dimensional metaballs. + They are great to make bloppy shapes. An isosurface is created whenever the metaball function + crosses a certain threshold, called isolevel. The metaball function describes the total influences + of each metaball to a given points. A metaball influence is a function between its radius and distance to the point: + + f(point) = (radius * radius) / (distance * distance) + + By summing up all these influences, you effectively describes all the points that are greater than + the isolevel as inside, and less than the isolevel as outside (or vice versa). + As an observation, the bigger the metaball's radius is, the bigger its influence is. + -------------------------------------------------------------------------------------------------- + */ + + var isovalue = 0.0;//1.1; //BRINGING IT TO 0 WILL ONLY SHOW THE GRID CELL CUBES WHERE THE ISOVALUE IS >= 1 (but this doesn't always work?) + + for(var i = 0; i < this.numMetaballs; i++) + { + var currBall = this.balls[i]; + var dist = Math.sqrt(Math.pow(currBall.pos.x - point.x, 2) + Math.pow(currBall.pos.y - point.y, 2) + Math.pow(currBall.pos.z - point.z, 2)); + + isovalue += ((currBall.radius * currBall.radius) / (dist * dist)); + } return isovalue; - } + }//end sample function update() { @@ -160,23 +210,61 @@ export default class MarchingCubes { ball.update(); }); + + /* SAMPLING AT CORNERS (15 POINTS) + In order to polygonize a voxel, generate new samples at each corner of the voxel. + Their isovalues must be updated as the metaball function changes due of metaballs moving. + */ for (var c = 0; c < this.res3; c++) { // Sampling the center point this.voxels[c].center.isovalue = this.sample(this.voxels[c].center.pos); + this.voxels[c].FUpperLeft.isovalue = this.sample(this.voxels[c].FUpperLeft.pos); + this.voxels[c].FUpperRight.isovalue = this.sample(this.voxels[c].FUpperRight.pos); + this.voxels[c].FLowerLeft.isovalue = this.sample(this.voxels[c].FLowerLeft.pos); + this.voxels[c].FLowerRight.isovalue = this.sample(this.voxels[c].FLowerRight.pos); + //back face verts + this.voxels[c].BUpperLeft.isovalue = this.sample(this.voxels[c].BUpperLeft.pos); + this.voxels[c].BUpperRight.isovalue = this.sample(this.voxels[c].BUpperRight.pos); + this.voxels[c].BLowerLeft.isovalue = this.sample(this.voxels[c].BLowerLeft.pos); + this.voxels[c].BLowerRight.isovalue = this.sample(this.voxels[c].BLowerRight.pos); + // Visualizing grid if (VISUAL_DEBUG && this.showGrid) { - + // Toggle voxels on or off if (this.voxels[c].center.isovalue > this.isolevel) { this.voxels[c].show(); - } else { + } + else { this.voxels[c].hide(); } + this.voxels[c].center.updateLabel(this.camera); - } else { + + this.voxels[c].FUpperLeft.updateLabel(this.camera); + this.voxels[c].FUpperRight.updateLabel(this.camera); + this.voxels[c].FLowerLeft.updateLabel(this.camera); + this.voxels[c].FLowerRight.updateLabel(this.camera); + this.voxels[c].BUpperLeft.updateLabel(this.camera); + this.voxels[c].BUpperRight.updateLabel(this.camera); + this.voxels[c].BLowerLeft.updateLabel(this.camera); + this.voxels[c].BLowerRight.updateLabel(this.camera); + + } + + else { this.voxels[c].center.clearLabel(); + + this.voxels[c].FUpperLeft.clearLabel(); + this.voxels[c].FUpperRight.clearLabel(); + this.voxels[c].FLowerLeft.clearLabel(); + this.voxels[c].FLowerRight.clearLabel(); + this.voxels[c].BUpperLeft.clearLabel(); + this.voxels[c].BUpperRight.clearLabel(); + this.voxels[c].BLowerLeft.clearLabel(); + this.voxels[c].BLowerRight.clearLabel(); } } @@ -205,14 +293,109 @@ export default class MarchingCubes { this.showGrid = false; }; + /* MESHING (18 POINTS) + The mesh for the metaball's isosurface should be created once. + At each frame, using the list of vertices and normals polygonized from the voxels, update the mesh's geometry for the isosurface. + Notice that the total volume of the mesh does change. + */ + makeMesh() { // @TODO - } + + this.meshGeom = new THREE.Mesh( new THREE.Geometry(), IRIDESCENCE ); + this.meshGeom.verticesNeedUpdate = true; + this.meshGeom.dynamic = true; + this.scene.add(this.meshGeom); + }//end makeMesh updateMesh() { // @TODO - } -}; + + //set the geometry to these new lists + var newVerticesList = []; //use new Array();??? + var newFacesList = []; + + //iterate through the vertices list + for (var c = 0; c < this.voxels.length; c++) { + var polygonizeResult = this.voxels[c].polygonize(this.isolevel, this.balls); + var vertList = polygonizeResult.vertPositions; + var normList = polygonizeResult.vertNormals; + // console.log("PRINTING NORM LIST: ") + // console.log(normList); + // console.log(vertList); + + //sanity check to make sure vertList is correct + if(vertList.length % 3 !== 0) + { + console.log("THE VERT LIST IS NOT DIVISIBLE BY THREE"); + } + + if(vertList.length != 0) + { + var _len = newVerticesList.length; + for(var i = 0; i < vertList.length; i += 3) + { + //var newVert = new THREE.Vector3(vertList[i].x, vertList[i].y, vertList[i].z); + var v1 = vertList[i]; + var v2 = vertList[i + 1]; + var v3 = vertList[i + 2]; + newVerticesList.push(v1); + newVerticesList.push(v2); + newVerticesList.push(v3); + + var newFace = new THREE.Face3(_len + i, _len + i + 1, _len + i + 2); + + newFace.vertexNormals[0] = normList[i]; + newFace.vertexNormals[1] = normList[i + 1]; + newFace.vertexNormals[2] = normList[i + 2]; + + newFacesList.push(newFace); + }//end for every vert + }//end if vertlist + }//end for every voxel loop + + if(this.meshGeom) + { + this.scene.remove(this.meshGeom); + } + + // var geom = new THREE.Geometry(); + // geom.vertices = newVerticesList; + // geom.faces = newFacesList; + // var object = new THREE.Mesh( geom, IRIDESCENCE ); + // this.meshGeom = object; + // + // this.meshGeom.dynamic = true; + // this.meshGeom.elementsNeedUpdate = true; + // this.meshGeom.verticesNeedUpdate = true; + // this.scene.add(this.meshGeom); + + + //WHY IS THIS NOT WORKING?!?!?! + // this.meshGeom.dynamic = true; + // this.meshGeom.geometry.vertices = newVerticesList; + // this.meshGeom.geometry.faces = newFacesList; + // // //this.meshGeom.computeFaceNormals(); + // this.meshGeom.elementsNeedUpdate = true; + // this.meshGeom.verticesNeedUpdate = true; + // this.scene.add(this.meshGeom); + + + //WILL THIS WORK?? + this.meshGeom.dynamic = true; + var geom = new THREE.Geometry(); + geom.vertices = newVerticesList; + geom.faces = newFacesList; + this.meshGeom.geometry = geom; + + this.meshGeom.elementsNeedUpdate = true; + this.meshGeom.verticesNeedUpdate = true; + this.scene.add(this.meshGeom); + + + }//end updateMesh + +};//end MarchingCube class // ------------------------------------------- // @@ -229,8 +412,8 @@ class Voxel { if (VISUAL_DEBUG) { this.makeMesh(); } - - this.makeInspectPoints(); + + this.makeInspectPoints(); } makeMesh() { @@ -282,7 +465,29 @@ class Voxel { var red = 0xff0000; // Center dot - this.center = new InspectPoint(new THREE.Vector3(x, y, z), 0, VISUAL_DEBUG); + this.center = new InspectPoint(new THREE.Vector3(x, y, z), 0, VISUAL_DEBUG); + + //8 corners + //front face verts + var frontupperleft = new THREE.Vector3(x - halfGridCellWidth, y + halfGridCellWidth, z + halfGridCellWidth); + var frontupperright = new THREE.Vector3(x + halfGridCellWidth, y + halfGridCellWidth, z + halfGridCellWidth); + var frontlowerleft = new THREE.Vector3(x - halfGridCellWidth, y - halfGridCellWidth, z + halfGridCellWidth); + var frontlowerright = new THREE.Vector3(x + halfGridCellWidth, y - halfGridCellWidth, z + halfGridCellWidth); + this.FUpperLeft = new InspectPoint(frontupperleft, 0, VISUAL_DEBUG); + this.FUpperRight = new InspectPoint(frontupperright, 0, VISUAL_DEBUG); + this.FLowerLeft = new InspectPoint(frontlowerleft, 0, VISUAL_DEBUG); + this.FLowerRight = new InspectPoint(frontlowerright, 0, VISUAL_DEBUG); + + //back face verts + var backupperleft = new THREE.Vector3(x - halfGridCellWidth, y + halfGridCellWidth, z - halfGridCellWidth); + var backupperright = new THREE.Vector3(x + halfGridCellWidth, y + halfGridCellWidth, z - halfGridCellWidth); + var backlowerleft = new THREE.Vector3(x - halfGridCellWidth, y - halfGridCellWidth, z - halfGridCellWidth); + var backlowerright = new THREE.Vector3(x + halfGridCellWidth, y - halfGridCellWidth, z - halfGridCellWidth); + this.BUpperLeft = new InspectPoint(backupperleft, 0, VISUAL_DEBUG); + this.BUpperRight = new InspectPoint(backupperright, 0, VISUAL_DEBUG); + this.BLowerLeft = new InspectPoint(backlowerleft, 0, VISUAL_DEBUG); + this.BLowerRight = new InspectPoint(backlowerright, 0, VISUAL_DEBUG); + } show() { @@ -308,22 +513,315 @@ class Voxel { } } + /* VERTICES + + To compute the vertices, we have provided the look-up tables from Paul Bourke's. The table assumes the following indexing scheme: + + The eight corners can be represented as an 8-bit number, where 1 means the isovalue is above or below the isolevel based on your implementation. + The twelve edges can be represented as a 12-bit number, where 1 means that the isosurface intersects with this edge. + + EDGE_TABLE : This table returns a 12-bit number that represents the edges intersected by the isosurface. + For each intersected edge, compute the linearly interpolated vertex position on the edge + according to the isovalue at each end corner of the edge. + + TRI_TABLE : This table acts as the triangle indices. Every 16 elements in the table represents + a possible polygonizing configuration. Within each configuration, every three consecutive + elements represents the indices of a triangle that should be created from the edges above. + + + NORMALS + * Compute the normals using the gradient of the vertex with respect to the x, y, and z. + * The normals are then used for shading different materials. + */ + vertexInterpolation(isolevel, posA, posB) { // @TODO - var lerpPos; + + var lerpPos = new THREE.Vector3(0.0, 0.0, 0.0); + + // console.log("PRINTING THE 2 POS: "); + // console.log(posA); + // console.log(posB); + + var posAiso = posA.isovalue; + var posBiso = posB.isovalue; + var posApos = posA.pos; + var posBpos = posB.pos; + + // console.log("posAISO: " + posAiso); + // console.log("posBISO: " + posBiso); + + var mu = 0.0; + //var p = THREE.Vector3(0.0); + + if(Math.abs(isolevel - posAiso) < 0.00001) {return posApos;} + if(Math.abs(isolevel - posBiso) < 0.00001) {return posBpos;} + if(Math.abs(posAiso - posBiso) < 0.00001) {return posApos;} + + mu = (isolevel - posAiso) / (posBiso - posAiso); + // console.log("MU:" + mu); + + lerpPos.x = posApos.x + mu * (posBpos.x - posApos.x); + lerpPos.y = posApos.y + mu * (posBpos.y - posApos.y); + lerpPos.z = posApos.z + mu * (posBpos.z - posApos.z); + + // console.log("IM IN VERTEX INTERP: "); + // console.log(lerpPos); + return lerpPos; } - polygonize(isolevel) { + polygonize(isolevel, metaBallsList) { // @TODO + + // console.log("IM IN POLYGONIZE"); + // console.log("Metaballs list length: "); + // console.log(metaBallsList.length); + var vertexList = []; var normalList = []; + //output arrays + var vertPositions = []; + var vertNormals = []; + + var cubeIndex = 0; + //figure out the cubeIndex for the edge table + //to figure out which edges are being intersected + //|= --> bitwise or + //EX --> 5 | 1 --> 0101 | 0001 --> 0101 (anywhere there's a 1, result will be 1) + + // console.log(this.BLowerLeft.isovalue); + // console.log(this.BLowerRight.isovalue); + // console.log(this.FLowerRight.isovalue); + // console.log(this.FLowerLeft.isovalue); + // + // console.log(this.BUpperLeft.isovalue); + // console.log(this.BUpperRight.isovalue); + // console.log(this.FUpperRight.isovalue); + // console.log(this.FUpperLeft.isovalue); + // + // console.log(isolevel); + if(this.BLowerLeft.isovalue < isolevel) {cubeIndex |= 1;} //grid[0] + if(this.BLowerRight.isovalue < isolevel) {cubeIndex |= 2;} //grid[1] + if(this.FLowerRight.isovalue < isolevel) {cubeIndex |= 4;} //grid[2] + if(this.FLowerLeft.isovalue < isolevel) {cubeIndex |= 8;} //grid[3] + if(this.BUpperLeft.isovalue < isolevel) {cubeIndex |= 16;} //grid[4] + if(this.BUpperRight.isovalue < isolevel) {cubeIndex |= 32;} //grid[5] + if(this.FUpperRight.isovalue < isolevel) {cubeIndex |= 64;} //grid[6] + if(this.FUpperLeft.isovalue < isolevel) {cubeIndex |= 128;} //grid[7] + + // console.log("CUBE INDEX: " + cubeIndex); + + //FIND OUT WHICH BITS OF EDGETABLE VALUE TO RUN VERTEXINTERPOLATION ON + //if cubeIndex == 9, edgetable[9] = 1001 0000 0101 + //if 1001 0000 0101 has a first bit, then run vertexInterpolation + + //cube is completely inside or outside the isosurface + if(LUT.EDGE_TABLE[cubeIndex] == 0) + { + //return 0; + // vertPositions = []; + // vertNormals = []; + return { + vertPositions: vertPositions, + vertNormals: vertNormals + }; + } + if(LUT.EDGE_TABLE[cubeIndex] & 1) + { + var pt1 = this.vertexInterpolation(isolevel, this.BLowerLeft, this.BLowerRight); + // vertexList.push(pt1); + // var nor1 = this.calculateNormal(pt1, metaBallsList); + // normalList.push(nor1); + vertexList[0] = pt1; + normalList[0] = this.calculateNormal(pt1, metaBallsList); + } //0, 1 + if(LUT.EDGE_TABLE[cubeIndex] & 2) + { + var pt2 = this.vertexInterpolation(isolevel, this.BLowerRight, this.FLowerRight); + // vertexList.push(pt2); + // var nor2 = this.calculateNormal(pt2, metaBallsList); + // normalList.push(nor2); + vertexList[1] = pt2; + normalList[1] = this.calculateNormal(pt2, metaBallsList); + } //1, 2 + if(LUT.EDGE_TABLE[cubeIndex] & 4) + { + var pt3 = this.vertexInterpolation(isolevel, this.FLowerRight, this.FLowerLeft); + // vertexList.push(pt3); + // var nor3 = this.calculateNormal(pt3, metaBallsList); + // normalList.push(nor3); + vertexList[2] = pt3; + normalList[2] = this.calculateNormal(pt3, metaBallsList); + } //2, 3 + if(LUT.EDGE_TABLE[cubeIndex] & 8) + { + var pt4 = this.vertexInterpolation(isolevel, this.FLowerLeft, this.BLowerLeft); + // vertexList.push(pt4); + // var nor4 = this.calculateNormal(pt4, metaBallsList); + // normalList.push(nor4); + vertexList[3] = pt4; + normalList[3] = this.calculateNormal(pt4, metaBallsList); + } //3, 0 + if(LUT.EDGE_TABLE[cubeIndex] & 16) + { + var pt5 = this.vertexInterpolation(isolevel, this.BUpperLeft, this.BUpperRight); + // vertexList.push(pt5); + // var nor5 = this.calculateNormal(pt5, metaBallsList); + // normalList.push(nor5); + vertexList[4] = pt5; + normalList[4] = this.calculateNormal(pt5, metaBallsList); + } //4, 5 + if(LUT.EDGE_TABLE[cubeIndex] & 32) + { + var pt6 = this.vertexInterpolation(isolevel, this.BUpperRight, this.FUpperRight); + // vertexList.push(pt6); + // var nor6 = this.calculateNormal(pt6, metaBallsList); + // normalList.push(nor6); + vertexList[5] = pt6; + normalList[5] = this.calculateNormal(pt6, metaBallsList); + } //5,6 + if(LUT.EDGE_TABLE[cubeIndex] & 64) + { + var pt7 = this.vertexInterpolation(isolevel, this.FUpperRight, this.FUpperLeft); + // vertexList.push(pt7); + // var nor7 = this.calculateNormal(pt7, metaBallsList); + // normalList.push(nor7); + vertexList[6] = pt7; + normalList[6] = this.calculateNormal(pt7, metaBallsList); + } //6, 7 + if(LUT.EDGE_TABLE[cubeIndex] & 128) + { + var pt8 = this.vertexInterpolation(isolevel, this.BUpperLeft, this.FUpperLeft); + // vertexList.push(pt8); + // var nor8 = this.calculateNormal(pt8, metaBallsList); + // normalList.push(nor8); + vertexList[7] = pt8; + normalList[7] = this.calculateNormal(pt8, metaBallsList); + } //7, 4 + if(LUT.EDGE_TABLE[cubeIndex] & 256) + { + var pt9 = this.vertexInterpolation(isolevel, this.BLowerLeft, this.BUpperLeft); + // vertexList.push(pt9); + // var nor9 = this.calculateNormal(pt9, metaBallsList); + // normalList.push(nor9); + vertexList[8] = pt9; + normalList[8] = this.calculateNormal(pt9, metaBallsList); + } //0, 4 + if(LUT.EDGE_TABLE[cubeIndex] & 512) + { + var pt10 = this.vertexInterpolation(isolevel, this.BLowerRight, this.BUpperRight); + // vertexList.push(pt10); + // var nor10 = this.calculateNormal(pt10, metaBallsList); + // normalList.push(nor10); + vertexList[9] = pt10; + normalList[9] = this.calculateNormal(pt10, metaBallsList); + } //1, 5 + if(LUT.EDGE_TABLE[cubeIndex] & 1024) + { + var pt11 = this.vertexInterpolation(isolevel, this.FLowerRight, this.FUpperRight); + // vertexList.push(pt11); + // var nor11 = this.calculateNormal(pt11, metaBallsList); + // normalList.push(nor11); + vertexList[10] = pt11; + normalList[10] = this.calculateNormal(pt11, metaBallsList); + } //2, 6 + if(LUT.EDGE_TABLE[cubeIndex] & 2048) + { + var pt12 = this.vertexInterpolation(isolevel, this.FLowerLeft, this.FUpperLeft); + // vertexList.push(pt12); + // var nor12 = this.calculateNormal(pt12, metaBallsList); + // normalList.push(nor12); + vertexList[11] = pt12; + normalList[11] = this.calculateNormal(pt12, metaBallsList); + } //3, 7 + + // console.log("IM HERE AFTER 15 IF STATEMENTS"); + + // var currTriTableRow = LUT.TRI_TABLE[cubeIndex]; + // console.log(currTriTableRow); + + // console.log("IM PRINTING THE VERT LIST AFTER INTERPOLATING: "); + // console.log(vertexList); + + + //var tri = LUT.TRI_TABLE[cubeIndex * 16 + j]; + for(var i = 0; LUT.TRI_TABLE[cubeIndex * 16 + i] != -1; i += 3)//for(var i = 0; i < 16; i += 3) + { + // if(LUT.TRI_TABLE[cubeIndex * 16 + i] != -1) + // { + //get the 3 triangle points and put them in vertPositions + + // console.log("IM IN OKAY TRI TABLE CASE: "); + // console.log(LUT.TRI_TABLE[cubeIndex * 16 + i]); + + var p1 = vertexList[LUT.TRI_TABLE[cubeIndex * 16 + i]]; + var p2 = vertexList[LUT.TRI_TABLE[cubeIndex * 16 + i + 1]]; + var p3 = vertexList[LUT.TRI_TABLE[cubeIndex * 16 + i + 2]]; + vertPositions.push(p1); + vertPositions.push(p2); + vertPositions.push(p3); + + //do the same thing for normals + var n1 = normalList[LUT.TRI_TABLE[cubeIndex * 16 + i]]; + var n2 = normalList[LUT.TRI_TABLE[cubeIndex * 16 + i + 1]]; + var n3 = normalList[LUT.TRI_TABLE[cubeIndex * 16 + i + 2]]; + vertNormals.push(n1); + vertNormals.push(n2); + vertNormals.push(n3); + // } + }//end for loop + + // console.log("IM HERE AFTER TRIANGLE FOR LOOP"); + // console.log("PRINTING VERT POSITIONS: ") + // console.log(vertPositions); + return { vertPositions: vertPositions, vertNormals: vertNormals }; }; -} \ No newline at end of file + + calculateNormal(point, ballList) + { + //console.log("IM IN CALCULATE NORMAL"); + var grad = new THREE.Vector3(0.0, 0.0, 0.0); + var offset = 0.0001; + + var p1x = new THREE.Vector3(point.x + offset, point.y, point.z); + var p2x = new THREE.Vector3(point.x - offset, point.y, point.z); + grad.x = this.sampleNormal(p1x, ballList) - this.sampleNormal(p2x, ballList); + + var p1y = new THREE.Vector3(point.x, point.y + offset, point.z); + var p2y = new THREE.Vector3(point.x, point.y - offset, point.z); + grad.y = this.sampleNormal(p1y, ballList) - this.sampleNormal(p2y, ballList); + + var p1z = new THREE.Vector3(point.x, point.y, point.z + offset); + var p2z = new THREE.Vector3(point.x, point.y, point.z - offset); + grad.z = this.sampleNormal(p1z, ballList) - this.sampleNormal(p2z, ballList); + + //var _len = grad.length(); + //var outputNormal = new THREE.Vector3(-grad.x / _len, -grad.y / _len, -grad.z / _len); + //return outputNormal; + return grad.normalize(); + }; + + sampleNormal(point, _ballList) + { + //console.log("IM IN SAMPLENORMAL"); + var isovalue = 0.0; + //var pt = new THREE.Vector3(point[0], point[1], point[2]) + + for(var i = 0; i < _ballList.length; i++) + { + var currBall = _ballList[i]; + var dist = Math.sqrt(Math.pow(currBall.pos.x - point.x, 2) + Math.pow(currBall.pos.y - point.y, 2) + Math.pow(currBall.pos.z - point.z, 2)); + + isovalue += ((currBall.radius * currBall.radius) / (dist * dist)); + } + return isovalue; + }; +} diff --git a/src/metaball.js b/src/metaball.js index 6a057bc..11405b1 100644 --- a/src/metaball.js +++ b/src/metaball.js @@ -17,7 +17,7 @@ export default class Metaball { this.radius2 = radius * radius; this.mesh = null; - if (visualDebug) { + if (visualDebug) { this.makeMesh(); } } @@ -42,5 +42,52 @@ export default class Metaball { update() { // @TODO - } -} \ No newline at end of file + //Implement the update for metaballs to move its position based velocity. + //Reverse the velocity whenever the metaball goes out of bounds. + //Since the metaball function is not well defined at the boundaries, + //maintain an additional small margin so that the metaball + //can reverse its moving direction before reaching the bounds. + + //if there's a collision detected between the metaball position + EPSILON and the boundary position + //reverse the velocity (subtract velocity from position??) + //else ( if x, y, z of position is inside 0 to gridwidth, then you're still inside the boundary ) + //add velocity to position + + var offset2 = 0.5; + var offset = this.gridWidth - offset2; + + + // if(this.pos.x > 0.0 && this.pos.x < offset && this.pos.y > 0.0 && this.pos.y < offset && this.pos.z > 0.0 && this.pos.z < offset) + // { + // //always add velocity but update what it is + // this.pos += this.vel; + // } + // else { + // this.pos -= this.vel; + // } + + //if the metaball is NOT within the grid boundaries, then reverse velocity + if((this.pos.x <= offset2) || (this.pos.x > offset)) + { + this.vel.x = -this.vel.x; + } + if((this.pos.y <= offset2) || (this.pos.y > offset)) + { + this.vel.y = -this.vel.y; + } + if((this.pos.z <= offset2) || (this.pos.z > offset)) + { + this.vel.z = -this.vel.z; + } + // this.pos += this.vel; + // this.pos = new THREE.Vector3(this.pos.x + this.vel.x, this.pos.y + this.vel.y, this.pos.z + this.vel.z); + this.pos.x += this.vel.x; + this.pos.y += this.vel.y; + this.pos.z += this.vel.z; + + this.mesh.position.set(this.pos.x, this.pos.y, this.pos.z); + + //console.log(this.mesh.position); + //console.log(); + }//end update function +}