JSCAD User Group
    • Tags
    • Popular
    • Users
    • Groups
    • Register
    • Login

    applyTransforms

    Scheduled Pinned Locked Moved Design Discussions
    5 Posts 2 Posters 1.8k Views 1 Watching
    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.
    • sopattS Offline
      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?

      z3devZ 1 Reply Last reply Reply Quote 0
      • z3devZ Offline
        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.

        sopattS 1 Reply Last reply Reply Quote 0
        • sopattS Offline
          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.

          z3devZ 1 Reply Last reply Reply Quote 0
          • z3devZ Offline
            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.

            sopattS 1 Reply Last reply Reply Quote 0
            • sopattS Offline
              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

              Hello! It looks like you're interested in this conversation, but you don't have an account yet.

              Getting fed up of having to scroll through the same posts each visit? When you register for an account, you'll always come back to exactly where you were before, and choose to be notified of new replies (either via email, or push notification). You'll also be able to save bookmarks and upvote posts to show your appreciation to other community members.

              With your input, this post could be even better 💗

              Register Login
              • First post
                Last post
              Powered by NodeBB | Contributors