Expose 4x4 matrices on canvas in a way similar to SimpleMatrix, add example.
Bug: skia:9866 Change-Id: I718455743e482e4f60a462027b629dc19b1dbad3 Reviewed-on: https://skia-review.googlesource.com/c/skia/+/270201 Commit-Queue: Nathaniel Nifong <nifong@google.com> Reviewed-by: Kevin Lubick <kjlubick@google.com>
This commit is contained in:
parent
0e6832de5a
commit
77798b4585
@ -17,9 +17,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
`SkSurface.requestAnimationFrame` for animation logic).
|
||||
- `CanvasKit.parseColorString` which processes color strings like "#2288FF"
|
||||
- Particles module now exposes effect uniforms, which can be modified for live-updating.
|
||||
- Experimental 4x4 matrices added in `SkM44`
|
||||
- Vector math functions added in `SkVector`
|
||||
|
||||
### Changed
|
||||
- We now compile/ship with Emscripten v1.39.6.
|
||||
- `SkMatrix.multiply` can now accept any number of matrix arguments, multiplying them
|
||||
left-to-right.
|
||||
- SkMatrix.invert now returns null when the matrix is not invertible. Previously it would return an
|
||||
identity matrix. Callers must determine what behavior would be appropriate in this situation.
|
||||
|
||||
### Fixed
|
||||
- Support for .otf fonts (.woff and .woff2 still not supported).
|
||||
|
@ -7,8 +7,6 @@
|
||||
<style>
|
||||
canvas {
|
||||
border: 1px dashed #AAA;
|
||||
width: 300px;
|
||||
height: 300px;
|
||||
}
|
||||
|
||||
</style>
|
||||
@ -37,6 +35,9 @@
|
||||
<h2> CanvasKit can serialize/deserialize .skp files</h2>
|
||||
<canvas id=skp width=300 height=300></canvas>
|
||||
|
||||
<h2> 3D perspective transformations </h2>
|
||||
<canvas id=camera3d width=500 height=500></canvas>
|
||||
|
||||
<script type="text/javascript" src="/node_modules/canvaskit/bin/canvaskit.js"></script>
|
||||
|
||||
<script type="text/javascript" charset="utf-8">
|
||||
@ -81,6 +82,7 @@
|
||||
RTShaderAPI1(CanvasKit);
|
||||
|
||||
SkpExample(CanvasKit, skpData);
|
||||
Camera3D(CanvasKit);
|
||||
});
|
||||
|
||||
fetch('https://storage.googleapis.com/skia-cdn/misc/lego_loader.json').then((resp) => {
|
||||
@ -423,4 +425,252 @@ const curves = {
|
||||
// Intentionally just draw frame once
|
||||
surface.drawOnce(drawFrame);
|
||||
}
|
||||
|
||||
function Camera3D(canvas) {
|
||||
const surface = CanvasKit.MakeCanvasSurface('camera3d');
|
||||
if (!surface) {
|
||||
console.error('Could not make surface');
|
||||
return;
|
||||
}
|
||||
|
||||
const sizeX = document.getElementById('camera3d').width;
|
||||
const sizeY = document.getElementById('camera3d').height;
|
||||
|
||||
var clickToWorld = CanvasKit.SkM44.identity();
|
||||
var worldToClick = CanvasKit.SkM44.identity();
|
||||
// rotation of the cube shown in the demo
|
||||
var rotation = CanvasKit.SkM44.identity();
|
||||
// temporary during a click and drag
|
||||
var clickRotation = CanvasKit.SkM44.identity();
|
||||
|
||||
// A virtual sphere used for tumbling the object on screen.
|
||||
const vSphereCenter = [sizeX/2, sizeY/2];
|
||||
const vSphereRadius = Math.min(...vSphereCenter);
|
||||
|
||||
// The rounded rect used for each face
|
||||
const margin = vSphereRadius / 20;
|
||||
const rr = CanvasKit.RRectXY(CanvasKit.LTRBRect(margin, margin,
|
||||
vSphereRadius - margin, vSphereRadius - margin), margin*2.5, margin*2.5);
|
||||
|
||||
const camNear = 0.05;
|
||||
const camFar = 4;
|
||||
const camAngle = Math.PI / 12;
|
||||
|
||||
const camEye = [0, 0, 1 / Math.tan(camAngle/2) - 1];
|
||||
const camCOA = [0, 0, 0];
|
||||
const camUp = [0, 1, 0];
|
||||
|
||||
var mouseDown = false;
|
||||
var clickDown = [0, 0]; // location of click down
|
||||
var lastMouse = [0, 0]; // last mouse location
|
||||
|
||||
// keep spinning after mouse up. Also start spinning on load
|
||||
var axis = [0.4, 1, 1];
|
||||
var totalSpin = 0;
|
||||
var spinRate = 0.1;
|
||||
var lastRadians = 0;
|
||||
var spinning = setInterval(keepSpinning, 30);
|
||||
|
||||
const textPaint = new CanvasKit.SkPaint();
|
||||
textPaint.setColor(CanvasKit.BLACK);
|
||||
textPaint.setAntiAlias(true);
|
||||
const roboto = CanvasKit.SkFontMgr.RefDefault().MakeTypefaceFromData(robotoData);
|
||||
const textFont = new CanvasKit.SkFont(roboto, 30);
|
||||
|
||||
// Takes an x and y rotation in radians and a scale and returns a 4x4 matrix used to draw a
|
||||
// face of the cube in that orientation.
|
||||
function faceM44(rx, ry, scale) {
|
||||
return CanvasKit.SkM44.multiply(
|
||||
CanvasKit.SkM44.rotated([0,1,0], ry),
|
||||
CanvasKit.SkM44.rotated([1,0,0], rx),
|
||||
CanvasKit.SkM44.translated([0, 0, scale]));
|
||||
}
|
||||
|
||||
const faceScale = vSphereRadius/2
|
||||
const faces = [
|
||||
{matrix: faceM44( 0, 0, faceScale ), color:CanvasKit.RED}, // front
|
||||
{matrix: faceM44( 0, Math.PI, faceScale ), color:CanvasKit.GREEN}, // back
|
||||
|
||||
{matrix: faceM44( Math.PI/2, 0, faceScale ), color:CanvasKit.BLUE}, // top
|
||||
{matrix: faceM44(-Math.PI/2, 0, faceScale ), color:CanvasKit.CYAN}, // bottom
|
||||
|
||||
{matrix: faceM44( 0, Math.PI/2, faceScale ), color:CanvasKit.MAGENTA}, // left
|
||||
{matrix: faceM44( 0,-Math.PI/2, faceScale ), color:CanvasKit.YELLOW}, // right
|
||||
];
|
||||
|
||||
// Returns a component of the matrix m indicating whether it faces the camera.
|
||||
// If it's positive for one of the matrices representing the face of the cube,
|
||||
// that face is currently in front.
|
||||
function front(m) {
|
||||
// Is this invertible?
|
||||
var m2 = CanvasKit.SkM44.invert(m);
|
||||
if (m2 === null) {
|
||||
m2 = CanvasKit.SkM44.identity();
|
||||
}
|
||||
// look at the sign of the z-scale of the inverse of m.
|
||||
// that's the number in row 2, col 2.
|
||||
return m2[10]
|
||||
}
|
||||
|
||||
// Return the inverse of an SkM44. throw an error if it's not invertible
|
||||
function mustInvert(m) {
|
||||
var m2 = CanvasKit.SkM44.invert(m);
|
||||
if (m2 === null) {
|
||||
throw "Matrix not invertible";
|
||||
}
|
||||
return m2;
|
||||
}
|
||||
|
||||
function saveCamera(canvas, /* rect */ area, /* scalar */ zscale) {
|
||||
const camera = CanvasKit.SkM44.lookat(camEye, camCOA, camUp);
|
||||
const perspective = CanvasKit.SkM44.perspective(camNear, camFar, camAngle);
|
||||
// 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.experimental_saveCamera(
|
||||
CanvasKit.SkM44.multiply(viewport, perspective),
|
||||
CanvasKit.SkM44.multiply(camera, mustInvert(viewport)));
|
||||
}
|
||||
|
||||
function setClickToWorld(canvas, matrix) {
|
||||
const l2d = canvas.experimental_getLocalToDevice();
|
||||
worldToClick = CanvasKit.SkM44.multiply(mustInvert(matrix), l2d);
|
||||
clickToWorld = mustInvert(worldToClick);
|
||||
}
|
||||
|
||||
function drawCubeFace(canvas, m, color) {
|
||||
const trans = new CanvasKit.SkM44.translated([vSphereRadius/2, vSphereRadius/2, 0]);
|
||||
canvas.experimental_concat44(CanvasKit.SkM44.multiply(trans, m, mustInvert(trans)));
|
||||
const znormal = front(canvas.experimental_getLocalToDevice());
|
||||
if (znormal < 0) {
|
||||
return;// skip faces facing backwards
|
||||
}
|
||||
const paint = new CanvasKit.SkPaint();
|
||||
paint.setColor(color);
|
||||
// TODO replace color with a bump shader
|
||||
canvas.drawRRect(rr, paint);
|
||||
canvas.drawText(znormal.toFixed(2), faceScale*0.25, faceScale*0.4, textPaint, textFont);
|
||||
}
|
||||
|
||||
function drawFrame(canvas) {
|
||||
// TODO if not GPU backend, print an error to the console and return.
|
||||
// this demo only works in GL.
|
||||
const clickM = canvas.experimental_getLocalToDevice();
|
||||
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);
|
||||
setClickToWorld(canvas, clickM);
|
||||
for (let f of faces) {
|
||||
const saveCount = canvas.getSaveCount();
|
||||
canvas.save();
|
||||
drawCubeFace(canvas, CanvasKit.SkM44.multiply(clickRotation, rotation, f.matrix), f.color);
|
||||
canvas.restoreToCount(saveCount);
|
||||
}
|
||||
canvas.restore(); // camera
|
||||
canvas.restore(); // center the following content in the window
|
||||
|
||||
// draw virtual sphere outline.
|
||||
const paint = new CanvasKit.SkPaint();
|
||||
paint.setAntiAlias(true);
|
||||
paint.setStyle(CanvasKit.PaintStyle.Stroke);
|
||||
paint.setColor(CanvasKit.Color(64, 255, 0, 1.0));
|
||||
canvas.drawCircle(vSphereCenter[0], vSphereCenter[1], vSphereRadius, paint);
|
||||
canvas.drawLine(vSphereCenter[0], vSphereCenter[1] - vSphereRadius,
|
||||
vSphereCenter[0], vSphereCenter[1] + vSphereRadius, paint);
|
||||
canvas.drawLine(vSphereCenter[0] - vSphereRadius, vSphereCenter[1],
|
||||
vSphereCenter[0] + vSphereRadius, vSphereCenter[1], paint);
|
||||
}
|
||||
|
||||
// convert a 2D point in the circle displayed on screen to a 3D unit vector.
|
||||
// the virtual sphere is a technique selecting a 3D direction by clicking on a the projection
|
||||
// of a hemisphere.
|
||||
function vSphereUnitV3(p) {
|
||||
// v = (v - fCenter) * (1 / fRadius);
|
||||
let v = CanvasKit.SkVector.mulScalar(CanvasKit.SkVector.sub(p, vSphereCenter), 1/vSphereRadius);
|
||||
|
||||
// constrain the clicked point within the circle.
|
||||
let len2 = CanvasKit.SkVector.lengthSquared(v);
|
||||
if (len2 > 1) {
|
||||
v = CanvasKit.SkVector.normalize(v);
|
||||
len2 = 1;
|
||||
}
|
||||
// the closer to the edge of the circle you are, the closer z is to zero.
|
||||
const z = Math.sqrt(1 - len2);
|
||||
v.push(z);
|
||||
return v;
|
||||
}
|
||||
|
||||
function computeVSphereRotation(start, end) {
|
||||
const u = vSphereUnitV3(start);
|
||||
const v = vSphereUnitV3(end);
|
||||
// Axis is in the scope of the Camera3D function so it can be used in keepSpinning.
|
||||
axis = CanvasKit.SkVector.cross(u, v);
|
||||
const sinValue = CanvasKit.SkVector.length(axis);
|
||||
const cosValue = CanvasKit.SkVector.dot(u, v);
|
||||
|
||||
let m = new CanvasKit.SkM44.identity();
|
||||
if (Math.abs(sinValue) > 0.000000001) {
|
||||
m = CanvasKit.SkM44.rotatedUnitSinCos(
|
||||
CanvasKit.SkVector.mulScalar(axis, 1/sinValue), sinValue, cosValue);
|
||||
const radians = Math.atan(cosValue / sinValue);
|
||||
spinRate = lastRadians - radians;
|
||||
lastRadians = radians;
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
function keepSpinning() {
|
||||
totalSpin += spinRate;
|
||||
clickRotation = CanvasKit.SkM44.rotated(axis, totalSpin);
|
||||
spinRate *= .998;
|
||||
if (spinRate < 0.01) {
|
||||
stopSpinning();
|
||||
}
|
||||
surface.requestAnimationFrame(drawFrame);
|
||||
}
|
||||
|
||||
function stopSpinning() {
|
||||
clearInterval(spinning);
|
||||
rotation = CanvasKit.SkM44.multiply(clickRotation, rotation);
|
||||
clickRotation = CanvasKit.SkM44.identity();
|
||||
}
|
||||
|
||||
function interact(e) {
|
||||
const type = e.type;
|
||||
if (type === 'lostpointercapture' || type === 'pointerup' || type == 'pointerleave') {
|
||||
mouseDown = false;
|
||||
if (spinRate > 0.02) {
|
||||
stopSpinning();
|
||||
spinning = setInterval(keepSpinning, 30);
|
||||
}
|
||||
return;
|
||||
} else if (type === 'pointermove') {
|
||||
if (!mouseDown) { return; }
|
||||
lastMouse = [e.offsetX, e.offsetY];
|
||||
clickRotation = computeVSphereRotation(clickDown, lastMouse);
|
||||
} else if (type === 'pointerdown') {
|
||||
stopSpinning();
|
||||
mouseDown = true;
|
||||
clickDown = [e.offsetX, e.offsetY];
|
||||
lastMouse = clickDown;
|
||||
}
|
||||
surface.requestAnimationFrame(drawFrame);
|
||||
};
|
||||
|
||||
document.getElementById('camera3d').addEventListener('pointermove', interact);
|
||||
document.getElementById('camera3d').addEventListener('pointerdown', interact);
|
||||
document.getElementById('camera3d').addEventListener('lostpointercapture', interact);
|
||||
document.getElementById('camera3d').addEventListener('pointerleave', interact);
|
||||
document.getElementById('camera3d').addEventListener('pointerup', interact);
|
||||
|
||||
surface.requestAnimationFrame(drawFrame);
|
||||
}
|
||||
</script>
|
||||
|
@ -44,6 +44,7 @@
|
||||
#include "include/effects/SkImageFilters.h"
|
||||
#include "include/effects/SkRuntimeEffect.h"
|
||||
#include "include/effects/SkTrimPathEffect.h"
|
||||
#include "include/private/SkM44.h"
|
||||
#include "include/utils/SkParsePath.h"
|
||||
#include "include/utils/SkShadowUtils.h"
|
||||
#include "modules/skshaper/include/SkShaper.h"
|
||||
@ -91,6 +92,7 @@ using Bone = SkVertices::Bone;
|
||||
sk_sp<SkFontMgr> SkFontMgr_New_Custom_Data(const uint8_t** datas, const size_t* sizes, int n);
|
||||
#endif
|
||||
|
||||
// 3x3 Matrices
|
||||
struct SimpleMatrix {
|
||||
SkScalar scaleX, skewX, transX;
|
||||
SkScalar skewY, scaleY, transY;
|
||||
@ -110,6 +112,34 @@ SimpleMatrix toSimpleSkMatrix(const SkMatrix& sm) {
|
||||
return m;
|
||||
}
|
||||
|
||||
// Experimental 4x4 matrices, also represented in JS with arrays.
|
||||
struct SimpleM44 {
|
||||
SkScalar m0, m1, m2, m3;
|
||||
SkScalar m4, m5, m6, m7;
|
||||
SkScalar m8, m9, m10, m11;
|
||||
SkScalar m12, m13, m14, m15;
|
||||
};
|
||||
|
||||
SkM44 toSkM44(const SimpleM44& sm) {
|
||||
SkM44 result(
|
||||
sm.m0, sm.m1, sm.m2, sm.m3,
|
||||
sm.m4, sm.m5, sm.m6, sm.m7,
|
||||
sm.m8, sm.m9, sm.m10, sm.m11,
|
||||
sm.m12, sm.m13, sm.m14, sm.m15);
|
||||
return result;
|
||||
}
|
||||
|
||||
SimpleM44 toSimpleM44(const SkM44& sm) {
|
||||
SimpleM44 m {
|
||||
sm.rc(0,0), sm.rc(0,1), sm.rc(0,2), sm.rc(0,3),
|
||||
sm.rc(1,0), sm.rc(1,1), sm.rc(1,2), sm.rc(1,3),
|
||||
sm.rc(2,0), sm.rc(2,1), sm.rc(2,2), sm.rc(2,3),
|
||||
sm.rc(3,0), sm.rc(3,1), sm.rc(3,2), sm.rc(3,3),
|
||||
};
|
||||
return m;
|
||||
}
|
||||
|
||||
// Surface creation structs and helpers
|
||||
struct SimpleImageInfo {
|
||||
int width;
|
||||
int height;
|
||||
@ -1024,6 +1054,26 @@ EMSCRIPTEN_BINDINGS(Skia) {
|
||||
SkImageInfo dstInfo = toSkImageInfo(di);
|
||||
|
||||
return self.writePixels(dstInfo, pixels, srcRowBytes, dstX, dstY);
|
||||
}))
|
||||
// Experimental 4x4 matrix functions
|
||||
.function("experimental_saveCamera", optional_override([](SkCanvas& self,
|
||||
const SimpleM44& projection, const SimpleM44& camera) {
|
||||
self.experimental_saveCamera(toSkM44(projection), toSkM44(camera));
|
||||
}))
|
||||
.function("experimental_concat44", optional_override([](SkCanvas& self, const SimpleM44& m) {
|
||||
self.concat44(toSkM44(m));
|
||||
}))
|
||||
.function("experimental_getLocalToDevice", optional_override([](const SkCanvas& self)->SimpleM44 {
|
||||
SkM44 m = self.getLocalToDevice();
|
||||
return toSimpleM44(m);
|
||||
}))
|
||||
.function("experimental_getLocalToWorld", optional_override([](const SkCanvas& self)->SimpleM44 {
|
||||
SkM44 m = self.experimental_getLocalToWorld();
|
||||
return toSimpleM44(m);
|
||||
}))
|
||||
.function("experimental_getLocalToCamera", optional_override([](const SkCanvas& self)->SimpleM44 {
|
||||
SkM44 m = self.experimental_getLocalToCamera();
|
||||
return toSimpleM44(m);
|
||||
}));
|
||||
|
||||
class_<SkColorFilter>("SkColorFilter")
|
||||
@ -1632,7 +1682,7 @@ EMSCRIPTEN_BINDINGS(Skia) {
|
||||
|
||||
// A value object is much simpler than a class - it is returned as a JS
|
||||
// object and does not require delete().
|
||||
// https://kripken.github.io/emscripten-site/docs/porting/connecting_cpp_and_javascript/embind.html#value-types
|
||||
// https://emscripten.org/docs/porting/connecting_cpp_and_javascript/embind.html#value-types
|
||||
value_object<ShapedTextOpts>("ShapedTextOpts")
|
||||
.field("font", &ShapedTextOpts::font)
|
||||
.field("leftToRight", &ShapedTextOpts::leftToRight)
|
||||
@ -1724,9 +1774,17 @@ EMSCRIPTEN_BINDINGS(Skia) {
|
||||
.element(&SimpleMatrix::pers1)
|
||||
.element(&SimpleMatrix::pers2);
|
||||
|
||||
value_array<SimpleM44>("SkM44")
|
||||
.element(&SimpleM44::m0).element(&SimpleM44::m1).element(&SimpleM44::m2).element(&SimpleM44::m3)
|
||||
.element(&SimpleM44::m4).element(&SimpleM44::m5).element(&SimpleM44::m6).element(&SimpleM44::m7)
|
||||
.element(&SimpleM44::m8).element(&SimpleM44::m9).element(&SimpleM44::m10).element(&SimpleM44::m11)
|
||||
.element(&SimpleM44::m12).element(&SimpleM44::m13).element(&SimpleM44::m14).element(&SimpleM44::m15);
|
||||
|
||||
constant("TRANSPARENT", SK_ColorTRANSPARENT);
|
||||
constant("RED", SK_ColorRED);
|
||||
constant("GREEN", SK_ColorGREEN);
|
||||
constant("BLUE", SK_ColorBLUE);
|
||||
constant("MAGENTA", SK_ColorMAGENTA);
|
||||
constant("YELLOW", SK_ColorYELLOW);
|
||||
constant("CYAN", SK_ColorCYAN);
|
||||
constant("BLACK", SK_ColorBLACK);
|
||||
|
@ -1,3 +1,4 @@
|
||||
function SkDebug(msg) {
|
||||
console.warn(msg);
|
||||
}
|
||||
}
|
||||
/** @const */ var skIsDebug = true;
|
@ -280,6 +280,20 @@ var CanvasKit = {
|
||||
MakeMatrixTransform: function() {},
|
||||
},
|
||||
|
||||
// These are defined in interface.js
|
||||
SkM44: {
|
||||
identity: function() {},
|
||||
invert: function() {},
|
||||
multiply: function() {},
|
||||
rotatedUnitSinCos: function() {},
|
||||
rotated: function() {},
|
||||
scaled: function() {},
|
||||
translated: function() {},
|
||||
lookat: function() {},
|
||||
perspective: function() {},
|
||||
rc: function() {},
|
||||
},
|
||||
|
||||
SkMatrix: {
|
||||
identity: function() {},
|
||||
invert: function() {},
|
||||
@ -468,6 +482,18 @@ var CanvasKit = {
|
||||
_MakeFromText: function() {},
|
||||
},
|
||||
|
||||
// These are defined in interface.js
|
||||
SkVector: {
|
||||
add: function() {},
|
||||
sub: function() {},
|
||||
dot: function() {},
|
||||
cross: function() {},
|
||||
normalize: function() {},
|
||||
mulScalar: function() {},
|
||||
length: function() {},
|
||||
lengthSquared: function() {},
|
||||
},
|
||||
|
||||
SkVertices: {
|
||||
// public API (from C++ bindings)
|
||||
bounds: function() {},
|
||||
|
@ -16,29 +16,67 @@ CanvasKit.onRuntimeInitialized = function() {
|
||||
// have a mapPoints() function (which could maybe be tacked on here).
|
||||
// If DOMMatrix catches on, it would be worth re-considering this usage.
|
||||
CanvasKit.SkMatrix = {};
|
||||
function sdot(a, b, c, d, e, f) {
|
||||
e = e || 0;
|
||||
f = f || 0;
|
||||
return a * b + c * d + e * f;
|
||||
function sdot() { // to be called with an even number of scalar args
|
||||
var acc = 0;
|
||||
for (var i=0; i < arguments.length-1; i+=2) {
|
||||
acc += arguments[i] * arguments[i+1];
|
||||
}
|
||||
return acc;
|
||||
}
|
||||
|
||||
|
||||
// Private general matrix functions used in both 3x3s and 4x4s.
|
||||
// Return a square identity matrix of size n.
|
||||
var identityN = function(n) {
|
||||
var size = n*n;
|
||||
var m = new Array(size);
|
||||
while(size--) {
|
||||
m[size] = size%(n+1) == 0 ? 1.0 : 0.0;
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
// Stride, a function for compactly representing several ways of copying an array into another.
|
||||
// Write vector `v` into matrix `m`. `m` is a matrix encoded as an array in row-major
|
||||
// order. Its width is passed as `width`. `v` is an array with length < (m.length/width).
|
||||
// An element of `v` is copied into `m` starting at `offset` and moving `colStride` cols right
|
||||
// each row.
|
||||
//
|
||||
// For example, a width of 4, offset of 3, and stride of -1 would put the vector here.
|
||||
// _ _ 0 _
|
||||
// _ 1 _ _
|
||||
// 2 _ _ _
|
||||
// _ _ _ 3
|
||||
//
|
||||
var stride = function(v, m, width, offset, colStride) {
|
||||
for (var i=0; i<v.length; i++) {
|
||||
m[i * width + // column
|
||||
(i * colStride + offset + width) % width // row
|
||||
] = v[i];
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
CanvasKit.SkMatrix.identity = function() {
|
||||
return [
|
||||
1, 0, 0,
|
||||
0, 1, 0,
|
||||
0, 0, 1,
|
||||
];
|
||||
return identityN(3);
|
||||
};
|
||||
|
||||
// Return the inverse (if it exists) of this matrix.
|
||||
// Otherwise, return the identity.
|
||||
CanvasKit.SkMatrix.invert = function(m) {
|
||||
// Find the determinant by the sarrus rule. https://en.wikipedia.org/wiki/Rule_of_Sarrus
|
||||
var det = m[0]*m[4]*m[8] + m[1]*m[5]*m[6] + m[2]*m[3]*m[7]
|
||||
- m[2]*m[4]*m[6] - m[1]*m[3]*m[8] - m[0]*m[5]*m[7];
|
||||
if (!det) {
|
||||
SkDebug('Warning, uninvertible matrix');
|
||||
return CanvasKit.SkMatrix.identity();
|
||||
return null;
|
||||
}
|
||||
// Return the inverse by the formula adj(m)/det.
|
||||
// adj (adjugate) of a 3x3 is the transpose of it's cofactor matrix.
|
||||
// a cofactor matrix is a matrix where each term is +-det(N) where matrix N is the 2x2 formed
|
||||
// by removing the row and column we're currently setting from the source.
|
||||
// the sign alternates in a checkerboard pattern with a `+` at the top left.
|
||||
// that's all been combined here into one expression.
|
||||
return [
|
||||
(m[4]*m[8] - m[5]*m[7])/det, (m[2]*m[7] - m[1]*m[8])/det, (m[1]*m[5] - m[2]*m[4])/det,
|
||||
(m[5]*m[6] - m[3]*m[8])/det, (m[0]*m[8] - m[2]*m[6])/det, (m[2]*m[3] - m[0]*m[5])/det,
|
||||
@ -50,7 +88,7 @@ CanvasKit.onRuntimeInitialized = function() {
|
||||
// Results are done in place.
|
||||
// See SkMatrix.h::mapPoints for the docs on the math.
|
||||
CanvasKit.SkMatrix.mapPoints = function(matrix, ptArr) {
|
||||
if (ptArr.length % 2) {
|
||||
if (skIsDebug && (ptArr.length % 2)) {
|
||||
throw 'mapPoints requires an even length arr';
|
||||
}
|
||||
for (var i = 0; i < ptArr.length; i+=2) {
|
||||
@ -67,18 +105,56 @@ CanvasKit.onRuntimeInitialized = function() {
|
||||
return ptArr;
|
||||
};
|
||||
|
||||
CanvasKit.SkMatrix.multiply = function(m1, m2) {
|
||||
var result = [0,0,0, 0,0,0, 0,0,0];
|
||||
for (var r = 0; r < 3; r++) {
|
||||
for (var c = 0; c < 3; c++) {
|
||||
// m1 and m2 are 1D arrays pretending to be 2D arrays
|
||||
result[3*r + c] = sdot(m1[3*r + 0], m2[3*0 + c],
|
||||
m1[3*r + 1], m2[3*1 + c],
|
||||
m1[3*r + 2], m2[3*2 + c]);
|
||||
function isnumber(val) { return val !== NaN; };
|
||||
|
||||
// gereralized iterative algorithm for multiplying two matrices.
|
||||
function multiply(m1, m2, size) {
|
||||
|
||||
if (skIsDebug && (!m1.every(isnumber) || !m2.every(isnumber))) {
|
||||
throw 'Some members of matrices are NaN m1='+m1+', m2='+m2+'';
|
||||
}
|
||||
if (skIsDebug && (m1.length !== m2.length)) {
|
||||
throw 'Undefined for matrices of different sizes. m1.length='+m1.length+', m2.length='+m2.length;
|
||||
}
|
||||
if (skIsDebug && (size*size !== m1.length)) {
|
||||
throw 'Undefined for non-square matrices. array size was '+size;
|
||||
}
|
||||
|
||||
var result = Array(m1.length);
|
||||
for (var r = 0; r < size; r++) {
|
||||
for (var c = 0; c < size; c++) {
|
||||
// accumulate a sum of m1[r,k]*m2[k, c]
|
||||
var acc = 0;
|
||||
for (var k = 0; k < size; k++) {
|
||||
acc += m1[size * r + k] * m2[size * k + c];
|
||||
}
|
||||
result[r * size + c] = acc;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
// Accept an integer indicating the size of the matrices being multiplied (3 for 3x3), and any
|
||||
// number of matrices following it.
|
||||
function multiplyMany(size, listOfMatrices) {
|
||||
if (skIsDebug && (listOfMatrices.length < 2)) {
|
||||
throw 'multiplication expected two or more matrices';
|
||||
}
|
||||
var result = multiply(listOfMatrices[0], listOfMatrices[1], size);
|
||||
var next = 2;
|
||||
while (next < listOfMatrices.length) {
|
||||
result = multiply(result, listOfMatrices[next], size);
|
||||
next++;
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
// Accept any number 3x3 of matrices as arguments, multiply them together.
|
||||
// Matrix multiplication is associative but not commutatieve. the order of the arguments
|
||||
// matters, but it does not matter that this implementation multiplies them left to right.
|
||||
CanvasKit.SkMatrix.multiply = function() {
|
||||
return multiplyMany(3, arguments);
|
||||
};
|
||||
|
||||
// Return a matrix representing a rotation by n radians.
|
||||
// px, py optionally say which point the rotation should be around
|
||||
@ -98,31 +174,241 @@ CanvasKit.onRuntimeInitialized = function() {
|
||||
CanvasKit.SkMatrix.scaled = function(sx, sy, px, py) {
|
||||
px = px || 0;
|
||||
py = py || 0;
|
||||
return [
|
||||
sx, 0, px - sx * px,
|
||||
0, sy, py - sy * py,
|
||||
0, 0, 1,
|
||||
];
|
||||
var m = stride([sx, sy], identityN(3), 3, 0, 1);
|
||||
return stride([px-sx*px, py-sy*py], m, 3, 2, 0);
|
||||
};
|
||||
|
||||
CanvasKit.SkMatrix.skewed = function(kx, ky, px, py) {
|
||||
px = px || 0;
|
||||
py = py || 0;
|
||||
return [
|
||||
1, kx, -kx * px,
|
||||
ky, 1, -ky * py,
|
||||
0, 0, 1,
|
||||
];
|
||||
var m = stride([kx, ky], identityN(3), 3, 1, -1);
|
||||
return stride([-kx*px, -ky*py], m, 3, 2, 0);
|
||||
};
|
||||
|
||||
CanvasKit.SkMatrix.translated = function(dx, dy) {
|
||||
return [
|
||||
1, 0, dx,
|
||||
0, 1, dy,
|
||||
0, 0, 1,
|
||||
];
|
||||
return stride(arguments, identityN(3), 3, 2, 0);
|
||||
};
|
||||
|
||||
// Functions for manipulating vectors.
|
||||
// Loosely based off of SkV3 in SkM44.h but skia also has SkVec2 and Skv4. This combines them and
|
||||
// works on vectors of any length.
|
||||
CanvasKit.SkVector = {};
|
||||
CanvasKit.SkVector.dot = function(a, b) {
|
||||
if (skIsDebug && (a.length !== b.length)) {
|
||||
throw 'Cannot perform dot product on arrays of different length ('+a.length+' vs '+b.length+')';
|
||||
}
|
||||
return a.map(function(v, i) { return v*b[i] }).reduce(function(acc, cur) { return acc + cur; });
|
||||
}
|
||||
CanvasKit.SkVector.lengthSquared = function(v) {
|
||||
return CanvasKit.SkVector.dot(v, v);
|
||||
}
|
||||
CanvasKit.SkVector.length = function(v) {
|
||||
return Math.sqrt(CanvasKit.SkVector.lengthSquared(v));
|
||||
}
|
||||
CanvasKit.SkVector.mulScalar = function(v, s) {
|
||||
return v.map(function(i) { return i*s });
|
||||
}
|
||||
CanvasKit.SkVector.add = function(a, b) {
|
||||
return a.map(function(v, i) { return v+b[i] });
|
||||
}
|
||||
CanvasKit.SkVector.sub = function(a, b) {
|
||||
return a.map(function(v, i) { return v-b[i]; });
|
||||
}
|
||||
CanvasKit.SkVector.normalize = function(v) {
|
||||
return CanvasKit.SkVector.mulScalar(v, 1/CanvasKit.SkVector.length(v));
|
||||
}
|
||||
CanvasKit.SkVector.cross = function(a, b) {
|
||||
if (skIsDebug && (a.length !== 3 || a.length !== 3)) {
|
||||
throw 'Cross product is only defined for 3-dimensional vectors (a.length='+a.length+', b.length='+b.length+')';
|
||||
}
|
||||
return [
|
||||
a[1]*b[2] - a[2]*b[1],
|
||||
a[2]*b[0] - a[0]*b[2],
|
||||
a[0]*b[1] - a[1]*b[0],
|
||||
];
|
||||
}
|
||||
|
||||
// Functions for creating and manipulating 4x4 matrices. Accepted in place of SkM44 in canvas
|
||||
// methods, for the same reasons as the 3x3 matrices above.
|
||||
// ported from C++ code in SkM44.cpp
|
||||
CanvasKit.SkM44 = {};
|
||||
// Create a 4x4 identity matrix
|
||||
CanvasKit.SkM44.identity = function() {
|
||||
return identityN(4);
|
||||
}
|
||||
|
||||
// Anything named vec below is an array of length 3 representing a vector/point in 3D space.
|
||||
// Create a 4x4 matrix representing a translate by the provided 3-vec
|
||||
CanvasKit.SkM44.translated = function(vec) {
|
||||
return stride(vec, identityN(4), 4, 3, 0);
|
||||
}
|
||||
// Create a 4x4 matrix representing a scaling by the provided 3-vec
|
||||
CanvasKit.SkM44.scaled = function(vec) {
|
||||
return stride(vec, identityN(4), 4, 0, 1);
|
||||
}
|
||||
// Create a 4x4 matrix representing a rotation about the provided axis 3-vec.
|
||||
// axis does not need to be normalized.
|
||||
CanvasKit.SkM44.rotated = function(axisVec, radians) {
|
||||
return CanvasKit.SkM44.rotatedUnitSinCos(
|
||||
CanvasKit.SkVector.normalize(axisVec), Math.sin(radians), Math.cos(radians));
|
||||
}
|
||||
// Create a 4x4 matrix representing a rotation about the provided normalized axis 3-vec.
|
||||
// Rotation is provided redundantly as both sin and cos values.
|
||||
// This rotate can be used when you already have the cosAngle and sinAngle values
|
||||
// so you don't have to atan(cos/sin) to call roatated() which expects an angle in radians.
|
||||
// this does no checking! Behavior for invalid sin or cos values or non-normalized axis vectors
|
||||
// is incorrect. Prefer rotate().
|
||||
CanvasKit.SkM44.rotatedUnitSinCos = function(axisVec, sinAngle, cosAngle) {
|
||||
var x = axisVec[0];
|
||||
var y = axisVec[1];
|
||||
var z = axisVec[2];
|
||||
var c = cosAngle;
|
||||
var s = sinAngle;
|
||||
var t = 1 - c;
|
||||
return [
|
||||
t*x*x + c, t*x*y - s*z, t*x*z + s*y, 0,
|
||||
t*x*y + s*z, t*y*y + c, t*y*z - s*x, 0,
|
||||
t*x*z - s*y, t*y*z + s*x, t*z*z + c, 0,
|
||||
0, 0, 0, 1
|
||||
];
|
||||
}
|
||||
// Create a 4x4 matrix representing a camera at eyeVec, pointed at centerVec.
|
||||
CanvasKit.SkM44.lookat = function(eyeVec, centerVec, upVec) {
|
||||
var f = CanvasKit.SkVector.normalize(CanvasKit.SkVector.sub(centerVec, eyeVec));
|
||||
var u = CanvasKit.SkVector.normalize(upVec);
|
||||
var s = CanvasKit.SkVector.normalize(CanvasKit.SkVector.cross(f, u));
|
||||
|
||||
var m = CanvasKit.SkM44.identity();
|
||||
// set each column's top three numbers
|
||||
stride(s, m, 4, 0, 0);
|
||||
stride(CanvasKit.SkVector.cross(s, f), m, 4, 1, 0);
|
||||
stride(CanvasKit.SkVector.mulScalar(f, -1), m, 4, 2, 0);
|
||||
stride(eyeVec, m, 4, 3, 0);
|
||||
|
||||
var m2 = CanvasKit.SkM44.invert(m);
|
||||
if (m2 === null) {
|
||||
return CanvasKit.SkM44.identity();
|
||||
}
|
||||
return m2;
|
||||
}
|
||||
// Create a 4x4 matrix representing a perspective. All arguments are scalars.
|
||||
// angle is in radians.
|
||||
CanvasKit.SkM44.perspective = function(near, far, angle) {
|
||||
if (skIsDebug && (far <= near)) {
|
||||
throw "far must be greater than near when constructing SkM44 using perspective.";
|
||||
}
|
||||
var dInv = 1 / (far - near);
|
||||
var halfAngle = angle / 2;
|
||||
var cot = Math.cos(halfAngle) / Math.sin(halfAngle);
|
||||
return [
|
||||
cot, 0, 0, 0,
|
||||
0, cot, 0, 0,
|
||||
0, 0, (far+near)*dInv, 2*far*near*dInv,
|
||||
0, 0, -1, 1,
|
||||
];
|
||||
}
|
||||
// Returns the number at the given row and column in matrix m.
|
||||
CanvasKit.SkM44.rc = function(m, r, c) {
|
||||
return m[r*4+c];
|
||||
}
|
||||
// Accepts any number of 4x4 matrix arguments, multiplies them left to right.
|
||||
CanvasKit.SkM44.multiply = function() {
|
||||
return multiplyMany(4, arguments);
|
||||
}
|
||||
|
||||
// Invert the 4x4 matrix if it is invertible and return it. if not, return null.
|
||||
// taken from SkM44.cpp (altered to use row-major order)
|
||||
// m is not altered.
|
||||
CanvasKit.SkM44.invert = function(m) {
|
||||
if (skIsDebug && !m.every(isnumber)) {
|
||||
throw 'some members of matrix are NaN m='+m;
|
||||
}
|
||||
|
||||
var a00 = m[0];
|
||||
var a01 = m[4];
|
||||
var a02 = m[8];
|
||||
var a03 = m[12];
|
||||
var a10 = m[1];
|
||||
var a11 = m[5];
|
||||
var a12 = m[9];
|
||||
var a13 = m[13];
|
||||
var a20 = m[2];
|
||||
var a21 = m[6];
|
||||
var a22 = m[10];
|
||||
var a23 = m[14];
|
||||
var a30 = m[3];
|
||||
var a31 = m[7];
|
||||
var a32 = m[11];
|
||||
var a33 = m[15];
|
||||
|
||||
var b00 = a00 * a11 - a01 * a10;
|
||||
var b01 = a00 * a12 - a02 * a10;
|
||||
var b02 = a00 * a13 - a03 * a10;
|
||||
var b03 = a01 * a12 - a02 * a11;
|
||||
var b04 = a01 * a13 - a03 * a11;
|
||||
var b05 = a02 * a13 - a03 * a12;
|
||||
var b06 = a20 * a31 - a21 * a30;
|
||||
var b07 = a20 * a32 - a22 * a30;
|
||||
var b08 = a20 * a33 - a23 * a30;
|
||||
var b09 = a21 * a32 - a22 * a31;
|
||||
var b10 = a21 * a33 - a23 * a31;
|
||||
var b11 = a22 * a33 - a23 * a32;
|
||||
|
||||
// calculate determinate
|
||||
var det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06;
|
||||
var invdet = 1.0 / det;
|
||||
|
||||
// bail out if the matrix is not invertible
|
||||
if (det === 0 || invdet === Infinity) {
|
||||
SkDebug('Warning, uninvertible matrix');
|
||||
return null;
|
||||
}
|
||||
|
||||
b00 *= invdet;
|
||||
b01 *= invdet;
|
||||
b02 *= invdet;
|
||||
b03 *= invdet;
|
||||
b04 *= invdet;
|
||||
b05 *= invdet;
|
||||
b06 *= invdet;
|
||||
b07 *= invdet;
|
||||
b08 *= invdet;
|
||||
b09 *= invdet;
|
||||
b10 *= invdet;
|
||||
b11 *= invdet;
|
||||
|
||||
// store result in row major order
|
||||
var tmp = [
|
||||
a11 * b11 - a12 * b10 + a13 * b09,
|
||||
a12 * b08 - a10 * b11 - a13 * b07,
|
||||
a10 * b10 - a11 * b08 + a13 * b06,
|
||||
a11 * b07 - a10 * b09 - a12 * b06,
|
||||
|
||||
a02 * b10 - a01 * b11 - a03 * b09,
|
||||
a00 * b11 - a02 * b08 + a03 * b07,
|
||||
a01 * b08 - a00 * b10 - a03 * b06,
|
||||
a00 * b09 - a01 * b07 + a02 * b06,
|
||||
|
||||
a31 * b05 - a32 * b04 + a33 * b03,
|
||||
a32 * b02 - a30 * b05 - a33 * b01,
|
||||
a30 * b04 - a31 * b02 + a33 * b00,
|
||||
a31 * b01 - a30 * b03 - a32 * b00,
|
||||
|
||||
a22 * b04 - a21 * b05 - a23 * b03,
|
||||
a20 * b05 - a22 * b02 + a23 * b01,
|
||||
a21 * b02 - a20 * b04 - a23 * b00,
|
||||
a20 * b03 - a21 * b01 + a22 * b00,
|
||||
];
|
||||
|
||||
|
||||
if (!tmp.every(function(val) { return val !== NaN && val !== Infinity && val !== -Infinity; })) {
|
||||
SkDebug('inverted matrix contains infinities or NaN '+tmp);
|
||||
return null;
|
||||
}
|
||||
return tmp;
|
||||
}
|
||||
|
||||
|
||||
// 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
|
||||
@ -135,11 +421,6 @@ CanvasKit.onRuntimeInitialized = function() {
|
||||
// Much of this was hand-transcribed from SkColorMatrix.cpp, because it's easier to
|
||||
// deal with a Float32Array of length 20 than to try to expose the SkColorMatrix object.
|
||||
|
||||
var rScale = 0;
|
||||
var gScale = 6;
|
||||
var bScale = 12;
|
||||
var aScale = 18;
|
||||
|
||||
var rPostTrans = 4;
|
||||
var gPostTrans = 9;
|
||||
var bPostTrans = 14;
|
||||
@ -147,21 +428,21 @@ CanvasKit.onRuntimeInitialized = function() {
|
||||
|
||||
CanvasKit.SkColorMatrix = {};
|
||||
CanvasKit.SkColorMatrix.identity = function() {
|
||||
var m = new Float32Array(20);
|
||||
m[rScale] = 1;
|
||||
m[gScale] = 1;
|
||||
m[bScale] = 1;
|
||||
m[aScale] = 1;
|
||||
return m;
|
||||
return Float32Array.of([
|
||||
1, 0, 0, 0, 0,
|
||||
0, 1, 0, 0, 0,
|
||||
0, 0, 1, 0, 0,
|
||||
0, 0, 0, 1, 0,
|
||||
]);
|
||||
}
|
||||
|
||||
CanvasKit.SkColorMatrix.scaled = function(rs, gs, bs, as) {
|
||||
var m = new Float32Array(20);
|
||||
m[rScale] = rs;
|
||||
m[gScale] = gs;
|
||||
m[bScale] = bs;
|
||||
m[aScale] = as;
|
||||
return m;
|
||||
return Float32Array.of([
|
||||
rs, 0, 0, 0, 0,
|
||||
0, gs, 0, 0, 0,
|
||||
0, 0, bs, 0, 0,
|
||||
0, 0, 0, as, 0,
|
||||
]);
|
||||
}
|
||||
|
||||
var rotateIndices = [
|
||||
|
@ -1,4 +1,5 @@
|
||||
function SkDebug(msg) {
|
||||
// by leaving this blank, closure optimizes out calls (and the messages)
|
||||
// which trims down code size and marginally improves runtime speed.
|
||||
}
|
||||
}
|
||||
/** @const */ var skIsDebug = false;
|
@ -1 +1,229 @@
|
||||
//TODO test mapPoints and such
|
||||
describe('CanvasKit\'s Matrix Helpers', function() {
|
||||
|
||||
let expectArrayCloseTo = function(a, b) {
|
||||
//expect(a).not.toEqual(null);
|
||||
//expect(b).not.toEqual(null);
|
||||
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
|
||||
}
|
||||
};
|
||||
|
||||
describe('3x3 matrices', function() {
|
||||
|
||||
it('can make a translated 3x3 matrix', function(done) {
|
||||
LoadCanvasKit.then(catchException(done, () => {
|
||||
expectArrayCloseTo(
|
||||
CanvasKit.SkMatrix.translated(5, -1),
|
||||
[1, 0, 5,
|
||||
0, 1, -1,
|
||||
0, 0, 1]);
|
||||
done();
|
||||
}));
|
||||
});
|
||||
|
||||
it('can make a scaled 3x3 matrix', function(done) {
|
||||
LoadCanvasKit.then(catchException(done, () => {
|
||||
expectArrayCloseTo(
|
||||
CanvasKit.SkMatrix.scaled(2, 3),
|
||||
[2, 0, 0,
|
||||
0, 3, 0,
|
||||
0, 0, 1]);
|
||||
done();
|
||||
}));
|
||||
});
|
||||
|
||||
it('can make a rotated 3x3 matrix', function(done) {
|
||||
LoadCanvasKit.then(catchException(done, () => {
|
||||
expectArrayCloseTo(
|
||||
CanvasKit.SkMatrix.rotated(Math.PI, 9, 9),
|
||||
[-1, 0, 18,
|
||||
0, -1, 18,
|
||||
0, 0, 1]);
|
||||
done();
|
||||
}));
|
||||
});
|
||||
|
||||
it('can make a skewed 3x3 matrix', function(done) {
|
||||
LoadCanvasKit.then(catchException(done, () => {
|
||||
expectArrayCloseTo(
|
||||
CanvasKit.SkMatrix.skewed(4, 3, 2, 1),
|
||||
[1, 4, -8,
|
||||
3, 1, -3,
|
||||
0, 0, 1]);
|
||||
done();
|
||||
}));
|
||||
});
|
||||
|
||||
it('can multiply 3x3 matrices', function(done) {
|
||||
LoadCanvasKit.then(catchException(done, () => {
|
||||
const a = [
|
||||
0.1, 0.2, 0.3,
|
||||
0.0, 0.6, 0.7,
|
||||
0.9, -0.9, -0.8,
|
||||
];
|
||||
const b = [
|
||||
2.0, 3.0, 4.0,
|
||||
-3.0, -4.0, -5.0,
|
||||
7.0, 8.0, 9.0,
|
||||
];
|
||||
const expected = [
|
||||
1.7, 1.9, 2.1,
|
||||
3.1, 3.2, 3.3,
|
||||
-1.1, -0.1, 0.9,
|
||||
];
|
||||
expectArrayCloseTo(
|
||||
CanvasKit.SkMatrix.multiply(a, b),
|
||||
expected);
|
||||
done();
|
||||
}));
|
||||
});
|
||||
|
||||
it('satisfies the inverse rule for 3x3 matrics', function(done) {
|
||||
// a matrix times its inverse is the identity matrix.
|
||||
LoadCanvasKit.then(catchException(done, () => {
|
||||
const a = [
|
||||
0.1, 0.2, 0.3,
|
||||
0.0, 0.6, 0.7,
|
||||
0.9, -0.9, -0.8,
|
||||
];
|
||||
const b = CanvasKit.SkMatrix.invert(a);
|
||||
expectArrayCloseTo(
|
||||
CanvasKit.SkMatrix.multiply(a, b),
|
||||
CanvasKit.SkMatrix.identity());
|
||||
done();
|
||||
}));
|
||||
});
|
||||
|
||||
it('maps 2D points correctly with a 3x3 matrix', function(done) {
|
||||
LoadCanvasKit.then(catchException(done, () => {
|
||||
const a = [
|
||||
3, 0, -4,
|
||||
0, 2, 4,
|
||||
0, 0, 1,
|
||||
];
|
||||
const points = [
|
||||
0, 0,
|
||||
1, 1,
|
||||
];
|
||||
const expected = [
|
||||
-4, 4,
|
||||
-1, 6,
|
||||
];
|
||||
expectArrayCloseTo(
|
||||
CanvasKit.SkMatrix.mapPoints(a, points),
|
||||
expected);
|
||||
done();
|
||||
}));
|
||||
});
|
||||
|
||||
}); // describe 3x3
|
||||
describe('4x4 matrices', function() {
|
||||
|
||||
it('can make a translated 4x4 matrix', function(done) {
|
||||
LoadCanvasKit.then(catchException(done, () => {
|
||||
expectArrayCloseTo(
|
||||
CanvasKit.SkM44.translated([5, 6, 7]),
|
||||
[1, 0, 0, 5,
|
||||
0, 1, 0, 6,
|
||||
0, 0, 1, 7,
|
||||
0, 0, 0, 1]);
|
||||
done();
|
||||
}));
|
||||
});
|
||||
|
||||
it('can make a scaled 4x4 matrix', function(done) {
|
||||
LoadCanvasKit.then(catchException(done, () => {
|
||||
expectArrayCloseTo(
|
||||
CanvasKit.SkM44.scaled([5, 6, 7]),
|
||||
[5, 0, 0, 0,
|
||||
0, 6, 0, 0,
|
||||
0, 0, 7, 0,
|
||||
0, 0, 0, 1]);
|
||||
done();
|
||||
}));
|
||||
});
|
||||
|
||||
it('can make a rotated 4x4 matrix', function(done) {
|
||||
LoadCanvasKit.then(catchException(done, () => {
|
||||
expectArrayCloseTo(
|
||||
CanvasKit.SkM44.rotated([1,1,1], Math.PI),
|
||||
[-1/3, 2/3, 2/3, 0,
|
||||
2/3, -1/3, 2/3, 0,
|
||||
2/3, 2/3, -1/3, 0,
|
||||
0, 0, 0, 1]);
|
||||
done();
|
||||
}));
|
||||
});
|
||||
|
||||
it('can make a 4x4 matrix looking from eye to center', function(done) {
|
||||
LoadCanvasKit.then(catchException(done, () => {
|
||||
eye = [1, 0, 0];
|
||||
center = [1, 0, 1];
|
||||
up = [0, 1, 0]
|
||||
expectArrayCloseTo(
|
||||
CanvasKit.SkM44.lookat(eye, center, up),
|
||||
[-1, 0, 0, 1,
|
||||
0, 1, 0, 0,
|
||||
0, 0, -1, 0,
|
||||
0, 0, 0, 1]);
|
||||
done();
|
||||
}));
|
||||
});
|
||||
|
||||
it('can make a 4x4 prespective matrix', function(done) {
|
||||
LoadCanvasKit.then(catchException(done, () => {
|
||||
expectArrayCloseTo(
|
||||
CanvasKit.SkM44.perspective(2, 10, Math.PI/2),
|
||||
[1, 0, 0, 0,
|
||||
0, 1, 0, 0,
|
||||
0, 0, 1.5, 5,
|
||||
0, 0, -1, 1]);
|
||||
done();
|
||||
}));
|
||||
});
|
||||
|
||||
it('can multiply 4x4 matrices', function(done) {
|
||||
LoadCanvasKit.then(catchException(done, () => {
|
||||
const a = [
|
||||
0.1, 0.2, 0.3, 0.4,
|
||||
0.0, 0.6, 0.7, 0.8,
|
||||
0.9, -0.9, -0.8, -0.7,
|
||||
-0.6, -0.5, -0.4, -0.3,
|
||||
];
|
||||
const b = [
|
||||
2.0, 3.0, 4.0, 5.0,
|
||||
-3.0, -4.0, -5.0, -6.0,
|
||||
7.0, 8.0, 9.0, 10.0,
|
||||
-4.0, -3.0, -2.0, -1.0,
|
||||
];
|
||||
const expected = [
|
||||
0.1, 0.7, 1.3, 1.9,
|
||||
-0.1, 0.8, 1.7, 2.6,
|
||||
1.7, 2.0, 2.3, 2.6,
|
||||
-1.3, -2.1, -2.9, -3.7,
|
||||
];
|
||||
expectArrayCloseTo(
|
||||
CanvasKit.SkM44.multiply(a, b),
|
||||
expected);
|
||||
done();
|
||||
}));
|
||||
});
|
||||
|
||||
it('satisfies the identity rule for 4x4 matrices', function(done) {
|
||||
LoadCanvasKit.then(catchException(done, () => {
|
||||
const a = [
|
||||
0.1, 0.2, 0.3, 0.4,
|
||||
0.0, 0.6, 0.7, 0.8,
|
||||
0.9, 0.9, -0.8, -0.7,
|
||||
-0.6, -0.5, -0.4, -0.3,
|
||||
];
|
||||
const b = CanvasKit.SkM44.invert(a)
|
||||
expectArrayCloseTo(
|
||||
CanvasKit.SkM44.multiply(a, b),
|
||||
CanvasKit.SkM44.identity());
|
||||
done();
|
||||
}));
|
||||
});
|
||||
}); // describe 4x4
|
||||
});
|
Loading…
Reference in New Issue
Block a user