Skip to content

Commit

Permalink
Helper function for camera setup
Browse files Browse the repository at this point in the history
Change-Id: I839fea29c2e56cfc2a4e4557da3197bf211513eb
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/299654
Commit-Queue: Nathaniel Nifong <nifong@google.com>
Reviewed-by: Kevin Lubick <kjlubick@google.com>
  • Loading branch information
Nathaniel Nifong authored and Skia Commit-Bot committed Jul 7, 2020
1 parent ac45f49 commit 6130d50
Show file tree
Hide file tree
Showing 5 changed files with 69 additions and 37 deletions.
1 change: 1 addition & 0 deletions modules/canvaskit/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
the normal TypedArray version. The TypedArray which it returns is also backed by WASM memory
and when passed into CanvasKit will be used w/o copying the data (just like
`Malloc.toTypedArray`).
- `SkM44.setupCamera` to return a 4x4 matrix which sets up a perspective view from a camera.

### Changed
- In all places where color arrays are accepted (gradient makers, drawAtlas, and MakeSkVertices),
Expand Down
44 changes: 9 additions & 35 deletions modules/canvaskit/canvaskit/extra.html
Original file line number Diff line number Diff line change
Expand Up @@ -438,36 +438,6 @@ <h2> Support for extended color spaces </h2>
surface.drawOnce(drawFrame);
}

// Return the inverse of an SkM44. throw an error if it's not invertible
function mustInvert(m) {
const m2 = CanvasKit.SkM44.invert(m);
if (m2 === null) {
throw "Matrix not invertible";
}
return m2;
}

// TODO(nifong): This function is in desperate need of some explanation of what it does
// cam is a object having eye, coa, up, near, far, angle
function saveCamera(canvas, /* rect */ area, /* scalar */ zscale, cam) {
const camera = CanvasKit.SkM44.lookat(cam.eye, cam.coa, cam.up);
const perspective = CanvasKit.SkM44.perspective(cam.near, cam.far, cam.angle);
// Calculate viewport scale. Even through we know these values are all constants in this
// example it might be handy to change the size later.
const center = [(area.fLeft + area.fRight)/2, (area.fTop + area.fBottom)/2, 0];
const viewScale = [(area.fRight - area.fLeft)/2, (area.fBottom - area.fTop)/2, zscale];
const viewport = CanvasKit.SkM44.multiply(
CanvasKit.SkM44.translated(center),
CanvasKit.SkM44.scaled(viewScale));

// want "world" to be in our big coordinates (e.g. area), so apply this inverse
// as part of our "camera".
canvas.concat(CanvasKit.SkM44.multiply(viewport, perspective));
canvas.concat(CanvasKit.SkM44.multiply(camera, mustInvert(viewport)));
// Mark the matrix to make it available to the shader by this name.
canvas.markCTM('local_to_world');
}

