diff --git a/modules/canvaskit/CHANGELOG.md b/modules/canvaskit/CHANGELOG.md
index 7facb95265b08..732ebb5b11aab 100644
--- a/modules/canvaskit/CHANGELOG.md
+++ b/modules/canvaskit/CHANGELOG.md
@@ -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),
diff --git a/modules/canvaskit/canvaskit/extra.html b/modules/canvaskit/canvaskit/extra.html
index 720273cf17539..ebc711501c311 100644
--- a/modules/canvaskit/canvaskit/extra.html
+++ b/modules/canvaskit/canvaskit/extra.html
@@ -438,36 +438,6 @@
Support for extended color spaces
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) {
@@ -618,13 +588,13 @@ Support for extended color spaces
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
@@ -645,7 +615,10 @@ Support for extended color spaces
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();
@@ -863,7 +836,8 @@ Support for extended color spaces
}
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];
diff --git a/modules/canvaskit/externs.js b/modules/canvaskit/externs.js
index 81a3778a7072c..0a88787c028dc 100644
--- a/modules/canvaskit/externs.js
+++ b/modules/canvaskit/externs.js
@@ -339,6 +339,7 @@ var CanvasKit = {
SkM44: {
identity: function() {},
invert: function() {},
+ mustInvert: function() {},
multiply: function() {},
rotatedUnitSinCos: function() {},
rotated: function() {},
@@ -348,6 +349,7 @@ var CanvasKit = {
perspective: function() {},
rc: function() {},
transpose: function() {},
+ setupCamera: function() {},
},
SkMatrix: {
diff --git a/modules/canvaskit/interface.js b/modules/canvaskit/interface.js
index 0135dbbbaff1c..94029aaef7a94 100644
--- a/modules/canvaskit/interface.js
+++ b/modules/canvaskit/interface.js
@@ -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
diff --git a/modules/canvaskit/tests/matrix.spec.js b/modules/canvaskit/tests/matrix.spec.js
index 12a32ba620e8b..a47c42d2e9be1 100644
--- a/modules/canvaskit/tests/matrix.spec.js
+++ b/modules/canvaskit/tests/matrix.spec.js
@@ -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 {
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
});
\ No newline at end of file