xref: /aosp_15_r20/external/skia/demos.skia.org/demos/mesh2d/index.html (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1*c8dee2aaSAndroid Build Coastguard Worker<!DOCTYPE html>
2*c8dee2aaSAndroid Build Coastguard Worker<title>Mesh2D Demo</title>
3*c8dee2aaSAndroid Build Coastguard Worker<meta charset="utf-8" />
4*c8dee2aaSAndroid Build Coastguard Worker<meta name="viewport" content="width=device-width, initial-scale=1.0">
5*c8dee2aaSAndroid Build Coastguard Worker
6*c8dee2aaSAndroid Build Coastguard Worker<!-- Mesh2D origin trial (https://developer.chrome.com/origintrials/#/view_trial/2797298318550499329) -->
7*c8dee2aaSAndroid Build Coastguard Worker<meta http-equiv="origin-trial" content="AtLoDlklU0E4Hvr2CcMAmFtHYbi+esffS5I/qCK8i5bG9hhtiqpiJgM9qdK+7sbunIPtgSntEYNWExeHzn1tTAQAAABUeyJvcmlnaW4iOiJodHRwczovL2RlbW9zLnNraWEub3JnOjQ0MyIsImZlYXR1cmUiOiJDYW52YXMyZE1lc2giLCJleHBpcnkiOjE3NDk1OTk5OTl9">
8*c8dee2aaSAndroid Build Coastguard Worker
9*c8dee2aaSAndroid Build Coastguard Worker<style>
10*c8dee2aaSAndroid Build Coastguard Worker  canvas {
11*c8dee2aaSAndroid Build Coastguard Worker    width: 1024px;
12*c8dee2aaSAndroid Build Coastguard Worker    height: 1024px;
13*c8dee2aaSAndroid Build Coastguard Worker    background-color: #ccc;
14*c8dee2aaSAndroid Build Coastguard Worker    display: none;
15*c8dee2aaSAndroid Build Coastguard Worker  }
16*c8dee2aaSAndroid Build Coastguard Worker
17*c8dee2aaSAndroid Build Coastguard Worker  .root {
18*c8dee2aaSAndroid Build Coastguard Worker    display: flex;
19*c8dee2aaSAndroid Build Coastguard Worker  }
20*c8dee2aaSAndroid Build Coastguard Worker
21*c8dee2aaSAndroid Build Coastguard Worker  .controls {
22*c8dee2aaSAndroid Build Coastguard Worker    display: flex;
23*c8dee2aaSAndroid Build Coastguard Worker  }
24*c8dee2aaSAndroid Build Coastguard Worker  .controls-left  { width: 50%; }
25*c8dee2aaSAndroid Build Coastguard Worker  .controls-right { width: 50%; }
26*c8dee2aaSAndroid Build Coastguard Worker  .controls-right select { width: 100%; }
27*c8dee2aaSAndroid Build Coastguard Worker
28*c8dee2aaSAndroid Build Coastguard Worker  #loader {
29*c8dee2aaSAndroid Build Coastguard Worker    width: 1024px;
30*c8dee2aaSAndroid Build Coastguard Worker    height: 1024px;
31*c8dee2aaSAndroid Build Coastguard Worker    display: flex;
32*c8dee2aaSAndroid Build Coastguard Worker    flex-direction: column;
33*c8dee2aaSAndroid Build Coastguard Worker    justify-content: center;
34*c8dee2aaSAndroid Build Coastguard Worker    align-items: center;
35*c8dee2aaSAndroid Build Coastguard Worker    background-color: #f1f2f3;
36*c8dee2aaSAndroid Build Coastguard Worker    font: bold 2em monospace;
37*c8dee2aaSAndroid Build Coastguard Worker    color: #85a2b6;
38*c8dee2aaSAndroid Build Coastguard Worker  }
39*c8dee2aaSAndroid Build Coastguard Worker</style>
40*c8dee2aaSAndroid Build Coastguard Worker
41*c8dee2aaSAndroid Build Coastguard Worker<div class="root">
42*c8dee2aaSAndroid Build Coastguard Worker  <div id="loader">
43*c8dee2aaSAndroid Build Coastguard Worker    <img src="BeanEater-1s-200px.gif">
44*c8dee2aaSAndroid Build Coastguard Worker    <div>Fetching <a href="https://skia.org/docs/user/modules/canvaskit/">CanvasKit</a>...</div>
45*c8dee2aaSAndroid Build Coastguard Worker  </div>
46*c8dee2aaSAndroid Build Coastguard Worker
47*c8dee2aaSAndroid Build Coastguard Worker  <div id="canvas_wrapper">
48*c8dee2aaSAndroid Build Coastguard Worker    <canvas id="canvas2d" width="1024" height="1024"></canvas>
49*c8dee2aaSAndroid Build Coastguard Worker    <canvas id="canvas3d" width="1024" height="1024"></canvas>
50*c8dee2aaSAndroid Build Coastguard Worker  </div>
51*c8dee2aaSAndroid Build Coastguard Worker
52*c8dee2aaSAndroid Build Coastguard Worker  <div class="controls">
53*c8dee2aaSAndroid Build Coastguard Worker    <div class="controls-left">
54*c8dee2aaSAndroid Build Coastguard Worker      <div>Show mesh</div>
55*c8dee2aaSAndroid Build Coastguard Worker      <div>Level of detail</div>
56*c8dee2aaSAndroid Build Coastguard Worker      <div>Animator</div>
57*c8dee2aaSAndroid Build Coastguard Worker      <div>Renderer</div>
58*c8dee2aaSAndroid Build Coastguard Worker    </div>
59*c8dee2aaSAndroid Build Coastguard Worker    <div class="controls-right">
60*c8dee2aaSAndroid Build Coastguard Worker      <div>
61*c8dee2aaSAndroid Build Coastguard Worker        <input type="checkbox" id="show_mesh"/>
62*c8dee2aaSAndroid Build Coastguard Worker      </div>
63*c8dee2aaSAndroid Build Coastguard Worker      <div>
64*c8dee2aaSAndroid Build Coastguard Worker        <select id="lod">
65*c8dee2aaSAndroid Build Coastguard Worker          <option value="4">4x4</option>
66*c8dee2aaSAndroid Build Coastguard Worker          <option value="8" selected>8x8</option>
67*c8dee2aaSAndroid Build Coastguard Worker          <option value="16">16x16</option>
68*c8dee2aaSAndroid Build Coastguard Worker          <option value="32">32x32</option>
69*c8dee2aaSAndroid Build Coastguard Worker          <option value="64">64x64</option>
70*c8dee2aaSAndroid Build Coastguard Worker          <option value="128">128x128</option>
71*c8dee2aaSAndroid Build Coastguard Worker          <option value="255">255x255</option>
72*c8dee2aaSAndroid Build Coastguard Worker        </select>
73*c8dee2aaSAndroid Build Coastguard Worker      </div>
74*c8dee2aaSAndroid Build Coastguard Worker      <div>
75*c8dee2aaSAndroid Build Coastguard Worker        <select id="animator">
76*c8dee2aaSAndroid Build Coastguard Worker          <option value="">Manual</option>
77*c8dee2aaSAndroid Build Coastguard Worker          <option value="squircleAnimator">Squircle</option>
78*c8dee2aaSAndroid Build Coastguard Worker          <option value="twirlAnimator">Twirl</option>
79*c8dee2aaSAndroid Build Coastguard Worker          <option value="wiggleAnimator">Wiggle</option>
80*c8dee2aaSAndroid Build Coastguard Worker          <option value="cylinderAnimator" selected>Cylinder</option>
81*c8dee2aaSAndroid Build Coastguard Worker        </select>
82*c8dee2aaSAndroid Build Coastguard Worker      </div>
83*c8dee2aaSAndroid Build Coastguard Worker      <div>
84*c8dee2aaSAndroid Build Coastguard Worker        <select id="renderer" disabled>
85*c8dee2aaSAndroid Build Coastguard Worker          <option value="ckRenderer" selected>CanvasKit (polyfill)</option>
86*c8dee2aaSAndroid Build Coastguard Worker          <option value="nativeRenderer">Canvas2D (native)</option>
87*c8dee2aaSAndroid Build Coastguard Worker        </select>
88*c8dee2aaSAndroid Build Coastguard Worker      </div>
89*c8dee2aaSAndroid Build Coastguard Worker    </div>
90*c8dee2aaSAndroid Build Coastguard Worker  </div>
91*c8dee2aaSAndroid Build Coastguard Worker</div>
92*c8dee2aaSAndroid Build Coastguard Worker
93*c8dee2aaSAndroid Build Coastguard Worker<script type="text/javascript" src="canvaskit.js"></script>
94*c8dee2aaSAndroid Build Coastguard Worker
95*c8dee2aaSAndroid Build Coastguard Worker<script type="text/javascript">
96*c8dee2aaSAndroid Build Coastguard Worker  class MeshData {
97*c8dee2aaSAndroid Build Coastguard Worker    constructor(size, renderer) {
98*c8dee2aaSAndroid Build Coastguard Worker      const vertex_count = size*size;
99*c8dee2aaSAndroid Build Coastguard Worker
100*c8dee2aaSAndroid Build Coastguard Worker      // 2 floats per point
101*c8dee2aaSAndroid Build Coastguard Worker      this.verts          = new Float32Array(vertex_count*2);
102*c8dee2aaSAndroid Build Coastguard Worker      this.animated_verts = new Float32Array(vertex_count*2);
103*c8dee2aaSAndroid Build Coastguard Worker      this.uvs            = new Float32Array(vertex_count*2);
104*c8dee2aaSAndroid Build Coastguard Worker
105*c8dee2aaSAndroid Build Coastguard Worker      let i = 0;
106*c8dee2aaSAndroid Build Coastguard Worker      for (let y = 0; y < size; ++y) {
107*c8dee2aaSAndroid Build Coastguard Worker        for (let x = 0; x < size; ++x) {
108*c8dee2aaSAndroid Build Coastguard Worker          // To keep things simple, all vertices are normalized.
109*c8dee2aaSAndroid Build Coastguard Worker          this.verts[i + 0] = this.uvs[i + 0] = x / (size - 1);
110*c8dee2aaSAndroid Build Coastguard Worker          this.verts[i + 1] = this.uvs[i + 1] = y / (size - 1);
111*c8dee2aaSAndroid Build Coastguard Worker
112*c8dee2aaSAndroid Build Coastguard Worker          i += 2;
113*c8dee2aaSAndroid Build Coastguard Worker        }
114*c8dee2aaSAndroid Build Coastguard Worker      }
115*c8dee2aaSAndroid Build Coastguard Worker
116*c8dee2aaSAndroid Build Coastguard Worker      // 2 triangles per LOD square, 3 indices per triangle
117*c8dee2aaSAndroid Build Coastguard Worker      this.indices = new Uint16Array((size - 1)*(size - 1)*6);
118*c8dee2aaSAndroid Build Coastguard Worker      i = 0;
119*c8dee2aaSAndroid Build Coastguard Worker      for (let y = 0; y < size - 1; ++y) {
120*c8dee2aaSAndroid Build Coastguard Worker        for (let x = 0; x < size - 1; ++x) {
121*c8dee2aaSAndroid Build Coastguard Worker          const vidx0 = x + y*size;
122*c8dee2aaSAndroid Build Coastguard Worker          const vidx1 = vidx0 + size;
123*c8dee2aaSAndroid Build Coastguard Worker
124*c8dee2aaSAndroid Build Coastguard Worker          this.indices[i++] = vidx0;
125*c8dee2aaSAndroid Build Coastguard Worker          this.indices[i++] = vidx0 + 1;
126*c8dee2aaSAndroid Build Coastguard Worker          this.indices[i++] = vidx1 + 1;
127*c8dee2aaSAndroid Build Coastguard Worker
128*c8dee2aaSAndroid Build Coastguard Worker          this.indices[i++] = vidx0;
129*c8dee2aaSAndroid Build Coastguard Worker          this.indices[i++] = vidx1;
130*c8dee2aaSAndroid Build Coastguard Worker          this.indices[i++] = vidx1 + 1;
131*c8dee2aaSAndroid Build Coastguard Worker        }
132*c8dee2aaSAndroid Build Coastguard Worker      }
133*c8dee2aaSAndroid Build Coastguard Worker
134*c8dee2aaSAndroid Build Coastguard Worker      // These can be cached upfront (constant during animation).
135*c8dee2aaSAndroid Build Coastguard Worker      this.uvBuffer    = renderer.makeUVBuffer(this.uvs);
136*c8dee2aaSAndroid Build Coastguard Worker      this.indexBuffer = renderer.makeIndexBuffer(this.indices);
137*c8dee2aaSAndroid Build Coastguard Worker    }
138*c8dee2aaSAndroid Build Coastguard Worker
139*c8dee2aaSAndroid Build Coastguard Worker    animate(animator) {
140*c8dee2aaSAndroid Build Coastguard Worker      function bezier(t, p0, p1, p2, p3){
141*c8dee2aaSAndroid Build Coastguard Worker        return (1 - t)*(1 - t)*(1 - t)*p0 +
142*c8dee2aaSAndroid Build Coastguard Worker                   3*(1 - t)*(1 - t)*t*p1 +
143*c8dee2aaSAndroid Build Coastguard Worker                         3*(1 - t)*t*t*p2 +
144*c8dee2aaSAndroid Build Coastguard Worker                                 t*t*t*p3;
145*c8dee2aaSAndroid Build Coastguard Worker      }
146*c8dee2aaSAndroid Build Coastguard Worker
147*c8dee2aaSAndroid Build Coastguard Worker      // Tuned for non-linear transition.
148*c8dee2aaSAndroid Build Coastguard Worker      function ease(t) { return bezier(t, 0, 0.4, 1, 1); }
149*c8dee2aaSAndroid Build Coastguard Worker
150*c8dee2aaSAndroid Build Coastguard Worker      if (!animator) {
151*c8dee2aaSAndroid Build Coastguard Worker        return;
152*c8dee2aaSAndroid Build Coastguard Worker      }
153*c8dee2aaSAndroid Build Coastguard Worker
154*c8dee2aaSAndroid Build Coastguard Worker      const ms = Date.now() - timeBase;
155*c8dee2aaSAndroid Build Coastguard Worker      const  t = Math.abs((ms / 1000) % 2 - 1);
156*c8dee2aaSAndroid Build Coastguard Worker
157*c8dee2aaSAndroid Build Coastguard Worker      animator(this.verts, this.animated_verts, t);
158*c8dee2aaSAndroid Build Coastguard Worker    }
159*c8dee2aaSAndroid Build Coastguard Worker
160*c8dee2aaSAndroid Build Coastguard Worker    generateTriangles(func) {
161*c8dee2aaSAndroid Build Coastguard Worker      for (let i = 0; i < this.indices.length; i += 3) {
162*c8dee2aaSAndroid Build Coastguard Worker        const i0 = 2*this.indices[i + 0];
163*c8dee2aaSAndroid Build Coastguard Worker        const i1 = 2*this.indices[i + 1];
164*c8dee2aaSAndroid Build Coastguard Worker        const i2 = 2*this.indices[i + 2];
165*c8dee2aaSAndroid Build Coastguard Worker
166*c8dee2aaSAndroid Build Coastguard Worker        func(this.animated_verts[i0 + 0], this.animated_verts[i0 + 1],
167*c8dee2aaSAndroid Build Coastguard Worker             this.animated_verts[i1 + 0], this.animated_verts[i1 + 1],
168*c8dee2aaSAndroid Build Coastguard Worker             this.animated_verts[i2 + 0], this.animated_verts[i2 + 1]);
169*c8dee2aaSAndroid Build Coastguard Worker      }
170*c8dee2aaSAndroid Build Coastguard Worker    }
171*c8dee2aaSAndroid Build Coastguard Worker  }
172*c8dee2aaSAndroid Build Coastguard Worker
173*c8dee2aaSAndroid Build Coastguard Worker  class PatchControls {
174*c8dee2aaSAndroid Build Coastguard Worker    constructor() {
175*c8dee2aaSAndroid Build Coastguard Worker      this.controls = [
176*c8dee2aaSAndroid Build Coastguard Worker        { pos: [ 0.00, 0.33], color: '#0ff', deps: []      },
177*c8dee2aaSAndroid Build Coastguard Worker        { pos: [ 0.00, 0.00], color: '#0f0', deps: [0, 2]  },
178*c8dee2aaSAndroid Build Coastguard Worker        { pos: [ 0.33, 0.00], color: '#0ff', deps: []      },
179*c8dee2aaSAndroid Build Coastguard Worker
180*c8dee2aaSAndroid Build Coastguard Worker        { pos: [ 0.66, 0.00], color: '#0ff', deps: []      },
181*c8dee2aaSAndroid Build Coastguard Worker        { pos: [ 1.00, 0.00], color: '#0f0', deps: [3, 5]  },
182*c8dee2aaSAndroid Build Coastguard Worker        { pos: [ 1.00, 0.33], color: '#0ff', deps: []      },
183*c8dee2aaSAndroid Build Coastguard Worker
184*c8dee2aaSAndroid Build Coastguard Worker        { pos: [ 1.00, 0.66], color: '#0ff', deps: []      },
185*c8dee2aaSAndroid Build Coastguard Worker        { pos: [ 1.00, 1.00], color: '#0f0', deps: [6, 8]  },
186*c8dee2aaSAndroid Build Coastguard Worker        { pos: [ 0.66, 1.00], color: '#0ff', deps: []      },
187*c8dee2aaSAndroid Build Coastguard Worker
188*c8dee2aaSAndroid Build Coastguard Worker        { pos: [ 0.33, 1.00], color: '#0ff', deps: []      },
189*c8dee2aaSAndroid Build Coastguard Worker        { pos: [ 0.00, 1.00], color: '#0f0', deps: [9, 11] },
190*c8dee2aaSAndroid Build Coastguard Worker        { pos: [ 0.00, 0.66], color: '#0ff', deps: []      },
191*c8dee2aaSAndroid Build Coastguard Worker      ];
192*c8dee2aaSAndroid Build Coastguard Worker
193*c8dee2aaSAndroid Build Coastguard Worker      this.radius = 0.01;
194*c8dee2aaSAndroid Build Coastguard Worker      this.drag_target = null;
195*c8dee2aaSAndroid Build Coastguard Worker    }
196*c8dee2aaSAndroid Build Coastguard Worker
197*c8dee2aaSAndroid Build Coastguard Worker    mapMouse(ev) {
198*c8dee2aaSAndroid Build Coastguard Worker      const w = canvas2d.width,
199*c8dee2aaSAndroid Build Coastguard Worker            h = canvas2d.height;
200*c8dee2aaSAndroid Build Coastguard Worker      return [
201*c8dee2aaSAndroid Build Coastguard Worker        (ev.offsetX - w*(1 - meshScale)*0.5)/(w*meshScale),
202*c8dee2aaSAndroid Build Coastguard Worker        (ev.offsetY - h*(1 - meshScale)*0.5)/(h*meshScale),
203*c8dee2aaSAndroid Build Coastguard Worker      ];
204*c8dee2aaSAndroid Build Coastguard Worker    }
205*c8dee2aaSAndroid Build Coastguard Worker
206*c8dee2aaSAndroid Build Coastguard Worker    onMouseDown(ev) {
207*c8dee2aaSAndroid Build Coastguard Worker      const mouse_pos = this.mapMouse(ev);
208*c8dee2aaSAndroid Build Coastguard Worker
209*c8dee2aaSAndroid Build Coastguard Worker      for (let i = this.controls.length - 1; i >= 0; --i) {
210*c8dee2aaSAndroid Build Coastguard Worker        const dx = this.controls[i].pos[0] - mouse_pos[0],
211*c8dee2aaSAndroid Build Coastguard Worker              dy = this.controls[i].pos[1] - mouse_pos[1];
212*c8dee2aaSAndroid Build Coastguard Worker
213*c8dee2aaSAndroid Build Coastguard Worker        if (dx*dx + dy*dy <= this.radius*this.radius) {
214*c8dee2aaSAndroid Build Coastguard Worker          this.drag_target = this.controls[i];
215*c8dee2aaSAndroid Build Coastguard Worker          this.drag_offset = [dx, dy];
216*c8dee2aaSAndroid Build Coastguard Worker          break;
217*c8dee2aaSAndroid Build Coastguard Worker        }
218*c8dee2aaSAndroid Build Coastguard Worker      }
219*c8dee2aaSAndroid Build Coastguard Worker    }
220*c8dee2aaSAndroid Build Coastguard Worker
221*c8dee2aaSAndroid Build Coastguard Worker    onMouseMove(ev) {
222*c8dee2aaSAndroid Build Coastguard Worker      if (!this.drag_target) return;
223*c8dee2aaSAndroid Build Coastguard Worker
224*c8dee2aaSAndroid Build Coastguard Worker      const mouse_pos = this.mapMouse(ev),
225*c8dee2aaSAndroid Build Coastguard Worker                   dx = mouse_pos[0] + this.drag_offset[0] - this.drag_target.pos[0],
226*c8dee2aaSAndroid Build Coastguard Worker                   dy = mouse_pos[1] + this.drag_offset[1] - this.drag_target.pos[1];
227*c8dee2aaSAndroid Build Coastguard Worker
228*c8dee2aaSAndroid Build Coastguard Worker      this.drag_target.pos = [ this.drag_target.pos[0] + dx, this.drag_target.pos[1] + dy ];
229*c8dee2aaSAndroid Build Coastguard Worker
230*c8dee2aaSAndroid Build Coastguard Worker      for (let dep_index of this.drag_target.deps) {
231*c8dee2aaSAndroid Build Coastguard Worker        const dep = this.controls[dep_index];
232*c8dee2aaSAndroid Build Coastguard Worker        dep.pos = [ dep.pos[0] + dx, dep.pos[1] + dy ];
233*c8dee2aaSAndroid Build Coastguard Worker      }
234*c8dee2aaSAndroid Build Coastguard Worker
235*c8dee2aaSAndroid Build Coastguard Worker      this.updateVerts();
236*c8dee2aaSAndroid Build Coastguard Worker    }
237*c8dee2aaSAndroid Build Coastguard Worker
238*c8dee2aaSAndroid Build Coastguard Worker    onMouseUp(ev) {
239*c8dee2aaSAndroid Build Coastguard Worker      this.drag_target = null;
240*c8dee2aaSAndroid Build Coastguard Worker    }
241*c8dee2aaSAndroid Build Coastguard Worker
242*c8dee2aaSAndroid Build Coastguard Worker    updateVerts() {
243*c8dee2aaSAndroid Build Coastguard Worker      this.samplePatch(parseInt(lodSelectUI.value), meshData.animated_verts);
244*c8dee2aaSAndroid Build Coastguard Worker    }
245*c8dee2aaSAndroid Build Coastguard Worker
246*c8dee2aaSAndroid Build Coastguard Worker    drawUI(line_func, circle_func) {
247*c8dee2aaSAndroid Build Coastguard Worker      for (let i = 0; i < this.controls.length; i += 3) {
248*c8dee2aaSAndroid Build Coastguard Worker        const c0 = this.controls[i + 0],
249*c8dee2aaSAndroid Build Coastguard Worker              c1 = this.controls[i + 1],
250*c8dee2aaSAndroid Build Coastguard Worker              c2 = this.controls[i + 2];
251*c8dee2aaSAndroid Build Coastguard Worker
252*c8dee2aaSAndroid Build Coastguard Worker        line_func(c0.pos, c1.pos, '#f00');
253*c8dee2aaSAndroid Build Coastguard Worker        line_func(c1.pos, c2.pos, '#f00');
254*c8dee2aaSAndroid Build Coastguard Worker        circle_func(c0.pos, this.radius, c0.color);
255*c8dee2aaSAndroid Build Coastguard Worker        circle_func(c1.pos, this.radius, c1.color);
256*c8dee2aaSAndroid Build Coastguard Worker        circle_func(c2.pos, this.radius, c2.color);
257*c8dee2aaSAndroid Build Coastguard Worker      }
258*c8dee2aaSAndroid Build Coastguard Worker    }
259*c8dee2aaSAndroid Build Coastguard Worker
260*c8dee2aaSAndroid Build Coastguard Worker    // Based on https://github.com/google/skia/blob/de56f293eb41d65786b9e6224fdf9a4702b30f51/src/utils/SkPatchUtils.cpp#L84
261*c8dee2aaSAndroid Build Coastguard Worker    sampleCubic(cind, lod) {
262*c8dee2aaSAndroid Build Coastguard Worker      const divisions = lod - 1,
263*c8dee2aaSAndroid Build Coastguard Worker                    h = 1/divisions,
264*c8dee2aaSAndroid Build Coastguard Worker                   h2 = h*h,
265*c8dee2aaSAndroid Build Coastguard Worker                   h3 = h*h2,
266*c8dee2aaSAndroid Build Coastguard Worker                  pts = [
267*c8dee2aaSAndroid Build Coastguard Worker                          this.controls[cind[0]].pos,
268*c8dee2aaSAndroid Build Coastguard Worker                          this.controls[cind[1]].pos,
269*c8dee2aaSAndroid Build Coastguard Worker                          this.controls[cind[2]].pos,
270*c8dee2aaSAndroid Build Coastguard Worker                          this.controls[cind[3]].pos,
271*c8dee2aaSAndroid Build Coastguard Worker                        ],
272*c8dee2aaSAndroid Build Coastguard Worker               coeffs = [
273*c8dee2aaSAndroid Build Coastguard Worker                          [
274*c8dee2aaSAndroid Build Coastguard Worker                            pts[3][0] + 3*(pts[1][0] - pts[2][0]) - pts[0][0],
275*c8dee2aaSAndroid Build Coastguard Worker                            pts[3][1] + 3*(pts[1][1] - pts[2][1]) - pts[0][1],
276*c8dee2aaSAndroid Build Coastguard Worker                          ],
277*c8dee2aaSAndroid Build Coastguard Worker                          [
278*c8dee2aaSAndroid Build Coastguard Worker                            3*(pts[2][0] - 2*pts[1][0] + pts[0][0]),
279*c8dee2aaSAndroid Build Coastguard Worker                            3*(pts[2][1] - 2*pts[1][1] + pts[0][1]),
280*c8dee2aaSAndroid Build Coastguard Worker                          ],
281*c8dee2aaSAndroid Build Coastguard Worker                          [
282*c8dee2aaSAndroid Build Coastguard Worker                            3*(pts[1][0] - pts[0][0]),
283*c8dee2aaSAndroid Build Coastguard Worker                            3*(pts[1][1] - pts[0][1]),
284*c8dee2aaSAndroid Build Coastguard Worker                          ],
285*c8dee2aaSAndroid Build Coastguard Worker                          pts[0],
286*c8dee2aaSAndroid Build Coastguard Worker                        ],
287*c8dee2aaSAndroid Build Coastguard Worker              fwDiff3 = [
288*c8dee2aaSAndroid Build Coastguard Worker                          6*h3*coeffs[0][0],
289*c8dee2aaSAndroid Build Coastguard Worker                          6*h3*coeffs[0][1],
290*c8dee2aaSAndroid Build Coastguard Worker                        ];
291*c8dee2aaSAndroid Build Coastguard Worker
292*c8dee2aaSAndroid Build Coastguard Worker      let fwDiff = [
293*c8dee2aaSAndroid Build Coastguard Worker                     coeffs[3],
294*c8dee2aaSAndroid Build Coastguard Worker                     [
295*c8dee2aaSAndroid Build Coastguard Worker                       h3*coeffs[0][0] + h2*coeffs[1][0] + h*coeffs[2][0],
296*c8dee2aaSAndroid Build Coastguard Worker                       h3*coeffs[0][1] + h2*coeffs[1][1] + h*coeffs[2][1],
297*c8dee2aaSAndroid Build Coastguard Worker                     ],
298*c8dee2aaSAndroid Build Coastguard Worker                     [
299*c8dee2aaSAndroid Build Coastguard Worker                       fwDiff3[0] + 2*h2*coeffs[1][0],
300*c8dee2aaSAndroid Build Coastguard Worker                       fwDiff3[1] + 2*h2*coeffs[1][1],
301*c8dee2aaSAndroid Build Coastguard Worker                     ],
302*c8dee2aaSAndroid Build Coastguard Worker                     fwDiff3,
303*c8dee2aaSAndroid Build Coastguard Worker                   ];
304*c8dee2aaSAndroid Build Coastguard Worker
305*c8dee2aaSAndroid Build Coastguard Worker      let verts = [];
306*c8dee2aaSAndroid Build Coastguard Worker
307*c8dee2aaSAndroid Build Coastguard Worker      for (let i = 0; i <= divisions; ++i) {
308*c8dee2aaSAndroid Build Coastguard Worker        verts.push(fwDiff[0]);
309*c8dee2aaSAndroid Build Coastguard Worker        fwDiff[0] = [ fwDiff[0][0] + fwDiff[1][0], fwDiff[0][1] + fwDiff[1][1] ];
310*c8dee2aaSAndroid Build Coastguard Worker        fwDiff[1] = [ fwDiff[1][0] + fwDiff[2][0], fwDiff[1][1] + fwDiff[2][1] ];
311*c8dee2aaSAndroid Build Coastguard Worker        fwDiff[2] = [ fwDiff[2][0] + fwDiff[3][0], fwDiff[2][1] + fwDiff[3][1] ];
312*c8dee2aaSAndroid Build Coastguard Worker      }
313*c8dee2aaSAndroid Build Coastguard Worker
314*c8dee2aaSAndroid Build Coastguard Worker      return verts;
315*c8dee2aaSAndroid Build Coastguard Worker    }
316*c8dee2aaSAndroid Build Coastguard Worker
317*c8dee2aaSAndroid Build Coastguard Worker    // Based on https://github.com/google/skia/blob/de56f293eb41d65786b9e6224fdf9a4702b30f51/src/utils/SkPatchUtils.cpp#L256
318*c8dee2aaSAndroid Build Coastguard Worker    samplePatch(lod, verts) {
319*c8dee2aaSAndroid Build Coastguard Worker      const top_verts = this.sampleCubic([  1,  2,  3,  4 ], lod),
320*c8dee2aaSAndroid Build Coastguard Worker          right_verts = this.sampleCubic([  4,  5,  6,  7 ], lod),
321*c8dee2aaSAndroid Build Coastguard Worker         bottom_verts = this.sampleCubic([ 10,  9,  8,  7 ], lod),
322*c8dee2aaSAndroid Build Coastguard Worker           left_verts = this.sampleCubic([  1,  0, 11, 10 ], lod);
323*c8dee2aaSAndroid Build Coastguard Worker
324*c8dee2aaSAndroid Build Coastguard Worker      let i = 0;
325*c8dee2aaSAndroid Build Coastguard Worker      for (let y = 0; y < lod; ++y) {
326*c8dee2aaSAndroid Build Coastguard Worker        const v = y/(lod - 1),
327*c8dee2aaSAndroid Build Coastguard Worker           left = left_verts[y],
328*c8dee2aaSAndroid Build Coastguard Worker          right = right_verts[y];
329*c8dee2aaSAndroid Build Coastguard Worker
330*c8dee2aaSAndroid Build Coastguard Worker        for (let x = 0; x < lod; ++x) {
331*c8dee2aaSAndroid Build Coastguard Worker          const u = x/(lod - 1),
332*c8dee2aaSAndroid Build Coastguard Worker              top = top_verts[x],
333*c8dee2aaSAndroid Build Coastguard Worker           bottom = bottom_verts[x],
334*c8dee2aaSAndroid Build Coastguard Worker
335*c8dee2aaSAndroid Build Coastguard Worker               s0 = [
336*c8dee2aaSAndroid Build Coastguard Worker                      (1 - v)*top[0] + v*bottom[0],
337*c8dee2aaSAndroid Build Coastguard Worker                      (1 - v)*top[1] + v*bottom[1],
338*c8dee2aaSAndroid Build Coastguard Worker                    ],
339*c8dee2aaSAndroid Build Coastguard Worker               s1 = [
340*c8dee2aaSAndroid Build Coastguard Worker                      (1 - u)*left[0] + u*right[0],
341*c8dee2aaSAndroid Build Coastguard Worker                      (1 - u)*left[1] + u*right[1],
342*c8dee2aaSAndroid Build Coastguard Worker                    ],
343*c8dee2aaSAndroid Build Coastguard Worker               s2 = [
344*c8dee2aaSAndroid Build Coastguard Worker                      (1 - v)*((1 - u)*this.controls[ 1].pos[0] + u*this.controls[4].pos[0]) +
345*c8dee2aaSAndroid Build Coastguard Worker                            v*((1 - u)*this.controls[10].pos[0] + u*this.controls[7].pos[0]),
346*c8dee2aaSAndroid Build Coastguard Worker                      (1 - v)*((1 - u)*this.controls[ 1].pos[1] + u*this.controls[4].pos[1]) +
347*c8dee2aaSAndroid Build Coastguard Worker                            v*((1 - u)*this.controls[10].pos[1] + u*this.controls[7].pos[1]),
348*c8dee2aaSAndroid Build Coastguard Worker                    ];
349*c8dee2aaSAndroid Build Coastguard Worker
350*c8dee2aaSAndroid Build Coastguard Worker          verts[i++] = s0[0] + s1[0] - s2[0];
351*c8dee2aaSAndroid Build Coastguard Worker          verts[i++] = s0[1] + s1[1] - s2[1];
352*c8dee2aaSAndroid Build Coastguard Worker        }
353*c8dee2aaSAndroid Build Coastguard Worker      }
354*c8dee2aaSAndroid Build Coastguard Worker    }
355*c8dee2aaSAndroid Build Coastguard Worker  }
356*c8dee2aaSAndroid Build Coastguard Worker
357*c8dee2aaSAndroid Build Coastguard Worker  class CKRenderer {
358*c8dee2aaSAndroid Build Coastguard Worker    constructor(ck, img, canvasElement) {
359*c8dee2aaSAndroid Build Coastguard Worker      this.ck = ck;
360*c8dee2aaSAndroid Build Coastguard Worker      this.surface = ck.MakeCanvasSurface(canvasElement);
361*c8dee2aaSAndroid Build Coastguard Worker      this.meshPaint = new ck.Paint();
362*c8dee2aaSAndroid Build Coastguard Worker
363*c8dee2aaSAndroid Build Coastguard Worker      // UVs are normalized, so we scale the image shader down to 1x1.
364*c8dee2aaSAndroid Build Coastguard Worker      const skimg = ck.MakeImageFromCanvasImageSource(img);
365*c8dee2aaSAndroid Build Coastguard Worker      const localMatrix = [1/skimg.width(),  0, 0,
366*c8dee2aaSAndroid Build Coastguard Worker                           0, 1/skimg.height(), 0,
367*c8dee2aaSAndroid Build Coastguard Worker                           0,                0, 1];
368*c8dee2aaSAndroid Build Coastguard Worker
369*c8dee2aaSAndroid Build Coastguard Worker      this.meshPaint.setShader(skimg.makeShaderOptions(ck.TileMode.Decal,
370*c8dee2aaSAndroid Build Coastguard Worker                                                       ck.TileMode.Decal,
371*c8dee2aaSAndroid Build Coastguard Worker                                                       ck.FilterMode.Linear,
372*c8dee2aaSAndroid Build Coastguard Worker                                                       ck.MipmapMode.None,
373*c8dee2aaSAndroid Build Coastguard Worker                                                       localMatrix));
374*c8dee2aaSAndroid Build Coastguard Worker
375*c8dee2aaSAndroid Build Coastguard Worker      this.gridPaint = new ck.Paint();
376*c8dee2aaSAndroid Build Coastguard Worker      this.gridPaint.setColor(ck.BLUE);
377*c8dee2aaSAndroid Build Coastguard Worker      this.gridPaint.setAntiAlias(true);
378*c8dee2aaSAndroid Build Coastguard Worker      this.gridPaint.setStyle(ck.PaintStyle.Stroke);
379*c8dee2aaSAndroid Build Coastguard Worker
380*c8dee2aaSAndroid Build Coastguard Worker      this.controlsPaint = new ck.Paint();
381*c8dee2aaSAndroid Build Coastguard Worker      this.controlsPaint.setAntiAlias(true);
382*c8dee2aaSAndroid Build Coastguard Worker      this.controlsPaint.setStyle(ck.PaintStyle.Fill);
383*c8dee2aaSAndroid Build Coastguard Worker    }
384*c8dee2aaSAndroid Build Coastguard Worker
385*c8dee2aaSAndroid Build Coastguard Worker    // Unlike the native renderer, CK drawVertices() takes typed arrays directly - so
386*c8dee2aaSAndroid Build Coastguard Worker    // we don't need to allocate separate buffers.
387*c8dee2aaSAndroid Build Coastguard Worker    makeVertexBuffer(buf) { return buf; }
388*c8dee2aaSAndroid Build Coastguard Worker    makeUVBuffer    (buf) { return buf; }
389*c8dee2aaSAndroid Build Coastguard Worker    makeIndexBuffer (buf) { return buf; }
390*c8dee2aaSAndroid Build Coastguard Worker
391*c8dee2aaSAndroid Build Coastguard Worker    meshPath(mesh) {
392*c8dee2aaSAndroid Build Coastguard Worker      // 4 commands per triangle, 3 floats per cmd
393*c8dee2aaSAndroid Build Coastguard Worker      const cmds = new Float32Array(mesh.indices.length*12);
394*c8dee2aaSAndroid Build Coastguard Worker      let ci = 0;
395*c8dee2aaSAndroid Build Coastguard Worker      mesh.generateTriangles((x0, y0, x1, y1, x2, y2) => {
396*c8dee2aaSAndroid Build Coastguard Worker        cmds[ci++] = this.ck.MOVE_VERB; cmds[ci++] = x0; cmds[ci++] = y0;
397*c8dee2aaSAndroid Build Coastguard Worker        cmds[ci++] = this.ck.LINE_VERB; cmds[ci++] = x1; cmds[ci++] = y1;
398*c8dee2aaSAndroid Build Coastguard Worker        cmds[ci++] = this.ck.LINE_VERB; cmds[ci++] = x2; cmds[ci++] = y2;
399*c8dee2aaSAndroid Build Coastguard Worker        cmds[ci++] = this.ck.LINE_VERB; cmds[ci++] = x0; cmds[ci++] = y0;
400*c8dee2aaSAndroid Build Coastguard Worker      });
401*c8dee2aaSAndroid Build Coastguard Worker      return this.ck.Path.MakeFromCmds(cmds);
402*c8dee2aaSAndroid Build Coastguard Worker    }
403*c8dee2aaSAndroid Build Coastguard Worker
404*c8dee2aaSAndroid Build Coastguard Worker    drawMesh(mesh, ctrls) {
405*c8dee2aaSAndroid Build Coastguard Worker      const vertices = this.ck.MakeVertices(this.ck.VertexMode.Triangles,
406*c8dee2aaSAndroid Build Coastguard Worker                                            this.makeVertexBuffer(mesh.animated_verts),
407*c8dee2aaSAndroid Build Coastguard Worker                                            mesh.uvBuffer, null, mesh.indexBuffer, false);
408*c8dee2aaSAndroid Build Coastguard Worker
409*c8dee2aaSAndroid Build Coastguard Worker      const canvas = this.surface.getCanvas();
410*c8dee2aaSAndroid Build Coastguard Worker      const w = this.surface.width(),
411*c8dee2aaSAndroid Build Coastguard Worker            h = this.surface.height();
412*c8dee2aaSAndroid Build Coastguard Worker
413*c8dee2aaSAndroid Build Coastguard Worker      canvas.save();
414*c8dee2aaSAndroid Build Coastguard Worker        canvas.translate(w*(1-meshScale)*0.5, h*(1-meshScale)*0.5);
415*c8dee2aaSAndroid Build Coastguard Worker        canvas.scale(w*meshScale, h*meshScale);
416*c8dee2aaSAndroid Build Coastguard Worker
417*c8dee2aaSAndroid Build Coastguard Worker        canvas.drawVertices(vertices, this.ck.BlendMode.Dst, this.meshPaint);
418*c8dee2aaSAndroid Build Coastguard Worker
419*c8dee2aaSAndroid Build Coastguard Worker        if (showMeshUI.checked) {
420*c8dee2aaSAndroid Build Coastguard Worker          canvas.drawPath(this.meshPath(mesh), this.gridPaint);
421*c8dee2aaSAndroid Build Coastguard Worker        }
422*c8dee2aaSAndroid Build Coastguard Worker
423*c8dee2aaSAndroid Build Coastguard Worker        ctrls?.drawUI(
424*c8dee2aaSAndroid Build Coastguard Worker            (p0, p1, color) => {
425*c8dee2aaSAndroid Build Coastguard Worker                this.controlsPaint.setColor(this.ck.parseColorString(color));
426*c8dee2aaSAndroid Build Coastguard Worker                canvas.drawLine(p0[0], p0[1], p1[0], p1[1], this.controlsPaint);
427*c8dee2aaSAndroid Build Coastguard Worker            },
428*c8dee2aaSAndroid Build Coastguard Worker            (c, r, color) => {
429*c8dee2aaSAndroid Build Coastguard Worker                this.controlsPaint.setColor(this.ck.parseColorString(color));
430*c8dee2aaSAndroid Build Coastguard Worker                canvas.drawCircle(c[0], c[1], r, this.controlsPaint);
431*c8dee2aaSAndroid Build Coastguard Worker            }
432*c8dee2aaSAndroid Build Coastguard Worker        );
433*c8dee2aaSAndroid Build Coastguard Worker      canvas.restore();
434*c8dee2aaSAndroid Build Coastguard Worker      this.surface.flush();
435*c8dee2aaSAndroid Build Coastguard Worker    }
436*c8dee2aaSAndroid Build Coastguard Worker  }
437*c8dee2aaSAndroid Build Coastguard Worker
438*c8dee2aaSAndroid Build Coastguard Worker  class NativeRenderer {
439*c8dee2aaSAndroid Build Coastguard Worker    constructor(img, canvasElement) {
440*c8dee2aaSAndroid Build Coastguard Worker      this.img = img;
441*c8dee2aaSAndroid Build Coastguard Worker      this.ctx = canvasElement.getContext("2d");
442*c8dee2aaSAndroid Build Coastguard Worker    }
443*c8dee2aaSAndroid Build Coastguard Worker
444*c8dee2aaSAndroid Build Coastguard Worker    // New Mesh2D API: https://github.com/fserb/canvas2D/blob/master/spec/mesh2d.md#mesh2d-api
445*c8dee2aaSAndroid Build Coastguard Worker    makeVertexBuffer(buf) { return this.ctx.createMesh2DVertexBuffer(buf); }
446*c8dee2aaSAndroid Build Coastguard Worker    makeUVBuffer(buf) {
447*c8dee2aaSAndroid Build Coastguard Worker        return this.ctx.createMesh2DUVBuffer(buf);
448*c8dee2aaSAndroid Build Coastguard Worker    }
449*c8dee2aaSAndroid Build Coastguard Worker    makeIndexBuffer(buf)  { return this.ctx.createMesh2DIndexBuffer(buf); }
450*c8dee2aaSAndroid Build Coastguard Worker
451*c8dee2aaSAndroid Build Coastguard Worker    meshPath(mesh) {
452*c8dee2aaSAndroid Build Coastguard Worker      const path = new Path2D();
453*c8dee2aaSAndroid Build Coastguard Worker      mesh.generateTriangles((x0, y0, x1, y1, x2, y2) => {
454*c8dee2aaSAndroid Build Coastguard Worker        path.moveTo(x0, y0);
455*c8dee2aaSAndroid Build Coastguard Worker        path.lineTo(x1, y1);
456*c8dee2aaSAndroid Build Coastguard Worker        path.lineTo(x2, y2);
457*c8dee2aaSAndroid Build Coastguard Worker        path.lineTo(x0, y0);
458*c8dee2aaSAndroid Build Coastguard Worker      });
459*c8dee2aaSAndroid Build Coastguard Worker      return path;
460*c8dee2aaSAndroid Build Coastguard Worker    }
461*c8dee2aaSAndroid Build Coastguard Worker
462*c8dee2aaSAndroid Build Coastguard Worker    drawMesh(mesh, ctrls) {
463*c8dee2aaSAndroid Build Coastguard Worker      const vbuf = this.ctx.createMesh2DVertexBuffer(mesh.animated_verts);
464*c8dee2aaSAndroid Build Coastguard Worker      const w = canvas2d.width,
465*c8dee2aaSAndroid Build Coastguard Worker            h = canvas2d.height;
466*c8dee2aaSAndroid Build Coastguard Worker
467*c8dee2aaSAndroid Build Coastguard Worker      this.ctx.clearRect(0, 0, canvas2d.width, canvas2d.height);
468*c8dee2aaSAndroid Build Coastguard Worker      this.ctx.save();
469*c8dee2aaSAndroid Build Coastguard Worker        this.ctx.translate(w*(1-meshScale)*0.5, h*(1-meshScale)*0.5);
470*c8dee2aaSAndroid Build Coastguard Worker        this.ctx.scale(w*meshScale, h*meshScale);
471*c8dee2aaSAndroid Build Coastguard Worker
472*c8dee2aaSAndroid Build Coastguard Worker        this.ctx.drawMesh(vbuf, mesh.uvBuffer, mesh.indexBuffer, this.img);
473*c8dee2aaSAndroid Build Coastguard Worker
474*c8dee2aaSAndroid Build Coastguard Worker        if (showMeshUI.checked) {
475*c8dee2aaSAndroid Build Coastguard Worker          this.ctx.strokeStyle = "blue";
476*c8dee2aaSAndroid Build Coastguard Worker          this.ctx.lineWidth = 0.001;
477*c8dee2aaSAndroid Build Coastguard Worker          this.ctx.stroke(this.meshPath(mesh));
478*c8dee2aaSAndroid Build Coastguard Worker        }
479*c8dee2aaSAndroid Build Coastguard Worker
480*c8dee2aaSAndroid Build Coastguard Worker        ctrls?.drawUI(
481*c8dee2aaSAndroid Build Coastguard Worker            (p0, p1, color) => {
482*c8dee2aaSAndroid Build Coastguard Worker                this.ctx.lineWidth = 0.001;
483*c8dee2aaSAndroid Build Coastguard Worker                this.ctx.strokeStyle = color;
484*c8dee2aaSAndroid Build Coastguard Worker                this.ctx.beginPath();
485*c8dee2aaSAndroid Build Coastguard Worker                this.ctx.moveTo(p0[0], p0[1]);
486*c8dee2aaSAndroid Build Coastguard Worker                this.ctx.lineTo(p1[0], p1[1]);
487*c8dee2aaSAndroid Build Coastguard Worker                this.ctx.stroke();
488*c8dee2aaSAndroid Build Coastguard Worker            },
489*c8dee2aaSAndroid Build Coastguard Worker            (c, r, color) => {
490*c8dee2aaSAndroid Build Coastguard Worker                this.ctx.fillStyle = color;
491*c8dee2aaSAndroid Build Coastguard Worker                this.ctx.beginPath();
492*c8dee2aaSAndroid Build Coastguard Worker                this.ctx.arc(c[0], c[1], r, 0, 2*Math.PI);
493*c8dee2aaSAndroid Build Coastguard Worker                this.ctx.fill();
494*c8dee2aaSAndroid Build Coastguard Worker            }
495*c8dee2aaSAndroid Build Coastguard Worker        );
496*c8dee2aaSAndroid Build Coastguard Worker      this.ctx.restore();
497*c8dee2aaSAndroid Build Coastguard Worker    }
498*c8dee2aaSAndroid Build Coastguard Worker  }
499*c8dee2aaSAndroid Build Coastguard Worker
500*c8dee2aaSAndroid Build Coastguard Worker  function squircleAnimator(verts, animated_verts, t) {
501*c8dee2aaSAndroid Build Coastguard Worker    function lerp(a, b, t) { return a + t*(b - a); }
502*c8dee2aaSAndroid Build Coastguard Worker
503*c8dee2aaSAndroid Build Coastguard Worker    for (let i = 0; i < verts.length; i += 2) {
504*c8dee2aaSAndroid Build Coastguard Worker      const uvx = verts[i + 0] - 0.5,
505*c8dee2aaSAndroid Build Coastguard Worker            uvy = verts[i + 1] - 0.5,
506*c8dee2aaSAndroid Build Coastguard Worker              d = Math.sqrt(uvx*uvx + uvy*uvy)*0.5/Math.max(Math.abs(uvx), Math.abs(uvy)),
507*c8dee2aaSAndroid Build Coastguard Worker              s = d > 0 ? lerp(1, (0.5/ d), t) : 1;
508*c8dee2aaSAndroid Build Coastguard Worker      animated_verts[i + 0] = uvx*s + 0.5;
509*c8dee2aaSAndroid Build Coastguard Worker      animated_verts[i + 1] = uvy*s + 0.5;
510*c8dee2aaSAndroid Build Coastguard Worker    }
511*c8dee2aaSAndroid Build Coastguard Worker  }
512*c8dee2aaSAndroid Build Coastguard Worker
513*c8dee2aaSAndroid Build Coastguard Worker  function twirlAnimator(verts, animated_verts, t) {
514*c8dee2aaSAndroid Build Coastguard Worker    const kMaxRotate = Math.PI*4;
515*c8dee2aaSAndroid Build Coastguard Worker
516*c8dee2aaSAndroid Build Coastguard Worker    for (let i = 0; i < verts.length; i += 2) {
517*c8dee2aaSAndroid Build Coastguard Worker      const uvx = verts[i + 0] - 0.5,
518*c8dee2aaSAndroid Build Coastguard Worker            uvy = verts[i + 1] - 0.5,
519*c8dee2aaSAndroid Build Coastguard Worker              r = Math.sqrt(uvx*uvx + uvy*uvy),
520*c8dee2aaSAndroid Build Coastguard Worker              a = kMaxRotate * r * t;
521*c8dee2aaSAndroid Build Coastguard Worker      animated_verts[i + 0] = uvx*Math.cos(a) - uvy*Math.sin(a) + 0.5;
522*c8dee2aaSAndroid Build Coastguard Worker      animated_verts[i + 1] = uvy*Math.cos(a) + uvx*Math.sin(a) + 0.5;
523*c8dee2aaSAndroid Build Coastguard Worker    }
524*c8dee2aaSAndroid Build Coastguard Worker  }
525*c8dee2aaSAndroid Build Coastguard Worker
526*c8dee2aaSAndroid Build Coastguard Worker  function wiggleAnimator(verts, animated_verts, t) {
527*c8dee2aaSAndroid Build Coastguard Worker    const radius = t*0.2/(Math.sqrt(verts.length/2) - 1);
528*c8dee2aaSAndroid Build Coastguard Worker
529*c8dee2aaSAndroid Build Coastguard Worker    for (let i = 0; i < verts.length; i += 2) {
530*c8dee2aaSAndroid Build Coastguard Worker      const phase = i*Math.PI*0.1505;
531*c8dee2aaSAndroid Build Coastguard Worker      const angle = phase + t*Math.PI*2;
532*c8dee2aaSAndroid Build Coastguard Worker      animated_verts[i + 0] = verts[i + 0] + radius*Math.cos(angle);
533*c8dee2aaSAndroid Build Coastguard Worker      animated_verts[i + 1] = verts[i + 1] + radius*Math.sin(angle);
534*c8dee2aaSAndroid Build Coastguard Worker    }
535*c8dee2aaSAndroid Build Coastguard Worker  }
536*c8dee2aaSAndroid Build Coastguard Worker
537*c8dee2aaSAndroid Build Coastguard Worker  function cylinderAnimator(verts, animated_verts, t) {
538*c8dee2aaSAndroid Build Coastguard Worker    const kCylRadius = .2;
539*c8dee2aaSAndroid Build Coastguard Worker    const cyl_pos = t;
540*c8dee2aaSAndroid Build Coastguard Worker
541*c8dee2aaSAndroid Build Coastguard Worker    for (let i = 0; i < verts.length; i += 2) {
542*c8dee2aaSAndroid Build Coastguard Worker      const uvx = verts[i + 0],
543*c8dee2aaSAndroid Build Coastguard Worker            uvy = verts[i + 1];
544*c8dee2aaSAndroid Build Coastguard Worker
545*c8dee2aaSAndroid Build Coastguard Worker      if (uvx <= cyl_pos) {
546*c8dee2aaSAndroid Build Coastguard Worker        animated_verts[i + 0] = uvx;
547*c8dee2aaSAndroid Build Coastguard Worker        animated_verts[i + 1] = uvy;
548*c8dee2aaSAndroid Build Coastguard Worker        continue;
549*c8dee2aaSAndroid Build Coastguard Worker      }
550*c8dee2aaSAndroid Build Coastguard Worker
551*c8dee2aaSAndroid Build Coastguard Worker      const arc_len = uvx - cyl_pos,
552*c8dee2aaSAndroid Build Coastguard Worker            arc_ang = arc_len/kCylRadius;
553*c8dee2aaSAndroid Build Coastguard Worker
554*c8dee2aaSAndroid Build Coastguard Worker      animated_verts[i + 0] = cyl_pos + Math.sin(arc_ang)*kCylRadius;
555*c8dee2aaSAndroid Build Coastguard Worker      animated_verts[i + 1] = uvy;
556*c8dee2aaSAndroid Build Coastguard Worker    }
557*c8dee2aaSAndroid Build Coastguard Worker  }
558*c8dee2aaSAndroid Build Coastguard Worker
559*c8dee2aaSAndroid Build Coastguard Worker  function drawFrame() {
560*c8dee2aaSAndroid Build Coastguard Worker    meshData.animate(animator);
561*c8dee2aaSAndroid Build Coastguard Worker    currentRenderer.drawMesh(meshData, patchControls);
562*c8dee2aaSAndroid Build Coastguard Worker    requestAnimationFrame(drawFrame);
563*c8dee2aaSAndroid Build Coastguard Worker  }
564*c8dee2aaSAndroid Build Coastguard Worker
565*c8dee2aaSAndroid Build Coastguard Worker  function switchRenderer(renderer) {
566*c8dee2aaSAndroid Build Coastguard Worker    currentRenderer = renderer;
567*c8dee2aaSAndroid Build Coastguard Worker    meshData = new MeshData(parseInt(lodSelectUI.value), currentRenderer);
568*c8dee2aaSAndroid Build Coastguard Worker
569*c8dee2aaSAndroid Build Coastguard Worker    const showCanvas = renderer == ckRenderer ? canvas3d : canvas2d;
570*c8dee2aaSAndroid Build Coastguard Worker    const hideCanvas = renderer == ckRenderer ? canvas2d : canvas3d;
571*c8dee2aaSAndroid Build Coastguard Worker    showCanvas.style.display = 'block';
572*c8dee2aaSAndroid Build Coastguard Worker    hideCanvas.style.display = 'none';
573*c8dee2aaSAndroid Build Coastguard Worker
574*c8dee2aaSAndroid Build Coastguard Worker    patchControls?.updateVerts();
575*c8dee2aaSAndroid Build Coastguard Worker  }
576*c8dee2aaSAndroid Build Coastguard Worker
577*c8dee2aaSAndroid Build Coastguard Worker  const canvas2d = document.getElementById("canvas2d");
578*c8dee2aaSAndroid Build Coastguard Worker  const canvas3d = document.getElementById("canvas3d");
579*c8dee2aaSAndroid Build Coastguard Worker  const hasMesh2DAPI = 'drawMesh' in CanvasRenderingContext2D.prototype;
580*c8dee2aaSAndroid Build Coastguard Worker  const showMeshUI = document.getElementById("show_mesh");
581*c8dee2aaSAndroid Build Coastguard Worker  const lodSelectUI = document.getElementById("lod");
582*c8dee2aaSAndroid Build Coastguard Worker  const animatorSelectUI = document.getElementById("animator");
583*c8dee2aaSAndroid Build Coastguard Worker  const rendererSelectUI = document.getElementById("renderer");
584*c8dee2aaSAndroid Build Coastguard Worker
585*c8dee2aaSAndroid Build Coastguard Worker  const meshScale = 0.75;
586*c8dee2aaSAndroid Build Coastguard Worker
587*c8dee2aaSAndroid Build Coastguard Worker  const loadCK = CanvasKitInit({ locateFile: (file) => 'https://demos.skia.org/demo/mesh2d/' + file });
588*c8dee2aaSAndroid Build Coastguard Worker  const loadImage = new Promise(resolve => {
589*c8dee2aaSAndroid Build Coastguard Worker    const image = new Image();
590*c8dee2aaSAndroid Build Coastguard Worker    image.addEventListener('load', () => { resolve(image); });
591*c8dee2aaSAndroid Build Coastguard Worker    image.src = 'baby_tux.png';
592*c8dee2aaSAndroid Build Coastguard Worker  });
593*c8dee2aaSAndroid Build Coastguard Worker
594*c8dee2aaSAndroid Build Coastguard Worker  var ckRenderer;
595*c8dee2aaSAndroid Build Coastguard Worker  var nativeRenderer;
596*c8dee2aaSAndroid Build Coastguard Worker  var currentRenderer;
597*c8dee2aaSAndroid Build Coastguard Worker  var meshData;
598*c8dee2aaSAndroid Build Coastguard Worker  var image;
599*c8dee2aaSAndroid Build Coastguard Worker
600*c8dee2aaSAndroid Build Coastguard Worker  const timeBase = Date.now();
601*c8dee2aaSAndroid Build Coastguard Worker
602*c8dee2aaSAndroid Build Coastguard Worker  var animator = window[animatorSelectUI.value];
603*c8dee2aaSAndroid Build Coastguard Worker  var patchControls = animator ? null : new PatchControls();
604*c8dee2aaSAndroid Build Coastguard Worker
605*c8dee2aaSAndroid Build Coastguard Worker  Promise.all([loadCK, loadImage]).then(([ck, img]) => {
606*c8dee2aaSAndroid Build Coastguard Worker    ckRenderer = new CKRenderer(ck, img, canvas3d);
607*c8dee2aaSAndroid Build Coastguard Worker    nativeRenderer = 'drawMesh' in CanvasRenderingContext2D.prototype
608*c8dee2aaSAndroid Build Coastguard Worker        ? new NativeRenderer(img, canvas2d)
609*c8dee2aaSAndroid Build Coastguard Worker        : null;
610*c8dee2aaSAndroid Build Coastguard Worker
611*c8dee2aaSAndroid Build Coastguard Worker    rendererSelectUI.disabled = !nativeRenderer;
612*c8dee2aaSAndroid Build Coastguard Worker    rendererSelectUI.value = nativeRenderer ? "nativeRenderer" : "ckRenderer";
613*c8dee2aaSAndroid Build Coastguard Worker
614*c8dee2aaSAndroid Build Coastguard Worker    document.getElementById('loader').style.display = 'none';
615*c8dee2aaSAndroid Build Coastguard Worker    switchRenderer(nativeRenderer ? nativeRenderer : ckRenderer);
616*c8dee2aaSAndroid Build Coastguard Worker
617*c8dee2aaSAndroid Build Coastguard Worker    requestAnimationFrame(drawFrame);
618*c8dee2aaSAndroid Build Coastguard Worker  });
619*c8dee2aaSAndroid Build Coastguard Worker
620*c8dee2aaSAndroid Build Coastguard Worker  lodSelectUI.onchange      = () => { switchRenderer(currentRenderer); }
621*c8dee2aaSAndroid Build Coastguard Worker  rendererSelectUI.onchange = () => { switchRenderer(window[rendererSelectUI.value]); }
622*c8dee2aaSAndroid Build Coastguard Worker  animatorSelectUI.onchange = () => {
623*c8dee2aaSAndroid Build Coastguard Worker    animator = window[animatorSelectUI.value];
624*c8dee2aaSAndroid Build Coastguard Worker    patchControls = animator ? null : new PatchControls();
625*c8dee2aaSAndroid Build Coastguard Worker    patchControls?.updateVerts();
626*c8dee2aaSAndroid Build Coastguard Worker  }
627*c8dee2aaSAndroid Build Coastguard Worker
628*c8dee2aaSAndroid Build Coastguard Worker  const cwrapper = document.getElementById('canvas_wrapper');
629*c8dee2aaSAndroid Build Coastguard Worker  cwrapper.onmousedown = (ev) => { patchControls?.onMouseDown(ev); }
630*c8dee2aaSAndroid Build Coastguard Worker  cwrapper.onmousemove = (ev) => { patchControls?.onMouseMove(ev); }
631*c8dee2aaSAndroid Build Coastguard Worker  cwrapper.onmouseup   = (ev) => { patchControls?.onMouseUp(ev); }
632*c8dee2aaSAndroid Build Coastguard Worker</script>
633