function Camera3D(canvas, textureImgData, normalImgData, robotoData) {
const surface = CanvasKit.MakeCanvasSurface('camera3d');
if (!surface) {
Expand Down Expand Up @@ -618,13 +588,13 @@ <h2> Support for extended color spaces </h2>

function setClickToWorld(canvas, matrix) {
const l2d = canvas.getLocalToDevice();
worldToClick = CanvasKit.SkM44.multiply(mustInvert(matrix), l2d);
clickToWorld = mustInvert(worldToClick);
worldToClick = CanvasKit.SkM44.multiply(CanvasKit.SkM44.mustInvert(matrix), l2d);
clickToWorld = CanvasKit.SkM44.mustInvert(worldToClick);
}

function drawCubeFace(canvas, m, color) {
const trans = new CanvasKit.SkM44.translated([vSphereRadius/2, vSphereRadius/2, 0]);
canvas.concat(CanvasKit.SkM44.multiply(trans, m, mustInvert(trans)));
canvas.concat(CanvasKit.SkM44.multiply(trans, m, CanvasKit.SkM44.mustInvert(trans)));
const znormal = front(canvas.getLocalToDevice());
if (znormal < 0) {
return; // skip faces facing backwards
Expand All @@ -645,7 +615,10 @@ <h2> Support for extended color spaces </h2>
canvas.save();
canvas.translate(vSphereCenter[0] - vSphereRadius/2, vSphereCenter[1] - vSphereRadius/2);
// pass surface dimensions as viewport size.
saveCamera(canvas, CanvasKit.LTRBRect(0, 0, vSphereRadius, vSphereRadius), vSphereRadius/2, cam);
canvas.concat(CanvasKit.SkM44.setupCamera(
CanvasKit.LTRBRect(0, 0, vSphereRadius, vSphereRadius), vSphereRadius/2, cam));
// Mark the matrix to make it available to the shader by this name.
canvas.markCTM('local_to_world');
setClickToWorld(canvas, clickM);
for (let f of faces) {
const saveCount = canvas.getSaveCount();
Expand Down Expand Up @@ -863,7 +836,8 @@ <h2> Support for extended color spaces </h2>
}
canvas.save();
// Set up 3D view enviroment
saveCamera(canvas, CanvasKit.LTRBRect(0, 0, sizeX, sizeY), halfDim, cam);
canvas.concat(CanvasKit.SkM44.setupCamera(
CanvasKit.LTRBRect(0, 0, sizeX, sizeY), halfDim, cam));

// Rotate the whole paragraph as a unit.
const paraRotPoint = [halfDim, halfDim, 1];
Expand Down
2 changes: 2 additions & 0 deletions modules/canvaskit/externs.js
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,7 @@ var CanvasKit = {
SkM44: {
identity: function() {},
invert: function() {},
mustInvert: function() {},
multiply: function() {},
rotatedUnitSinCos: function() {},
rotated: function() {},
Expand All @@ -348,6 +349,7 @@ var CanvasKit = {
perspective: function() {},
rc: function() {},
transpose: function() {},
setupCamera: function() {},
},

SkMatrix: {
Expand Down
34 changes: 34 additions & 0 deletions modules/canvaskit/interface.js
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,40 @@ CanvasKit.onRuntimeInitialized = function() {
];
}

// Return the inverse of an SkM44. throw an error if it's not invertible
CanvasKit.SkM44.mustInvert = function(m) {
var m2 = CanvasKit.SkM44.invert(m);
if (m2 === null) {
throw "Matrix not invertible";
}
return m2;
}

// returns a matrix that sets up a 3D perspective view from a given camera.
//
// area - a rect describing the viewport. (0, 0, canvas_width, canvas_height) suggested
// zscale - a scalar describing the scale of the z axis. min(width, height)/2 suggested
// cam - an object with the following attributes
// const cam = {
// 'eye' : [0, 0, 1 / Math.tan(Math.PI / 24) - 1], // a 3D point locating the camera
// 'coa' : [0, 0, 0], // center of attention - the 3D point the camera is looking at.
// 'up' : [0, 1, 0], // a unit vector pointing in the camera's up direction, because eye and coa alone leave roll unspecified.
// 'near' : 0.02, // near clipping plane
// 'far' : 4, // far clipping plane
// 'angle': Math.PI / 12, // field of view in radians
// };
CanvasKit.SkM44.setupCamera = function(area, zscale, cam) {
var camera = CanvasKit.SkM44.lookat(cam['eye'], cam['coa'], cam['up']);
var perspective = CanvasKit.SkM44.perspective(cam['near'], cam['far'], cam['angle']);
var center = [(area.fLeft + area.fRight)/2, (area.fTop + area.fBottom)/2, 0];
var viewScale = [(area.fRight - area.fLeft)/2, (area.fBottom - area.fTop)/2, zscale];
var viewport = CanvasKit.SkM44.multiply(
CanvasKit.SkM44.translated(center),
CanvasKit.SkM44.scaled(viewScale));
return CanvasKit.SkM44.multiply(
viewport, perspective, camera, CanvasKit.SkM44.mustInvert(viewport));
}

// An SkColorMatrix is a 4x4 color matrix that transforms the 4 color channels
// with a 1x4 matrix that post-translates those 4 channels.
// For example, the following is the layout with the scale (S) and post-transform
Expand Down
25 changes: 23 additions & 2 deletions modules/canvaskit/tests/matrix.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ describe('CanvasKit\'s Matrix Helpers', () => {
await LoadCanvasKit;
});

const expectArrayCloseTo = (a, b) => {
const expectArrayCloseTo = (a, b, precision) => {
precision = precision || 14 // digits of precision in base 10
expect(a.length).toEqual(b.length);
for (let i=0; i<a.length; i++) {
expect(a[i]).toBeCloseTo(b[i], 14); // 14 digits of precision in base 10
expect(a[i]).toBeCloseTo(b[i], precision);
}
};

Expand Down Expand Up @@ -185,5 +186,25 @@ describe('CanvasKit\'s Matrix Helpers', () => {
CanvasKit.SkM44.multiply(a, b),
CanvasKit.SkM44.identity());
});

it('can create a camera setup matrix', () => {
const camAngle = Math.PI / 12;
const cam = {
'eye' : [0, 0, 1 / Math.tan(camAngle/2) - 1],
'coa' : [0, 0, 0],
'up' : [0, 1, 0],
'near' : 0.02,
'far' : 4,
'angle': camAngle,
};
const mat = CanvasKit.SkM44.setupCamera(CanvasKit.LTRBRect(0, 0, 200, 200), 200, cam);
// these values came from an invocation of setupCamera visually inspected.
const expected = [
7.595754, 0, -0.5, 0,
0, 7.595754, -0.5, 0,
0, 0, 1.010050, -1324.368418,
0, 0, -0.005, 7.595754];
expectArrayCloseTo(mat, expected, 5);
});
}); // describe 4x4
});

0 comments on commit 6130d50

Please sign in to comment.