p5.js icon indicating copy to clipboard operation
p5.js copied to clipboard

Adds geometry to downloadable obj files functionality

Open diyaayay opened this issue 1 year ago • 2 comments
trafficstars

Resolves #6769 Changes: 1- Added obj file support to save(). 2- Created a method called geometryToObj() in p5.Geometry.js .

Screenshots of the change: Sketch used:

let particles;
let button;

function setup() {
  createCanvas(400, 400, WEBGL); // Increase canvas size
  button = createButton('New');
  button.mousePressed(makeParticles);
  makeParticles();
  particles.geometryToObj();
}

function makeParticles() {
  if (particles) freeGeometry(particles);

  particles = buildGeometry(() => {
    for (let i = 0; i < 2; i++) {
      push();
      // Adjust translation values to fit within the canvas
      translate(
        randomGaussian(-50, 50),
        randomGaussian(-50, 50),
        randomGaussian(-50, 50)
      );
      sphere(5);
      pop();
    }
  });
}

Downloaded Obj File: image

Sketch to check the downloaded obj file:

let octa;
function preload() {
 octa = loadModel('model.obj');
}
function setup() {
    createCanvas(400, 400, WEBGL);
}

function draw() {
 background(200);
   model(octa);
   }

Downloaded obj file on canvas: image

PR Checklist

diyaayay avatar Feb 15 '24 06:02 diyaayay

@davepagurek Please take a look and let me know if this works and if further changes are needed, maybe unit tests, examples, or even how I've implemented this. Thanks.

diyaayay avatar Feb 15 '24 06:02 diyaayay

@davepagurek made some changes, let me know if these work.Thanks.

diyaayay avatar Feb 17 '24 03:02 diyaayay

Hi @diyaayay! I just wanted to check in to see whether you're still interested and have time to work on this? No problem at all of not, and would you be open to other contributors or myself continuing off of this branch to finish up the feature?

davepagurek avatar Jun 20 '24 11:06 davepagurek

Hi @diyaayay! I just wanted to check in to see whether you're still interested and have time to work on this? No problem at all of not, and would you be open to other contributors or myself continuing off of this branch to finish up the feature?

@davepagurek I've been a bit occupied lately with multiple stuff. I'm okay with any other contributors taking this up further and finishing the feature.

diyaayay avatar Jun 20 '24 20:06 diyaayay

No problem, thanks for the great foundation you've made for this feature so far 🙂 I hope GSoC is going well!

davepagurek avatar Jun 20 '24 20:06 davepagurek

I've got a version of this that might work that can download both stl and obj filetypes, but I'm not sure how to submit it.

dgrantham01 avatar Jun 20 '24 21:06 dgrantham01

Thanks @dgrantham01! How have you been working on it so far? Need any help making a PR?

davepagurek avatar Jun 20 '24 21:06 davepagurek

I made a little library type thing for my students to make generative sculptures last year and just modified it a bit to fit within the p5.Geometry class. It basically adds a save function to the class rather than going through the save function in the core library. I imagine that we could modify the save function in the core library similarly to the saveJson and saveStrings logic there.

