canvas点击产生裂缝缝隙扩散动画效果代码

代码语言:html

所属分类:动画

代码描述:canvas点击产生裂缝缝隙扩散动画效果代码

代码标签: canvas 产生 裂缝 缝隙 扩散 动画

下面为部分代码预览,完整代码请点击下载或在bfwstudio webide中打开

<!DOCTYPE html>
<html lang="en" >

<head>
  <meta charset="UTF-8">
  

  
<style>
body {
  font-family: Arial, Helvetica, "Liberation Sans", FreeSans, sans-serif;
  background-color: #000;
  margin: 0;
  padding: 0;
  border-width: 0;
}
</style>

  
  
  
</head>

<body >

  
      <script  >
"use strict";

window.addEventListener("load", function () {
  let canv, ctx; // canvas and context
  //            let maxx, maxy;   // canvas dimensions
  let tr;
  let maxx, maxy;
  let hue, gr, gr2;
  let ended;
  // for animation
  let messages;

  // shortcuts for Math.
  const mrandom = Math.random;
  const mfloor = Math.floor;
  const mround = Math.round;
  const mceil = Math.ceil;
  const mabs = Math.abs;
  const mmin = Math.min;
  const mmax = Math.max;

  const mPI = Math.PI;
  const mPIS2 = Math.PI / 2;
  const mPIS3 = Math.PI / 3;
  const m2PI = Math.PI * 2;
  const m2PIS3 = Math.PI * 2 / 3;
  const msin = Math.sin;
  const mcos = Math.cos;
  const matan2 = Math.atan2;

  const mhypot = Math.hypot;
  const msqrt = Math.sqrt;

  //------------------------------------------------------------------------

  function alea(mini, maxi) {
    // random number in given range

    if (typeof maxi == "undefined") return mini * mrandom(); // range 0..mini

    return mini + mrandom() * (maxi - mini); // range mini..maxi
  }
  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  function intAlea(mini, maxi) {
    // random integer in given range (mini..maxi - 1 or 0..mini - 1)
    //
    if (typeof maxi == "undefined") return mfloor(mini * mrandom()); // range 0..mini - 1
    return mini + mfloor(mrandom() * (maxi - mini)); // range mini .. maxi - 1
  }
  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  function lerp(p0, p1, alpha) {
    const uma = 1 - alpha;
    return {
      x: uma * p0.x + alpha * p1.x,
      y: uma * p0.y + alpha * p1.y };

  }
  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  function cutQ(quadra, alpha) {
    // cut quadratic
    /* quadra is a set of 3 points definining a quadratic Bézier curve
        this function returns a set of 2 quadratic curves (sets of 3 points) repsenting a division of the 2 initial curves,
        cut at an intermediate point defined by alpha (0..1)
        the two sub-arcs share a common point, and re-use copies of quadra
        caution : alpha is not a length ratio, it is only the value of the t variable of the parametric equation of the curve
        */
    const pa = lerp(quadra[0], quadra[1], alpha);
    const pb = lerp(quadra[1], quadra[2], alpha);
    const pc = lerp(pa, pb, alpha);
    return [
    [quadra[0], pa, pc],
    [pc, pb, quadra[2]]];

  } // cutQ
  //------------------------------------------------------------------------
  //------------------------------------------------------------------------

  function generatePoints(t) {
    return t.points.map(p => ({ x: p.x, y: p.y })); // shallow copy
  } // generate points

  //------------------------------------------------------------------------

  class SortedArray {
    /* creates a sorted array of any kind of things, by intersting them into an initially empty array
           the comparison function used for sorting is given to the constructor
           Things are inserted using .insert method
           sorted array is in the .tb property of the instance
        */
    /*
        the indexOf property lets you know if thing already existed according to fCompar, and at what index
        just use doInsert(no parameters) after indexOf to insert thing at found (not -1) index
        */

    /* CAUTION : if duplicates are allowed, indexOf is NOT garanteed to return the index of actual thing - only a thing
              which returns 0 when compared with given thing. Use regular Array.indexOf on instance.tb instead.
        */
    constructor(fCompar, keepDuplicates = false) {
      /* fCompar is the function which will be called to compare the things that will be inserted
          in  this.tb
              fCompar(a,b) must return  number < 0 if a must be placed before b
              == 0 if a and b are considered equal
              > 0 if a must be placed after b
          */
      this.tb = [];
      this.fCompar = fCompar;
      this.keepDuplicates = keepDuplicates;
    }

    indexOf(thing) {
      this.thing = thing;
      // search for place to insert thing, using comparison function this.fCompar
      // if found, returns the index of thing in this.tb, else returns -1
      // sets this.insertAt for future insertion

      let cmp;
      if (this.tb.length == 0) {
        this.insertAt = 0;
        return -1;
      }
      let a = 0,
      c = this.tb.length - 1;
      let b;

      do {
        b = Math.floor((a + c) / 2);
        cmp = this.fCompar(this.tb[b], thing);
        switch (true) {
          case cmp < 0: // thing after b
            if (b == c) {
              // beyond c
              this.insertAt = c + 1;
              return -1;
            }
            if (a == b) ++b; // not to stay on same interval, if its length is 1 or 2
            a = b; // after b : search (b, c) now
            break;
          case cmp == 0:
            this.insertAt = b;
            return b; // found !

          default:
            //thing before b
            if (b == a) {
              // before a
              this.insertAt = a;
              return -1;
            }
            c = b; // search (a, b) now
        }
      } while (true);
    } // indexOf

    doInsert() {
      // DO NOT CALL TWICE WITHOUT getting new (!= -1) indexOf
      this.tb.splice(this.insertAt, 0, this.thing);
    }

    insert(thing) {
      /* inserts thing */
      if (this.indexOf(thing) != -1 && !this.keepDuplicates) return; // already exists and refused
      this.tb.splice(this.insertAt, 0, thing);
    }}
  // class SortedArray
  //------------------------------------------------------------------------

  class Edge {
    constructor(p0, p1) {
      if (p0.kp <= p1.kp) {
        this.p0 = p0;
        this.p1 = p1;
      } else {
        this.p0 = p1;
        this.p1 = p0;
      }
      this.tris = []; // to record up to 2 triangles attached to this edge
    }

    attachTriangle(tri) {
      // includes a triangle in an edge's tris array
      // AND includes itself in this triangle's edges array
      // AND more

      if (!this.p0.tris.includes(tri)) this.p0.tris.push(tri);
      if (!this.p1.tris.includes(tri)) this.p1.tris.push(tri);

      if (!this.p0.edges.includes(this)) this.p0.edges.push(this);
      if (!this.p1.edges.includes(this)) this.p1.edges.push(this);

      if (tri.a == this.p0) {
        if (tri.b == this.p1) {
          this.tris[0] = tri;
          tri.edges[0] = this;
        } else {
          this.tris[1] = tri;
          tri.edges[2] = this;
        }
        return;
      }
      if (tri.b == this.p0) {
        if (tri.c == this.p1) {
          this.tris[0] = tri;
          tri.edges[1] = this;
        } else {
          this.tris[1] = tri;
          tri.edges[0] = this;
        }
        return;
      }
      if (tri.c == this.p0) {
        if (tri.a == this.p1) {
          this.tris[0] = tri;
          tri.edges[2] = this;
        } else {
          this.tris[1] = tri;
          tri.edges[1] = this;
        }
      }
    }}
  // class Edge
  //------------------------------------------------------------------------

  class Triangle {
    constructor(a, b, c) {
      this.a = a;
      this.b = b;
      this.c = c;
      this.edges = [];
      this.vertices = [this.a, this.b, this.c];

      const m11 = 2 * (b.x - a.x);
      const m21 = 2 * (c.x - a.x);
      const m12 = 2 * (b.y - a.y);
      const m22 = 2 * (c.y - a.y);
      const c1 = b.x * b.x - a.x * a.x + b.y * b.y - a.y * a.y;
      const c2 = c.x * c.x - a.x * a.x + c.y * c.y - a.y * a.y;
      const det = m11 * m22 - m21 * m12;
      this.xc = (c1 * m22 - c2 * m12) / det;
      this.yc = (m11 * c2 - m21 * c1) / det;
      this.r = Math.hypot(this.xc - this.a.x, this.yc - this.a.y);
    } // constructor

    inCircumCircle(p) {
      return Math.hypot(p.x - this.xc, p.y - this.yc) < this.r;
    }
    hasEdge(p1, p2) {
      // (was written before the above "Edge" class)
      return (
        (p1 == this.a || p1 == this.b || p1 == this.c) && (
        p2 == this.a || p2 == this.b || p2 == this.c));

    }

    //------------------------------------------------------------------------
    inTriangle(p) {
      let va = { x: this.a.x - p.x, y: this.a.y - p.y };
      let vb = { x: this.b.x - p.x, y: this.b.y - p.y };
      let vc = { x: this.c.x - p.x, y: this.c.y - p.y };
      if (va.x * vb.y - va.y * vb.x < 0) return false;
      if (vb.x * vc.y - vb.y * vc.x < 0) return false;
      if (vc.x * va.y - vc.y * va.x < 0) return false;
      return true;
    } // inTriangle
  } // Triangle

  //------------------------------------------------------------------------
  function createPath(tr) {
    // begin with triangle close to the center - arbitrary choice
    const picked = [
    tr.triangulation.find((tri) =>
    tri.inTriangle({ x: maxx / 2, y: maxy / 2 }))];


    tr.tr0 = picked[0]; // root of path
    tr.tr0.krunin = 0; // simulates index 0 for entering link (which does not actually exist on 1st triangle
    let tri, l1, l2;

    while (picked.length) {
      tri = picked[intAlea(picked.length)];

      pwet: while (true) {
        let ntri = tri;

        tri.picked = true;
        let possibles = [];
        tri.edges.forEach(edge => {
          let otherTri = edge.tris[0] === tri ? edge.tris[1] : edge.tris[0];
          if (!otherTri) return; // no other triangle here
          if (
          otherTri.vertices.find(
          vert => !vert.tris.find(ttri => ttri.picked)))


          possibles.push(otherTri);
        });
        while (possibles.length) {
          tri = possibles.splice(intAlea(possibles.length), 1)[0];
          tri.from = ntri;
          tri.krunin = ntri.krunin + 2;
          if (!ntri.to) ntri.to = [];
          ntri.to.push(tri);
          picked.push(tri);
          // record which sides is connected to which triangle
          // on tri side
          let kedge = ntri.edges.findIndex(edge => tri.edges.includes(edge));

          ntri.links = ntri.links || [];
          ntri.links.push(l1 = { kedge, tri: ntri, krun: ntri.krunin + 1 });
          kedge = tri.edges.findIndex(edge => ntri.edges.includes(edge));

          tri.links = tri.links || [];
          tri.links.push(l2 = { kedge, tri, krun: tri.krunin });
          l1.reciprocal = l2;
          l2.reciprocal = l1;
          if (ntri == tr.tr0) break; // because we want tr0 to be at the end of a branch
          continue pwet;
        } // while possibles.length
        picked.splice(picked.indexOf(ntri), 1); // no more possibilities on this triangles, throw it
        break;
      } // while true;
    } // while (picked.length)
  } // createPath

  //------------------------------------------------------------------------
  function smoothPath(tr) {
    /* Does not smooth anything. Only calculates gravity centers and intermediate points for future "smoothing"
     */
    tr.triangulation.forEach(tri => tri.picked = false);

    (function smoothSubPath(tri) {
      let ntri, pinterm, nlink;

      if (tri.picked) return; // already processed
      tri.picked = true; // will be processed now

      if (!tri.g) {
        // only for 1st point in fact
        tri.g = {
          x: tri.vertices.reduce((sum, v) => sum + v.x, 0) / 3,
          y: tri.vertices.reduce((sum, v) => sum + v.y, 0) / 3 };

      }
      if (tri.links.length == 2) tri.curved = true;
      tri.links.forEach(link => {
        if (link.pinterm) return; // this link already studied coming from other side

        ntri = link.reciprocal.tri;
        if (!ntri.g) {
          ntri.g = {
            x: ntri.vertices.reduce((sum, v) => sum + v.x, 0) / 3,
            y: ntri.vertices.reduce((sum, v) => sum + v.y, 0) / 3 };

        }

        pinterm = lerp(tri.g, ntri.g, 0.5);
        link.pinterm = pinterm;
        link.reciprocal.pinterm = pinterm;
        smoothSubPath(ntri);
      });
    })(
    // smoothSubPath
    tr.tr0);

  } // smoothPath

  //------------------------------------------------------------------------
  function createInnerPerimeter(tr) {
    /* "walks" around the path, starting from tr.tr0.g and turning cw like the external perimeter
        describes this "inner perimeter" as a list of links, the first being travelled from edge to center, the second from center to edge
        remark : two consecutive links may be the same (U-turn at the end of branches)
        */

    let nlink, tri;
    let link = tr.tr0.links[0]; // we know there is only one for this first
    const innerp = [];

    do {
      innerp.push(link);
      tri = link.tri; // current triangle

      // find next link , beginning by the 1st clockwise
      nlink = tri.links.find(l => l.kedge == (link.kedge + 1) % 3);
      if (!nlink)
      nlink = tri.links.find(l => l.kedge == (link.kedge + 2) % 3);
      if (!nlink) nlink = link; // (U turn)
      innerp.push(nlink);

      link = nlink.reciprocal;
    } while (link !== tr.tr0.links[0]);

    return innerp;
  } // createInnerPerimeter

  //------------------------------------------------------------------------
  function createPerimeter(tr) {
    // after createPath, create a closed path with the edges of triangles surrounding the path

    const perim = [];
    let tri = tr.tr0;
    // find starting point, vertex of tr.tr0 which does not belong to tr.tr0.to
    let kp = tri.vertices.findIndex(p => !tri.to[0].vertices.includes(p));
    let p0 = tri.vertices[kp];
    perim.push(p0);
    p0.ktriafter = p0.ktribefore = p0.tris.indexOf(tri);
    p0.kper = 0;
    let p = p0;
    while (true) {
      // search for the point which comes after p = tri.vertices[kp]

      if ((!p.edges[0].tris[0] || !p.edges[0].tris[1]) && p.tris[0].picked) {
        kp = 1;
        tri = p.tris[0];
        p.ktriafter = 0;
      } else {
        tri = p.tris.find(
        (tri, k) =>
        tri.picked && (
        !p.tris[(k + p.tris.length - 1) % p.tris.length] ||
        !p.tris[(k + p.tris.length - 1) % p.tris.length].picked));

        p.ktriafter = p.tris.indexOf(tri);
      }

      kp = (tri.vertices.indexOf(p) + 1) % 3;
      p = tri.vertices[kp];
      p.ktribefore = p.tris.indexOf(tri);

      if (p == p0) break;
      p.kper = perim.length; // index of point in perimeter
      perim.push(p);
    } // while (true)

    let prevp = perim[perim.length - 1];
    perim.forEach((p, k) => {
      let n = p.ktribefore - p.ktriafter;
      if (n < 0) n += p.tris.length;
      p.nbTrisPer = n + 1; // number of triangles involved in perimeter
      let np = lerp(prevp, p, 0.5);
      prevp.pafter = np;
      p.pbefore = np;
      prevp = p;
    });
    return perim;
  } // createPerimeter
  //------------------------------------------------------------------------
  function matchPerimeters(inner, outer) {
    /* will attach every point in outer the list of correponding links from inner
         (this list may be empty in the case of a U-turn of the perimeter)
        */
    let anim = [],
    anim1,
    arcs;
    /* simulate we have just processed the last link of inner perimeter */
    let kinner = 1;
    outer.forEach(pouter => {
      pouter.inner = [];
      if (pouter.nbTrisPer == 1) {
        // special case for U-turns
        let tri = pouter.tris[pouter.ktribefore];
        anim.push([
        [tri.g, tri.g, tri.g],
        [pouter.pbefore, pouter, pouter.pafter],
        tri.krunin]);

      } else {
        /* cut the outer perimeter bezier quadratic into as many parts as required to match every inner perimeter corresponding part
         */
        let nbParts = (pouter.nbTrisPer - 1) * 2;
        let initArc = [pouter.pbefore, pouter, pouter.pafter];
        for (let k = 0; k < nbParts; ++k) {
          if (k == nbParts - 1) {
            // no need to cut for the last piece !
            anim1 = [initArc];
          } else {
            arcs = cutQ(initArc, 1 / (nbParts - k));
            anim1 = [arcs[0]];
            initArc = arcs[1];
          }
          /* done with the outer part. inner, now */
          let link = inner[kinner++];
          if (kinner >= inner.length) kinner = 0; // only useful for last point
          if (link.tri.links.length != 2) {
            // inner is a straight line segment
            let pts = [link.tri.g, link.pinterm];
            if (k & 1) pts.reverse(); // for link entering the triangle (tested by parity of k...)
            anim1.unshift([pts[0], lerp(pts[0], pts[1], 0.5), pts[1]]);
          } else {
            // inner is a quadratic
            let outlink =
            link.tri.links[0] == link ? link.tri.links[1] : link.tri.links[0];
            arcs = cutQ([link.pinterm, link.tri.g, outlink.pinterm], 0.5); // will be done twice, sorry
            if (!(k & 1)) arcs[0].reverse();
            anim1.unshift(arcs[0]);
          } // inner is a quadratic
          anim1.push(link.krun);
          anim.push(anim1);
        } // for k
      } // not U_turn
.........完整代码请登录后点击上方下载按钮下载查看

网友评论0