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;
        }
        
        #explain {
          position: absolute;
          left: 50%;
          transform: translateX(-50%);
          bottom: 10px;
          background-color: rgba(255, 255, 255, 0.9);
          border: 1px solid black;
          border-radius: 10px;
          padding: 10px;
          color: black;
          z-index: 1;
        }
        
        .hidden {
          display: none;
        }
        
        #explain p.buttline {
          text-align: center;
        }
    </style>

 

</head>

<body >
    <div id=explain>
        <div id="txtexplain"></div>
        <p class=buttline><button type="button" id="butt2">next &gt;&gt;</button> <button type="button" id="butt3">skip it
      all</button></p>
    </div>
 


    <script>
        "use strict";
        
        window.addEventListener("load", function () {
          let triWidth, triHeight; // length of triangle side and altitude
          let firstRun = !location.pathname.includes("/fullcpgrid/");
          let canv, ctx; // canvas and context
        
          let maxx, maxy, lRef; // canvas dimensions (lRef is average)
        
          let grid;
          let nbx, nby;
          let hnbx, hnby; // number of triangles in the half of the width, height of the canvas
          let blocks, nbLines;
          let events;
        
          // 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 m2PI = Math.PI * 2;
          const msin = Math.sin;
          const mcos = Math.cos;
          const matan2 = Math.atan2;
        
          const mhypot = Math.hypot;
          const msqrt = Math.sqrt;
        
          const rac3 = msqrt(3);
          const rac3s2 = rac3 / 2;
          const mPIS3 = Math.PI / 3;
        
          //------------------------------------------------------------------------
        
          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 arrayShuffle(array) {
            /* randomly changes the order of items in an array
                   only the order is modified, not the elements
                */
            let k1, temp;
            for (let k = array.length - 1; k >= 1; --k) {
              k1 = intAlea(0, k + 1);
              temp = array[k];
              array[k] = array[k1];
              array[k1] = temp;
            } // for k
            return array;
          } // arrayShuffle
        
          // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        
          /* returns lerp point between p0 and p1,
                alpha = 0 will return p0, alpha = 1 will return p1
                values of alpha outside [0,1] may be used to compute points outside the p0-p1 segment
              */
          function lerp(p0, p1, alpha) {
            return {
              x: (1 - alpha) * p0.x + alpha * p1.x,
              y: (1 - alpha) * p0.y + alpha * p1.y };
        
          } // function lerp
        
          //------------------------------------------------------------------------
        
          function addP(text) {
            let p = document.createElement("p");
            p.append(text);
            txtexplain.append(p);
          }
        
          //------------------------------------------------------------------------
          class Triangle {
            /* numbering of vertices / edges
                       0                        2---1---1
                     / \                        \     /
                    2   0                        2   0
                   /     \                        \ /
                  2---1---1                        0
            */
        
        
            constructor(kx, ky) {
              this.kx = kx;
              this.ky = ky;
              this.kxc = kx - hnbx;
              this.kyc = ky - hnby;
              this.upsideDown = this.kxc + this.kyc & 1; // 0 or 1
              this.setXY();
            }
        
            // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        
            setXY() {
              let xa, ya, vertices, deltay, upsideDown;
        
              // centre of this triangle (middle of height, not gravity centre)
              this.ya = ya = maxy / 2 + this.kyc * triHeight;
              this.xa = xa = maxx / 2 + this.kxc * triWidth / 2;
        
              this.vertices = vertices = [];
              deltay = triHeight / 2;
              if (this.upsideDown) deltay = -deltay;
        
              vertices[0] = { x: xa, y: ya - deltay };
              vertices[1] = { x: xa + triWidth / 2, y: ya + deltay };
              vertices[2] = { x: xa - triWidth / 2, y: ya + deltay };
            }
            // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
            setBase(side) {
              // where side is 0,1,2
              this.base = side; // which side is the base ?
              this.dirBase = [
              [0, 1, 2],
              [2, 1, 0]][
              this.upsideDown][this.base]; // orientation of base (== base side number for normally oriented triangle)
            }
        
            // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        
            setNeighbors() {
              /* to be called only after the whole grid has been created */
              let kx, ky;
              this.neighbors = [];
              for (let k = 0; k < 3; ++k) {
                kx = this.kx + [1, 0, -1][k];
                ky =
                this.ky +
                [
                [0, 1, 0],
                [0, -1, 0]][
                this.upsideDown][k];
                this.neighbors[k] =
                kx < 0 || kx >= nbx || ky < 0 || ky >= nby ? false : grid[ky][kx];
              } // for k
            } // Triangle.setNeighbor
        
            // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
            setOneaab(side, value) {
              // sets value of this.aab[side] and the matching neighbor (if any) too
              // sets the "base" property to this side if value is 1
              this.aab[side] = value;
              if (this.neighbors[side]) {
                this.neighbors[side].aab[2 - side] = value;
              }
              if (value == 1) {
                this.setBase(side);
                if (this.neighbors[side]) {
                  this.neighbors[side].setBase(2 - side);
                }
              }
            }
        
            setaab() {
              /* sets 3 values of this.aab, taking into account the already filled values and the constraints relative to the neighborhood
               */
              let neigh, possible, choice;
              const zeroes = [];
              const ones = [];
              const empty = [];
        
              // count 1s and 0s already present
              for (let k = 0; k < 3; ++k) {
                if (this.aab[k] === 0) zeroes.push(k);else
                if (this.aab[k] === 1) ones.push(k);else
                empty.push(k);
              }
        
              if (empty.length == 0) {
                // already completed
                if (ones.length != 1) throw "wtf ???";
                return; // already completed, ok
              }
              if (ones.length > 1) throw "wtf ???";
        
              if (ones.length == 1) {
                // already a 1, no choice
                empty.forEach(s => this.setOneaab(s, 0));
                return;
              }
              // no "1" already present. Check empty edges to check if 1 are possible
              possible = [];
              empty.forEach(s => {
                neigh = this.neighbors[s];
                if (!neigh || !neigh.aab.includes(1)) possible.push(s);
              });
              if (possible.length == 0) throw "impossible to add a 1";
              choice = possible[intAlea(possible.length)];
              this.setOneaab(choice, 1);
              empty.splice(empty.indexOf(choice), 1);
              // fill the rest with 0
              while (empty.length > 0) {
                choice = empty.pop();
                this.setOneaab(choice, 0);
              }
            } // setaab
            // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
            drawaab(lineWidth, opacity) {
              ctx.beginPath();
              this.aab.forEach((v, k) => {
                ctx.beginPath();
                ctx.moveTo(this.vertices[k].x, this.vertices[k].y);
                ctx.lineTo(this.vertices[(k + 1) % 3].x, this.vertices[(k + 1) % 3].y);
                ctx.lineWidth = lineWidth;
                ctx.strokeStyle = `hsla(${[120, 0][v]},100%,50%,${opacity})`;
                ctx.stroke();
              });
            } // draw aab
        
            // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
            hatchaab(alpha, hue, lineWidth, lum = 50) {
              // for alpha = 0..1
              const pa = lerp(this.vertices[this.a], this.vertices[this.b], alpha);
              const pb = lerp(this.vertices[this.c], this.vertices[this.b], alpha);
              ctx.beginPath();
              ctx.moveTo(pa.x, pa.y);
              ctx.lineTo(pb.x, pb.y);
              ctx.lineWidth = lineWidth;
              ctx.strokeStyle = `hsl(${hue},100%,${lum}%)`;
              ctx.stroke();
            }}
          // class Triangle
        
          //------------------------------------------------------------------------
        
          function createGrid() {
            let kx1, ky1, cell;
            let stackDist = [];
            grid = [];
        
            for (let ky = 0; ky < nby; ++ky) {
              grid[ky] = [];
              for (let kx = 0; kx < nbx; ++kx) {
                grid[ky][kx] = new Triangle(kx, ky);
              } // for kx
            } // for ky
        
            grid.forEach(line => line.forEach(tri => tri.setNeighbors()));
            grid.forEach(line => line.forEach(tri => tri.aab = []));
            grid.forEach(line => line.forEach(tri => tri.setaab()));
          } // createGrid
        
          //------------------------------------------------------------------------
          function createBlock(tri, side) {
            if (tri.block) return false; // already belongs to a block : forget
            const initTri = tri;
            let nextTri;
            const block = [];
            while (true) {
              block.push(tri);
              tri.entry = side;
              tri.exit = tri.aab.findIndex((v, k) => k != tri.entry && v == 0);
              tri.block = block;
              /* fetch next triangle - if any */
              nextTri = tri.neighbors[tri.exit];
              if (nextTri === false) {
                block.closed = false;
                return block; // finished open path
              }
              if (nextTri === initTri) {
                block.closed = true;
                return block; // finished closed
              }
              side = 2 - tri.exit;
              tri = nextTri;
            } // while
          } // createBlock
        
          //------------------------------------------------------------------------
        
          function createBlocks() {
            blocks = [];
            let block;
            // create all opened paths (begining on edge)
            grid.forEach((line) =>
            line.forEach(tri => {
              if (tri.block) return; // already belongs to a block
              let ext = [];
              tri.neighbors.forEach((v, k) => {
                if (v === false && tri.aab[k] == 0) ext.push(k);
              });
              if (ext.length == 0) return; // not on external side, ignore
              block = createBlock(tri, ext[intAlea(ext.length)]);
              if (block) blocks.push(block);
            }));
        
            // create all closed paths
            grid.forEach((line) =>
            line.forEach(tri => {
              if (tri.block) return; // already belongs to a block
              block = createBlock(
              tri,
              tri.aab.findIndex(v => v === 0));
        
              if (block) blocks.push(block);
            }));
        
            blocks.forEach(orientBlock);
            blocks.forEach(block => block.hue = intAlea(360));
          }
          //------------------------------------------------------------------------
          function orientBlock(block) {
            block.forEach((tri, k) => {
              if ((tri.entry + 1) % 3 == tri.base) {
                tri.a = (tri.entry + 1) % 3; // base point of entry
                tri.b = tri.entry; // summit
                tri.c = (tri.entry + 2) % 3; // base point of exit
              } else {
                tri.a = tri.entry; // base point of entry
                tri.b = (tri.entry + 1) % 3; // summit
                tri.c = (tri.entry + 2) % 3; // base point of exit
              }
              let ntri = block[(k + 1) % block.length];
              tri.invertAlpha = ntri.dirBase == tri.dirBase;
            }); // block
          } // orientBlock
          //------------------------------------------------------------------------
          function drawBlock(block, alpha, hue, light, widthCoeff = 1) {
            const tri = block[0];
            let pint0;
            let pa = lerp(tri.vertices[tri.a], tri.vertices[tri.b], alpha);
            let pts = [pa];
            let pint;
            block.forEach(tri => {
              pts.push(lerp(tri.vertices[tri.c], tri.vertices[tri.b], alpha));
              if (tri.invertAlpha) alpha = 1 - alpha;
            });
        
            ctx.beginPath();
            pts.forEach((p, k) => {
              if (k == 0) {
                pint0 = pint = lerp(p, pts[1], 0.5);
                if (block.closed) {
                  ctx.moveTo(pint.x, pint.y);
                } else {
                  ctx.moveTo(pa.x, pa.y);
                  ctx.lineTo(pint.x, pint.y);
                }
              } else if (k == pts.length - 1) {
                if (block.closed) {
                  ctx.quadraticCurveTo(pa.x, pa.y, pint0.x, pint0.y);
                  ctx.closePath();
                } else ctx.lineTo(p.x, p.y);
              } else {
                pint = lerp(p, pts[k + 1], 0.5);
                ctx.quadraticCurveTo(p.x, p.y, pint.x, pint.y);
              }
            });
            const lw = 3 + (triHeight / nbLines - 3) * widthCoeff; // (width 3 for coeff 0)
            for (let l = lw; l > 0; --l) {
              ctx.lineWidth = l;
              ctx.strokeStyle = `hsl(${hue},100%,${20 + (light - 20) * (1 - l / lw)}%)`;
              ctx.stroke();
            }
          } // drawBlock
          //------------------------------------------------------------------------
          //------------------------------------------------------------------------
          function drawBlockRounded(block, alpha, hue, light, round) {
            /* similiar to drawBlock, the round parameter determines how corners are rounded, from 0(angular) to 1 (totally rounded)
                and no glossy effect is applied
                */
            const tri = block[0];
            let pint0;
            let pa = lerp(tri.vertices[tri.a], tri.vertices[tri.b], alpha);
            let pts = [pa];
            let pinta, pintb;
            block.forEach(tri => {
              pts.push(lerp(tri.vertices[tri.c], tri.vertices[tri.b], alpha));
              if (tri.invertAlpha) alpha = 1 - alpha;
            });
        
            ctx.beginPath();
            pts.forEach((p, k) => {
              if (k == 0) {
                pint0 = p.........完整代码请登录后点击上方下载按钮下载查看

网友评论0