Skip to content

Commit

Permalink
Transformation (#56)
Browse files Browse the repository at this point in the history
* Add simple transformations to WorkingGeometry

* Scaling tests

* Scaling tests and linting

* Add rotation tests

* Fix rotations

* Fix ci

* Address comments
  • Loading branch information
pbardea authored Jun 1, 2018
1 parent 88566f3 commit 0fd672e
Show file tree
Hide file tree
Showing 3 changed files with 388 additions and 19 deletions.
150 changes: 147 additions & 3 deletions src/geometry/WorkingGeometry.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { vec3, vec4 } from 'gl-matrix';
import { mat4, quat, vec3, vec4 } from 'gl-matrix';
import { Affine } from '../utils/affine';
import { BakedGeometry } from './BakedGeometry';

Expand Down Expand Up @@ -54,7 +54,7 @@ export class WorkingGeometry {
* Creates a working geometry from a given set of vertices, faces, and control points.
*
* @param {vec3[]} vertices: The points that make up the geometry.
* @param {Face[]} faces: The surfaces of the object, relating the vertices to eachother.
* @param {Face[]} faces: The surfaces of the object, relating the vertices to each other.
* @param {vec3[]} controlPoints: A set of points to snap to or reference.
* @return {WorkingGeometry}
*/
Expand Down Expand Up @@ -99,7 +99,7 @@ export class WorkingGeometry {
return vec3.cross(vec3.create(), v1, v2);
});

// Make all of the baked shapes red for now
// Make all of the baked shapes red for now.
const bakedColors: vec3[] = bakedVertices.map(() => vec3.fromValues(1, 0, 0));

return {
Expand All @@ -110,6 +110,83 @@ export class WorkingGeometry {
};
}

/**
* Translate.
*
* @param {vec3} v: translation vector.
*/
public translate(v: vec3) {
const translationMatrix: mat4 = mat4.fromTranslation(mat4.create(), v);
this.transform(translationMatrix);
}

/**
* Rotate.
*
* @param {vec3} axis: axis of rotation.
* @param {number} angle: amount to rotate in radians.
* @param {vec3} holdPoint: point to rotate from, default to true origin.
*/
public rotate(axis: vec3, angle: number, holdPoint: vec3 = vec3.create()) {
const normalAxis = vec3.normalize(vec3.create(), axis);
const quaternion = quat.setAxisAngle(quat.create(), normalAxis, angle);
const rotationMatrix: mat4 = mat4.fromRotationTranslationScaleOrigin(
mat4.create(),
quaternion,
vec3.create(),
vec3.fromValues(1, 1, 1),
holdPoint
);
this.transform(rotationMatrix);
}

/**
* Scale by pulling a point to a new point.
*
* @param {vec3} pullPoint: point that scales to destination_point after transformation.
* @param {vec3} destinationPoint: point that is the result of pull_point after transformation.
* @param {vec3} holdPoint: point to scale from, default to true origin.
*/
public scale(pullPoint: vec3, destinationPoint: vec3, holdPoint: vec3 = vec3.create()) {
const destinationVector: vec3 = vec3.sub(vec3.create(), destinationPoint, holdPoint);
const pullVector: vec3 = vec3.sub(vec3.create(), pullPoint, holdPoint);
const scalingVector: vec3 = this.getScalingVector(destinationVector, pullVector);
const scalingMatrix: mat4 = mat4.fromRotationTranslationScaleOrigin(
mat4.create(),
quat.create(),
vec3.create(),
scalingVector,
holdPoint
);
this.transform(scalingMatrix);
}

/**
* Scale by a given factor between 2 points.
*
* @param {number} factor: scaling factor.
* @param {vec3} pullPoint: point that will be scaled by factor away from holdPoint.
* @param {vec3} holdPoint: point to scale from, default to true origin.
*/
public scaleByFactor(factor: number, pullPoint: vec3, holdPoint: vec3 = vec3.create()) {
const scalingDirection: vec3 = vec3.sub(vec3.create(), pullPoint, holdPoint);
const absScalingDirection: vec3 = vec3.fromValues(
Math.abs(scalingDirection[0]),
Math.abs(scalingDirection[1]),
Math.abs(scalingDirection[2])
);
const factorVector: vec3 = vec3.fromValues(factor, factor, factor);
const scalingVector: vec3 = vec3.mul(vec3.create(), absScalingDirection, factorVector);
const scalingMatrix: mat4 = mat4.fromRotationTranslationScaleOrigin(
mat4.create(),
quat.create(),
vec3.create(),
scalingVector,
holdPoint
);
this.transform(scalingMatrix);
}

/**
* Merge the child objects into the current one by updating the current vertices, faces, and
* control points.
Expand All @@ -130,4 +207,71 @@ export class WorkingGeometry {
vertexCount += child.vertices.length;
}
}

/**
* Special divide for scaling vectors. Treats undeterminate as 1.
*
* @param {number} a: Numerator.
* @param {number} b: Denominator.
* @return {number}: Result of division.
*/
private scalingDivide(a: number, b: number): number {
if (b === 0 && a === 0) {
return 1;
} else if (b === 0 || a === 0) {
// TODO: Handle skew case here. Should not return NaN.
// This should also never be run since call site checks
// that vectors are in the same direction.
return NaN;
} else {
return a / b;
}
}

/**
* Determines if 2 vectors are pointing in the same direction.
*
* @param {vec3} a: The first vector.
* @param {vec3} b: The second vector.
* @return {boolean}: Whether the 2 vectors are in the same direction.
*/
private areSameDirection(a: vec3, b: vec3): boolean {
const base: vec3 = vec3.normalize(vec3.create(), b);
const positiveNorm: vec3 = vec3.normalize(vec3.create(), a);
const negativeNorm: vec3 = vec3.normalize(vec3.create(), vec3.scale(vec3.create(), a, -1));

return vec3.equals(positiveNorm, base) || vec3.equals(negativeNorm, base);
}

/**
* Computes scaling vector by dividing destination by pull vectors using
* special division.
*
* @param {vec3} destination: Destination point of scaling.
* @param {vec3} pull: Starting pull point of scaling.
* @return {vec3}: Scaling vector.
* @throws {Error}: If the destination and pull vectors are not in the same direction.
*/
private getScalingVector(destination: vec3, pull: vec3): vec3 {
if (!this.areSameDirection(destination, pull)) {
// TODO: Add the vector values in the error, but the test needs to be updated to a regex.
throw new Error('Destination and pull vectors must be in the same direction.');
}
const x: number = this.scalingDivide(destination[0], pull[0]);
const y: number = this.scalingDivide(destination[1], pull[1]);
const z: number = this.scalingDivide(destination[2], pull[2]);

return vec3.fromValues(x, y, z);
}

/**
* Iteratively perform transforms on all vertices.
*
* @param {mat4} matrix: transformation matrix.
*/
private transform(matrix: mat4) {
this.vertices = this.vertices.map((workingVec: vec4) =>
vec4.transformMat4(vec4.create(), workingVec, matrix)
);
}
}
Loading

0 comments on commit 0fd672e

Please sign in to comment.