This is the code though I haven't tested it in this context.

    save(filename = "model.obj", binary = false){
   let output;
   let nameAndExt = {
    "name": substring(0, filename.lastIndexOf(".")),
    "ext": filename.split('.').pop()
  }


  if(nameAndExt[1] === "stl"){
    faceNormals = [];
    for (let f of this.faces) {
      const U = p5.Vector.sub(this.vertices[f[1]], this.vertices[f[0]]);
      const V = p5.Vector.sub(this.vertices[f[2]], this.vertices[f[0]]);
      const nx = U.y * V.z - U.z * V.y;
      const ny = U.z * V.x - U.x * V.z;
      const nz = U.x * V.y - U.y * V.x;
      faceNormals.push(createVector(nx, ny, nz).normalize());
    }
    if(this.binary){
      let offset = 80;
      const bufferLength = this.faces.length * 2 + this.faces.length * 3 * 4 * 4 + 80 + 4;
      const arrayBuffer = new ArrayBuffer( bufferLength );
      output = new DataView( arrayBuffer );
      output.setUint32( offset, this.faces.length, true ); 
      offset += 4;

      for (const [key, f] of Object.entries(faces)){
        const normal = faceNormals[key];
        output.setFloat32(offset, normal.x, true ); 
        offset += 4;
        output.setFloat32(offset, normal.y, true ); 
        offset += 4;
        output.setFloat32(offset, normal.z, true ); 
        offset += 4;

        for (let vertexIndex of f) {
          const vertex = this.vertices[vertexIndex];
          output.setFloat32(offset, vertex.x, true ); 
          offset += 4;
          output.setFloat32(offset, vertex.y, true ); 
          offset += 4;
          output.setFloat32(offset, vertex.z, true ); 
          offset += 4;
        }
        output.setUint16(offset, 0, true ); 
        offset += 2;

      }  
    }
    else{
    output = ["solid " + nameAndExt[0]];
      // console.log(this.vertices)
    for (const [key, f] of Object.entries(faces)){
      const normal = faceNormals[key];
      output.push("facet normal " + normal.x + " " + normal.y + " " + normal.z);
      output.push(" outer loop");
      for (let vertexIndex of f) {
        const vertex = this.vertices[vertexIndex];
        output.push("vertex " + vertex.x + " " + vertex.y + " " + vertex.z);
      }
      output.push(" endloop");
      output.push("endfacet");

    }  
    output.push("endsolid " + nameAndExt[0] + "\n");
  }
  }
  else{
    output = []
    for (let v of this.vertices) {
      output.push("v " + v.x + " " + v.y + " " + v.z);
    }
    for(let uv of this.uvs){
      output.push("vt " + uv.x + " " + uv.y);
    }
    for(let vn of this.vertexNormals){
      output.push("vn " + vn.x + " " + vn.y + " " + vn.z);
    }
    for (let f of this.faces) {
      output.push("f " + f[0] + " " + f[1] + " " + f[2]);
    }
  }
  downloadFile(new Blob([output], {type: 'text/plain'}), filename);

}

dgrantham01 avatar Jun 20 '24 22:06 dgrantham01

Nice! Are you able to clone this existing branch to add your STL exporter into the existing code, maybe by renaming the method to be format agnostic? Might be able to through here: image

For the OBJ version, I wonder if we have to handle the same issue mentioned in this comment https://github.com/processing/p5.js/pull/6812/files#r1500085929 and make it output something like f 1/1/1 2/2/2 3/3/3 in order to add both normals and UVs to the face. If you're interested in modifying the existing OBJ export code here to add handling for that in that would be great!

davepagurek avatar Jun 20 '24 23:06 davepagurek

I got it checked out. Never really contributed to this kind of thing before, but I'll see if I can get it all together.

One clarification about the Geometry data organization, are the indices of the vertexNormals and uvs the always the same as the vertices? The wiki on the obj format suggests that they could be different, but I just want to double check because I think that's not the case here.

dgrantham01 avatar Jun 21 '24 13:06 dgrantham01

That's right, although in an arbitrary .obj file they might be different, in p5.Geometry's internals, there will always be a 1:1 mapping of vertices to UVs, per-vertex colors and normals (if they exist -- vertices will always exist but the others may just be empty.)

davepagurek avatar Jun 21 '24 13:06 davepagurek

Ok. I fixed the obj and added stl and I think I committed it through the github online thing. Should be working. My test sketch was working and both stl and objs opened in blender no proble. A quick scan over the text inside looked like it was correct as well.

dgrantham01 avatar Jun 21 '24 14:06 dgrantham01

ok nice! does it let you push the code to a new branch and make a PR? Also by online thing, is that Github Codespaces?

davepagurek avatar Jun 21 '24 15:06 davepagurek

Yes. Codespaces is the online thing. I believe I managed to get it to commit and create a PR.

dgrantham01 avatar Jun 21 '24 15:06 dgrantham01

I don't see it yet unfortunately, do you have a link to the PR?

davepagurek avatar Jun 21 '24 17:06 davepagurek

I guess my commits aren't even going through because of a "husky" node version error, which I can't seem to resolve in codespaces, visualstudio, or github desktop. Here is a link to a forked version of the geometry file which should be correct.

https://github.com/dgrantham01/p5.js/blob/main/src/webgl/p5.Geometry.js

dgrantham01 avatar Jun 21 '24 18:06 dgrantham01

Thanks! If getting it all working through GitHub is a problem, this weekend I can try to coalesce everything together into this PR to get both of your changes in.

davepagurek avatar Jun 22 '24 00:06 davepagurek

@all-contributors please add @diyaayay for code

davepagurek avatar Jun 23 '24 14:06 davepagurek

@davepagurek

@diyaayay already contributed before to code

allcontributors[bot] avatar Jun 23 '24 14:06 allcontributors[bot]

@all-contributors please add @dgrantham01 for code

davepagurek avatar Jun 23 '24 14:06 davepagurek

@davepagurek

I've put up a pull request to add @dgrantham01! :tada:

allcontributors[bot] avatar Jun 23 '24 14:06 allcontributors[bot]