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