xref: /aosp_15_r20/external/skia/site/docs/user/modules/canvaskit.md (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
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