JSCAD User Group

    • Register
    • Login
    • Search
    • Categories
    • Recent
    • Tags
    • Popular
    • Users
    • Groups

    applyTransforms

    Design Discussions
    2
    5
    530
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • sopatt
      sopatt last edited by sopatt

      Hello,

      In the documentation here, it says:

      (static) transform(matrix, geometry) → {geom3}
      Source:
      modeling/src/geometries/geom3/transform.js, line 15
      Transform the given geometry using the given matrix. This is a lazy transform of the polygons, as this function only adjusts the transforms. See applyTransforms() for the actual application of the transforms to the polygons.

      I can't find any other reference to applyTransforms. When I add objects to a union, it doesn't seem to apply the transforms. If I return an array of objects from my main function, it transforms them before rendering. That's fine I guess, but shouldn't union() work? And is there an applyTransforms() somewhere?

      z3dev 1 Reply Last reply Reply Quote 0
      • sopatt
        sopatt @z3dev last edited by sopatt

        @z3dev if there is anything I can do to help, I'm willing.

        I'm happy to share my code as well.

        Here is PosableGeom3.js:

        "use strict";
        const jscad = require('@jscad/modeling');
        const { Pose } = require('./Pose');
        const { mat4, vec3, vec4 } = jscad.maths;
        const { geom3 } = jscad.geometries;
        const { PI } = Math;
        // Base class for geom3 objects with named Poses
        class PosableGeom3 {
          constructor(geometry, poses) {
            this._geometry = geometry || geom3.create();
            this._poses = {};
            if (poses) {
              Object.keys(poses).forEach(key => {
                this._poses[key] = poses[key].clone();
              });
            }
          }
          get polygons() { return this._geometry.polygons; }
          set polygons() { this._geometry.polygons = value; }
          get transforms() { return this._geometry.transforms; }
          set transforms(value) { this._geometry.transforms = value; }
          clone() {
            // Create a new geom3 with deep-copied polygons and transforms
            const clonedGeom = geom3.clone(this._geometry);
            // Clone the poses
            const clonedPoses = {};
            Object.keys(this._poses).forEach(key => {
              clonedPoses[key] = this._poses[key].clone();
            });
            // Create a new PosableGeom3 with the cloned geometry and poses
            return new PosableGeom3(clonedGeom, clonedPoses);
          }
          transform(matrix) {
            this._geometry = geom3.transform(matrix, this._geometry);
            Object.keys(this._poses).forEach(key => {
              this._poses[key].transform(matrix);
            });
            return this;
          }
          getPose(name) {
            return this._poses[name] || new Pose();
          }
        	applyTransforms(){
        		this._geometry = geom3.create(geom3.toPolygons(this._geometry));
        		return this;
        	}
          alignTo(port, targetPose) {
            const sourcePose = this.getPose(port);
            if (!sourcePose) {
              throw new Error(`Invalid port ${port}`);
            }
            if (!targetPose) {
              throw new Error(`Invalid targetPose`);
            }
            return this.transform(
              sourcePose.getMatrix(targetPose)
            );
          }
        }
        module.exports = { PosableGeom3 };
        

        And here is Pose.js:

        "use strict";
        const jscad = require('@jscad/modeling');
        const { vec3, vec4, mat4 } = jscad.maths;
        const { geom3 } = jscad.geometries;
        const { abs } = Math;
        const { translate } = jscad.transforms;
        const { union } = jscad.booleans;
        const { cylinder, sphere, cylinderElliptic } = jscad.primitives;
        
        const x = 0; const y = 1; const z = 2; const w = 3;
        
        class Pose {
          constructor(point, heading, up) {
            // Default position to origin if not provided
            this._point = point ? vec3.clone(point) : vec3.create();
            // Default heading to Y+ (0, 1, 0) if not provided
            this._heading = vec3.normalize(
              vec3.create(), 
              heading ? vec3.clone(heading) : [0, 1, 0]
            );
            // Default up to Z+ (0, 0, 1) if not provided
            this._up = up ? vec3.clone(up) : [0, 0, 1];
        
            // Project up onto the plane perpendicular to heading
            const dot = vec3.dot(this._up, this._heading);
            const projectedUp = vec3.subtract(
              vec3.create(),
              this._up,
              vec3.scale(vec3.create(), this._heading, dot)
            );
            // Check if up is parallel to heading 
            // (length of projectedUp near zero)
            if (vec3.length(projectedUp) < 0.00001) {
              // Choose a perpendicular vector based on heading
              if (abs(this._heading[z]) < 0.99999) {
                this._up = vec3.cross(
                  vec3.create(), this._heading, [0, 0, 1]
                );
              } else {
                this._up = vec3.cross(
                  vec3.create(), this._heading, [1, 0, 0]
                );
              }
            } else {
              this._up = vec3.normalize(vec3.create(), projectedUp);
            }
            // Ensure up is normalized
            vec3.normalize(this._up, this._up);
          }
          get point() {
            return vec3.clone(this._point);
          }
          get heading() {
            return vec3.clone(this._heading);
          }
          get up() {
            return vec3.clone(this._up);
          }
          clone() {
            return new Pose(this._point, this._heading, this._up);
          }
          transform(matrix) {
            // Transform point (w = 1)
            let v = this._point.concat(1); // Convert vec3 to vec4
            v = vec4.transform(vec4.create(), v, matrix);
            this._point = vec3.clone(v); // Convert back to vec3
        
            // Transform heading (w = 0)
            v = this._heading.concat(0); // Convert vec3 to vec4
            v = vec4.transform(vec4.create(), v, matrix);
            this._heading = vec3.clone(v); // Convert back to vec3
        
            // Transform up (w = 0)
            v = this._up.concat(0); // Convert vec3 to vec4
            v = vec4.transform(vec4.create(), v, matrix);
            this._up = vec3.clone(v); // Convert back to vec3
        
            return this;
          }
        	getMatrix(targetPose) {
        		let t = mat4.create();
        
        		// Step 1: Translate to origin
        		t = mat4.multiply(
        			mat4.create(),
        			mat4.fromTranslation(mat4.create(), vec3.scale(vec3.create(), this._point, -1)),
        			t
        		);
        
        		// Step 2: Align heading with targetPose
        		t = mat4.multiply(
        			mat4.create(),
        			mat4.fromVectorRotation(mat4.create(), this._heading, targetPose._heading),
        			t
        		);
        
        		// Step 3: Roll around heading to align up vector
        		const p = this.clone().transform(t);
        		const dot = vec3.dot(
        			vec3.cross(vec3.create(), p._up, targetPose._up),
        			p._heading
        		);
        		let angle = vec3.angle(p._up, targetPose._up);
        		if (dot < 0) angle = -angle;
        
        		if (Math.abs(angle) > 1e-6) {
        			t = mat4.multiply(
        				mat4.create(),
        				mat4.fromRotation(mat4.create(), angle, targetPose._heading),
        				t
        			);
        		}
        
        		// Step 4: Translate to targetPose.point
        		t = mat4.multiply(
        			mat4.create(),
        			mat4.fromTranslation(mat4.create(), targetPose._point),
        			t
        		);
        
        		return t;
        	}
          render() {
            const vecGeom = (vector) => {
              const vectorLength = vec3.length(vector) || 1;;
              const vectorRadius = vectorLength / 10;
              const arrowLength = vectorLength / 5;
              const arrowRadius = vectorLength / 5;
              let out = union(
                cylinder({ // arrow body
                  center: [0, 0, vectorLength / 2],
                  height: vectorLength,
                  radius: vectorRadius
                }),
                cylinderElliptic({ // arrow head
                  center: [0, 0, vectorLength],
                  startRadius: [arrowRadius, arrowRadius],
                  endRadius: [0, 0],
                  height: arrowLength
                })
              );
              out = geom3.transform(
                mat4.fromVectorRotation(
                  mat4.create(),
                  [0, 0, vectorLength],
                  vector
                ),
                out
              );
              return out;
            };
        
            let g = translate(
              this._point,
              union(
                sphere({ radius: 0.2 }),
                vecGeom(this._heading),
                vecGeom(this._up)
              )
            );
            return g;
          }
          roll(angle) {
            if (angle === 0) return this;
            // Create a rotation matrix around the heading vector
            const rotationMatrix = mat4.create();
            mat4.rotate(
              rotationMatrix, rotationMatrix, angle, this._heading
            );
            // Transform the up vector using the rotation matrix (w = 0)
            const transformedUp = vec4.transform(
              vec4.create(), this._up.concat(0), rotationMatrix
            );
            this._up = vec3.clone(transformedUp);
        
            return this;
          }
          translate(vector) {
            this._point = vec3.add(
              vec3.create(),
              vector,
              this._point
            );
          }
        }
        
        module.exports = { Pose }; // end of file
        
        1 Reply Last reply Reply Quote 0
        • z3dev
          z3dev @sopatt last edited by

          @sopatt @sopatt That's cool stuff. In the early versions there was a 'connector' concept but that was left behind. We really want to bring that back in V3, so I'm wondering if your efforts could contribute to a better version.

          sopatt 1 Reply Last reply Reply Quote 0
          • sopatt
            sopatt @z3dev last edited by sopatt

            @z3dev hello and thank you for the reply.

            I am extending jscad with some custom classes Pose, PosableGeom3 which wraps geom3, and derivative what-not, that allows me to snap different pieces of a composition to each other based on Poses, which is an object consisting of two vectors and a point which represent a position and orientation. So for example I create tubes with different types of bends (curve, straight, U, dogleg) each having a start pose and an end pose. I do something like myGeom1. alignTo('start', myGeom2.getPose('end')); to snap one's start to another's end using a transform. I can then snap the next piece to the end of that one, etc. It's a heck of a lot easier to do this than calculate every object's individual 3D position, orientation, and rotation separately. It's very powerful and solves problems I was struggling with in OpenSCAD.

            Anyway alignTo calls this.geometry = geom3.transform(matrix, this. geometry) but I was finding my geometry was rendering as if the transforms were never applied. Incidentally PosableGeom3 exposes the underlying geom3's polygons and transforms properties through getters as well as transforms through a getter and a setter so I think that implies a PosableGeom3 "Is_a" geom3. Certainly when I pass it to library functions expecting a geom3 it "just works". I'm not sure why that's not enough but I guess this is an unconventional usage of the library hence why I was needing to apply transforms at times other than what the library expects. Again, that's a guess. In any case I was able to work around it by adding an applyTransforms method to my class which contains this one line: this._geometry = geom3.create(geom3.toPolygons(this._geometry));

            Thanks again for all your effort on this. It's been such a fun, useful, and powerful tool.

            z3dev 1 Reply Last reply Reply Quote 0
            • z3dev
              z3dev @sopatt last edited by

              @sopatt Welcome

              You don't have to worry about applying the transforms. That's done automatically before other operations like booleans, etc

              You just have to position the shapes properly.

              If you provide an example then we can help out. Also see some of the more complex examples.

              sopatt 1 Reply Last reply Reply Quote 0
              • First post
                Last post
              Powered by NodeBB | Contributors