Template:Duesseldorf/prototype2.js

     // math functions
     // pythagoras
     function calculateDistance(x1, y1, x2, y2) {
       return Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
     }
     // random number function
     function randomNumber(start, end) {
       // both including;
       return Math.floor(Math.random() * end) + start;
     }
           // parameters
           var _scale = 1; // canvas scale, useful for zoom
     var _speed = 50; // tick interval speed
     // panning vars
     var netPanningX = 0;
     var netPanningY = 0;
     // plant modelling
     var PlantsArray = [];
     var id = 0;
     function Plant(x, y, radius, wi = false, vi = false, li = false) {
       this.id = id++;
       this.XPos = x;
       this.YPos = y;
       this.radius = radius; // size of plant
       // our binary data
       this.waterstress = wi;
       this.virusinfection = vi;
       this.lightstress = li;
       // waterlevel
       this.waterLevel = 50;
       this.waterTolerance = 200; // +- this value is fine for the plants waterlevel
       this.bestWaterLevel = 75; // perfect waterLevelValue for this plant
       this.waterStressAmount = 0;
       this.waterStressMax = 100000; // lethal stress
       this.alive = true;
       this.virus = {};
       PlantsArray.push(this);
       this.initRandomStress = () => {
         this.waterstress = Math.random() > 0.5 ? false : true;
         this.virusinfection = Math.random() > 0.5 ? false : true;
         this.lightstress = Math.random() > 0.5 ? false : true;
       };
       // neighbors
       this.neighbors = { n: null, e: null, s: null, w: null }; // north, east, south, west
       this.getNearestNeighbors = () => {};
       this.render = () => {
         drawCircle(
           this.XPos + netPanningX,
           this.YPos + netPanningY,
           this.radius,
           this.alive ? "green" : "black",
           this.virusinfection ? "red" : "green"
         );
         drawRect(
           this.XPos + this.radius / 2 + netPanningX,
           this.YPos + netPanningY,
           this.radius - 10,
           this.radius + 10,
           "white",
           "blue"
         );
         // waterlevel
         let wl = this.waterLevel / 100;
         drawRect(
           this.XPos + this.radius / 2 + netPanningX,
           this.YPos + netPanningY,
           this.radius - 10,
           this.radius + 10,
           "blue",
           "blue"
         );
         writeMessage(
           this.id,
           this.XPos + netPanningX,
           this.YPos + netPanningY
         );
         writeMessage(
           this.waterLevel,
           this.XPos + this.radius / 2 + netPanningX,
           this.YPos + 25 + netPanningY
         );
       };
     }
     // draw all plants
     function render(mode = "standard") {
       context.clearRect(0, 0, canvas.width, canvas.height);
       context.save();
       context.scale(_scale, _scale);
       if (mode === "standard") {
         for (let i = 0; i < PlantsArray.length; i++) {
           PlantsArray[i].render();
         }
       }
       context.restore();
     }
     let time = 1;
     function nextTick() {
       // random rain
       let rain = 0;
       if (randomNumber(1, 50) === 7) {
         rain = randomNumber(20, 80); // rain amount
       }
       // events
       for (let i = 0; i < PlantsArray.length; i++) {
         if (randomNumber(1, 20000) === 7) {
           if (PlantsArray[i].virusinfection) {
             PlantsArray[i].alive = false;
           } else {
             PlantsArray[i].virusinfection = true;
           }
         }
         if (PlantsArray[i].virusinfection && PlantsArray[i].alive) {
           if (randomNumber(1, 100) === 7) {
             try {
               PlantsArray[i].neighbors.s.virusinfection = true;
             } catch (e) {}
           }
         }
         // waterstress
         if (PlantsArray[i].alive) {
           PlantsArray[i].waterLevel -= 1;
         }
         // random rain
         if (rain > 0) {
           PlantsArray[i].waterLevel += rain;
         }
         if (
           PlantsArray[i].waterLevel >
             PlantsArray[i].bestWaterLevel + PlantsArray[i].waterTolerance ||
           PlantsArray[i].waterLevel <
             PlantsArray[i].bestWaterLevel + PlantsArray[i].waterTolerance
         ) {
           PlantsArray[i].waterstress = true;
         } else {
           PlantsArray[i].waterstress = false;
         }
         if (PlantsArray[i].waterstress) {
           PlantsArray[i].waterStressAmount++;
         }
         if (
           PlantsArray[i].waterStressAmount > PlantsArray[i].waterStressMax
         ) {
           PlantsArray[i].alive = false;
         }
       }
       time++;
     }
     var dayCounter = 0;
     function renderNextTick() {
       nextTick();
       render();
       dayCounter++;
     }
     // throttle function to prevent functions spamming too much and killing performance
     // (function, ms, null)
     function throttle(t, u, a) {
       var e,
         i,
         r,
         o = null,
         c = 0;
       a = a || {};
       function p() {
         (c = !1 === a.leading ? 0 : Date.now()),
           (o = null),
           (r = t.apply(e, i)),
           o || (e = i = null);
       }
       return function () {
         var n = Date.now();
         c || !1 !== a.leading || (c = n);
         var l = u - (n - c);
         return (
           (e = this),
           (i = arguments),
           l <= 0 || u < l
             ? (o && (clearTimeout(o), (o = null)),
               (c = n),
               (r = t.apply(e, i)),
               o || (e = i = null))
             : o || !1 === a.trailing || (o = setTimeout(p, l)),
           r
         );
       };
     }
     // scroll functions
     let Body = document.body;
     let lastScrollTop = 0;
     function scrollFunc() {
       var e = window.pageYOffset || document.documentElement.scrollTop,
         t = Body.offsetHeight;
       Body.classList.remove("scrolling-none"),
         0 === e
           ? (Body.classList.remove("scroll-foot"),
             Body.classList.add("scroll-head"))
           : window.innerHeight + window.scrollY >= t &&
             (Body.classList.remove("scroll-head"),
             Body.classList.add("scroll-foot")),
         0 < e &&
           e <= t &&
           (Body.classList.remove("scroll-head"),
           Body.classList.remove("scroll-foot")),
         300 < e
           ? (Body.classList.remove("scroll-lt-300"),
             Body.classList.add("scroll-gt-300"))
           : (Body.classList.remove("scroll-gt-300"),
             Body.classList.add("scroll-lt-300")),
         1200 < e
           ? (Body.classList.remove("scroll-lt-1200"),
             Body.classList.add("scroll-gt-1200"))
           : (Body.classList.remove("scroll-gt-1200"),
             Body.classList.add("scroll-lt-1200")),
         1500 < e
           ? (Body.classList.remove("scroll-lt-1500"),
             Body.classList.add("scroll-gt-1500"))
           : (Body.classList.remove("scroll-gt-1500"),
             Body.classList.add("scroll-lt-1500")),
         e > lastScrollTop
           ? (Body.classList.remove("scrolling-up"),
             Body.classList.add("scrolling-down"),
             50 < e && Body.classList.remove("menu-open"))
           : e < lastScrollTop
           ? (Body.classList.remove("scrolling-down"),
             Body.classList.add("scrolling-up"),
             Body.classList.remove("menu-open"))
           : e === lastScrollTop && Body.classList.add("scrolling-none"),
         (lastScrollTop = e <= 0 ? 0 : e),
         e > screen.height
           ? (Body.classList.remove("scroll-lt-screenheight"),
             Body.classList.add("scroll-gt-screenheight"))
           : (Body.classList.remove("scroll-gt-screenheight"),
             Body.classList.add("scroll-lt-screenheight"));
     }
     window.addEventListener("scroll", throttle(scrollFunc, 150, null), {
       passive: !0,
     }),
       window.addEventListener("resize", throttle(scrollFunc, 80, null));
     document.addEventListener("DOMContentLoaded", function () {
       scrollFunc();
     });
     // draw functions
     const canvas = document.getElementById("canvas");
     const context = canvas.getContext("2d");
     function writeMessage(message, x, y) {
       context.font = "15pt Verdana";
       context.fillStyle = "black";
       context.fillText(message, x, y);
     }
     function drawCircle(
       centerX,
       centerY,
       radius,
       fillC = "white",
       strokeC = "black"
     ) {
       context.beginPath();
       context.arc(centerX, centerY, radius, 0, 2 * Math.PI, false);
       context.fillStyle = fillC;
       context.fill();
       context.lineWidth = 5;
       context.strokeStyle = strokeC || "#003300";
       context.stroke();
     }
     function drawRect(
       centerX,
       centerY,
       width,
       height,
       fillC = "white",
       strokeC = "black"
     ) {
       context.beginPath();
       context.lineWidth = 2;
       context.fillStyle = fillC || "white";
       context.strokeStyle = strokeC;
       context.rect(centerX, centerY, width, height);
       context.stroke();
       context.fill();
     }
     // html listener and functions
     function getMousePos(canvas, evt) {
       var rect = canvas.getBoundingClientRect();
       return {
         x: evt.clientX - rect.left,
         y: evt.clientY - rect.top,
       };
     }
     // hover overlay
     let info = document.getElementById("INFO");
     canvas.addEventListener(
       "mousemove",
       function (evt) {
         var mousePos = getMousePos(canvas, evt);
         let clientWidth = document.body.clientWidth;
         let canvasWidth = canvas.width;
         let factor = (clientWidth / canvasWidth) * _scale;
         //console.log(message);
         for (let i = 0; i < PlantsArray.length; i++) {
           if (
             mousePos.x <=
             (PlantsArray[i].XPos + PlantsArray[i].radius + netPanningX) *
               factor
           ) {
             if (
               mousePos.y <=
               (PlantsArray[i].YPos + PlantsArray[i].radius + netPanningY) *
                 factor
             ) {
               // console.log(PlantsArray[i]);
               var message =
                 PlantsArray[i].id +
                 " is alive: " +
                 PlantsArray[i].alive +
                 "
" + " waterlevel: " + PlantsArray[i].waterLevel + "
" + " is infected?: "; // message += PlantsArray[i].waterStresswaterStressAmount > 900 ? "yes" : "no"; message += PlantsArray[i].virusinfection ? "yes" : "no"; message += "
days since simulation started: " + dayCounter; info.style.transform = "translate(" + evt.clientX + "px," + evt.clientY + "px)"; info.innerHTML = message; // PlantsArray[i].virusinfection = true; break; } } } }, false );
     // start and stop functions
     let x;
     let running = false;
     let startstopbtn = document.getElementById("startstopbtn");
     function startInterval() {
       if (running) return;
       x = setInterval(() => {
         renderNextTick();
       }, _speed);
       running = true;
     }
     function stopInterval() {
       clearInterval(x);
       running = false;
     }
     function startStop() {
       if (running) {
         startstopbtn.innerHTML = "Start";
         stopInterval();
       } else {
         startstopbtn.innerHTML = "Stop";
         startInterval();
       }
     }
     // parameter handling
     // set scale
     let zoominput = document.getElementById("zoom");
     let zoomvaluedis = document.getElementById("zoomvalue");
     function setScale(e = 1) {
       if (e != 1 && e.value) {
         _scale = e.value;
       } else {
         _scale = e;
       }
       zoominput.value = _scale;
       zoomvaluedis.innerHTML = _scale;
       render();
     }
     // set speed
     let speedinput = document.getElementById("speed");
     let speedvaluedis = document.getElementById("speedvalue");
     function setSpeed(e = 1) {
       if (e != 1 && e.value) {
         _speed = e.value;
       } else {
         _scale = e;
       }
       speedinput.value = _speed;
       speedvaluedis.innerHTML = _speed;
       if (running) {
         stopInterval();
         startInterval();
       }
     }
     // panning
     let isDragging = false;
     let startX = 0;
     let startY = 0;
     // account for scrolling
     function reOffset() {
       var BB = canvas.getBoundingClientRect();
       offsetX = BB.left;
       offsetY = BB.top;
     }
     var offsetX, offsetY;
     reOffset();
     window.onscroll = function (e) {
       reOffset();
     };
     window.onresize = function (e) {
       reOffset();
     };
     canvas.addEventListener("mousedown", (e) => handleMouseDown(e));
     canvas.addEventListener("mouseup", (e) => handleMouseUp(e));
     canvas.addEventListener("mousemove", (e) => {
       handleMouseMove(e);
     });
     canvas.addEventListener("mouseout", (e) => handleMouseOut(e));
     function handleMouseDown(e) {
       e.preventDefault();
       e.stopPropagation();
       // calc the starting mouse X,Y for the drag
       startX = parseInt(e.clientX - offsetX);
       startY = parseInt(e.clientY - offsetY);
       isDragging = true;
     }
     function handleMouseUp(e) {
       e.preventDefault();
       e.stopPropagation();
       isDragging = false;
     }
     function handleMouseOut(e) {
       e.preventDefault();
       e.stopPropagation();
       isDragging = false;
     }
     function handleMouseMove(e) {
       if (!isDragging) {
         return;
       }
       e.preventDefault();
       e.stopPropagation();
       // get the current mouse position
       mouseX = parseInt(e.clientX - offsetX);
       mouseY = parseInt(e.clientY - offsetY);
       // dx & dy are the distance the mouse has moved since
       // the last mousemove event
       let dx = mouseX - startX;
       let dy = mouseY - startY;
       // reset the vars for next mousemove
       startX = mouseX;
       startY = mouseY;
       // accumulate the net panning done
       netPanningX += dx;
       netPanningY += dy;
       render();
     }
     var ln = [];
     function initLines(mode = "rectangle") {
       if (mode === "rectangle") {
         for (let i = 30; i > 0; i--) {
           ln.push({
             width: 3800,
             marginToNextLine: 45 + randomNumber(-15, 15),
           });
         }
       }
     }
     function initField(lines, plantsize = 40) {
       let x = 90;
       let y = 90;
       let m = lines[0].marginToNextLine;
       let x1 = x + randomNumber(-m / 2, m * 1.5);
       let y1 = y + randomNumber(-m / 2, 0);
       let previousPlant = null;
       let previousLine = [];
       let thisLine = [];
       // for every line
       for (let i = 0; i < lines.length; i++) {
         let count = 0;
         while (x1 < lines[i].width) {
           let plant = new Plant(
             x1 + randomNumber(-5, 5),
             y1 + randomNumber(-5, 15),
             plantsize
           );
           thisLine.push(plant);
           plant.neighbors.w = previousPlant;
           if (previousPlant) {
             previousPlant.neighbors.e = plant;
           }
           if (previousLine.length > 0) {
             // find north neighbor
             let pd = 0;
             let d = 0;
             let shortest = 0;
             for (let i = 0; i < previousLine.length; i++) {
               //console.log(i);
               pd = calculateDistance(
                 plant.XPos,
                 plant.YPos,
                 previousLine[i].XPos,
                 previousLine[i].YPos
               );
               try {
                 d = calculateDistance(
                   plant.XPos,
                   plant.YPos,
                   previousLine[i + 1].XPos,
                   previousLine[i + 1].YPos
                 );
               } catch (e) {
                 //end of line
               }
               if (d < pd) {
                 shortest = d;
                 continue;
               }
               plant.neighbors.n = previousLine[i];
               PlantsArray[previousLine[i].id].neighbors.s = plant;
               break;
             }
           }
           count++;
           previousPlant = plant;
           x1 += x + lines[i].marginToNextLine + randomNumber(-m / 2, m * 1.5);
         }
         console.log(lines[i] + " finished with " + count + " plants");
         previousLine = thisLine;
         thisLine = [];
         x1 = x + randomNumber(-m / 2, m / 2);
         y1 += y + lines[i].marginToNextLine + randomNumber(-m / 2, 0);
       }
     }
     initLines();
     initField(ln);
     render();