javaprocessingcsg

Is it possible to use jcsg library with Processing?


Processing is a creative coding platform - language, IDE & ecosystem - maintained by the Processing community with the backing of the Processing Foundation https://processing.org. Processing Java Mode can usually benefit from code in Java libraries.

JCSG is a Java implementation of BSP based CSG (Constructive Solid Geometry) https://github.com/miho/JCSG.


Solution

  • There are few hoops to jumps through coming from Processing alone, but yes, you can use any Java library in Processing. (Simply drag the library .jar on top of the saved sketch)

    First you'll need to compile the JCSG .jar library along with the VVecMath .jar library to run the sample code.

    To do that you'll need Gradle. You can install it from scratch or use an existing installation from your system if you've used the Android SDK / Android Studio / IntelliJ / etc.

    As the readme mentions, on OSX/Linux/etc. from each library folder run:

    bash gradlew assemble
    

    On Windows run:

    gradlew assemble
    

    In my case I used the Gradle install that came with Android Studio which I had on my mac:

    bash /Applications/IDEsAndEditors/Android\ Studio.app/Contents/plugins/android/lib/templates/gradle/wrapper/gradlew assemble
    

    Once you've got the .jar files compiled you can simply drop them into a saved Processing sketch. This will create a code folder. At this stage you can use the libraries in that sketch.

    (Tip: Go to Processing > Preferences and enable Code completion with Ctrl+Space to make easier to see the available method and properties (IDEs like eclipse/IntelliJ/NetBeans/etc. do that by default))

    I've done a quick test, however the OBJ file JCSG saves can't be parsed by Processing's built-in PShape OBJ loader.

    import java.nio.file.Paths;
    
    PShape csgResult;
    
    void setup(){
      size(900,900,P3D);
      // we use cube and sphere as base geometries
      CSG cube = new Cube(2).toCSG();
      CSG sphere = new Sphere(1.25).toCSG();
    
      // perform union, difference and intersection
      CSG cubePlusSphere = cube.union(sphere);
      CSG cubeMinusSphere = cube.difference(sphere);
      CSG cubeIntersectSphere = cube.intersect(sphere);
    
      // translate geometries to prevent overlapping 
      CSG union = cube.
              union(sphere.transformed(Transform.unity().translateX(3))).
              union(cubePlusSphere.transformed(Transform.unity().translateX(6))).
              union(cubeMinusSphere.transformed(Transform.unity().translateX(9))).
              union(cubeIntersectSphere.transformed(Transform.unity().translateX(12)));
    
      // save union as stl
      try {
          FileUtil.write(
                  Paths.get(sketchPath("sample.obj")),
                  union.toObjString()
          );
      } catch (IOException ex) {
          ex.printStackTrace();
      }
    
      //load shape into Processing
      csgResult = loadShape(sketchPath("sample.obj"));
    
    }
    
    void draw(){
      background(0);
      translate(width * 0.5, height * 0.5,0);
      scale(sin(frameCount * 0.1) * 100);
      if(csgResult != null){
        shape(csgResult);
      }
    }
    

    I did test and the vertices are there, the faces are missing through:

    JCSG demo vertices

    Feel try to try the STL format and a different Processing library to load, otherwise access the vertices and draw them in Processing directly taking into account units/scales are different between JSCG and Processing:

    import java.nio.file.Paths;
    
    CSG union;
    
    void setup(){
      size(900,900,P3D);
      stroke(255);
      //strokeWeight(3);
      // we use cube and sphere as base geometries
      CSG cube = new Cube(2).toCSG();
      CSG sphere = new Sphere(1.25).toCSG();
    
      // perform union, difference and intersection
      CSG cubePlusSphere = cube.union(sphere);
      CSG cubeMinusSphere = cube.difference(sphere);
      CSG cubeIntersectSphere = cube.intersect(sphere);
    
      // translate geometries to prevent overlapping 
      union = cube.
              union(sphere.transformed(Transform.unity().translateX(3))).
              union(cubePlusSphere.transformed(Transform.unity().translateX(6))).
              union(cubeMinusSphere.transformed(Transform.unity().translateX(9))).
              union(cubeIntersectSphere.transformed(Transform.unity().translateX(12)));
    
    }
    
    void drawCSG(CSG mesh,float scale){
      beginShape(POINTS);
      for(Polygon p : mesh.getPolygons()){
        for(Vertex v : p.vertices){
          vertex((float)v.pos.getX() * scale,(float)v.pos.getY() * scale,(float)v.pos.getZ() * scale);
        }
      }
      endShape();
    }
    
    void draw(){
      background(0);
      translate(width * 0.5, height * 0.5,0);
      rotateY(map(mouseX,0,width,-PI,PI));
      rotateX(map(mouseY,0,height,PI,-PI));
    
      drawCSG(union,sin(frameCount * 0.01) * 100);
    }
    

    JSCG vertex traversal

    You can download the above sketch (with pre-compiled libraries) here (and the JCSG generated documentation here). Be sure to go through the libraries' documentation/source code for more advanced usage.

    Update: For efficiency you can make use of createShape() to create a group of PShape objects once in setup, then simply render in draw() (as opposed to my previous example which traverses all polygons and vertices over and over again):

    // the PShape reference which will contain the converted 
    PShape csgResult;
    
    void setup(){
      size(900,900,P3D);
      noStroke();
      // JCSG sample code:
        // we use cube and sphere as base geometries
        CSG cube = new Cube(2).toCSG();
        CSG sphere = new Sphere(1.25).toCSG();
    
        // perform union, difference and intersection
        CSG cubePlusSphere = cube.union(sphere);
        CSG cubeMinusSphere = cube.difference(sphere);
        CSG cubeIntersectSphere = cube.intersect(sphere);
    
        // translate geometries to prevent overlapping 
        CSG union = cube.
                union(sphere.transformed(Transform.unity().translateX(3))).
                union(cubePlusSphere.transformed(Transform.unity().translateX(6))).
                union(cubeMinusSphere.transformed(Transform.unity().translateX(9))).
                union(cubeIntersectSphere.transformed(Transform.unity().translateX(12)));
    
        // translate merged geometry back by half the total translation to pivot around centre
        union = union.transformed(Transform.unity().translateX(-6));
    
      // Convert CSG to PShape -> Note: CSG units are small so we scale them up so the shapes are visible in Processing
      csgResult = CSGToPShape(union,45);
    }
    // re-usable function to convert a CSG mesh to a Processing PShape
    PShape CSGToPShape(CSG mesh,float scale){
      // allocate a PShape group
      PShape csgResult = createShape(GROUP);
      // for each CSG polygon (Note: these can have 3,4 or more vertices)
      for(Polygon p : mesh.getPolygons()){
        // make a child PShape
        PShape polyShape = createShape();
        // begin setting vertices to it
        polyShape.beginShape();
        // for each vertex in the polygon
        for(Vertex v : p.vertices){
          // add each (scaled) polygon vertex 
          polyShape.vertex((float)v.pos.getX() * scale,(float)v.pos.getY() * scale,(float)v.pos.getZ() * scale);
        }
        // finish this polygon
        polyShape.endShape();
        //append the child PShape to the parent
        csgResult.addChild(polyShape);
      }
      return csgResult;
    }
    
    void draw(){
      background(0);
      lights();
      translate(width * 0.5, height * 0.5,0);
      rotateY(map(mouseX,0,width,-PI,PI));
      rotateX(map(mouseY,0,height,PI,-PI));
      shape(csgResult);
    }
    

    JCSG rendered as PShape in Processing