{"id":3062,"date":"2021-10-21T13:41:26","date_gmt":"2021-10-21T13:41:26","guid":{"rendered":"https:\/\/rengga.dev\/blog\/?p=3062"},"modified":"2023-02-28T00:12:52","modified_gmt":"2023-02-28T00:12:52","slug":"js-tutorial-math-random-generative-art","status":"publish","type":"post","link":"https:\/\/rengga.dev\/blog\/js-tutorial-math-random-generative-art\/","title":{"rendered":"JS Tutorial &#8211; Math.random() Generative Art"},"content":{"rendered":"<p><span style=\"color: #ef3207;\"><a style=\"color: #ef3207;\" href=\"https:\/\/rengga.dev\/\" target=\"_blank\" rel=\"noopener\"><strong>Rengga Dev<\/strong><\/a> <\/span>&#8211; <code>Math.random()<\/code> is an API in JavaScript. It is a function that gives you a random number. The number returned will be between 0 (inclusive, as in, it\u2019s possible for an actual 0 to be returned) and 1 (exclusive, as in, it\u2019s not possible for an actual 1 to be returned).<\/p>\n<p><iframe style=\"width: 100%;\" title=\"Generative Art from Rectangleworld\" src=\"https:\/\/codepen.io\/renggagumilar\/embed\/WNzaXLq?default-tab=result&amp;theme-id=dark\" height=\"600\" frameborder=\"no\" scrolling=\"no\" allowfullscreen=\"allowfullscreen\"><br \/>\nSee the Pen <a href=\"https:\/\/codepen.io\/renggagumilar\/pen\/WNzaXLq\"><br \/>\nGenerative Art from Rectangleworld<\/a> by Rengga Gumilar (<a href=\"https:\/\/codepen.io\/renggagumilar\">@renggagumilar<\/a>)<br \/>\non <a href=\"https:\/\/codepen.io\">CodePen<\/a>.<br \/>\n<\/iframe><\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"js\">window.addEventListener(\"load\", windowLoadHandler, false);\r\n\r\n\/\/for debug messages while testing code\r\nvar Debugger = function() { };\r\nDebugger.log = function(message) {\r\n    try {\r\n        console.log(message);\r\n    }\r\n    catch (exception) {\r\n        return;\r\n    }\r\n}\r\n\r\nfunction windowLoadHandler() {\r\n    canvasApp();\r\n}\r\n\r\nfunction canvasSupport() {\r\n    return Modernizr.canvas;\r\n}\r\n\r\nfunction canvasApp() {\r\n    if (!canvasSupport()) {\r\n        return;\r\n    }\r\n    \r\n    var displayCanvas = document.getElementById(\"displayCanvas\");\r\n    var context = displayCanvas.getContext(\"2d\");\r\n    var displayWidth = displayCanvas.width;\r\n    var displayHeight = displayCanvas.height;\r\n    \r\n    \/\/off screen canvas used only when exporting image\r\n    var exportCanvas = document.createElement('canvas');\r\n    exportCanvas.width = displayWidth;\r\n    exportCanvas.height = displayHeight;\r\n    var exportCanvasContext = exportCanvas.getContext(\"2d\");\r\n    \r\n    \/\/var exportImage = document.createElement('img');\r\n    \r\n    \/\/buttons\r\n    var btnExport = document.getElementById(\"btnExport\");\r\n    btnExport.addEventListener(\"click\", exportPressed, false);\r\n    \r\n    var btnRegenerate = document.getElementById(\"btnRegenerate\");\r\n    btnRegenerate.addEventListener(\"click\", regeneratePressed, false);\r\n    \r\n    var numCircles;\r\n    var maxMaxRad;\r\n    var minMaxRad;\r\n    var minRadFactor;\r\n    var circles;\r\n    var iterations;\r\n    var numPoints;\r\n    var timer;\r\n    var drawsPerFrame;\r\n    var drawCount;\r\n    var bgColor,urlColor;\r\n    var lineWidth;\r\n    var colorParamArray;\r\n    var colorArray;\r\n    var dataLists;\r\n    var minX, maxX, minY, maxY;\r\n    var xSpace, ySpace;\r\n    var lineNumber;\r\n    var twistAmount;\r\n    var fullTurn;\r\n    var lineAlpha;\r\n    var maxColorValue;\r\n    var minColorValue;\r\n    \r\n    init();\r\n    \r\n    function init() {\r\n        numCircles = 15; \/\/35\r\n        maxMaxRad = 200;\r\n        minMaxRad = 200;\r\n        minRadFactor = 0;\r\n        iterations = 11;\r\n        numPoints = Math.pow(2,iterations)+1;\r\n        drawsPerFrame = 4;\r\n        \r\n        fullTurn = Math.PI*2*numPoints\/(1+numPoints);\r\n        \r\n        minX = -maxMaxRad;\r\n        maxX = displayWidth + maxMaxRad;\r\n        minY = displayHeight\/2-50;\r\n        maxY = displayHeight\/2+50;\r\n        \r\n        twistAmount = 0.67*Math.PI*2;\r\n        \r\n        stepsPerSegment = Math.floor(800\/numCircles);\r\n        \r\n        maxColorValue = 100;\r\n        minColorValue = 20;\r\n        lineAlpha = 0.10;\r\n        \r\n        bgColor = \"#000000\";\r\n        urlColor = \"#333333\";\r\n        \r\n        lineWidth = 1.01;\r\n        \r\n        startGenerate();\r\n    }\r\n        \r\n    function startGenerate() {\r\n        drawCount = 0;\r\n        context.setTransform(1,0,0,1,0,0);\r\n        \r\n        context.clearRect(0,0,displayWidth,displayHeight);\r\n        \r\n        setCircles();\r\n        \r\n        colorArray = setColorList(iterations);\r\n                \r\n        lineNumber = 0;\r\n        \r\n        if(timer) {clearInterval(timer);}\r\n        timer = setInterval(onTimer,1000\/60);\r\n        \r\n    }\r\n    \r\n    function setColorList(iter) {\r\n        var r0,g0,b0;\r\n        var r1,g1,b1;\r\n        var r2,g2,b2;\r\n        var param;\r\n        var colorArray;\r\n        var lastColorObject;\r\n        var i, len;\r\n        \r\n        var maxComponentDistance = 32;\r\n        var maxComponentFactor = 0.5;\r\n        \r\n        \r\n        r0 = minColorValue + Math.random()*(maxColorValue-minColorValue);\r\n        g0 = minColorValue + Math.random()*(maxColorValue-minColorValue);\r\n        b0 = minColorValue + Math.random()*(maxColorValue-minColorValue);;\r\n        \r\n        r1 = minColorValue + Math.random()*(maxColorValue-minColorValue);\r\n        g1 = minColorValue + Math.random()*(maxColorValue-minColorValue);\r\n        b1 = minColorValue + Math.random()*(maxColorValue-minColorValue);\r\n        \r\n        \r\n        \/*\r\n        \/\/can also set colors explicitly here if you like.\r\n        r1 = 90;\r\n        g1 = 60;\r\n        b1 = 20;\r\n        \r\n        r0 = 30;\r\n        g0 = 77;\r\n        b0 = 66;\r\n        *\/\r\n        \r\n        a = lineAlpha;\r\n        \r\n        var colorParamArray = setLinePoints(iter);\r\n        colorArray = [];\r\n        \r\n        len = colorParamArray.length;\r\n        \r\n        for (i = 0; i &lt; len; i++) {\r\n            param = colorParamArray[i];\r\n            \r\n            r = Math.floor(r0 + param*(r1 - r0));\r\n            g = Math.floor(g0 + param*(g1 - g0));\r\n            b = Math.floor(b0 + param*(b1 - b0));\r\n                \r\n            var newColor = \"rgba(\"+r+\",\"+g+\",\"+b+\",\"+a+\")\";\r\n            \r\n            colorArray.push(newColor);\r\n        }\r\n        \r\n        return colorArray;\r\n        \r\n    }\r\n    \r\n    function setCircles() {\r\n        var i;\r\n        var r,g,b,a;\r\n        var grad;\r\n        \r\n        circles = [];\r\n        \r\n        for (i = 0; i &lt; numCircles; i++) {\r\n            maxR = minMaxRad+Math.random()*(maxMaxRad-minMaxRad);\r\n            minR = minRadFactor*maxR;\r\n            \r\n            var newCircle = {\r\n                centerX: minX + i\/(numCircles-1)*(maxX - minX),\r\n                centerY: minY + i\/(numCircles-1)*(maxY - minY),\r\n                \/\/centerY: minY + Math.random()*(maxY - minY),\r\n                maxRad : maxR,\r\n                minRad : minR,\r\n                phase : i\/(numCircles-1)*twistAmount,\r\n                pointArray : setLinePoints(iterations)\r\n                };\r\n            circles.push(newCircle);\r\n        }\r\n    }\r\n    \r\n    function onTimer() {\r\n        var i;\r\n        var cosTheta, sinTheta;\r\n        var theta;\r\n        \r\n        var numCircles = circles.length;\r\n\r\n        var linParam;\r\n        var cosParam;\r\n        var centerX, centerY;\r\n        var xSqueeze = 0.75;\r\n        var x0,y0;\r\n        var rad, rad0, rad1;\r\n        var phase, phase0, phase1;\r\n        \r\n        for (var k = 0; k &lt; drawsPerFrame; k++) {\r\n        \r\n            theta = lineNumber\/(numPoints-1)*fullTurn;\r\n            \r\n            context.globalCompositeOperation = \"lighter\";\r\n            \r\n            context.lineJoin = \"miter\";\r\n            \r\n            context.strokeStyle = colorArray[lineNumber];\r\n            context.lineWidth = lineWidth;\r\n            context.beginPath();\r\n            \r\n            \/\/move to first point\r\n            centerX = circles[0].centerX;\r\n            centerY = circles[0].centerY;\r\n            rad = circles[0].minRad + circles[0].pointArray[lineNumber]*(circles[0].maxRad - circles[0].minRad);\r\n            phase = circles[0].phase;\r\n            x0 = centerX + xSqueeze*rad*Math.cos(theta + phase);\r\n            y0 = centerY + rad*Math.sin(theta + phase);\r\n            context.moveTo(x0,y0);\r\n            \r\n            for (i=0; i&lt; numCircles-1; i++) {\r\n                \/\/draw between i and i+1 circle\r\n                rad0 = circles[i].minRad + circles[i].pointArray[lineNumber]*(circles[i].maxRad - circles[i].minRad);\r\n                rad1 = circles[i+1].minRad + circles[i+1].pointArray[lineNumber]*(circles[i+1].maxRad - circles[i+1].minRad);\r\n                phase0 = circles[i].phase;\r\n                phase1 = circles[i+1].phase;\r\n                \r\n                for (j = 0; j &lt; stepsPerSegment; j++) {\r\n                    linParam = j\/(stepsPerSegment-1);\r\n                    cosParam = 0.5-0.5*Math.cos(linParam*Math.PI);\r\n                    \r\n                    \/\/interpolate center\r\n                    centerX = circles[i].centerX + linParam*(circles[i+1].centerX - circles[i].centerX);\r\n                    centerY = circles[i].centerY + cosParam*(circles[i+1].centerY - circles[i].centerY);\r\n                    \r\n                    \/\/interpolate radius\r\n                    rad = rad0 + cosParam*(rad1 - rad0);\r\n                    \r\n                    \/\/interpolate phase\r\n                    phase = phase0 + cosParam*(phase1 - phase0);\r\n                    \r\n                    x0 = centerX + xSqueeze*rad*Math.cos(theta + phase);\r\n                    y0 = centerY + rad*Math.sin(theta + phase);\r\n                    \r\n                    context.lineTo(x0,y0);\r\n                    \r\n                }\r\n                \r\n            }\r\n            \r\n            context.stroke();\r\n                    \r\n            lineNumber++;\r\n            if (lineNumber &gt; numPoints-1) {\r\n                clearInterval(timer);\r\n                timer = null;\r\n                break;\r\n            }\r\n        }\r\n    }\r\n        \r\n    \/\/Here is the function that defines a noisy (but not wildly varying) data set which we will use to draw the curves.\r\n    \/\/We first define the points in a linked list, but then store the values in an array.\r\n    function setLinePoints(iterations) {\r\n        var pointList = {};\r\n        var pointArray = [];\r\n        pointList.first = {x:0, y:1};\r\n        var lastPoint = {x:1, y:1}\r\n        var minY = 1;\r\n        var maxY = 1;\r\n        var point;\r\n        var nextPoint;\r\n        var dx, newX, newY;\r\n        var ratio;\r\n        \r\n        var minRatio = 0.5;\r\n                \r\n        pointList.first.next = lastPoint;\r\n        for (var i = 0; i &lt; iterations; i++) {\r\n            point = pointList.first;\r\n            while (point.next != null) {\r\n                nextPoint = point.next;\r\n                \r\n                dx = nextPoint.x - point.x;\r\n                newX = 0.5*(point.x + nextPoint.x);\r\n                newY = 0.5*(point.y + nextPoint.y);\r\n                newY += dx*(Math.random()*2 - 1);\r\n                \r\n                var newPoint = {x:newX, y:newY};\r\n                \r\n                \/\/min, max\r\n                if (newY &lt; minY) {\r\n                    minY = newY;\r\n                }\r\n                else if (newY &gt; maxY) {\r\n                    maxY = newY;\r\n                }\r\n                \r\n                \/\/put between points\r\n                newPoint.next = nextPoint;\r\n                point.next = newPoint;\r\n                \r\n                point = nextPoint;\r\n            }\r\n        }\r\n        \r\n        \/\/normalize to values between 0 and 1\r\n        \/\/Also store y values in array here.\r\n        if (maxY != minY) {\r\n            var normalizeRate = 1\/(maxY - minY);\r\n            point = pointList.first;\r\n            while (point != null) {\r\n                point.y = normalizeRate*(point.y - minY);\r\n                pointArray.push(point.y);\r\n                point = point.next;\r\n            }\r\n        }\r\n        \/\/unlikely that max = min, but could happen if using zero iterations. In this case, set all points equal to 1.\r\n        else {\r\n            point = pointList.first;\r\n            while (point != null) {\r\n                point.y = 1;\r\n                pointArray.push(point.y);\r\n                point = point.next;\r\n            }\r\n        }\r\n                \r\n        return pointArray;\t\t\r\n    }\r\n        \r\n    function exportPressed(evt) {\r\n        \/\/background - otherwise background will be transparent.\r\n        exportCanvasContext.fillStyle = bgColor;\r\n        exportCanvasContext.fillRect(0,0,displayWidth,displayHeight);\r\n        \r\n        \/\/draw\r\n        exportCanvasContext.drawImage(displayCanvas, 0,0,displayWidth,displayHeight,0,0,displayWidth,displayHeight);\r\n        \r\n        \/\/add printed url to image\r\n        exportCanvasContext.fillStyle = urlColor;\r\n        exportCanvasContext.font = 'bold italic 16px Helvetica, Arial, sans-serif';\r\n        exportCanvasContext.textBaseline = \"top\";\r\n        var metrics = exportCanvasContext.measureText(\"rectangleworld.com\");\r\n        exportCanvasContext.fillText(\"rectangleworld.com\", displayWidth - metrics.width - 10, 5);\r\n        \r\n        \/\/we will open a new window with the image contained within:\t\t\r\n        \/\/retrieve canvas image as data URL:\r\n        var dataURL = exportCanvas.toDataURL(\"image\/png\");\r\n        \/\/open a new window of appropriate size to hold the image:\r\n        var imageWindow = window.open(\"\", \"fractalLineImage\", \"left=0,top=0,width=\"+displayWidth+\",height=\"+displayHeight+\",toolbar=0,resizable=0\");\r\n        \/\/write some html into the new window, creating an empty image:\r\n        imageWindow.document.write(\"&lt;title&gt;Export Image&lt;\/title&gt;\")\r\n        imageWindow.document.write(\"&lt;img id='exportImage'\"\r\n                                    + \" alt=''\"\r\n                                    + \" height='\" + displayHeight + \"'\"\r\n                                    + \" width='\"  + displayWidth  + \"'\"\r\n                                    + \" style='position:absolute;left:0;top:0'\/&gt;\");\r\n        imageWindow.document.close();\r\n        \/\/copy the image into the empty img in the newly opened window:\r\n        var exportImage = imageWindow.document.getElementById(\"exportImage\");\r\n        exportImage.src = dataURL;\r\n    }\r\n    \r\n    function regeneratePressed(evt) {\r\n        startGenerate();\r\n    }\r\n    \r\n}<\/pre>\n<p>In this morphing fractal curve, Math.random is used twice to set the colors for the gradient and once more for the max radius of the curves. This is a great way to construct an entirely new appearance with every iteration!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Rengga Dev &#8211; Math.random() is an API in JavaScript. It is a <a class=\"read-more\" href=\"https:\/\/rengga.dev\/blog\/js-tutorial-math-random-generative-art\/\" title=\"JS Tutorial &#8211; Math.random() Generative Art\" itemprop=\"url\"><\/a><\/p>\n","protected":false},"author":1,"featured_media":3794,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[20],"tags":[279,264,263,274,103,227],"newstopic":[],"class_list":{"0":"post-3062","1":"post","2":"type-post","3":"status-publish","4":"format-standard","5":"has-post-thumbnail","7":"category-javascript","8":"tag-generative-art","9":"tag-javascript-tutorial","10":"tag-js-tutorial","11":"tag-math-random","12":"tag-web-design","13":"tag-web-designer"},"_links":{"self":[{"href":"https:\/\/rengga.dev\/blog\/wp-json\/wp\/v2\/posts\/3062","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/rengga.dev\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/rengga.dev\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/rengga.dev\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/rengga.dev\/blog\/wp-json\/wp\/v2\/comments?post=3062"}],"version-history":[{"count":1,"href":"https:\/\/rengga.dev\/blog\/wp-json\/wp\/v2\/posts\/3062\/revisions"}],"predecessor-version":[{"id":3065,"href":"https:\/\/rengga.dev\/blog\/wp-json\/wp\/v2\/posts\/3062\/revisions\/3065"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/rengga.dev\/blog\/wp-json\/wp\/v2\/media\/3794"}],"wp:attachment":[{"href":"https:\/\/rengga.dev\/blog\/wp-json\/wp\/v2\/media?parent=3062"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/rengga.dev\/blog\/wp-json\/wp\/v2\/categories?post=3062"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/rengga.dev\/blog\/wp-json\/wp\/v2\/tags?post=3062"},{"taxonomy":"newstopic","embeddable":true,"href":"https:\/\/rengga.dev\/blog\/wp-json\/wp\/v2\/newstopic?post=3062"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}