1*c8dee2aaSAndroid Build Coastguard Worker--- 2*c8dee2aaSAndroid Build Coastguard Workertitle: 'CanvasKit - Skia + WebAssembly' 3*c8dee2aaSAndroid Build Coastguard WorkerlinkTitle: 'CanvasKit - Skia + WebAssembly' 4*c8dee2aaSAndroid Build Coastguard Worker 5*c8dee2aaSAndroid Build Coastguard Workerweight: 20 6*c8dee2aaSAndroid Build Coastguard Worker--- 7*c8dee2aaSAndroid Build Coastguard Worker 8*c8dee2aaSAndroid Build Coastguard WorkerSkia now offers a WebAssembly build for easy deployment of our graphics APIs on 9*c8dee2aaSAndroid Build Coastguard Workerthe web. 10*c8dee2aaSAndroid Build Coastguard Worker 11*c8dee2aaSAndroid Build Coastguard WorkerCanvasKit provides a playground for testing new Canvas and SVG platform APIs, 12*c8dee2aaSAndroid Build Coastguard Workerenabling fast-paced development on the web platform. It can also be used as a 13*c8dee2aaSAndroid Build Coastguard Workerdeployment mechanism for custom web apps requiring cutting-edge features, like 14*c8dee2aaSAndroid Build Coastguard WorkerSkia's [Lottie animation](https://skia.org/docs/user/modules/skottie) support. 15*c8dee2aaSAndroid Build Coastguard Worker 16*c8dee2aaSAndroid Build Coastguard Worker## Features 17*c8dee2aaSAndroid Build Coastguard Worker 18*c8dee2aaSAndroid Build Coastguard Worker- WebGL context encapsulated as an SkSurface, allowing for direct drawing to an 19*c8dee2aaSAndroid Build Coastguard Worker HTML canvas 20*c8dee2aaSAndroid Build Coastguard Worker- Core set of Skia canvas/paint/path/text APIs available, see bindings 21*c8dee2aaSAndroid Build Coastguard Worker- Draws to a hardware-accelerated backend 22*c8dee2aaSAndroid Build Coastguard Worker- Security tested with Skia's fuzzers 23*c8dee2aaSAndroid Build Coastguard Worker 24*c8dee2aaSAndroid Build Coastguard Worker## Samples 25*c8dee2aaSAndroid Build Coastguard Worker 26*c8dee2aaSAndroid Build Coastguard Worker<style> 27*c8dee2aaSAndroid Build Coastguard Worker #demo canvas { 28*c8dee2aaSAndroid Build Coastguard Worker border: 1px dashed #AAA; 29*c8dee2aaSAndroid Build Coastguard Worker margin: 2px; 30*c8dee2aaSAndroid Build Coastguard Worker } 31*c8dee2aaSAndroid Build Coastguard Worker 32*c8dee2aaSAndroid Build Coastguard Worker #patheffect, #ink, #shaping, #shader1, #camera3d { 33*c8dee2aaSAndroid Build Coastguard Worker width: 400px; 34*c8dee2aaSAndroid Build Coastguard Worker height: 400px; 35*c8dee2aaSAndroid Build Coastguard Worker } 36*c8dee2aaSAndroid Build Coastguard Worker 37*c8dee2aaSAndroid Build Coastguard Worker #sk_legos, #sk_drinks, #sk_party, #sk_onboarding { 38*c8dee2aaSAndroid Build Coastguard Worker width: 300px; 39*c8dee2aaSAndroid Build Coastguard Worker height: 300px; 40*c8dee2aaSAndroid Build Coastguard Worker } 41*c8dee2aaSAndroid Build Coastguard Worker 42*c8dee2aaSAndroid Build Coastguard Worker figure { 43*c8dee2aaSAndroid Build Coastguard Worker display: inline-block; 44*c8dee2aaSAndroid Build Coastguard Worker margin: 0; 45*c8dee2aaSAndroid Build Coastguard Worker } 46*c8dee2aaSAndroid Build Coastguard Worker 47*c8dee2aaSAndroid Build Coastguard Worker figcaption > a { 48*c8dee2aaSAndroid Build Coastguard Worker margin: 2px 10px; 49*c8dee2aaSAndroid Build Coastguard Worker } 50*c8dee2aaSAndroid Build Coastguard Worker 51*c8dee2aaSAndroid Build Coastguard Worker</style> 52*c8dee2aaSAndroid Build Coastguard Worker 53*c8dee2aaSAndroid Build Coastguard Worker<div id=demo> 54*c8dee2aaSAndroid Build Coastguard Worker <h3>Paragraph shaping, custom shaders, and perspective transformation</h3> 55*c8dee2aaSAndroid Build Coastguard Worker <figure> 56*c8dee2aaSAndroid Build Coastguard Worker <canvas id=shaping width=500 height=500></canvas> 57*c8dee2aaSAndroid Build Coastguard Worker <figcaption> 58*c8dee2aaSAndroid Build Coastguard Worker <a href="https://jsfiddle.skia.org/canvaskit/6a5c211a8cb4a7752297674b3533f7e1bbc2a78dd37f117c29a77bcc68411f31" 59*c8dee2aaSAndroid Build Coastguard Worker target=_blank rel=noopener> 60*c8dee2aaSAndroid Build Coastguard Worker SkParagraph JSFiddle</a> 61*c8dee2aaSAndroid Build Coastguard Worker </figcaption> 62*c8dee2aaSAndroid Build Coastguard Worker </figure> 63*c8dee2aaSAndroid Build Coastguard Worker <figure> 64*c8dee2aaSAndroid Build Coastguard Worker <canvas id=shader1 width=512 height=512></canvas> 65*c8dee2aaSAndroid Build Coastguard Worker <figcaption> 66*c8dee2aaSAndroid Build Coastguard Worker <a href="https://jsfiddle.skia.org/canvaskit/ac0574825f9e517f2dfa8e822126ee75b005e8156c3de4a95d4ffd17ab6ca57b" 67*c8dee2aaSAndroid Build Coastguard Worker target=_blank rel=noopener> 68*c8dee2aaSAndroid Build Coastguard Worker Shader JSFiddle</a> 69*c8dee2aaSAndroid Build Coastguard Worker </figcaption> 70*c8dee2aaSAndroid Build Coastguard Worker </figure> 71*c8dee2aaSAndroid Build Coastguard Worker <figure> 72*c8dee2aaSAndroid Build Coastguard Worker <canvas id=camera3d width=400 height=400></canvas> 73*c8dee2aaSAndroid Build Coastguard Worker <figcaption> 74*c8dee2aaSAndroid Build Coastguard Worker <a href="https://jsfiddle.skia.org/canvaskit/289946b783390c3242cb5cc117d7bcaf2bcb610bf3d1e67a1dd9c46c1e66b968" 75*c8dee2aaSAndroid Build Coastguard Worker target=_blank rel=noopener> 76*c8dee2aaSAndroid Build Coastguard Worker 3D Cube JSFiddle</a> 77*c8dee2aaSAndroid Build Coastguard Worker </figcaption> 78*c8dee2aaSAndroid Build Coastguard Worker </figure> 79*c8dee2aaSAndroid Build Coastguard Worker 80*c8dee2aaSAndroid Build Coastguard Worker <h3>Play back bodymovin lottie files with skottie (click for fiddles)</h3> 81*c8dee2aaSAndroid Build Coastguard Worker <a href="https://jsfiddle.skia.org/canvaskit/cb0b72eadb45f7e75b4015db7251e9da2cc202a2ce1cbec8eb2e453d83a619a6" 82*c8dee2aaSAndroid Build Coastguard Worker target=_blank rel=noopener> 83*c8dee2aaSAndroid Build Coastguard Worker <canvas id=sk_legos width=300 height=300></canvas> 84*c8dee2aaSAndroid Build Coastguard Worker </a> 85*c8dee2aaSAndroid Build Coastguard Worker <a href="https://jsfiddle.skia.org/canvaskit/e77274c30d63645d3bb82fd366991e27c1e1c3df39def04e999b4fcce9f425a2" 86*c8dee2aaSAndroid Build Coastguard Worker target=_blank rel=noopener> 87*c8dee2aaSAndroid Build Coastguard Worker <canvas id=sk_drinks width=500 height=500></canvas> 88*c8dee2aaSAndroid Build Coastguard Worker </a> 89*c8dee2aaSAndroid Build Coastguard Worker <a href="https://jsfiddle.skia.org/canvaskit/e42700132d80efd3470b0f08334556028490ac08d1938210fa618504c6109c99" 90*c8dee2aaSAndroid Build Coastguard Worker target=_blank rel=noopener> 91*c8dee2aaSAndroid Build Coastguard Worker <canvas id=sk_party width=500 height=500></canvas> 92*c8dee2aaSAndroid Build Coastguard Worker </a> 93*c8dee2aaSAndroid Build Coastguard Worker <a href="https://jsfiddle.skia.org/canvaskit/987b1f99f4703f9f44dbfb2f43a5ed107672334f68d6262cd53ba44ed7a09236" 94*c8dee2aaSAndroid Build Coastguard Worker target=_blank rel=noopener> 95*c8dee2aaSAndroid Build Coastguard Worker <canvas id=sk_onboarding width=500 height=500></canvas> 96*c8dee2aaSAndroid Build Coastguard Worker </a> 97*c8dee2aaSAndroid Build Coastguard Worker 98*c8dee2aaSAndroid Build Coastguard Worker <h3>Go beyond the HTML Canvas2D</h3> 99*c8dee2aaSAndroid Build Coastguard Worker <figure> 100*c8dee2aaSAndroid Build Coastguard Worker <canvas id=patheffect width=400 height=400></canvas> 101*c8dee2aaSAndroid Build Coastguard Worker <figcaption> 102*c8dee2aaSAndroid Build Coastguard Worker <a href="https://jsfiddle.skia.org/canvaskit/3588b3b0a7cc93f36d9fa4f08b397c38971dcb1f80a36107f9ad93c051f2cb28" 103*c8dee2aaSAndroid Build Coastguard Worker target=_blank rel=noopener> 104*c8dee2aaSAndroid Build Coastguard Worker Star JSFiddle</a> 105*c8dee2aaSAndroid Build Coastguard Worker </figcaption> 106*c8dee2aaSAndroid Build Coastguard Worker </figure> 107*c8dee2aaSAndroid Build Coastguard Worker <figure> 108*c8dee2aaSAndroid Build Coastguard Worker <canvas id=ink width=400 height=400></canvas> 109*c8dee2aaSAndroid Build Coastguard Worker <figcaption> 110*c8dee2aaSAndroid Build Coastguard Worker <a href="https://jsfiddle.skia.org/canvaskit/bd42c174a0dcb2f65ff1f3c803397df14014d1e66b92185e9980dc631a49f258" 111*c8dee2aaSAndroid Build Coastguard Worker target=_blank rel=noopener> 112*c8dee2aaSAndroid Build Coastguard Worker Ink JSFiddle</a> 113*c8dee2aaSAndroid Build Coastguard Worker </figcaption> 114*c8dee2aaSAndroid Build Coastguard Worker </figure> 115*c8dee2aaSAndroid Build Coastguard Worker 116*c8dee2aaSAndroid Build Coastguard Worker</div> 117*c8dee2aaSAndroid Build Coastguard Worker 118*c8dee2aaSAndroid Build Coastguard Worker<script type="text/javascript" charset="utf-8"> 119*c8dee2aaSAndroid Build Coastguard Worker(function() { 120*c8dee2aaSAndroid Build Coastguard Worker // Tries to load the WASM version if supported, shows error otherwise 121*c8dee2aaSAndroid Build Coastguard Worker let s = document.createElement('script'); 122*c8dee2aaSAndroid Build Coastguard Worker let locate_file = ''; 123*c8dee2aaSAndroid Build Coastguard Worker if (window.WebAssembly && typeof window.WebAssembly.compile === 'function') { 124*c8dee2aaSAndroid Build Coastguard Worker console.log('WebAssembly is supported!'); 125*c8dee2aaSAndroid Build Coastguard Worker locate_file = 'https://unpkg.com/[email protected]/bin/full/'; 126*c8dee2aaSAndroid Build Coastguard Worker } else { 127*c8dee2aaSAndroid Build Coastguard Worker console.log('WebAssembly is not supported (yet) on this browser.'); 128*c8dee2aaSAndroid Build Coastguard Worker document.getElementById('demo').innerHTML = "<div>WASM not supported by your browser. Try a recent version of Chrome, Firefox, Edge, or Safari.</div>"; 129*c8dee2aaSAndroid Build Coastguard Worker return; 130*c8dee2aaSAndroid Build Coastguard Worker } 131*c8dee2aaSAndroid Build Coastguard Worker s.src = locate_file + 'canvaskit.js'; 132*c8dee2aaSAndroid Build Coastguard Worker s.onload = () => { 133*c8dee2aaSAndroid Build Coastguard Worker let CanvasKit = null; 134*c8dee2aaSAndroid Build Coastguard Worker let legoJSON = null; 135*c8dee2aaSAndroid Build Coastguard Worker let drinksJSON = null; 136*c8dee2aaSAndroid Build Coastguard Worker let confettiJSON = null; 137*c8dee2aaSAndroid Build Coastguard Worker let onboardingJSON = null; 138*c8dee2aaSAndroid Build Coastguard Worker let fullBounds = [0, 0, 500, 500]; 139*c8dee2aaSAndroid Build Coastguard Worker const ckLoaded = CanvasKitInit({ 140*c8dee2aaSAndroid Build Coastguard Worker locateFile: (file) => locate_file + file, 141*c8dee2aaSAndroid Build Coastguard Worker }); 142*c8dee2aaSAndroid Build Coastguard Worker 143*c8dee2aaSAndroid Build Coastguard Worker ckLoaded.then((CK) => { 144*c8dee2aaSAndroid Build Coastguard Worker CanvasKit = CK; 145*c8dee2aaSAndroid Build Coastguard Worker DrawingExample(CanvasKit); 146*c8dee2aaSAndroid Build Coastguard Worker InkExample(CanvasKit); 147*c8dee2aaSAndroid Build Coastguard Worker ShapingExample(CanvasKit); 148*c8dee2aaSAndroid Build Coastguard Worker // Set bounds to fix the 4:3 resolution of the legos 149*c8dee2aaSAndroid Build Coastguard Worker SkottieExample(CanvasKit, 'sk_legos', legoJSON, [-183, -100, 483, 400]); 150*c8dee2aaSAndroid Build Coastguard Worker // Re-size to fit 151*c8dee2aaSAndroid Build Coastguard Worker SkottieExample(CanvasKit, 'sk_drinks', drinksJSON, fullBounds); 152*c8dee2aaSAndroid Build Coastguard Worker SkottieExample(CanvasKit, 'sk_party', confettiJSON, fullBounds); 153*c8dee2aaSAndroid Build Coastguard Worker SkottieExample(CanvasKit, 'sk_onboarding', onboardingJSON, fullBounds); 154*c8dee2aaSAndroid Build Coastguard Worker ShaderExample1(CanvasKit); 155*c8dee2aaSAndroid Build Coastguard Worker }); 156*c8dee2aaSAndroid Build Coastguard Worker 157*c8dee2aaSAndroid Build Coastguard Worker fetch('https://storage.googleapis.com/skia-cdn/misc/lego_loader.json').then((resp) => { 158*c8dee2aaSAndroid Build Coastguard Worker resp.text().then((str) => { 159*c8dee2aaSAndroid Build Coastguard Worker legoJSON = str; 160*c8dee2aaSAndroid Build Coastguard Worker SkottieExample(CanvasKit, 'sk_legos', legoJSON, [-183, -100, 483, 400]); 161*c8dee2aaSAndroid Build Coastguard Worker }); 162*c8dee2aaSAndroid Build Coastguard Worker }); 163*c8dee2aaSAndroid Build Coastguard Worker 164*c8dee2aaSAndroid Build Coastguard Worker fetch('https://storage.googleapis.com/skia-cdn/misc/drinks.json').then((resp) => { 165*c8dee2aaSAndroid Build Coastguard Worker resp.text().then((str) => { 166*c8dee2aaSAndroid Build Coastguard Worker drinksJSON = str; 167*c8dee2aaSAndroid Build Coastguard Worker SkottieExample(CanvasKit, 'sk_drinks', drinksJSON, fullBounds); 168*c8dee2aaSAndroid Build Coastguard Worker }); 169*c8dee2aaSAndroid Build Coastguard Worker }); 170*c8dee2aaSAndroid Build Coastguard Worker 171*c8dee2aaSAndroid Build Coastguard Worker fetch('https://storage.googleapis.com/skia-cdn/misc/confetti.json').then((resp) => { 172*c8dee2aaSAndroid Build Coastguard Worker resp.text().then((str) => { 173*c8dee2aaSAndroid Build Coastguard Worker confettiJSON = str; 174*c8dee2aaSAndroid Build Coastguard Worker SkottieExample(CanvasKit, 'sk_party', confettiJSON, fullBounds); 175*c8dee2aaSAndroid Build Coastguard Worker }); 176*c8dee2aaSAndroid Build Coastguard Worker }); 177*c8dee2aaSAndroid Build Coastguard Worker 178*c8dee2aaSAndroid Build Coastguard Worker fetch('https://storage.googleapis.com/skia-cdn/misc/onboarding.json').then((resp) => { 179*c8dee2aaSAndroid Build Coastguard Worker resp.text().then((str) => { 180*c8dee2aaSAndroid Build Coastguard Worker onboardingJSON = str; 181*c8dee2aaSAndroid Build Coastguard Worker SkottieExample(CanvasKit, 'sk_onboarding', onboardingJSON, fullBounds); 182*c8dee2aaSAndroid Build Coastguard Worker }); 183*c8dee2aaSAndroid Build Coastguard Worker }); 184*c8dee2aaSAndroid Build Coastguard Worker 185*c8dee2aaSAndroid Build Coastguard Worker const loadBrickTex = fetch('https://storage.googleapis.com/skia-cdn/misc/brickwork-texture.jpg').then((response) => response.arrayBuffer()); 186*c8dee2aaSAndroid Build Coastguard Worker const loadBrickBump = fetch('https://storage.googleapis.com/skia-cdn/misc/brickwork_normal-map.jpg').then((response) => response.arrayBuffer()); 187*c8dee2aaSAndroid Build Coastguard Worker Promise.all([ckLoaded, loadBrickTex, loadBrickBump]).then((results) => {Camera3D(...results)}); 188*c8dee2aaSAndroid Build Coastguard Worker 189*c8dee2aaSAndroid Build Coastguard Worker function preventScrolling(canvas) { 190*c8dee2aaSAndroid Build Coastguard Worker canvas.addEventListener('touchmove', (e) => { 191*c8dee2aaSAndroid Build Coastguard Worker // Prevents touch events in the canvas from scrolling the canvas. 192*c8dee2aaSAndroid Build Coastguard Worker e.preventDefault(); 193*c8dee2aaSAndroid Build Coastguard Worker e.stopPropagation(); 194*c8dee2aaSAndroid Build Coastguard Worker }); 195*c8dee2aaSAndroid Build Coastguard Worker } 196*c8dee2aaSAndroid Build Coastguard Worker 197*c8dee2aaSAndroid Build Coastguard Worker function DrawingExample(CanvasKit) { 198*c8dee2aaSAndroid Build Coastguard Worker const surface = CanvasKit.MakeCanvasSurface('patheffect'); 199*c8dee2aaSAndroid Build Coastguard Worker if (!surface) { 200*c8dee2aaSAndroid Build Coastguard Worker console.log('Could not make surface'); 201*c8dee2aaSAndroid Build Coastguard Worker } 202*c8dee2aaSAndroid Build Coastguard Worker const paint = new CanvasKit.Paint(); 203*c8dee2aaSAndroid Build Coastguard Worker 204*c8dee2aaSAndroid Build Coastguard Worker const textPaint = new CanvasKit.Paint(); 205*c8dee2aaSAndroid Build Coastguard Worker textPaint.setColor(CanvasKit.Color(40, 0, 0, 1.0)); 206*c8dee2aaSAndroid Build Coastguard Worker textPaint.setAntiAlias(true); 207*c8dee2aaSAndroid Build Coastguard Worker 208*c8dee2aaSAndroid Build Coastguard Worker const textFont = new CanvasKit.Font(null, 30); 209*c8dee2aaSAndroid Build Coastguard Worker 210*c8dee2aaSAndroid Build Coastguard Worker let i = 0; 211*c8dee2aaSAndroid Build Coastguard Worker 212*c8dee2aaSAndroid Build Coastguard Worker let X = 200; 213*c8dee2aaSAndroid Build Coastguard Worker let Y = 200; 214*c8dee2aaSAndroid Build Coastguard Worker 215*c8dee2aaSAndroid Build Coastguard Worker function drawFrame(canvas) { 216*c8dee2aaSAndroid Build Coastguard Worker const path = starPath(CanvasKit, X, Y); 217*c8dee2aaSAndroid Build Coastguard Worker const dpe = CanvasKit.PathEffect.MakeDash([15, 5, 5, 10], i/5); 218*c8dee2aaSAndroid Build Coastguard Worker i++; 219*c8dee2aaSAndroid Build Coastguard Worker 220*c8dee2aaSAndroid Build Coastguard Worker paint.setPathEffect(dpe); 221*c8dee2aaSAndroid Build Coastguard Worker paint.setStyle(CanvasKit.PaintStyle.Stroke); 222*c8dee2aaSAndroid Build Coastguard Worker paint.setStrokeWidth(5.0 + -3 * Math.cos(i/30)); 223*c8dee2aaSAndroid Build Coastguard Worker paint.setAntiAlias(true); 224*c8dee2aaSAndroid Build Coastguard Worker paint.setColor(CanvasKit.Color(66, 129, 164, 1.0)); 225*c8dee2aaSAndroid Build Coastguard Worker 226*c8dee2aaSAndroid Build Coastguard Worker canvas.clear(CanvasKit.Color(255, 255, 255, 1.0)); 227*c8dee2aaSAndroid Build Coastguard Worker 228*c8dee2aaSAndroid Build Coastguard Worker canvas.drawPath(path, paint); 229*c8dee2aaSAndroid Build Coastguard Worker canvas.drawText('Try Clicking!', 10, 380, textPaint, textFont); 230*c8dee2aaSAndroid Build Coastguard Worker dpe.delete(); 231*c8dee2aaSAndroid Build Coastguard Worker path.delete(); 232*c8dee2aaSAndroid Build Coastguard Worker surface.requestAnimationFrame(drawFrame); 233*c8dee2aaSAndroid Build Coastguard Worker } 234*c8dee2aaSAndroid Build Coastguard Worker surface.requestAnimationFrame(drawFrame); 235*c8dee2aaSAndroid Build Coastguard Worker 236*c8dee2aaSAndroid Build Coastguard Worker // Make animation interactive 237*c8dee2aaSAndroid Build Coastguard Worker let interact = (e) => { 238*c8dee2aaSAndroid Build Coastguard Worker if (!e.buttons) { 239*c8dee2aaSAndroid Build Coastguard Worker return; 240*c8dee2aaSAndroid Build Coastguard Worker } 241*c8dee2aaSAndroid Build Coastguard Worker X = e.offsetX; 242*c8dee2aaSAndroid Build Coastguard Worker Y = e.offsetY; 243*c8dee2aaSAndroid Build Coastguard Worker }; 244*c8dee2aaSAndroid Build Coastguard Worker document.getElementById('patheffect').addEventListener('pointermove', interact); 245*c8dee2aaSAndroid Build Coastguard Worker document.getElementById('patheffect').addEventListener('pointerdown', interact); 246*c8dee2aaSAndroid Build Coastguard Worker preventScrolling(document.getElementById('patheffect')); 247*c8dee2aaSAndroid Build Coastguard Worker 248*c8dee2aaSAndroid Build Coastguard Worker // A client would need to delete this if it didn't go on forever. 249*c8dee2aaSAndroid Build Coastguard Worker // font.delete(); 250*c8dee2aaSAndroid Build Coastguard Worker // paint.delete(); 251*c8dee2aaSAndroid Build Coastguard Worker } 252*c8dee2aaSAndroid Build Coastguard Worker 253*c8dee2aaSAndroid Build Coastguard Worker function InkExample(CanvasKit) { 254*c8dee2aaSAndroid Build Coastguard Worker const surface = CanvasKit.MakeCanvasSurface('ink'); 255*c8dee2aaSAndroid Build Coastguard Worker if (!surface) { 256*c8dee2aaSAndroid Build Coastguard Worker console.log('Could not make surface'); 257*c8dee2aaSAndroid Build Coastguard Worker } 258*c8dee2aaSAndroid Build Coastguard Worker let paint = new CanvasKit.Paint(); 259*c8dee2aaSAndroid Build Coastguard Worker paint.setAntiAlias(true); 260*c8dee2aaSAndroid Build Coastguard Worker paint.setColor(CanvasKit.Color(0, 0, 0, 1.0)); 261*c8dee2aaSAndroid Build Coastguard Worker paint.setStyle(CanvasKit.PaintStyle.Stroke); 262*c8dee2aaSAndroid Build Coastguard Worker paint.setStrokeWidth(4.0); 263*c8dee2aaSAndroid Build Coastguard Worker // This effect smooths out the drawn lines a bit. 264*c8dee2aaSAndroid Build Coastguard Worker paint.setPathEffect(CanvasKit.PathEffect.MakeCorner(50)); 265*c8dee2aaSAndroid Build Coastguard Worker 266*c8dee2aaSAndroid Build Coastguard Worker // Draw I N K 267*c8dee2aaSAndroid Build Coastguard Worker let path = new CanvasKit.Path(); 268*c8dee2aaSAndroid Build Coastguard Worker path.moveTo(80, 30); 269*c8dee2aaSAndroid Build Coastguard Worker path.lineTo(80, 80); 270*c8dee2aaSAndroid Build Coastguard Worker 271*c8dee2aaSAndroid Build Coastguard Worker path.moveTo(100, 80); 272*c8dee2aaSAndroid Build Coastguard Worker path.lineTo(100, 15); 273*c8dee2aaSAndroid Build Coastguard Worker path.lineTo(130, 95); 274*c8dee2aaSAndroid Build Coastguard Worker path.lineTo(130, 30); 275*c8dee2aaSAndroid Build Coastguard Worker 276*c8dee2aaSAndroid Build Coastguard Worker path.moveTo(150, 30); 277*c8dee2aaSAndroid Build Coastguard Worker path.lineTo(150, 80); 278*c8dee2aaSAndroid Build Coastguard Worker path.moveTo(170, 30); 279*c8dee2aaSAndroid Build Coastguard Worker path.lineTo(150, 55); 280*c8dee2aaSAndroid Build Coastguard Worker path.lineTo(170, 80); 281*c8dee2aaSAndroid Build Coastguard Worker 282*c8dee2aaSAndroid Build Coastguard Worker let paths = [path]; 283*c8dee2aaSAndroid Build Coastguard Worker let paints = [paint]; 284*c8dee2aaSAndroid Build Coastguard Worker 285*c8dee2aaSAndroid Build Coastguard Worker function drawFrame(canvas) { 286*c8dee2aaSAndroid Build Coastguard Worker canvas.clear(CanvasKit.WHITE); 287*c8dee2aaSAndroid Build Coastguard Worker for (let i = 0; i < paints.length && i < paths.length; i++) { 288*c8dee2aaSAndroid Build Coastguard Worker canvas.drawPath(paths[i], paints[i]); 289*c8dee2aaSAndroid Build Coastguard Worker } 290*c8dee2aaSAndroid Build Coastguard Worker surface.requestAnimationFrame(drawFrame); 291*c8dee2aaSAndroid Build Coastguard Worker } 292*c8dee2aaSAndroid Build Coastguard Worker 293*c8dee2aaSAndroid Build Coastguard Worker let hold = false; 294*c8dee2aaSAndroid Build Coastguard Worker let interact = (e) => { 295*c8dee2aaSAndroid Build Coastguard Worker let type = e.type; 296*c8dee2aaSAndroid Build Coastguard Worker if (type === 'lostpointercapture' || type === 'pointerup' || !e.pressure ) { 297*c8dee2aaSAndroid Build Coastguard Worker hold = false; 298*c8dee2aaSAndroid Build Coastguard Worker return; 299*c8dee2aaSAndroid Build Coastguard Worker } 300*c8dee2aaSAndroid Build Coastguard Worker if (hold) { 301*c8dee2aaSAndroid Build Coastguard Worker path.lineTo(e.offsetX, e.offsetY); 302*c8dee2aaSAndroid Build Coastguard Worker } else { 303*c8dee2aaSAndroid Build Coastguard Worker paint = paint.copy(); 304*c8dee2aaSAndroid Build Coastguard Worker paint.setColor(CanvasKit.Color(Math.random() * 255, Math.random() * 255, Math.random() * 255, Math.random() + .2)); 305*c8dee2aaSAndroid Build Coastguard Worker paints.push(paint); 306*c8dee2aaSAndroid Build Coastguard Worker path = new CanvasKit.Path(); 307*c8dee2aaSAndroid Build Coastguard Worker paths.push(path); 308*c8dee2aaSAndroid Build Coastguard Worker path.moveTo(e.offsetX, e.offsetY); 309*c8dee2aaSAndroid Build Coastguard Worker } 310*c8dee2aaSAndroid Build Coastguard Worker hold = true; 311*c8dee2aaSAndroid Build Coastguard Worker }; 312*c8dee2aaSAndroid Build Coastguard Worker document.getElementById('ink').addEventListener('pointermove', interact); 313*c8dee2aaSAndroid Build Coastguard Worker document.getElementById('ink').addEventListener('pointerdown', interact); 314*c8dee2aaSAndroid Build Coastguard Worker document.getElementById('ink').addEventListener('lostpointercapture', interact); 315*c8dee2aaSAndroid Build Coastguard Worker document.getElementById('ink').addEventListener('pointerup', interact); 316*c8dee2aaSAndroid Build Coastguard Worker preventScrolling(document.getElementById('ink')); 317*c8dee2aaSAndroid Build Coastguard Worker surface.requestAnimationFrame(drawFrame); 318*c8dee2aaSAndroid Build Coastguard Worker } 319*c8dee2aaSAndroid Build Coastguard Worker 320*c8dee2aaSAndroid Build Coastguard Worker function ShapingExample(CanvasKit) { 321*c8dee2aaSAndroid Build Coastguard Worker const surface = CanvasKit.MakeCanvasSurface('shaping'); 322*c8dee2aaSAndroid Build Coastguard Worker if (!surface) { 323*c8dee2aaSAndroid Build Coastguard Worker console.log('Could not make surface'); 324*c8dee2aaSAndroid Build Coastguard Worker return; 325*c8dee2aaSAndroid Build Coastguard Worker } 326*c8dee2aaSAndroid Build Coastguard Worker let robotoData = null; 327*c8dee2aaSAndroid Build Coastguard Worker fetch('https://storage.googleapis.com/skia-cdn/google-web-fonts/Roboto-Regular.ttf').then((resp) => { 328*c8dee2aaSAndroid Build Coastguard Worker resp.arrayBuffer().then((buffer) => { 329*c8dee2aaSAndroid Build Coastguard Worker robotoData = buffer; 330*c8dee2aaSAndroid Build Coastguard Worker }); 331*c8dee2aaSAndroid Build Coastguard Worker }); 332*c8dee2aaSAndroid Build Coastguard Worker 333*c8dee2aaSAndroid Build Coastguard Worker let emojiData = null; 334*c8dee2aaSAndroid Build Coastguard Worker fetch('https://storage.googleapis.com/skia-cdn/misc/NotoColorEmoji.ttf').then((resp) => { 335*c8dee2aaSAndroid Build Coastguard Worker resp.arrayBuffer().then((buffer) => { 336*c8dee2aaSAndroid Build Coastguard Worker emojiData = buffer; 337*c8dee2aaSAndroid Build Coastguard Worker }); 338*c8dee2aaSAndroid Build Coastguard Worker }); 339*c8dee2aaSAndroid Build Coastguard Worker 340*c8dee2aaSAndroid Build Coastguard Worker const font = new CanvasKit.Font(null, 18); 341*c8dee2aaSAndroid Build Coastguard Worker const fontPaint = new CanvasKit.Paint(); 342*c8dee2aaSAndroid Build Coastguard Worker fontPaint.setStyle(CanvasKit.PaintStyle.Fill); 343*c8dee2aaSAndroid Build Coastguard Worker fontPaint.setAntiAlias(true); 344*c8dee2aaSAndroid Build Coastguard Worker 345*c8dee2aaSAndroid Build Coastguard Worker let paragraph = null; 346*c8dee2aaSAndroid Build Coastguard Worker let X = 250; 347*c8dee2aaSAndroid Build Coastguard Worker let Y = 250; 348*c8dee2aaSAndroid Build Coastguard Worker const str = 'The quick brown fox ate a zesty hamburgerfons .\nThe laughed.'; 349*c8dee2aaSAndroid Build Coastguard Worker 350*c8dee2aaSAndroid Build Coastguard Worker function drawFrame(canvas) { 351*c8dee2aaSAndroid Build Coastguard Worker surface.requestAnimationFrame(drawFrame); 352*c8dee2aaSAndroid Build Coastguard Worker if (robotoData && emojiData && !paragraph) { 353*c8dee2aaSAndroid Build Coastguard Worker const fontMgr = CanvasKit.FontMgr.FromData([robotoData, emojiData]); 354*c8dee2aaSAndroid Build Coastguard Worker 355*c8dee2aaSAndroid Build Coastguard Worker const paraStyle = new CanvasKit.ParagraphStyle({ 356*c8dee2aaSAndroid Build Coastguard Worker textStyle: { 357*c8dee2aaSAndroid Build Coastguard Worker color: CanvasKit.BLACK, 358*c8dee2aaSAndroid Build Coastguard Worker fontFamilies: ['Roboto', 'Noto Color Emoji'], 359*c8dee2aaSAndroid Build Coastguard Worker fontSize: 50, 360*c8dee2aaSAndroid Build Coastguard Worker }, 361*c8dee2aaSAndroid Build Coastguard Worker textAlign: CanvasKit.TextAlign.Left, 362*c8dee2aaSAndroid Build Coastguard Worker maxLines: 7, 363*c8dee2aaSAndroid Build Coastguard Worker ellipsis: '...', 364*c8dee2aaSAndroid Build Coastguard Worker }); 365*c8dee2aaSAndroid Build Coastguard Worker 366*c8dee2aaSAndroid Build Coastguard Worker const builder = CanvasKit.ParagraphBuilder.Make(paraStyle, fontMgr); 367*c8dee2aaSAndroid Build Coastguard Worker builder.addText(str); 368*c8dee2aaSAndroid Build Coastguard Worker paragraph = builder.build(); 369*c8dee2aaSAndroid Build Coastguard Worker } 370*c8dee2aaSAndroid Build Coastguard Worker if (!paragraph) { 371*c8dee2aaSAndroid Build Coastguard Worker canvas.drawText(`Fetching Font data...`, 5, 450, fontPaint, font); 372*c8dee2aaSAndroid Build Coastguard Worker return; 373*c8dee2aaSAndroid Build Coastguard Worker } 374*c8dee2aaSAndroid Build Coastguard Worker canvas.clear(CanvasKit.WHITE); 375*c8dee2aaSAndroid Build Coastguard Worker 376*c8dee2aaSAndroid Build Coastguard Worker let wrapTo = 350 + 150 * Math.sin(Date.now() / 2000); 377*c8dee2aaSAndroid Build Coastguard Worker paragraph.layout(wrapTo); 378*c8dee2aaSAndroid Build Coastguard Worker canvas.drawParagraph(paragraph, 0, 0); 379*c8dee2aaSAndroid Build Coastguard Worker canvas.drawLine(wrapTo, 0, wrapTo, 400, fontPaint); 380*c8dee2aaSAndroid Build Coastguard Worker 381*c8dee2aaSAndroid Build Coastguard Worker const posA = paragraph.getGlyphPositionAtCoordinate(X, Y); 382*c8dee2aaSAndroid Build Coastguard Worker const cp = str.codePointAt(posA.pos); 383*c8dee2aaSAndroid Build Coastguard Worker if (cp) { 384*c8dee2aaSAndroid Build Coastguard Worker const glyph = String.fromCodePoint(cp); 385*c8dee2aaSAndroid Build Coastguard Worker canvas.drawText(`At (${X.toFixed(2)}, ${Y.toFixed(2)}) glyph is '${glyph}'`, 5, 450, fontPaint, font); 386*c8dee2aaSAndroid Build Coastguard Worker } 387*c8dee2aaSAndroid Build Coastguard Worker } 388*c8dee2aaSAndroid Build Coastguard Worker 389*c8dee2aaSAndroid Build Coastguard Worker surface.requestAnimationFrame(drawFrame); 390*c8dee2aaSAndroid Build Coastguard Worker // Make animation interactive 391*c8dee2aaSAndroid Build Coastguard Worker let interact = (e) => { 392*c8dee2aaSAndroid Build Coastguard Worker // multiply by 4/5 to account for the difference in the canvas width and the CSS width. 393*c8dee2aaSAndroid Build Coastguard Worker // The 10 accounts for where the mouse actually is compared to where it is drawn. 394*c8dee2aaSAndroid Build Coastguard Worker X = (e.offsetX * 4/5) - 10; 395*c8dee2aaSAndroid Build Coastguard Worker Y = e.offsetY * 4/5; 396*c8dee2aaSAndroid Build Coastguard Worker }; 397*c8dee2aaSAndroid Build Coastguard Worker document.getElementById('shaping').addEventListener('pointermove', interact); 398*c8dee2aaSAndroid Build Coastguard Worker document.getElementById('shaping').addEventListener('pointerdown', interact); 399*c8dee2aaSAndroid Build Coastguard Worker document.getElementById('shaping').addEventListener('lostpointercapture', interact); 400*c8dee2aaSAndroid Build Coastguard Worker document.getElementById('shaping').addEventListener('pointerup', interact); 401*c8dee2aaSAndroid Build Coastguard Worker preventScrolling(document.getElementById('shaping')); 402*c8dee2aaSAndroid Build Coastguard Worker surface.requestAnimationFrame(drawFrame); 403*c8dee2aaSAndroid Build Coastguard Worker } 404*c8dee2aaSAndroid Build Coastguard Worker 405*c8dee2aaSAndroid Build Coastguard Worker function starPath(CanvasKit, X=128, Y=128, R=116) { 406*c8dee2aaSAndroid Build Coastguard Worker let p = new CanvasKit.Path(); 407*c8dee2aaSAndroid Build Coastguard Worker p.moveTo(X + R, Y); 408*c8dee2aaSAndroid Build Coastguard Worker for (let i = 1; i < 8; i++) { 409*c8dee2aaSAndroid Build Coastguard Worker let a = 2.6927937 * i; 410*c8dee2aaSAndroid Build Coastguard Worker p.lineTo(X + R * Math.cos(a), Y + R * Math.sin(a)); 411*c8dee2aaSAndroid Build Coastguard Worker } 412*c8dee2aaSAndroid Build Coastguard Worker return p; 413*c8dee2aaSAndroid Build Coastguard Worker } 414*c8dee2aaSAndroid Build Coastguard Worker 415*c8dee2aaSAndroid Build Coastguard Worker function SkottieExample(CanvasKit, id, jsonStr, bounds) { 416*c8dee2aaSAndroid Build Coastguard Worker if (!CanvasKit || !jsonStr) { 417*c8dee2aaSAndroid Build Coastguard Worker return; 418*c8dee2aaSAndroid Build Coastguard Worker } 419*c8dee2aaSAndroid Build Coastguard Worker const animation = CanvasKit.MakeAnimation(jsonStr); 420*c8dee2aaSAndroid Build Coastguard Worker const duration = animation.duration() * 1000; 421*c8dee2aaSAndroid Build Coastguard Worker const size = animation.size(); 422*c8dee2aaSAndroid Build Coastguard Worker let c = document.getElementById(id); 423*c8dee2aaSAndroid Build Coastguard Worker bounds = bounds || {fLeft: 0, fTop: 0, fRight: size.w, fBottom: size.h}; 424*c8dee2aaSAndroid Build Coastguard Worker 425*c8dee2aaSAndroid Build Coastguard Worker const surface = CanvasKit.MakeCanvasSurface(id); 426*c8dee2aaSAndroid Build Coastguard Worker if (!surface) { 427*c8dee2aaSAndroid Build Coastguard Worker console.log('Could not make surface'); 428*c8dee2aaSAndroid Build Coastguard Worker } 429*c8dee2aaSAndroid Build Coastguard Worker let firstFrame = new Date().getTime(); 430*c8dee2aaSAndroid Build Coastguard Worker 431*c8dee2aaSAndroid Build Coastguard Worker function drawFrame(canvas) { 432*c8dee2aaSAndroid Build Coastguard Worker let now = new Date().getTime(); 433*c8dee2aaSAndroid Build Coastguard Worker let seek = ((now - firstFrame) / duration) % 1.0; 434*c8dee2aaSAndroid Build Coastguard Worker 435*c8dee2aaSAndroid Build Coastguard Worker animation.seek(seek); 436*c8dee2aaSAndroid Build Coastguard Worker animation.render(canvas, bounds); 437*c8dee2aaSAndroid Build Coastguard Worker 438*c8dee2aaSAndroid Build Coastguard Worker surface.requestAnimationFrame(drawFrame); 439*c8dee2aaSAndroid Build Coastguard Worker } 440*c8dee2aaSAndroid Build Coastguard Worker surface.requestAnimationFrame(drawFrame); 441*c8dee2aaSAndroid Build Coastguard Worker //animation.delete(); 442*c8dee2aaSAndroid Build Coastguard Worker } 443*c8dee2aaSAndroid Build Coastguard Worker 444*c8dee2aaSAndroid Build Coastguard Worker function ShaderExample1(CanvasKit) { 445*c8dee2aaSAndroid Build Coastguard Worker if (!CanvasKit) { 446*c8dee2aaSAndroid Build Coastguard Worker return; 447*c8dee2aaSAndroid Build Coastguard Worker } 448*c8dee2aaSAndroid Build Coastguard Worker const surface = CanvasKit.MakeCanvasSurface('shader1'); 449*c8dee2aaSAndroid Build Coastguard Worker if (!surface) { 450*c8dee2aaSAndroid Build Coastguard Worker throw 'Could not make surface'; 451*c8dee2aaSAndroid Build Coastguard Worker } 452*c8dee2aaSAndroid Build Coastguard Worker const paint = new CanvasKit.Paint(); 453*c8dee2aaSAndroid Build Coastguard Worker 454*c8dee2aaSAndroid Build Coastguard Worker const prog = ` 455*c8dee2aaSAndroid Build Coastguard Workeruniform float rad_scale; 456*c8dee2aaSAndroid Build Coastguard Workeruniform float2 in_center; 457*c8dee2aaSAndroid Build Coastguard Workeruniform float4 in_colors0; 458*c8dee2aaSAndroid Build Coastguard Workeruniform float4 in_colors1; 459*c8dee2aaSAndroid Build Coastguard Worker 460*c8dee2aaSAndroid Build Coastguard Workerhalf4 main(float2 p) { 461*c8dee2aaSAndroid Build Coastguard Worker float2 pp = p - in_center; 462*c8dee2aaSAndroid Build Coastguard Worker float radius = sqrt(dot(pp, pp)); 463*c8dee2aaSAndroid Build Coastguard Worker radius = sqrt(radius); 464*c8dee2aaSAndroid Build Coastguard Worker float angle = atan(pp.y / pp.x); 465*c8dee2aaSAndroid Build Coastguard Worker float t = (angle + 3.1415926/2) / (3.1415926); 466*c8dee2aaSAndroid Build Coastguard Worker t += radius * rad_scale; 467*c8dee2aaSAndroid Build Coastguard Worker t = fract(t); 468*c8dee2aaSAndroid Build Coastguard Worker return half4(mix(in_colors0, in_colors1, t)); 469*c8dee2aaSAndroid Build Coastguard Worker} 470*c8dee2aaSAndroid Build Coastguard Worker`; 471*c8dee2aaSAndroid Build Coastguard Worker 472*c8dee2aaSAndroid Build Coastguard Worker const fact = CanvasKit.RuntimeEffect.Make(prog); 473*c8dee2aaSAndroid Build Coastguard Worker function drawFrame(canvas) { 474*c8dee2aaSAndroid Build Coastguard Worker canvas.clear(CanvasKit.WHITE); 475*c8dee2aaSAndroid Build Coastguard Worker const shader = fact.makeShader([ 476*c8dee2aaSAndroid Build Coastguard Worker Math.sin(Date.now() / 2000) / 5, 477*c8dee2aaSAndroid Build Coastguard Worker 256, 256, 478*c8dee2aaSAndroid Build Coastguard Worker 1, 0, 0, 1, 479*c8dee2aaSAndroid Build Coastguard Worker 0, 1, 0, 1]); 480*c8dee2aaSAndroid Build Coastguard Worker 481*c8dee2aaSAndroid Build Coastguard Worker paint.setShader(shader); 482*c8dee2aaSAndroid Build Coastguard Worker canvas.drawRect(CanvasKit.LTRBRect(0, 0, 512, 512), paint); 483*c8dee2aaSAndroid Build Coastguard Worker shader.delete(); 484*c8dee2aaSAndroid Build Coastguard Worker surface.requestAnimationFrame(drawFrame); 485*c8dee2aaSAndroid Build Coastguard Worker } 486*c8dee2aaSAndroid Build Coastguard Worker surface.requestAnimationFrame(drawFrame); 487*c8dee2aaSAndroid Build Coastguard Worker } 488*c8dee2aaSAndroid Build Coastguard Worker 489*c8dee2aaSAndroid Build Coastguard Worker function Camera3D(canvas, textureImgData, normalImgData) { 490*c8dee2aaSAndroid Build Coastguard Worker const surface = CanvasKit.MakeCanvasSurface('camera3d'); 491*c8dee2aaSAndroid Build Coastguard Worker if (!surface) { 492*c8dee2aaSAndroid Build Coastguard Worker console.error('Could not make surface'); 493*c8dee2aaSAndroid Build Coastguard Worker return; 494*c8dee2aaSAndroid Build Coastguard Worker } 495*c8dee2aaSAndroid Build Coastguard Worker 496*c8dee2aaSAndroid Build Coastguard Worker const sizeX = document.getElementById('camera3d').width; 497*c8dee2aaSAndroid Build Coastguard Worker const sizeY = document.getElementById('camera3d').height; 498*c8dee2aaSAndroid Build Coastguard Worker 499*c8dee2aaSAndroid Build Coastguard Worker let clickToWorld = CanvasKit.M44.identity(); 500*c8dee2aaSAndroid Build Coastguard Worker let worldToClick = CanvasKit.M44.identity(); 501*c8dee2aaSAndroid Build Coastguard Worker // rotation of the cube shown in the demo 502*c8dee2aaSAndroid Build Coastguard Worker let rotation = CanvasKit.M44.identity(); 503*c8dee2aaSAndroid Build Coastguard Worker // temporary during a click and drag 504*c8dee2aaSAndroid Build Coastguard Worker let clickRotation = CanvasKit.M44.identity(); 505*c8dee2aaSAndroid Build Coastguard Worker 506*c8dee2aaSAndroid Build Coastguard Worker // A virtual sphere used for tumbling the object on screen. 507*c8dee2aaSAndroid Build Coastguard Worker const vSphereCenter = [sizeX/2, sizeY/2]; 508*c8dee2aaSAndroid Build Coastguard Worker const vSphereRadius = Math.min(...vSphereCenter); 509*c8dee2aaSAndroid Build Coastguard Worker 510*c8dee2aaSAndroid Build Coastguard Worker // The rounded rect used for each face 511*c8dee2aaSAndroid Build Coastguard Worker const margin = vSphereRadius / 20; 512*c8dee2aaSAndroid Build Coastguard Worker const rr = CanvasKit.RRectXY(CanvasKit.LTRBRect(margin, margin, 513*c8dee2aaSAndroid Build Coastguard Worker vSphereRadius - margin, vSphereRadius - margin), margin*2.5, margin*2.5); 514*c8dee2aaSAndroid Build Coastguard Worker 515*c8dee2aaSAndroid Build Coastguard Worker const camAngle = Math.PI / 12; 516*c8dee2aaSAndroid Build Coastguard Worker const cam = { 517*c8dee2aaSAndroid Build Coastguard Worker 'eye' : [0, 0, 1 / Math.tan(camAngle/2) - 1], 518*c8dee2aaSAndroid Build Coastguard Worker 'coa' : [0, 0, 0], 519*c8dee2aaSAndroid Build Coastguard Worker 'up' : [0, 1, 0], 520*c8dee2aaSAndroid Build Coastguard Worker 'near' : 0.05, 521*c8dee2aaSAndroid Build Coastguard Worker 'far' : 4, 522*c8dee2aaSAndroid Build Coastguard Worker 'angle': camAngle, 523*c8dee2aaSAndroid Build Coastguard Worker }; 524*c8dee2aaSAndroid Build Coastguard Worker 525*c8dee2aaSAndroid Build Coastguard Worker let mouseDown = false; 526*c8dee2aaSAndroid Build Coastguard Worker let clickDown = [0, 0]; // location of click down 527*c8dee2aaSAndroid Build Coastguard Worker let lastMouse = [0, 0]; // last mouse location 528*c8dee2aaSAndroid Build Coastguard Worker 529*c8dee2aaSAndroid Build Coastguard Worker // keep spinning after mouse up. Also start spinning on load 530*c8dee2aaSAndroid Build Coastguard Worker let axis = [0.4, 1, 1]; 531*c8dee2aaSAndroid Build Coastguard Worker let totalSpin = 0; 532*c8dee2aaSAndroid Build Coastguard Worker let spinRate = 0.1; 533*c8dee2aaSAndroid Build Coastguard Worker let lastRadians = 0; 534*c8dee2aaSAndroid Build Coastguard Worker let spinning = setInterval(keepSpinning, 30); 535*c8dee2aaSAndroid Build Coastguard Worker 536*c8dee2aaSAndroid Build Coastguard Worker const imgscale = CanvasKit.Matrix.scaled(2, 2); 537*c8dee2aaSAndroid Build Coastguard Worker const textureShader = CanvasKit.MakeImageFromEncoded(textureImgData).makeShaderCubic( 538*c8dee2aaSAndroid Build Coastguard Worker CanvasKit.TileMode.Clamp, CanvasKit.TileMode.Clamp, 1/3, 1/3, imgscale); 539*c8dee2aaSAndroid Build Coastguard Worker const normalShader = CanvasKit.MakeImageFromEncoded(normalImgData).makeShaderCubic( 540*c8dee2aaSAndroid Build Coastguard Worker CanvasKit.TileMode.Clamp, CanvasKit.TileMode.Clamp, 1/3, 1/3, imgscale); 541*c8dee2aaSAndroid Build Coastguard Worker const children = [textureShader, normalShader]; 542*c8dee2aaSAndroid Build Coastguard Worker 543*c8dee2aaSAndroid Build Coastguard Worker const prog = ` 544*c8dee2aaSAndroid Build Coastguard Worker uniform shader color_map; 545*c8dee2aaSAndroid Build Coastguard Worker uniform shader normal_map; 546*c8dee2aaSAndroid Build Coastguard Worker 547*c8dee2aaSAndroid Build Coastguard Worker uniform float3 lightPos; 548*c8dee2aaSAndroid Build Coastguard Worker uniform float4x4 localToWorld; 549*c8dee2aaSAndroid Build Coastguard Worker uniform float4x4 localToWorldAdjInv; 550*c8dee2aaSAndroid Build Coastguard Worker 551*c8dee2aaSAndroid Build Coastguard Worker float3 convert_normal_sample(half4 c) { 552*c8dee2aaSAndroid Build Coastguard Worker float3 n = 2 * c.rgb - 1; 553*c8dee2aaSAndroid Build Coastguard Worker n.y = -n.y; 554*c8dee2aaSAndroid Build Coastguard Worker return n; 555*c8dee2aaSAndroid Build Coastguard Worker } 556*c8dee2aaSAndroid Build Coastguard Worker 557*c8dee2aaSAndroid Build Coastguard Worker half4 main(float2 p) { 558*c8dee2aaSAndroid Build Coastguard Worker float3 norm = convert_normal_sample(normal_map.eval(p)); 559*c8dee2aaSAndroid Build Coastguard Worker float3 plane_norm = normalize(localToWorldAdjInv * float4(norm, 0)).xyz; 560*c8dee2aaSAndroid Build Coastguard Worker 561*c8dee2aaSAndroid Build Coastguard Worker float3 plane_pos = (localToWorld * float4(p, 0, 1)).xyz; 562*c8dee2aaSAndroid Build Coastguard Worker float3 light_dir = normalize(lightPos - plane_pos); 563*c8dee2aaSAndroid Build Coastguard Worker 564*c8dee2aaSAndroid Build Coastguard Worker float ambient = 0.2; 565*c8dee2aaSAndroid Build Coastguard Worker float dp = dot(plane_norm, light_dir); 566*c8dee2aaSAndroid Build Coastguard Worker float scale = min(ambient + max(dp, 0), 1); 567*c8dee2aaSAndroid Build Coastguard Worker 568*c8dee2aaSAndroid Build Coastguard Worker return color_map.eval(p) * half4(float4(scale, scale, scale, 1)); 569*c8dee2aaSAndroid Build Coastguard Worker } 570*c8dee2aaSAndroid Build Coastguard Worker`; 571*c8dee2aaSAndroid Build Coastguard Worker 572*c8dee2aaSAndroid Build Coastguard Worker const fact = CanvasKit.RuntimeEffect.Make(prog); 573*c8dee2aaSAndroid Build Coastguard Worker 574*c8dee2aaSAndroid Build Coastguard Worker // properties of light 575*c8dee2aaSAndroid Build Coastguard Worker let lightLocation = [...vSphereCenter]; 576*c8dee2aaSAndroid Build Coastguard Worker let lightDistance = vSphereRadius; 577*c8dee2aaSAndroid Build Coastguard Worker let lightIconRadius = 12; 578*c8dee2aaSAndroid Build Coastguard Worker let draggingLight = false; 579*c8dee2aaSAndroid Build Coastguard Worker 580*c8dee2aaSAndroid Build Coastguard Worker function computeLightWorldPos() { 581*c8dee2aaSAndroid Build Coastguard Worker return CanvasKit.Vector.add(CanvasKit.Vector.mulScalar([...vSphereCenter, 0], 0.5), 582*c8dee2aaSAndroid Build Coastguard Worker CanvasKit.Vector.mulScalar(vSphereUnitV3(lightLocation), lightDistance)); 583*c8dee2aaSAndroid Build Coastguard Worker } 584*c8dee2aaSAndroid Build Coastguard Worker 585*c8dee2aaSAndroid Build Coastguard Worker let lightWorldPos = computeLightWorldPos(); 586*c8dee2aaSAndroid Build Coastguard Worker 587*c8dee2aaSAndroid Build Coastguard Worker function drawLight(canvas) { 588*c8dee2aaSAndroid Build Coastguard Worker const paint = new CanvasKit.Paint(); 589*c8dee2aaSAndroid Build Coastguard Worker paint.setAntiAlias(true); 590*c8dee2aaSAndroid Build Coastguard Worker paint.setColor(CanvasKit.WHITE); 591*c8dee2aaSAndroid Build Coastguard Worker canvas.drawCircle(...lightLocation, lightIconRadius + 2, paint); 592*c8dee2aaSAndroid Build Coastguard Worker paint.setColor(CanvasKit.BLACK); 593*c8dee2aaSAndroid Build Coastguard Worker canvas.drawCircle(...lightLocation, lightIconRadius, paint); 594*c8dee2aaSAndroid Build Coastguard Worker } 595*c8dee2aaSAndroid Build Coastguard Worker 596*c8dee2aaSAndroid Build Coastguard Worker // Takes an x and y rotation in radians and a scale and returns a 4x4 matrix used to draw a 597*c8dee2aaSAndroid Build Coastguard Worker // face of the cube in that orientation. 598*c8dee2aaSAndroid Build Coastguard Worker function faceM44(rx, ry, scale) { 599*c8dee2aaSAndroid Build Coastguard Worker return CanvasKit.M44.multiply( 600*c8dee2aaSAndroid Build Coastguard Worker CanvasKit.M44.rotated([0,1,0], ry), 601*c8dee2aaSAndroid Build Coastguard Worker CanvasKit.M44.rotated([1,0,0], rx), 602*c8dee2aaSAndroid Build Coastguard Worker CanvasKit.M44.translated([0, 0, scale])); 603*c8dee2aaSAndroid Build Coastguard Worker } 604*c8dee2aaSAndroid Build Coastguard Worker 605*c8dee2aaSAndroid Build Coastguard Worker const faceScale = vSphereRadius/2 606*c8dee2aaSAndroid Build Coastguard Worker const faces = [ 607*c8dee2aaSAndroid Build Coastguard Worker {matrix: faceM44( 0, 0, faceScale ), color:CanvasKit.RED}, // front 608*c8dee2aaSAndroid Build Coastguard Worker {matrix: faceM44( 0, Math.PI, faceScale ), color:CanvasKit.GREEN}, // back 609*c8dee2aaSAndroid Build Coastguard Worker 610*c8dee2aaSAndroid Build Coastguard Worker {matrix: faceM44( Math.PI/2, 0, faceScale ), color:CanvasKit.BLUE}, // top 611*c8dee2aaSAndroid Build Coastguard Worker {matrix: faceM44(-Math.PI/2, 0, faceScale ), color:CanvasKit.CYAN}, // bottom 612*c8dee2aaSAndroid Build Coastguard Worker 613*c8dee2aaSAndroid Build Coastguard Worker {matrix: faceM44( 0, Math.PI/2, faceScale ), color:CanvasKit.MAGENTA}, // left 614*c8dee2aaSAndroid Build Coastguard Worker {matrix: faceM44( 0,-Math.PI/2, faceScale ), color:CanvasKit.YELLOW}, // right 615*c8dee2aaSAndroid Build Coastguard Worker ]; 616*c8dee2aaSAndroid Build Coastguard Worker 617*c8dee2aaSAndroid Build Coastguard Worker // Returns a component of the matrix m indicating whether it faces the camera. 618*c8dee2aaSAndroid Build Coastguard Worker // If it's positive for one of the matrices representing the face of the cube, 619*c8dee2aaSAndroid Build Coastguard Worker // that face is currently in front. 620*c8dee2aaSAndroid Build Coastguard Worker function front(m) { 621*c8dee2aaSAndroid Build Coastguard Worker // Is this invertible? 622*c8dee2aaSAndroid Build Coastguard Worker var m2 = CanvasKit.M44.invert(m); 623*c8dee2aaSAndroid Build Coastguard Worker if (m2 === null) { 624*c8dee2aaSAndroid Build Coastguard Worker m2 = CanvasKit.M44.identity(); 625*c8dee2aaSAndroid Build Coastguard Worker } 626*c8dee2aaSAndroid Build Coastguard Worker // look at the sign of the z-scale of the inverse of m. 627*c8dee2aaSAndroid Build Coastguard Worker // that's the number in row 2, col 2. 628*c8dee2aaSAndroid Build Coastguard Worker return m2[10] 629*c8dee2aaSAndroid Build Coastguard Worker } 630*c8dee2aaSAndroid Build Coastguard Worker 631*c8dee2aaSAndroid Build Coastguard Worker function setClickToWorld(canvas, matrix) { 632*c8dee2aaSAndroid Build Coastguard Worker const l2d = canvas.getLocalToDevice(); 633*c8dee2aaSAndroid Build Coastguard Worker worldToClick = CanvasKit.M44.multiply(CanvasKit.M44.mustInvert(matrix), l2d); 634*c8dee2aaSAndroid Build Coastguard Worker clickToWorld = CanvasKit.M44.mustInvert(worldToClick); 635*c8dee2aaSAndroid Build Coastguard Worker } 636*c8dee2aaSAndroid Build Coastguard Worker 637*c8dee2aaSAndroid Build Coastguard Worker function normalMatrix(m) { 638*c8dee2aaSAndroid Build Coastguard Worker m[3] = 0; 639*c8dee2aaSAndroid Build Coastguard Worker m[7] = 0; 640*c8dee2aaSAndroid Build Coastguard Worker m[11] = 0; 641*c8dee2aaSAndroid Build Coastguard Worker m[12] = 0; 642*c8dee2aaSAndroid Build Coastguard Worker m[13] = 0; 643*c8dee2aaSAndroid Build Coastguard Worker m[14] = 0; 644*c8dee2aaSAndroid Build Coastguard Worker m[15] = 1; 645*c8dee2aaSAndroid Build Coastguard Worker return CanvasKit.M44.transpose(CanvasKit.M44.mustInvert(m)); 646*c8dee2aaSAndroid Build Coastguard Worker } 647*c8dee2aaSAndroid Build Coastguard Worker 648*c8dee2aaSAndroid Build Coastguard Worker function drawCubeFace(canvas, m, color) { 649*c8dee2aaSAndroid Build Coastguard Worker const trans = new CanvasKit.M44.translated([vSphereRadius/2, vSphereRadius/2, 0]); 650*c8dee2aaSAndroid Build Coastguard Worker const localToWorld = new CanvasKit.M44.multiply(m, CanvasKit.M44.mustInvert(trans)); 651*c8dee2aaSAndroid Build Coastguard Worker canvas.concat(CanvasKit.M44.multiply(trans, localToWorld)); 652*c8dee2aaSAndroid Build Coastguard Worker const znormal = front(canvas.getLocalToDevice()); 653*c8dee2aaSAndroid Build Coastguard Worker if (znormal < 0) { 654*c8dee2aaSAndroid Build Coastguard Worker return; // skip faces facing backwards 655*c8dee2aaSAndroid Build Coastguard Worker } 656*c8dee2aaSAndroid Build Coastguard Worker const uniforms = [...lightWorldPos, ...localToWorld, ...normalMatrix(localToWorld)]; 657*c8dee2aaSAndroid Build Coastguard Worker const paint = new CanvasKit.Paint(); 658*c8dee2aaSAndroid Build Coastguard Worker paint.setAntiAlias(true); 659*c8dee2aaSAndroid Build Coastguard Worker const shader = fact.makeShaderWithChildren(uniforms, children); 660*c8dee2aaSAndroid Build Coastguard Worker paint.setShader(shader); 661*c8dee2aaSAndroid Build Coastguard Worker canvas.drawRRect(rr, paint); 662*c8dee2aaSAndroid Build Coastguard Worker } 663*c8dee2aaSAndroid Build Coastguard Worker 664*c8dee2aaSAndroid Build Coastguard Worker function drawFrame(canvas) { 665*c8dee2aaSAndroid Build Coastguard Worker const clickM = canvas.getLocalToDevice(); 666*c8dee2aaSAndroid Build Coastguard Worker canvas.save(); 667*c8dee2aaSAndroid Build Coastguard Worker canvas.translate(vSphereCenter[0] - vSphereRadius/2, vSphereCenter[1] - vSphereRadius/2); 668*c8dee2aaSAndroid Build Coastguard Worker // pass surface dimensions as viewport size. 669*c8dee2aaSAndroid Build Coastguard Worker canvas.concat(CanvasKit.M44.setupCamera( 670*c8dee2aaSAndroid Build Coastguard Worker CanvasKit.LTRBRect(0, 0, vSphereRadius, vSphereRadius), vSphereRadius/2, cam)); 671*c8dee2aaSAndroid Build Coastguard Worker setClickToWorld(canvas, clickM); 672*c8dee2aaSAndroid Build Coastguard Worker for (let f of faces) { 673*c8dee2aaSAndroid Build Coastguard Worker const saveCount = canvas.getSaveCount(); 674*c8dee2aaSAndroid Build Coastguard Worker canvas.save(); 675*c8dee2aaSAndroid Build Coastguard Worker drawCubeFace(canvas, CanvasKit.M44.multiply(clickRotation, rotation, f.matrix), f.color); 676*c8dee2aaSAndroid Build Coastguard Worker canvas.restoreToCount(saveCount); 677*c8dee2aaSAndroid Build Coastguard Worker } 678*c8dee2aaSAndroid Build Coastguard Worker canvas.restore(); // camera 679*c8dee2aaSAndroid Build Coastguard Worker canvas.restore(); // center the following content in the window 680*c8dee2aaSAndroid Build Coastguard Worker 681*c8dee2aaSAndroid Build Coastguard Worker // draw virtual sphere outline. 682*c8dee2aaSAndroid Build Coastguard Worker const paint = new CanvasKit.Paint(); 683*c8dee2aaSAndroid Build Coastguard Worker paint.setAntiAlias(true); 684*c8dee2aaSAndroid Build Coastguard Worker paint.setStyle(CanvasKit.PaintStyle.Stroke); 685*c8dee2aaSAndroid Build Coastguard Worker paint.setColor(CanvasKit.Color(64, 255, 0, 1.0)); 686*c8dee2aaSAndroid Build Coastguard Worker canvas.drawCircle(vSphereCenter[0], vSphereCenter[1], vSphereRadius, paint); 687*c8dee2aaSAndroid Build Coastguard Worker canvas.drawLine(vSphereCenter[0], vSphereCenter[1] - vSphereRadius, 688*c8dee2aaSAndroid Build Coastguard Worker vSphereCenter[0], vSphereCenter[1] + vSphereRadius, paint); 689*c8dee2aaSAndroid Build Coastguard Worker canvas.drawLine(vSphereCenter[0] - vSphereRadius, vSphereCenter[1], 690*c8dee2aaSAndroid Build Coastguard Worker vSphereCenter[0] + vSphereRadius, vSphereCenter[1], paint); 691*c8dee2aaSAndroid Build Coastguard Worker 692*c8dee2aaSAndroid Build Coastguard Worker drawLight(canvas); 693*c8dee2aaSAndroid Build Coastguard Worker } 694*c8dee2aaSAndroid Build Coastguard Worker 695*c8dee2aaSAndroid Build Coastguard Worker // convert a 2D point in the circle displayed on screen to a 3D unit vector. 696*c8dee2aaSAndroid Build Coastguard Worker // the virtual sphere is a technique selecting a 3D direction by clicking on a the projection 697*c8dee2aaSAndroid Build Coastguard Worker // of a hemisphere. 698*c8dee2aaSAndroid Build Coastguard Worker function vSphereUnitV3(p) { 699*c8dee2aaSAndroid Build Coastguard Worker // v = (v - fCenter) * (1 / fRadius); 700*c8dee2aaSAndroid Build Coastguard Worker let v = CanvasKit.Vector.mulScalar(CanvasKit.Vector.sub(p, vSphereCenter), 1/vSphereRadius); 701*c8dee2aaSAndroid Build Coastguard Worker 702*c8dee2aaSAndroid Build Coastguard Worker // constrain the clicked point within the circle. 703*c8dee2aaSAndroid Build Coastguard Worker let len2 = CanvasKit.Vector.lengthSquared(v); 704*c8dee2aaSAndroid Build Coastguard Worker if (len2 > 1) { 705*c8dee2aaSAndroid Build Coastguard Worker v = CanvasKit.Vector.normalize(v); 706*c8dee2aaSAndroid Build Coastguard Worker len2 = 1; 707*c8dee2aaSAndroid Build Coastguard Worker } 708*c8dee2aaSAndroid Build Coastguard Worker // the closer to the edge of the circle you are, the closer z is to zero. 709*c8dee2aaSAndroid Build Coastguard Worker const z = Math.sqrt(1 - len2); 710*c8dee2aaSAndroid Build Coastguard Worker v.push(z); 711*c8dee2aaSAndroid Build Coastguard Worker return v; 712*c8dee2aaSAndroid Build Coastguard Worker } 713*c8dee2aaSAndroid Build Coastguard Worker 714*c8dee2aaSAndroid Build Coastguard Worker function computeVSphereRotation(start, end) { 715*c8dee2aaSAndroid Build Coastguard Worker const u = vSphereUnitV3(start); 716*c8dee2aaSAndroid Build Coastguard Worker const v = vSphereUnitV3(end); 717*c8dee2aaSAndroid Build Coastguard Worker // Axis is in the scope of the Camera3D function so it can be used in keepSpinning. 718*c8dee2aaSAndroid Build Coastguard Worker axis = CanvasKit.Vector.cross(u, v); 719*c8dee2aaSAndroid Build Coastguard Worker const sinValue = CanvasKit.Vector.length(axis); 720*c8dee2aaSAndroid Build Coastguard Worker const cosValue = CanvasKit.Vector.dot(u, v); 721*c8dee2aaSAndroid Build Coastguard Worker 722*c8dee2aaSAndroid Build Coastguard Worker let m = new CanvasKit.M44.identity(); 723*c8dee2aaSAndroid Build Coastguard Worker if (Math.abs(sinValue) > 0.000000001) { 724*c8dee2aaSAndroid Build Coastguard Worker m = CanvasKit.M44.rotatedUnitSinCos( 725*c8dee2aaSAndroid Build Coastguard Worker CanvasKit.Vector.mulScalar(axis, 1/sinValue), sinValue, cosValue); 726*c8dee2aaSAndroid Build Coastguard Worker const radians = Math.atan(cosValue / sinValue); 727*c8dee2aaSAndroid Build Coastguard Worker spinRate = lastRadians - radians; 728*c8dee2aaSAndroid Build Coastguard Worker lastRadians = radians; 729*c8dee2aaSAndroid Build Coastguard Worker } 730*c8dee2aaSAndroid Build Coastguard Worker return m; 731*c8dee2aaSAndroid Build Coastguard Worker } 732*c8dee2aaSAndroid Build Coastguard Worker 733*c8dee2aaSAndroid Build Coastguard Worker function keepSpinning() { 734*c8dee2aaSAndroid Build Coastguard Worker totalSpin += spinRate; 735*c8dee2aaSAndroid Build Coastguard Worker clickRotation = CanvasKit.M44.rotated(axis, totalSpin); 736*c8dee2aaSAndroid Build Coastguard Worker spinRate *= .998; 737*c8dee2aaSAndroid Build Coastguard Worker if (spinRate < 0.01) { 738*c8dee2aaSAndroid Build Coastguard Worker stopSpinning(); 739*c8dee2aaSAndroid Build Coastguard Worker } 740*c8dee2aaSAndroid Build Coastguard Worker surface.requestAnimationFrame(drawFrame); 741*c8dee2aaSAndroid Build Coastguard Worker } 742*c8dee2aaSAndroid Build Coastguard Worker 743*c8dee2aaSAndroid Build Coastguard Worker function stopSpinning() { 744*c8dee2aaSAndroid Build Coastguard Worker clearInterval(spinning); 745*c8dee2aaSAndroid Build Coastguard Worker rotation = CanvasKit.M44.multiply(clickRotation, rotation); 746*c8dee2aaSAndroid Build Coastguard Worker clickRotation = CanvasKit.M44.identity(); 747*c8dee2aaSAndroid Build Coastguard Worker } 748*c8dee2aaSAndroid Build Coastguard Worker 749*c8dee2aaSAndroid Build Coastguard Worker function interact(e) { 750*c8dee2aaSAndroid Build Coastguard Worker const type = e.type; 751*c8dee2aaSAndroid Build Coastguard Worker let eventPos = [e.offsetX, e.offsetY]; 752*c8dee2aaSAndroid Build Coastguard Worker if (type === 'lostpointercapture' || type === 'pointerup' || type == 'pointerleave') { 753*c8dee2aaSAndroid Build Coastguard Worker if (draggingLight) { 754*c8dee2aaSAndroid Build Coastguard Worker draggingLight = false; 755*c8dee2aaSAndroid Build Coastguard Worker } else if (mouseDown) { 756*c8dee2aaSAndroid Build Coastguard Worker mouseDown = false; 757*c8dee2aaSAndroid Build Coastguard Worker if (spinRate > 0.02) { 758*c8dee2aaSAndroid Build Coastguard Worker stopSpinning(); 759*c8dee2aaSAndroid Build Coastguard Worker spinning = setInterval(keepSpinning, 30); 760*c8dee2aaSAndroid Build Coastguard Worker } 761*c8dee2aaSAndroid Build Coastguard Worker } else { 762*c8dee2aaSAndroid Build Coastguard Worker return; 763*c8dee2aaSAndroid Build Coastguard Worker } 764*c8dee2aaSAndroid Build Coastguard Worker return; 765*c8dee2aaSAndroid Build Coastguard Worker } else if (type === 'pointermove') { 766*c8dee2aaSAndroid Build Coastguard Worker if (draggingLight) { 767*c8dee2aaSAndroid Build Coastguard Worker lightLocation = eventPos; 768*c8dee2aaSAndroid Build Coastguard Worker lightWorldPos = computeLightWorldPos(); 769*c8dee2aaSAndroid Build Coastguard Worker } else if (mouseDown) { 770*c8dee2aaSAndroid Build Coastguard Worker lastMouse = eventPos; 771*c8dee2aaSAndroid Build Coastguard Worker clickRotation = computeVSphereRotation(clickDown, lastMouse); 772*c8dee2aaSAndroid Build Coastguard Worker } else { 773*c8dee2aaSAndroid Build Coastguard Worker return; 774*c8dee2aaSAndroid Build Coastguard Worker } 775*c8dee2aaSAndroid Build Coastguard Worker } else if (type === 'pointerdown') { 776*c8dee2aaSAndroid Build Coastguard Worker // Are we repositioning the light? 777*c8dee2aaSAndroid Build Coastguard Worker if (CanvasKit.Vector.dist(eventPos, lightLocation) < lightIconRadius) { 778*c8dee2aaSAndroid Build Coastguard Worker draggingLight = true; 779*c8dee2aaSAndroid Build Coastguard Worker return; 780*c8dee2aaSAndroid Build Coastguard Worker } 781*c8dee2aaSAndroid Build Coastguard Worker stopSpinning(); 782*c8dee2aaSAndroid Build Coastguard Worker mouseDown = true; 783*c8dee2aaSAndroid Build Coastguard Worker clickDown = eventPos; 784*c8dee2aaSAndroid Build Coastguard Worker lastMouse = eventPos; 785*c8dee2aaSAndroid Build Coastguard Worker } 786*c8dee2aaSAndroid Build Coastguard Worker surface.requestAnimationFrame(drawFrame); 787*c8dee2aaSAndroid Build Coastguard Worker }; 788*c8dee2aaSAndroid Build Coastguard Worker 789*c8dee2aaSAndroid Build Coastguard Worker document.getElementById('camera3d').addEventListener('pointermove', interact); 790*c8dee2aaSAndroid Build Coastguard Worker document.getElementById('camera3d').addEventListener('pointerdown', interact); 791*c8dee2aaSAndroid Build Coastguard Worker document.getElementById('camera3d').addEventListener('lostpointercapture', interact); 792*c8dee2aaSAndroid Build Coastguard Worker document.getElementById('camera3d').addEventListener('pointerleave', interact); 793*c8dee2aaSAndroid Build Coastguard Worker document.getElementById('camera3d').addEventListener('pointerup', interact); 794*c8dee2aaSAndroid Build Coastguard Worker 795*c8dee2aaSAndroid Build Coastguard Worker surface.requestAnimationFrame(drawFrame); 796*c8dee2aaSAndroid Build Coastguard Worker } 797*c8dee2aaSAndroid Build Coastguard Worker 798*c8dee2aaSAndroid Build Coastguard Worker } 799*c8dee2aaSAndroid Build Coastguard Worker document.head.appendChild(s); 800*c8dee2aaSAndroid Build Coastguard Worker})(); 801*c8dee2aaSAndroid Build Coastguard Worker</script> 802*c8dee2aaSAndroid Build Coastguard Worker 803*c8dee2aaSAndroid Build Coastguard WorkerLottie files courtesy of the lottiefiles.com community: 804*c8dee2aaSAndroid Build Coastguard Worker[Lego Loader](https://www.lottiefiles.com/410-lego-loader), 805*c8dee2aaSAndroid Build Coastguard Worker[I'm thirsty](https://www.lottiefiles.com/77-im-thirsty), 806*c8dee2aaSAndroid Build Coastguard Worker[Confetti](https://www.lottiefiles.com/1370-confetti), 807*c8dee2aaSAndroid Build Coastguard Worker[Onboarding](https://www.lottiefiles.com/1134-onboarding-1) 808*c8dee2aaSAndroid Build Coastguard Worker 809*c8dee2aaSAndroid Build Coastguard Worker## Test server 810*c8dee2aaSAndroid Build Coastguard Worker 811*c8dee2aaSAndroid Build Coastguard WorkerTest your code on our [CanvasKit Fiddle](https://jsfiddle.skia.org/canvaskit) 812*c8dee2aaSAndroid Build Coastguard Worker 813*c8dee2aaSAndroid Build Coastguard Worker## Download 814*c8dee2aaSAndroid Build Coastguard Worker 815*c8dee2aaSAndroid Build Coastguard WorkerGet [CanvasKit on NPM](https://www.npmjs.com/package/canvaskit-wasm). 816*c8dee2aaSAndroid Build Coastguard WorkerDocumentation and Typescript definitions are available in the `types/` subfolder 817*c8dee2aaSAndroid Build Coastguard Workerof the npm package or from the 818*c8dee2aaSAndroid Build Coastguard Worker[Skia repo](https://github.com/google/skia/tree/main/modules/canvaskit/npm_build/types). 819*c8dee2aaSAndroid Build Coastguard Worker 820*c8dee2aaSAndroid Build Coastguard WorkerCheck out the [quickstart guide](../quickstart) as well. 821