From b878b04f863dad6ced092d1bc2a6d6541ca71adc Mon Sep 17 00:00:00 2001 From: Dan Zen Date: Sun, 28 Jan 2024 15:46:42 -0500 Subject: [PATCH] ZIM 016 - with Shaders, Speech and more! --- package-lock.json | 4 +- package.json | 3 +- src/zim.js | 3864 +++++++++++++--- ts-src/typings/zim/index.d.ts | 8205 +++++++++++++++++---------------- 4 files changed, 7298 insertions(+), 4778 deletions(-) diff --git a/package-lock.json b/package-lock.json index d8f235f..f2f6de9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "zimjs", - "version": "15.0.14", + "version": "15.0.16", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "zimjs", - "version": "15.0.14", + "version": "15.0.16", "license": "ISC", "dependencies": { "@danzen/createjs": "^1.4.1" diff --git a/package.json b/package.json index d453268..b81a428 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "zimjs", - "version": "15.0.16", + "version": "16.0.0", "type": "module", "main": "./src/zim.js", "types": "./ts-src/typings/zim", @@ -26,6 +26,7 @@ "EaselJS", "Generative Art", "Data Visualization", + "Shaders", "Games", "Puzzles", "InfoActives", diff --git a/src/zim.js b/src/zim.js index c6fe485..8e1fdb4 100644 --- a/src/zim.js +++ b/src/zim.js @@ -937,7 +937,7 @@ RETURNS the seed zim.seedRandom = function(seed) { z_d("9.01"); // Copyright 2014 David Bau. - // See message at http://davidbau.com/encode/seedrandom.js + // See message at https://davidbau.com/encode/seedrandom.js !function(a,b,c,d,e,f,g,h,i){function j(a){var b,c=a.length,e=this,f=0,g=e.i=e.j=0,h=e.S=[];for(c||(a=[c++]);d>f;)h[f]=f++;for(f=0;d>f;f++)h[f]=h[g=s&g+a[f%c]+(b=h[f])],h[g]=b;(e.g=function(a){for(var b,c=0,f=e.i,g=e.j,h=e.S;a--;)b=h[f=s&f+1],c=c*d+h[s&(h[f]=h[g=s&g+b])+(h[g]=b)];return e.i=f,e.j=g,c})(d)}function k(a,b){var c,d=[],e=typeof a;if(b&&"object"==e)for(c in a)try{d.push(k(a[c],b-1))}catch(f){+function(){}();}return d.length?d:"string"==e?a:a+"\0"}function l(a,b){for(var c,d=a+"",e=0;ea;)a=(a+c)*d,b*=d,c=s.g(1);for(;a>=r;)a/=2,b/=2,c>>>=1;return(a+c)/b},o,"global"in f?f.global:this==c)};if(l(c[i](),b),g&&g.exports){g.exports=t;try{o=require("crypto")}catch(u){}}else h&&h.amd&&h(function(){return t})}(this,[],Math,256,6,52,"object"==typeof module&&module,"function"==typeof define&&define,"random"); if (!zim.randomO) zim.randomO = Math.random; if (zot(seed)) Math.random = zim.randomO; @@ -1436,7 +1436,7 @@ RETURNS a function that can be called many times - each time returning the next if (zot(value)) value = true; if (value) dir = -1; else dir = 1; - count = array.length-1; + if (count==0) count = array.length-1; return f; }; f.bounce = function(value) { @@ -1752,9 +1752,9 @@ RETURNS any value returned from the loop - or true if no value is returned from } } else { zim.interval(interval, function(io) { - i = io.count; + i = io.count + start; if (!immediate) i--; - if (step) i *= step; + if (step != 1) i = -start + i*step; if (type=="number") { r = call(i, total, start, end, obj, io); @@ -1815,9 +1815,9 @@ RETURNS any value returned from the loop - or true if no value is returned from } } else { zim.interval(interval, function(io) { - i = io.count; + i = io.count + start; if (!immediate) i--; - if (step) i *= step; + if (step != 1) i = -start + i*step; r = call(props[i], obj[props[i]], i, total, start, end, obj); @@ -2182,7 +2182,7 @@ NOTE: as of ZIM 5.5.0 the zim namespace is no longer required (unless zns is set EXAMPLE // existing JSONp service: // assuming that we have a callback function called test as shown below -async("http://ip-api.com/json?callback=async.test",test); +async("https://ip-api.com/json?callback=async.test",test); function test(data) {zog(data.country);} // note that the callback we pass the service is async.test not just test // this allows zim to handle scope issues and garbage collect the dynamic script when done @@ -2212,7 +2212,7 @@ END EXAMPLE EXAMPLE // CLIENT - your own server script: // assuming we have a callback function called myFunction as shown below -async("http://yourserver.com/script.php?id=72&name=dan", myFunction); +async("https://yourserver.com/script.php?id=72&name=dan", myFunction); function myFunction(data){ zog(data.test); // wow - assuming you have the PHP file below (JSON is automatically parsed) zog(data.score[1]); // 2 @@ -2420,7 +2420,8 @@ convertColor zim function DESCRIPTION -Converts color to HEX numbers - for example: "#333333" +Converts color to HEX - for example: "#333333" +Converts color to HEX NUMBER - for example: 0x333333 Or converts color to HTML string - for example: "red" Or converts color to RGB - for example: "rgb(0,0,0)" Or converts color to RGBA - for example: "rgba(0,0,0,.5)" @@ -2443,10 +2444,11 @@ color = convertColor("rgba(0,0,0,.2)", "hex", .5); // result adds alpha to hex " color = convertColor("red", "hexNumber"); // result is 0xff0000 color = convertColor("rgba(0,0,0,.2)", "array"); // result is [0,0,0,.2] color = convertColor(blue, "hsl"); // result is [173.28,49.57,54.12] // first is angle then two percentages +color = convertColor(0x00FF00); // result is "#00FF00"; END EXAMPLE PARAMETERS -color - (default black) the HTML string or HEX color (case insensitive) +color - (default black) the HTML string or HEX color (case insensitive) or HEX number starting with 0x HEX can be a # followed by 6, 3, 8 or 4 characters (3 or 4 characters will be duplicated to 6 or 8) toColorType - (default "hex") or use "string", "rgb", "rgba", "array", "hsl" (different than "hsv") "zim", "hexNumber" if "string" and color does not match existing HTML string color @@ -2463,8 +2465,9 @@ RETURNS a String with the converted color zim.convertColor = function(color, toColorType, alpha) { if (!zim.convertColorCheck) {z_d("27.5"); zim.convertColorCheck=true;} if (zot(toColorType)) toColorType = "hex"; - if (zot(color) || !color.toLowerCase) return; color = zik(color); + if (typeof color == "number") color = "#" + color.toString(16); + if (zot(color) || !color.toLowerCase) return; color = color.toLowerCase(); toColorType = toColorType.toLowerCase(); var colors = ['black','aliceblue','antiquewhite','aqua','aquamarine','azure','beige','bisque','blanchedalmond','blue','blueviolet','brown','burlywood','cadetblue','chartreuse','chocolate','coral','cornflowerblue','cornsilk','crimson','cyan','darkblue','darkcyan','darkgoldenrod','darkgray','darkgrey','darkgreen','darkkhaki','darkmagenta','darkolivegreen','darkorange','darkorchid','darkred','darksalmon','darkseagreen','darkslateblue','darkslategray','darkslategrey','darkturquoise','darkviolet','deeppink','deepskyblue','dimgray','dimgrey','dodgerblue','firebrick','floralwhite','forestgreen','fuchsia','gainsboro','ghostwhite','gold','goldenrod','gray','grey','green','greenyellow','honeydew','hotpink','indianred','indigo','ivory','khaki','lavender','lavenderblush','lawngreen','lemonchiffon','lightblue','lightcoral','lightcyan','lightgoldenrodyellow','lightgray','lightgrey','lightgreen','lightpink','lightsalmon','lightseagreen','lightskyblue','lightslategray','lightslategrey','lightsteelblue','lightyellow','lime','limegreen','linen','magenta','maroon','mediumaquamarine','mediumblue','mediumorchid','mediumpurple','mediumseagreen','mediumslateblue','mediumspringgreen','mediumturquoise','mediumvioletred','midnightblue','mintcream','mistyrose','moccasin','navajowhite','navy','oldlace','olive','olivedrab','orange','orangered','orchid','palegoldenrod','palegreen','paleturquoise','palevioletred','papayawhip','peachpuff','peru','pink','plum','powderblue','purple','rebeccapurple','red','rosybrown','royalblue','saddlebrown','salmon','sandybrown','seagreen','seashell','sienna','silver','skyblue','slateblue','slategray','slategrey','snow','springgreen','steelblue','tan','teal','thistle','tomato','turquoise','violet','wheat','white','whitesmoke','yellow','yellowgreen']; @@ -2681,8 +2684,10 @@ rect.wiggle("colorRange", .5, .2, .5, 1, 5); // wiggles the color in the range END EXAMPLE PARAMETERS -color1 - (default null) the first color as an HTML string or hex color (case insensitive) -color2 - (default black) the second color as an HTML string or hex color (case insensitive) +** supports VEE - parameters marked with ZIM VEE mean a zim Pick() object or Pick Literal can be passed + Pick Literal formats: [1,3,2] - random; {min:10, max:20} - range; series(1,2,3) - order, function(){return result;} - function +color1 - |ZIM VEE| (default null) the first color as an HTML string or hex color (case insensitive) +color2 - |ZIM VEE| (default black) the second color as an HTML string or hex color (case insensitive) ratio - (default .5) the ratio where 0 is the first color and 1 the second color RETURNS a hex color string @@ -2696,6 +2701,8 @@ RETURNS a hex color string ratio = Math.max(0, Math.min(1, ratio)); if (zot(color1)) color1 = "white"; if (zot(color2)) color2 = "black"; + color1 = zik(color1); + color2 = zik(color2); var color1O = color1; var color2O = color2; var c1 = zim.convertColor(color1, "rgb"); @@ -3048,7 +3055,7 @@ RETURNS an easing function for ZIM animate() or CreateJS TweenJS zim.zimEase = function(points, polynomials, mirror, reverse, lockEnds) { z_d("27.69"); // with thanks to Timothée Groleau and all before - // http://www.timotheegroleau.com/Flash/experiments/easing_function_generator.htm + // https://www.timotheegroleau.com/Flash/experiments/easing_function_generator.htm var two = false; if (zot(lockEnds)) lockEnds=true; @@ -4307,7 +4314,7 @@ RETURNS the subclass if (!Array.isArray(override)) override = [override]; if (zot(prototype)) prototype = true; // modified CreateJS extend() to include any prototype members already added - // see http://www.createjs.com/docs/easeljs/classes/Utility%20Methods.html + // see https://www.createjs.com/docs/easeljs/classes/Utility%20Methods.html var existingP = {}; for (var f in subclass.prototype) Object.defineProperty(existingP,f,Object.getOwnPropertyDescriptor(subclass.prototype, f)); function o() {this.constructor = subclass;} @@ -4351,7 +4358,7 @@ zim function DESCRIPTION Copies arrays and basic objects: -modified http://stackoverflow.com/users/35881/a-levy +modified https://stackoverflow.com/users/35881/a-levy If you have var obj = {prop:"val"}; and then try and copy obj to obj2 like so: obj2 = obj; then obj2 and obj refer to the same object. @@ -6718,8 +6725,9 @@ baseMax - max for the input scale (say x value) targetMin - (default 0) min for the output scale (say volume) targetMax - (default 1) max for the output scale (say volume) damp - (default .1) the damp value with 1 being no damping and 0 being no movement -factor (default 1) is going the same direction and -1 is going in opposite direction -targetRound (default false) set to true to round the converted number +factor - (default 1) is going the same direction and -1 is going in opposite direction +targetRound - (default false) set to true to round the converted number +clamp - (default true) set to false to let results go outside min and max range METHODS convert(input) - converts a base value to a target value @@ -8073,7 +8081,7 @@ if (createjs == null) {if (zon) {zogr("ZIM >= 4.3.0 requires createjs namespace // Zim Display (formerly Zim Build) adds common display classes for Zapps (apps, art, games and gadgets with ZIM) // classes in this module require createjs namespace to exist and in particular easel.js -// available at http://createjs.com +// available at https://createjs.com // SUBSECTION BASE DISPLAY @@ -8718,20 +8726,45 @@ See the CreateJS documentation for x, y, alpha, rotation, on(), addChild(), etc. NOTE: as of ZIM 5.5.0 the zim namespace is no longer required (unless zns is set to true before running zim) EXAMPLE -const container = new Container().loc(100,100); +const tile = new Tile(new Rectangle(70,70,white,black).reg(CENTER), 9, 1, 20) + .normalize("x", CENTER) + .center(); -// demonstration of adding drag() to a Container -const rect = new Rectangle(100, 100, blue) - .addTo(container); // add rectangle to container -const circle = new Circle(40, red) - .center(container) // add the circle to the container and center -container.drag(); // will drag either the rectangle or the circle -container.drag({all:true}); // will drag both the rectangle and the circle +// scale the items based on the distance from the center +// note, could set the strokeObj:{ignoreScale:true} param of Rectangle above too +tile.loop(item=>{ + zogy(decimals(item.ratio)); // 0, .3, .5, .8, 1, .8, .5, .3, 0 + item.sca(.5 + item.ratio*2); +}); + +// adjust the spacing by re-tiling the scaled items +const final = new Tile({ + obj:tile.items, + cols:tile.items.length, + rows:1, + spacingH:-10, + unique:true, // make sure we do not pick (ZIM VEE) from the array + valign:CENTER +}).center() + +tile.dispose(); + +final.sortBy("ratio"); // make more central objects come to front +END EXAMPLE -// below will reduce the alpha of the object in the container that was clicked (target) -container.on("click", e => {e.target.alpha = .5; S.update();}) -// below will reduce the alpha of all the objects in the container (currentTarget) -container.on("click", e => {e.currentTarget.alpha = .5; S.update();}) +EXAMPLE +// animate() the rate and use sequence:0 to apply different speed to each item +const tile = new Tile(new Rectangle(10, 10, series(green,blue,yellow)), 20, 20, 5, 5) + .normalize("reg", CENTER) + .center() + .noMouse() + .animate({ + props:{scale:2}, + time:2, + ease:"elasticOut", + rate:target=>{return 1 + target.ratio*4}, + sequence:0 // turn on per item animation + }); END EXAMPLE PARAMETERS @@ -8770,6 +8803,11 @@ cache(width||x, height||y, null||width, null||height, scale, options, margin, rt height - (default getBounds().height) the height of the chache - or null if first two parameters are provided scale - (default 1) set to 2 to cache with twice the fidelity if later scaling up options - (default null) additional parameters for cache logic - see CreateJS somewhere for details + also added adjustBounds property for options - set to true to set new bounds and registration point + on cached objects with x,y,width,height different than the original + this appears to NOT be the default with createjs cache() which keeps the original registration point and bounds + automatically fixing this changes lots of things so use the adjustBounds option when needed + for instance when caching parts of a container and requiring hit tests or the part to be draggable, etc. margin - (default 0) add or subtract a margin from the bounds eg. margin:10 will make the cache size 10 pixels more on all sides this can be handy when caching objects with borders - that go half outside the natural bounds @@ -8788,6 +8826,39 @@ setBounds(width||x||Boundary, height||y, null||width, null||height) - overrides getBounds(targetSpace) - overrides CreateJS getBounds() and returns a rectangle with x, y, width height of bounds (not including outside of border) pass in a Container (including Stage) to map rectangle to that coordinate space childrenToBitmap() - turns content to a Bitmap and adds bitmap to container - removing all other children +sortBy(prop) - sorts the children of the container by a property - must be numeric +normalize(prop, from, from2, min, max, factor, clamp) - sets a ratio property (1 to 0) for each item in a Container. + passing in no parameters will normalize to the last normalized parameters - also see normalized property + ** Supports ZIM DUO + PARAMETERS: prop - the property (as a string) to normalize + from - where the ratio will start at 1, values can be START (highest number), END (lowest number), CENTER, LEFT, RIGHT + from2 - used with "reg" with values of START, END, CENTER, TOP, BOTTOM - see the SPECIAL note down below. + min and max - can be set to override the calculated min and max values for the items in container - min and max are ignored for "reg" + factor - (default 1) is going the same direction and -1 is going in opposite direction + targetRound - (default false) set to true to round the converted number + clamp - (default true) set to false to let results go outside min and max range + WORKINGS: the ratio is 1 when it is closest to the from parameter(s) and 0 when farthest + For example, container.normalize("x") would provide a ratio property for each item in the container + with a default from START that would be 1-item.x/(largestX-smallestX) + In other words, if the item is the least distance in the x then ratio is 1, if the item is the most distance in the x then the ratio is 0 + The from parameter will determine where the ratio is the largest. + container.normalize("x", END) would be 0 for an x of 0 and 1 for an item on the right side + container.normalize("x", CENTER) would be 1 for an item in the middle and 0 for items at the start and end + Set factor to -1 to reverse the direction of the ratio - eg. 0 in the middle and 1 at the start and end. + Or set min and max values to override the calculated min and max + The clamp, if true, will keep values between min and max or let them go outside these if false + there is one ratio property per object but this can be set and used any number of times. + NOTE: The ratio property is like a ZIM Proportion convert() but to each item in the container. + A provided property is converted based on a min and max to a target of 1 to 0 (factor is inverted) + SPECIAL: a property of "reg" will actually set the registration to the from value + the still property of reg() is used to keep the item from shifting. + An _orX and _orY will be provided on each item for original regX and regY. + setting normalize("reg", CENTER) will do both horizontal and vertical to the container dimensions. + If this is not desired, use container.loop(item=>{item.reg(item._orX, item._orY, true)}); + to set the registration point back but still keep the ratio property + USAGE: the ratio property can be used by animate() with sequenceRate to set the rate of an animation based on ratio + Or the ratio property can be used directly - for instance, to set a scale based on a distance from somewhere + Or as another example, set the alpha based on the rotation of a shape in the container specialColor(colorCommand, colorObject) - used internally by ZIM Shapes to set GradientColor, RadialColor and BitmapColor on a fill or stroke color command hasProp(property as String) - returns true if property exists on object else returns false @@ -8814,6 +8885,10 @@ width - gets or sets the width. Setting the width will scale the height to keep height - gets or sets the height. Setting the height will scale the width to keep proportion (see heightOnly below) widthOnly - gets or sets the width. This sets only the width and may change the aspect ratio of the object heightOnly - gets or sets the height. This sets only the height and may change the aspect ratio of the object +ratio - get a ratio set by the Container normalize method() - or Tile itemRegX, itemRegY parameters + this will probably be a value from 1 to 0 as to how close the property is the end specified in the from parameter of normalize() + a value of 1 is the closest and a value of 0 is the farthest - see the normalize() method for details +normalized - get if the container has been normalized - see normalized parameter draggable - set to true for a default drag() and false for a noDrag() level - gets or sets the level of the object in its parent container (or the stage) - a property for parent.getChildIndex() and parent.setChildIndex() depth - for ZIM VR - the depth used to shift left and right channel and for parallax in VR - also see dep() ZIM Display method @@ -8914,7 +8989,11 @@ added, click, dblclick, mousedown, mouseout, mouseover, pressdown (ZIM), pressmo } if (zot(margin)) margin = 0; that.cjsContainer_cache(a-margin,b-margin,c+margin*2,d+margin*2,scale,options,rtl,willReadFrequently); - if (bounds) that.setBounds(bounds.x, bounds.y, bounds.width, bounds.height); + if (bounds && options && options.adjustBounds) { + that.setBounds(a, b, c, d); + if (a!=bounds.x || b!=bounds.y || c!=bounds.width || d!=bounds.height) that.reg(a,b); + that.hitArea = new zim.Shape().f(black).dr(a-margin,b-margin,c+margin*2,d+margin*2); + } that.z_bc = that.canvas = that.cacheCanvas; return that; }; @@ -8930,6 +9009,115 @@ added, click, dblclick, mousedown, mouseout, mouseover, pressdown (ZIM), pressmo pic.addTo(this); return this; }; + + this.sortBy = function(prop, inverse) { + if (zot(prop)) return this; + var sortFunction = function(obj1, obj2, options) { + if (obj1[prop] > obj2[prop]) { return inverse?-1:1; } + if (obj1[prop] < obj2[prop]) { return inverse?1:-1; } + return 0; + } + this.sortChildren(sortFunction); + return this; + } + + this.normalize = function(prop, from, from2, min, max, factor, clamp) { + + // TODO - special hue, saturation, etc. + + var sig = "prop, from, from2, min, max, overflow, flip"; + var duo; if (duo = zob(that.normalize, arguments, sig)) return duo; + + if (this.z_norm) { + if (zot(prop)) prop = this.z_norm.prop; + if (zot(from)) from = this.z_norm.from; + if (zot(from2)) from2 = this.z_norm.from2; + if (zot(from2)) min = this.z_norm.min; + if (zot(max)) max = this.z_norm.max; + if (zot(factor)) factor = this.z_norm.factor; + if (zot(clamp)) clamp = this.z_norm.clamp; + } + + if (zot(from)) from = "start"; + else if (from == "left") from = "start"; + else if (from == "right") from = "end"; + else if (from == "middle") from = "center"; + else if (from != "start" && from != "center" && from != "end") from = "start"; + if (from=="center" && zot(from2)) from2 = "center"; + if (from2 == "top") from2 = "start"; + else if (from2 == "bottom") from2 = "end"; + else if (from2 == "middle") from2 = "center"; + else if (from2 != "start" && from2 != "center" && from2 != "end") from2 = "start"; + + this.z_norm = {prop:prop, from:from, from2:from2, min:min, max:max, factor:factor, clamp:clamp}; + + this.normalized = true; + that.saveOriginalReg = function() { + that.loop(c=>{ + c._orX = c.regX; + c._orY = c.regY; + }); + } + that.resetOriginalReg = function() { + that.loop(c=>{ + c.reg(c._orX, c._orY, true); + }); + } + + if (prop == "reg") { + that.resetOriginalReg(); + that.saveOriginalReg(); + + // loop items and shift reg with third param true + var maxX = -10000000; + var maxY = -10000000; + + that.loop(c=>{ + c._dx = c._dy = null; + if (from == "center") c._dx = that.width/2-c.x; + else if (from == "end") c._dx = that.width-c.x; + else if (from == "start") c._dx = 0-c.x; + if (from2 == "center") c._dy = that.height/2-c.y; + else if (from2 == "start") c._dy = 0-c.y; + else if (from2 == "end") c._dy = that.height-c.y; + c.reg(c._dx,c._dy,true); + maxX = Math.max(maxX, Math.abs(c._orX-c._dx)); + maxY = Math.max(maxY, Math.abs(c._orY-c._dy)); + }); + that.loop(c=>{ + c.ratio = 1 - (Math.sqrt(Math.pow((c._orX-c._dx),2) + Math.pow((c._orY-c._dy),2)) / Math.sqrt(Math.pow(maxX,2) + Math.pow(maxY,2))); + if (factor==-1) c.ratio = 1-c.ratio; + if (clamp) c.ratio = zim.constrain(c.ratio, 0, 1); + }); + + } else { // not reg + if (zot(min) || zot(max)) setMM(); + var d; + if (from == "start") d = [0,1,-1]; + else if (from == "end") d = [0,1,1]; + if (from == "center") d = [-1,1,-1]; + that.proportion = new zim.Proportion(min, max, d[0], d[1], d[2], false, clamp); + that.loop(function(c) { + c.ratio = Math.abs(that.proportion.convert(c[prop])); + if (from == "center") c.ratio = 1-c.ratio; + }); + } + + function setMM() { + var minC = 1000000000; + var maxC = -1000000000; + that.loop(c=>{ + if (zot(min) && c[prop] < minC) minC = c[prop]; + if (zot(max) && c[prop] > maxC) maxC = c[prop]; + }); + if (zot(min)) min = minC; + if (zot(max)) max = maxC; + } + return this; + } + + + var frame = WW.zdf || 1; if (createjs && !createjs.stageTransformable && frame.retina) { this.localToGlobal = function(x,y) { @@ -9117,7 +9305,7 @@ ZIM Shape lets you draw dynamic shapes beyond the ZIM provided shapes. You make a new shape object and then draw in its graphics property using similar commands to the HTML Canvas commands (and Flash Bitmap drawing). See the CreateJS Easel Shapes and Graphics docs: -http://www.createjs.com/docs/easeljs/classes/Graphics.html +https://www.createjs.com/docs/easeljs/classes/Graphics.html ALSO SEE: https://zimjs.com/docs.html?item=Generator for dynamically drawing into shapes like Processing (P5js) ALSO SEE: https://zimjs.com/docs.html?item=Squiggle,Blob for odd shapes and user adjustable shapes @@ -9230,6 +9418,11 @@ cache(width||x, height||y, null||width, null||height, scale, options, margin, rt height - (default getBounds().height) the height of the chache - or null if first two parameters are provided scale - (default 1) set to 2 to cache with twice the fidelity if later scaling up options - (default null) additional parameters for cache logic - see CreateJS somewhere for details + also added adjustBounds property for options - set to true to set new bounds and registration point + on cached objects with x,y,width,height different than the original + this appears to NOT be the default with createjs cache() which keeps the original registration point and bounds + automatically fixing this changes lots of things so use the adjustBounds option when needed + for instance when caching parts of a container and requiring hit tests or the part to be draggable, etc. margin - (default 0) add or subtract a margin from the bounds eg. margin:10 will make the cache size 10 pixels more on all sides this can be handy when caching objects with borders - that go half outside the natural bounds @@ -9381,6 +9574,11 @@ added, click, dblclick, mousedown, mouseout, mouseover, pressdown (ZIM), pressmo } if (zot(margin)) margin = 0; that.cjsShape_cache(a-margin,b-margin,c+margin*2,d+margin*2,scale,options,rtl,willReadFrequently); + if (bounds && options && options.adjustBounds && (a!=bounds.x || b!=bounds.y || c!=bounds.width || d!=bounds.height)) { + that.setBounds(a, b, c, d); + that.reg(a,b); + that.hitArea = new zim.Shape().f(black).dr(a-margin,b-margin,c+margin*2,d+margin*2); + } that.z_bc = that.canvas = that.cacheCanvas; return that; }; @@ -9625,6 +9823,11 @@ cache(width||x, height||y, null||width, null||height, scale, options, rtl, willR height - (default getBounds().height) the height of the chache - or null if first two parameters are provided scale - (default 1) set to 2 to cache with twice the fidelity if later scaling up options - (default null) additional parameters for cache logic - see CreateJS somewhere for details + also added adjustBounds property for options - set to true to set new bounds and registration point + on cached objects with x,y,width,height different than the original + this appears to NOT be the default with createjs cache() which keeps the original registration point and bounds + automatically fixing this changes lots of things so use the adjustBounds option when needed + for instance when caching parts of a container and requiring hit tests or the part to be draggable, etc. rtl - (default null) set to true to use Right to Left. willReadFrequently - (default null) set to true if planning on using operations with getImageData() or toDataURL() if you get a warning in the console about willReadFrequently then set this to true @@ -9812,7 +10015,11 @@ zim.Bitmap = function(image, width, height, left, top, scale, style, group, inhe } bounds = that.getBounds(); that.cjsBitmap_cache(a,b,c,d,scale,options,rtl,willReadFrequently); - that.setBounds(bounds.x, bounds.y, bounds.width, bounds.height); + if (bounds && options && options.adjustBounds && (a!=bounds.x || b!=bounds.y || c!=bounds.width || d!=bounds.height)) { + that.setBounds(a, b, c, d); + that.reg(a,b); + that.hitArea = new zim.Shape().f(black).dr(a-margin,b-margin,c+margin*2,d+margin*2); + } that.z_bc = that.canvas = that.cacheCanvas; return that; }; @@ -9992,8 +10199,8 @@ NOTE: A ZIM Sprite handles both an evenly tiled spritesheet - use cols and rows and an un-evenly tiled spritesheet - use the json parameter. The json can come from TexturePacker for instance exported for EaselJS/CreateJS CreateJS Easel Sprite and SpriteSheet docs: -http://www.createjs.com/docs/easeljs/classes/Sprite.html -http://www.createjs.com/docs/easeljs/classes/SpriteSheet.html +https://www.createjs.com/docs/easeljs/classes/Sprite.html +https://www.createjs.com/docs/easeljs/classes/SpriteSheet.html You can optionally pass in an existing createjs.SpriteSheet as a parameter. When you do so, all other parameters are ignored. ** JSON hash and JSON array formats are now supported (Phaser formats) @@ -10746,7 +10953,7 @@ END EXAMPLE PARAMETERS ** supports DUO - parameters or single object with properties below ** supports OCT - parameter defaults can be set with STYLE control (like CSS) -// from the CreateJS MovieClip docs: http://www.createjs.com/docs/easeljs/classes/MovieClip.html +// from the CreateJS MovieClip docs: https://www.createjs.com/docs/easeljs/classes/MovieClip.html mode - (default "independent") or single_frame (based on startPosition) or synched (syncs to parent) startPosition - (default 0) the start position of the MovieClip (*could not get to work) loop - (default true) set to false not to loop (*did not seem to loop so use loop:true in zim.animate()) @@ -11014,7 +11221,7 @@ added, click, dblclick, mousedown, mouseout, mouseover, pressdown (ZIM), pressmo var approxUnitArc = function approxUnitArc(ang1, ang2) { // If 90 degree circular arc, use a constant - // as derived from http://spencermortensen.com/articles/bezier-circle + // as derived from https://spencermortensen.com/articles/bezier-circle var a = ang2 === 1.5707963267948966 ? 0.551915024494 : ang2 === -1.5707963267948966 ? -0.551915024494 : 4 / 3 * Math.tan(ang2 / 4); var x1 = Math.cos(ang1); var y1 = Math.sin(ang1); @@ -11951,6 +12158,1252 @@ added, click, dblclick, mousedown, mouseout, mouseover, pressdown (ZIM), pressmo zim.extend (zim.SVGContainer, zim.Container, "clone", "zimContainer", false); //-50.95 +/*-- +zim.Tag = function(width, height, id, frame, backgroundColor, padding, paddingH, paddingV, expand, style, group, inherit) + +Tag +zim class - extends a zim.Container which extends a createjs.Container + +DESCRIPTION +Creates a
with id of id and overlays it on the Canvas with the createjs DOMElement +The tag is scaled and positioned with ZIM code and can be filled with any HTML desired +Access to the HTML tag is provided with the tag property (so you can use innerHTML or style on this) +However a convenience innerHTML and style properties have been added to Tag +CSS Styles can be applied to the HTML tag as with any HTML div tag +Or use the chainable add() method to add a String of HTML (instead of setting innerHTML) + +SEE: https://zimjs.com/explore/tag.html + +NOTE: due to the HTML tag being overlayed, the tag.resize() must be called if it is manually scaled or moved +(This is called automatically when the stage is resized) + +NOTE: if the tag is placed in a container and the container is removed or added again +the tag must be manually removed or added again with tag.removeFrom() or tag.addTo(). +This is so ZIM does not have to keep track of HTML tags each time a container is added or removed. + +NOTE: rotation and skewing of Tag is not supported - although might work with custom CSS transformations + +NOTE: as of ZIM 5.5.0 the zim namespace is no longer required (unless zns is set to true before running zim) + +EXAMPLE +const tag = new Tag(300, 60).center().add("

TITLE TEXT

"); +tag.style.color = red; +END EXAMPLE + +PARAMETERS +** supports DUO - parameters or single object with properties below +** supports OCT - parameter defaults can be set with STYLE control (like CSS) +width - (default 250) the width of the div tag +height - (default 70) the height of the div tag + Note: to set scrollBars use CSS: tag.style.overflow = "auto"; +id - (default zimTag_randomNumber) a string id for the HTML div tag. +frame - (default the zdf) a reference to the Frame (to scale and position the HTML tag) +backgroundColor - (default "rgba(0,0,0,0)") a ZIM Rectangle used as a background +padding - (default 10) inner padding between edge of background rectangle and HTML tag +paddingH - (default padding) inner horizontal padding between edge of background rectangle and HTML tag +paddingV - (default padding) inner vertical padding between edge of background rectangle and HTML tag +expand - (default 20) hit area around background to count as a press on Tag - handy for dragging as HTML tag area will override mouse on canvas +style - (default true) set to false to ignore styles set with the STYLE - will receive original parameter defaults +group - (default null) set to String (or comma delimited String) so STYLE can set default styles to the group(s) (like a CSS class) +inherit - (default null) used internally but can receive an {} of styles directly + +METHODS +add(htmlString) - chainable method to add HTML to tag - calling add() again will append + see innerHTML property as alternative or to overwrite the innerHTML +resize(update) - call the resize event if the scale or position of the tag is changed + this will sync the location of the div tag + resize() is only needed if the scale or x, y of the tag (or its container) is changed + it is not needed for general window resizing - the Tag handles this + Note: if the Frame itself changes location in the HTML document, call a F.update() + this will then dispatch an update event to the Tag and it will resize() + this is not needed if resizing the window or scrolling - see Frame update() method docs + update defaults to true - set to false to not update the stage during a resize() +hasProp(property as String) - returns true if property exists on object else returns false +clone() - makes a copy with properties such as x, y, etc. also copied +dispose() - removes from parent, removes event listeners - must still set outside references to null for garbage collection + +ALSO: ZIM 4TH adds all the methods listed under Container (see above), such as: +drag(), hitTestRect(), animate(), sca(), reg(), mov(), center(), centerReg(), +addTo(), removeFrom(), loop(), outline(), place(), pos(), alp(), setMask(), etc. +ALSO: see the CreateJS Easel Docs for Container methods, such as: +on(), off(), getBounds(), setBounds(), cache(), uncache(), updateCache(), dispatchEvent(), +addChild(), removeChild(), addChildAt(), getChildAt(), contains(), removeAllChildren(), etc. + +PROPERTIES +type - holds the class name as a String +tagID - the assigned id of the tag +tag - the HTML div tag - just a regular HMTL div tag which can be styled +innerHTML - the innerHTML property of the tag (so myTag.tag.innerHTML is not needed) +background - access to the ZIM Rectangle used as the background +frame - get or set the frame - set this if changing frames + +ALSO: see ZIM Container for properties such as: +width, height, widthOnly, heightOnly, draggable, level, depth, group +blendMode, hue, saturation, brightness, contrast, etc. + +ALSO: see the CreateJS Easel Docs for Container properties, such as: +x, y, rotation, scaleX, scaleY, regX, regY, skewX, skewY, +alpha, cursor, shadow, name, mouseChildren, mouseEnabled, parent, numChildren, etc. + +EVENTS: See the CreateJS Easel Docs for Container events such as: +added, click, dblclick, mousedown, mouseout, mouseover, pressdown (ZIM), pressmove, pressup, removed, rollout, rollover +--*///+67.7 +zim.Tag = function(width, height, id, frame, backgroundColor, padding, paddingH, paddingV, expand, style, group, inherit) { + var sig = "width, height, id, frame, backgroundColor, padding, paddingH, paddingV, expand, style, group, inherit"; + var duo; if (duo = zob(zim.Tag, arguments, sig, this)) return duo; + z_d("67.7"); + this.group = group; + var DS = style===false?{}:zim.getStyle("Tag", this.group, inherit); + + if (zot(width)) width = DS.width!=null?DS.width:250; + if (zot(height)) height = DS.height!=null?DS.height:70; + if (zot(id)) id = DS.id!=null?DS.id:"zimTag_" + zim.rand(10000); + if (typeof frame == "undefined") { + if (WW.zdf) { + var frame = WW.zdf; + } else { + if (zon) {zogy("zim.Tag - please provide a reference to zim Frame");} return; + } + } + if (zot(backgroundColor)) backgroundColor = DS.backgroundColor!=null?DS.backgroundColor:"rgba(0,0,0,0)"; + if (zot(padding)) padding = DS.padding!=null?DS.padding:10; + if (zot(paddingH)) paddingH = DS.paddingH!=null?DS.paddingH:padding; + if (zot(paddingV)) paddingV = DS.paddingV!=null?DS.paddingV:padding; + if (zot(expand)) expand = DS.expand!=null?DS.expand:20; + if (expand === true) expand = 20; + + this.zimContainer_constructor(width, height, null, null, false); + this.type = "Tag"; + this.tagID = id; + this.id = id; + var that = this; + var stage = frame.stage; + + that.background = new zim.Rectangle(width+paddingH, height+paddingV, backgroundColor).center(this).expand(expand?expand:0); + + var tag = that.tag = document.createElement("div"); + if (frame.tag) { + frame.tag.appendChild(tag); + } else { + document.body.appendChild(tag); + } + + tag.setAttribute("id", id); + tag.setAttribute("name", id); + + tag.zimDisplay = tag.style.display || "inline-block"; + tag.style.cssText = "resize:none; z-index:3; width:" + width + "px; height:" + height + "px; overflow:hidden; outline:none;" + + "position:absolute; left:0px; top:0px; display:none;"; + + var cjsTag = new createjs.DOMElement(tag); + cjsTag.alpha = 0; + + var pRatio = frame.retina?(WW.devicePixelRatio || 1):1; + + this.resize = function(update, last) { + if (zot(update)) update = true; + if (that.time1) clearTimeout(that.time1); + // before ZIM ZIM 01 rand the setTimeout on the whole resize code + // but that was causing jutters in damped window scrolling + // so implemented a final call to help with a delayed update + // which was probably put in place to capture a mobile orientation change + if (!last) { + that.time1 = setTimeout(function() { + that.resize(null, true); + }, 40); + } + var displayProps = that.getConcatenatedMatrix().decompose(); + var point = that.localToGlobal(0, 0); + if (frame.retina) { + var sc = zim.browserZoom(); + if (frame.tag) { + cjsTag.x = point.x/pRatio/sc; + cjsTag.y = point.y/pRatio/sc; + } else { + cjsTag.x = frame.x/stage.scaleX + point.x/pRatio/sc; + cjsTag.y = frame.y/stage.scaleY + point.y/pRatio/sc; + } + // CreateJS DOMElement is scaling tag as stage scales + zim.sca(cjsTag, displayProps.scaleX/pRatio/stage.scaleX/sc, displayProps.scaleY/pRatio/stage.scaleY/sc); + } else { + cjsTag.x = frame.x + point.x * frame.scale; + cjsTag.y = frame.y + point.y * frame.scale; + zim.sca(cjsTag, frame.scale*displayProps.scaleX, frame.scale*displayProps.scaleY); + } + cjsTag.alpha = that.alpha; + if (that.parent) cjsTag.alpha *= that.parent.alpha; + if (update && that.stage) stage.update(); + // }, 50); + return that; + }; + this.resize(); + that.added(addedCallback); + function addedCallback() { + stage.addChild(cjsTag); + that.time2 = setTimeout(function() { + tag.style.display = tag.zimDisplay; + }, 50); + that.resize(); + that.on("added", addedCallback, null, true); // once + } + that.on("removed", function() { + if (that.time1) clearTimeout(that.time1); + if (that.time2) clearTimeout(that.time2); + stage.removeChild(cjsTag); + if (tag) tag.style.display = "none"; + }); + + this.add = function(html) { + tag.innerHTML += html; + return that; + }; + + Object.defineProperty(this, 'innerHTML', { + get: function() { + return tag.innerHTML; + }, + set: function(value) { + tag.innerHTML = value; + } + }); + + Object.defineProperty(this, 'style', { + get: function() { + return tag.style; + }, + set: function(value) { + tag.style = value; + } + }); + + if (style!==false) zim.styleTransforms(this, DS); + this.clone = function() { + var u = new zim.Tag(width, height, id, frame, backgroundColor, padding, paddingH, paddingV, expand, style, this.group, inherit); + return that.cloneProps(u); + }; + Object.defineProperty(this, 'frame', { + get: function() { + return frame; + }, + set: function(value) { + if (value != frame) { + frame.off("update", that.updateEvent); + frame.off("resize", that.resizeEvent); + frame = value; + that.resizeEvent = frame.on("resize", that.resize); + that.updateEvent = frame.on("update", that.resize); + that.resize(); + } + } + }); + this.resizeEvent = frame.on("resize", that.resize); + this.updateEvent = frame.on("update", that.resize); + this.dispose = function(a,b,disposing) { + var stage; + if (that.stage) stage = that.stage; + frame.off("update", that.updateEvent); + frame.off("resize", that.resizeEvent); + if (tag && tag.parentNode) { + tag.parentNode.removeChild(tag); + } + tag = null; + // if we are not coming from a container + // then call container's dispose + // let the container's dispose know + // that we have already disposed this custom dispose + // so it does not try and dispose it again + if (!disposing) { + this.zimContainer_dispose(true); + if (stage) stage.update(); + } + return true; + }; +}; +zim.extend(zim.Tag, zim.Container, ["clone", "dispose"], "zimContainer", false); +//-67.7 + + +// SUBSECTION SHADERS + +/*-- +zim.Shader = function(width, height, fragment, uniforms, vertex, dynamic, preCall, postCall, rate, version, canvas, vertexPosition, strip, log, style, group, inherit) + +Shader +zim class - extends a zim.Bitmap which extends a createjs.Bitmap + +DESCRIPTION +Makes a Bitmap from shader code. The Bitmap will update automatically if set to dynamic (default). +Shaders run on the GPU and are very fast for cool visual effects and are also the basis for 3D. +For 3D, we recommend using three.js with ZIM and the the Three helper module - overlayed or with TextureActive. +BUT - shaders are also commonly used for amazing 2D effects. + +Also see ShaderOverlay() that overlays the raw WebGL Canvas on ZIM as a Tag() rather than converting to a Bitmap. + +The best way to see what shaders are about are to look at the many examples on ShaderToy, for instance: +https://www.shadertoy.com + +Many of the shaders from ShaderToy will work in ZIM Shader() with a simple cut and paste. +However, ones with multiple channels are currently not supported - but you may be able to add equivilant code. +For instance, a picture can be brought into the shader rather than using a channel. +There are many more shaders other than in ShaderToy. + +Shaders have their own coding language called GLSL https://en.wikipedia.org/wiki/OpenGL_Shading_Language +ZIM Shader was built for OpenGL 3.0 but other versions may work. +This is a very complicated coding world based on C - so do not expect to easily make shaders. +Most likely, you will start with copying and perhaps modifying code - but it is not easy. +When looking at reference materials, note that ZIM has greatly simplified the requirements +So tend to ignore how shaders are set up to run +and concentrate on the actual fragment (and sometimes vertex) shader code +https://learnopengl.com/Getting-started/Shaders +https://thebookofshaders.com/ +https://thebookofshaders.com/glossary/ + +ZIM Shader() abstracts a couple hundred lines of somewhat famously complicated WebGL code. +There are three main inputs (beyond width and height) + +FRAGMENT +Shader code that changes the pixel colors. +This is the code that is displayed in ShaderToy and is most popular in 2D shaders. +Fragment shaders run after Vertex shaders but it is an early parameter because the Vertex shader is usually default. + +UNIFORMS +These are variables that we pass in to the shader. +ZIM provides a Uniforms() object for this purpose for easy animate() and wiggle() and component interactivity. +The uniforms will automatically be updated by ZIM Shader. +The following code will get inserted at the top of the shader code: + + #version 300 es // version number - see versions parameter + precision mediump float; // can reset a different precision if desired + uniform vec3 iResolution; // width and height of shader (in pixels) + uniform float iTime; // shader playback time (in seconds) + uniform float iTimeDelta; // render time (in seconds) + uniform float iFrameRate; // shader frame rate (frames per second) + uniform int iFrame; // shader playback frame + uniform vec4 iMouse; // mouse coordinates x, y (in pixels), down (0/1), click (0/1) + uniform vec4 iDate; // year (full), month (0-11), day, time (in seconds from midnight) + uniform float iChange; // increases with the rate (parameter or property) of the Shader + +VERTEX +Shader code that changes the verticies (points) of triangles. +This defaults to two triangles in a strip that make a rectangle based on the provided width and height. +This code will probably not be needed and is not shown in ShaderToy - but can be used in ZIM Shader() + +TIPS +In the shader code, data types matter, are declared and need to match properly - for example: + float num = 1.0; + int count = 1; + vec2 size = vec(0.0, 1.0); +Vectors are used a lot and can have 2, 3, or 4 components (like array elements) that we access with x, y, z, w (or rgba or stpq) +These can be accessed and adjusted in multiple ways - called "swizzling" (examples from learnopengl.com) + vec2 someVec; // has two components + vec4 differentVec = someVec.xyxx; // make 4 components from the two components - repeating is fine + vec3 anotherVec = differentVec.zyw; // has three components + vec4 otherVec = someVec.xxxx + anotherVec.yxzy; // added components together + vec2 vect = vec2(0.5, 0.7); // two components - use leading 0 and trailing 0 to keep as float if desired + vec4 result = vec4(vect, 0.0, 0.0); // vect will be spread across the first two components + vec4 otherResult = vec4(result.xyz, 1.0); // first three components of results are spread across otherResult +ShaderToy uses a slightly different format which is converted by ZIM - see the Docs under fragment parameter. + A main thing to watch out for is that ShaderToy fragCoord is a vec2 + whereas gl_FragCoord (WebGL 1.0) is a vec3 so use gl_FragCoord.xy to match fragCoord +Conditionals are often avoided for performance https://theorangeduck.com/page/avoiding-shader-conditionals + +ZIM Shader() defaults to dynamic which means that it will update. +Set the dynamic parameter to false if the shader does not need to update. + +SEE: https://zimjs.com/016/shaders for a mini-site of shaders in ZIM. + +NOTE: as of ZIM 5.5.0 the zim namespace is no longer required (unless zns is set to true before running zim) + +EXAMPLE +// A simple horizontal gradient shown four ways +// These four ways can be used throughout the examples +// but we will show the rest as the ShaderToy format. + +// https://www.shadertoy.com/view/MlK3DK +// iResolution is a default uniform (see Uniforms above) + +// VERSION 1 - ShaderToy mainImage(out, in) {} Format +const fragment = ` + void mainImage( out vec4 fragColor, in vec2 fragCoord ) { + fragColor = mix(vec4(1,1,0,1), vec4(0,1,1,1), fragCoord.x/iResolution.x); + } +`; +new Shader(W, H, fragment).center(); + +// VERSION 2 - main() {} - traditional GLSL 3 format +const fragment = ` + out vec4 fragColor; + void main() { + vec2 fragCoord = gl_FragCoord.xy; // convert default input vec3 gl_FragCoord to vec2 fragCoord + fragColor = mix(vec4(1,1,0,1), vec4(0,1,1,1), fragCoord.x/iResolution.x); + } +`; +new Shader(W, H, fragment).center(); + +// VERSION 3 - using gl_FragCoord directly +const fragment = ` + out vec4 fragColor; + void main() { + fragColor = mix(vec4(1,1,0,1), vec4(0,1,1,1), gl_FragCoord.x/iResolution.x); + } +`; +new Shader(W, H, fragment).center(); + +// VERSION 4 - traditional GLSL 1 format (note version param in Shader) +const fragment = ` + void main() { + gl_FragColor = mix(vec4(1,1,0,1), vec4(0,1,1,1), gl_FragCoord.x/iResolution.x); + } +`; +new Shader({width:W, height:H, fragment:fragment, version:""}).center(); +END EXAMPLE + +EXAMPLE +// Animating with the iTime uniform (see default uniforms) +const fragment = ` + void mainImage( out vec4 fragColor, in vec2 fragCoord ) { + vec2 uv = fragCoord.xy / iResolution.xy; + vec3 color = 0.5 + 0.5 * cos(iTime + uv.xyx + vec3(0,2,4)); + fragColor = vec4(vec3(color), 1.0); + } +`; +new Shader(W, H, fragment).center(); +END EXAMPLE + +EXAMPLE +// https://zimjs.com/016/shader/spiral.html +// loading shader from file - often a glsl extension +// VS Code has a glsl-literal extension that will color syntax shader code + +new Frame(FILL, 1024, 768, purple, black, ready, "spiral.glsl"); +function ready() { + new Shader(W, H, asset("spiral.glsl")).addTo(); +} +END EXAMPLE + +EXAMPLE +// A circle - using rgb() and circle() functions +const fragment = ` + // center will be wiggled outside in ZIM so need a uniform + uniform vec2 center; + + // prepare a function to convert RGB 0-255 to 0-1 + vec3 rgb(float r, float g, float b) { + return vec3(r/255.0, g/255.0, b/255.0); // 0-1 + } + + // prepare a function to see if each point (uv) is inside or outside radius + // from wherever the center of the circle is located (pos - based on center uniform) + vec4 circle(vec2 uv, vec2 pos, float rad, vec3 color) { + float d = length(pos - uv) - rad; + float t = clamp(d, 0.0, 1.0); // threshhold 0 if within radius range from center + return vec4(color, 1.0 - t); // alpha 0 if outside radius + } + + void mainImage( out vec4 fragColor, in vec2 fragCoord ) { + // circle will be 1/2 the height + float radius = 0.25 * iResolution.y; + + // Background + vec4 layer1 = vec4(rgb(255.0, 165.0, 0.0), 1.0); + + // Circle + vec3 red = rgb(255.0, 0.0, 0.0); + vec4 layer2 = circle(fragCoord.xy, center, radius, red); + + // Blend the two using the alpha of the circle + fragColor = mix(layer1, layer2, layer2.a); + } +`; + +const width = 500; +const height = 500; + +const uniforms = { + center:[width/2, height/2] +}; +const u = new Uniforms(uniforms).wiggle("center_A", width/2, 50,100, 2,4); +new Shader(width, height, fragment, u).center().drag(); +END EXAMPLE + +EXAMPLE +// a double spiral with speed set by iChange uniform +// which is controlled by the shader rate parameter/property +F.color = purple; +const fragment = ` + void mainImage(out vec4 fragColor, in vec2 fragCoord) { + // https://www.shadertoy.com/view/cl3BWr + vec2 uv = 0.5-fragCoord/iResolution.xy; + uv.y*=iResolution.y/iResolution.x; + float angle=atan(uv.y, uv.x); + float stage=sin(-iChange + 10.*(length(uv)*10.+.2*angle)); + fragColor = vec4(mix(.1,.9,smoothstep(-1.,1.,stage/fwidth(stage)))); + } +`; +const shader = new Shader(W, H, fragment).center(); + +// shader.animate({ // would animate the rate +// props:{rate:5}, +// time:5, +// rewind:true, +// loop:true +// }) + +const slider = new Slider({ + min:0, + max:1, + currentValue:.5, + barLength:100 +}).pos(50,50,RIGHT,BOTTOM).change(()=>{ + shader.rate = Math.exp(slider.currentValue*3)-1; +}); +shader.rate = Math.exp(slider.currentValue*3)-1; +END EXAMPLE + +EXAMPLE +// Make a chessboard with a Fragment Shader - from https://medium.com/@banksysan_10088/webgl-checkerboard-42e15490603c +// Change the col and row counts with a Stepper using Uniforms +const fragment = ` + uniform vec2 counts; // cols and rows + uniform vec4 color1; // rgba + uniform vec4 color2; + + void mainImage( out vec4 fragColor, in vec2 fragCoord ) { + vec2 boardCoordinates = floor(fragCoord.xy * counts.xy / iResolution.xy); + float xMod = mod(boardCoordinates.x, 2.0); + float yMod = mod(boardCoordinates.y, 2.0); + float state = mod(xMod + yMod, 2.0); + fragColor = mix(color1, color2, state); + } +`; + +const start = 8; +const uniforms = new Uniforms({ + counts:[start, start], + color1:[0,0,0,1], + color2:[1,1,1,1] +}); +const board = new Shader(500, 500, fragment, uniforms, null, false).center(); // false for not dynamic + +const stepper = new Stepper({min:1, max:20, currentValue:start}).sca(.8).pos(0,30,CENTER,BOTTOM).change(()=>{ + uniforms.counts_A = stepper.currentValue; + uniforms.counts_B = stepper.currentValue; + board.update(); // update needed as dynamic is set to false + S.update(); +}); +END EXAMPLE + +EXAMPLE +// make a chessboard from a little bw image - from https://medium.com/@banksysan_10088/webgl-checkerboard-42e15490603c +// not sure if this is the easiest or only way to do this... +new Frame(FIT, 1024, 768, light, dark, ready, "board.jpg"); +function ready() { + const vertex = ` + in vec3 vertexPosition; + out vec3 vPosition; // will get passed to fragment shader + void main() { + vPosition = vertexPosition; + gl_Position = vec4(vertexPosition, 1.0); + } + `; + + const fragment = ` + uniform sampler2D TEXTURE; // texture captured here + in vec3 vPosition; + void mainImage( out vec4 fragColor, in vec2 fragCoord ) { + // texture comes in flipped and on different coordinates + vec2 normalizedCoordinates = (vPosition.xy * vec2(0.5, -0.5)) + vec2(0.5, 0.5); + fragColor = texture(TEXTURE, normalizedCoordinates); // note this is texture2D() in OpenGL 1 + } + `; + + const before = function(program, gl, canvas) { + const textureImageElement = new Pic("board.jpg").image; + const texture = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, texture); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); // nearest as linear will blend + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, textureImageElement); + } + + const board = new Shader(500, 500, fragment, null, vertex, false, before).center(); +} +END EXAMPLE + +EXAMPLE +// make a chessboard with the Vertex Shader - from https://medium.com/@banksysan_10088/webgl-checkerboard-42e15490603c +// adding only certain squares - add a Fragment Shader with a color to change the default black +const after = function(program, gl, canvas, vertexData) { + const counts = [8,8]; + const size = {width:2/counts[0], height:2/counts[1]}; + let rowAlternate = true; + for (let i=-1; i<1; i+=size.width) { + let cellAlternate = rowAlternate; + for (let j=-1; j<1; j+=size.height) { + cellAlternate = !cellAlternate; + if (cellAlternate) continue; // do not add anything + const v1 = [i, j]; + const v2 = [i, j + size.height]; + const v3 = [i + size.width, j + size.height]; + const v4 = [i + size.width, j]; + vertexData.push(...v1); + vertexData.push(...v3); + vertexData.push(...v2); + vertexData.push(...v3); + vertexData.push(...v1); + vertexData.push(...v4); + } + rowAlternate = !rowAlternate; + } +} + +new Rectangle(500,500,white).center(); +const board = new Shader({ + width:500, + height:500, + dynamic:false, + postCall:after, + strip:false // use TRIANGLES not TRIANGEL_STRIP (might not show on iOS) +}).center(); +END EXAMPLE + +PARAMETERS +** supports DUO - parameters or single object with properties below +** supports OCT - parameter defaults can be set with STYLE control (like CSS) +width - (default 500) the width of the Shader + this can be retrieved in the Shader code as iResolution.x +height - (default 500) the height of the Shader + this can be retrieved in the Shader code as iResolution.y +fragment - provide the code as a string for the Fragment Shader which affects the pixels + This can be passed in as a variable with `` for multiline or preloaded as an asset and read using asset("filename.glsl") + ShaderToy uses "void mainImage(out vec4 fragColor, in vec2 fragCoord) {}" + But basic GLSL uses main(), fragColor for OUT, and gl_FragCoord as IN + ZIM converts the ShaderToy so that you can use either. + To see the final Fragment Shader in the console set the Shader() log parameter to true + Here is the default Fragment Shader for OpenGL 3 (verion 1 is slightly different): + // START AUTO - do not include below + #version 300 es + precision mediump float; + // plus all the default Uniforms mentioned above + // END AUTO - do not include above + void main() { + vec2 fragCoord = gl_FragCoord.xy; // a 2D variable of IN coordinates + gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0); // OUT variable + } +uniforms - (default null) a ZIM Uniform object or an object literal {} with uniform names and values (use arrays for vectors) + if a {} is passed in, a Uniform object will be made and in either case available as the uniform property of the Shader + the uniform properties match the {} properties except any array is made into individual properties to make animation easier + For instance, passing in: + { + alpha:.5, + position:[100,200] + } + would result in three properties on the uniform object: + shader.uniform.alpha // .5 + shader.position_A // 100 + shader.position_B // 200 + These could be animated or wiggled like: + shader.uniform.animate({position_A:300}, 2); // animate position.x inside the shader + shader.wiggle("alpha", .5, .1, .4, 1, 2); // wiggle alpha inside the shader - probably on the "z" or "a" component of a color vec4 +vertex - provide the code as a string for the Vertex Shader which affects the points + this will be used less with 2D outputs - see the Fragment Shader parameter for similar details + To see the final Fragment Shader in the console set the Shader() log parameter to true + Here is the default Vertex Shader for OpenGL 3 (verion 1 is slightly different): + // START AUTO - do not include below + precision mediump float; + // plus all the default Uniforms mentioned above + vec2 fragCoord = gl_FragCoord.xy; // a 2D variable of IN coordinates + // END AUTO - do not include above + void main() { + gl_Position = vec4(vertexPosition, 0.0, 1.0); // OUT variable + } +dynamic - (default true) set to false to not update the uniforms of the shaders constanty +preCall - (default null) a function to run just before before gl.useProgram(program); and after createProgram(gl, vertex, fragment) + the function will receive (program, gl, canvas) arguments to its parameters +postCall - (default null) a function to run after createProgram(gl, vertex, fragment) and gl.useProgram(program); + the function will receive (program, gl, canvas, vertexData) arguments to its parameters + Note: the vertexData is a [] and can be filled in this function + if it is not filled then it defaults to two triangles making a rectangle of width and height + Also see the strip parameter below +rate - |ZIM VEE| (default 1) set the rate of the iChange uniform. + many shaders use iTime to animate the effect. Instead, you can use an iChange uniform + and setting a rate of 2 will animate it twice as fast, .5 half as fast, etc. + this can be set with a ZIM VEE value and can also be adjusted with the rate property of the shader + (this could also be done with the iFrameRate uniform but then the Ticker.setFPS() would have to be changed) +version - (default "#version 300 es") string version to add to top of shader code - would suggest leaving this as default + use "" for lower versions. Higher versions probably will work using the # +canvas - (default a canvas tag) a canvas tag will be created for the Shader or pass in an existing canvas tag + this will be available as the canvas property +vertexPosition - (default "vertexPosition") the variable name for the vertexPosition +strip - (default true) use gl.TRIANGLE_STRIP - set to false to use gl.TRIANGLES for vertex data + Note: could not get gl.TRIANGLES working on iOS (iPad2) + strip:true will use vertexData = [1.0, 1.0, -1.0, 1.0, 1.0, -1.0, -1.0, -1.0]; // two triangles strips + strip:false will use: vertexData = [-1.0, -1.0, 1.0, 1.0, -1.0, 1.0, 1.0, 1.0, -1.0, -1.0, 1.0, -1.0]; // two triangles + in either case, if vertexData is set in the postCall then this overrides the default vertexData above. +log - (default false) - set to true to log the vertex and fragment shaders in the console - in that order +style - (default true) set to false to ignore styles set with the STYLE - will receive original parameter defaults +group - (default null) set to String (or comma delimited String) so STYLE can set default styles to the group(s) (like a CSS class) +inherit - (default null) used internally but can receive an {} of styles directly + +METHODS +update() - update the shader - not needed if dynamic is true (default) +clone() - makes a copy of the Shader +dispose() - removes from parent, removes event listeners - must still set outside references to null for garbage collection + +ALSO: ZIM 4TH adds all the methods listed under Bitmap (see above), such as: +drag(), hitTestRect(), animate(), sca(), reg(), mov(), center(), centerReg(), +addTo(), removeFrom(), loop(), outline(), place(), pos(), alp(), rot(), setMask(), etc. +ALSO: see the CreateJS Easel Docs for Container methods, such as: +on(), off(), getBounds(), setBounds(), uncache(), updateCache(), dispatchEvent(), etc. + +PROPERTIES +type - holds the class name as a String +dynamic - get or set whether the shader is dynamic +rate - |ZIM VEE| get or set the rate of the iChange uniform - can use for animating the shader - see also the rate parameter +canvas - the canvas for the shader +gl - the WebGL context for the canvas for the shader +uniforms - the uniforms object stored on the shader - use uniforms.obj for the original object literal + uniforms holds all the properties as individual properties + vectors (arrays) are accessed as vector_A, vector_B, vector_C, vector_D + see ZIM Uniforms for more info +fragment - the fragment shader - also see the log parameter to view final shaders in console +vertex - the vertex shader +program - the WebGL program with the shaders +ticker - the ZIM Ticker that runs the updates + +ALSO: see ZIM Bitmap for properties such as: +width, height, widthOnly, heightOnly, draggable, level, depth, group +blendMode, hue, saturation, brightness, contrast, etc. + +ALSO: see the CreateJS Easel Docs for Bitmap properties, such as: +x, y, rotation, scaleX, scaleY, regX, regY, skewX, skewY, +alpha, cursor, shadow, name, mouseEnabled, parent, etc. + +EVENTS +See the CreateJS Easel Docs for Bitmap events such as: +added, click, dblclick, mousedown, mouseout, mouseover, pressdown (ZIM), pressmove, pressup, removed, rollout, rollover +--*///+50.96 +zim.Shader = function(width, height, fragment, uniforms, vertex, dynamic, preCall, postCall, rate, version, canvas, vertexPosition, strip, log, style, group, inherit) { + var sig = "width, height, fragment, uniforms, vertex, dynamic, preCall, postCall, rate, version, canvas, vertexPosition, strip, log, style, group, inherit"; + var duo; if (duo = zob(zim.Shader, arguments, sig, this)) return duo; + z_d("50.96"); + this.group = group; + var DS = style===false?{}:zim.getStyle("Shader", this.group, inherit); + var s = makeShader(DS, width, height, fragment, uniforms, vertex, dynamic, preCall, postCall, rate, version, canvas, vertexPosition, strip, log, this); + + this.zimBitmap_constructor(s.canvas); + this.type = "Shader"; + + this.uniforms = s.uniforms; // now a Uniforms object - use uniforms.obj to see original + this.canvas = s.canvas; + this.gl = s.gl; + this.setUniform = s.setUniform; + this.update = s.update; + this.program = s.program; + this.fragment = s.fragment; + this.vertex = s.vertex; + this.ticker = s.ticker; + var that = this; + var _dynamic = s.dynamic; + + Object.defineProperty(this, 'dynamic', { + get: function() { + return _dynamic; + }, + set: function(value) { + if (value) { + zim.Ticker.add(this.ticker); + } else { + zim.Ticker.remove(this.ticker); + } + _dynamic = value?true:false; + } + }); + + if (style!==false) zim.styleTransforms(this, DS); + + this.clone = function() { + return that.cloneProps(new zim.Shader(width, height, fragment, uniforms, vertex, dynamic, preCall, postCall, rate, version, canvas, vertexPosition, strip, log, style, this.group, inherit)); + }; + + this.dispose = function(temp, b, disposing) { + if (this.ticker) zim.Ticker.remove(this.ticker); + if (this.uniforms) this.uniforms.dispose(); + this.canvas = null; + this.gl = null; + this.program = null; + this.ticker = null; + if (!disposing) this.zimBitmap_dispose(true); + return true; + } + +} +zim.extend(zim.Shader, zim.Bitmap, ["clone", "dispose"], "zimBitmap", false); +//-50.96 + +/*-- +zim.ShaderOverlay = function(width, height, fragment, uniforms, vertex, dynamic, preCall, postCall, rate, version, canvas, vertexPosition, strip, log, style, group, inherit) + +ShaderOverlay +zim class - extends a zim.Tag + +DESCRIPTION +ShaderOverlay is a ZIM Tag() that holds a canvas with a shader from provided shader code. +This is the same as ZIM Shader() so see the Docs for Shader above. +The only difference is that the shader canvas is placed in a Tag() so overlayed on ZIM. +This can also be underlayed by setting the z-index of the tag. + +The advantage of a tag is that the canvas is used directly with its WebGL context +rather than passed into a Bitmap() to be displayed on the Canvas2D context. + +The disadvantage is that the tag can only be overlayed or underlayed +and not be in the normal ZIM canvas container levels. +Also, if manually adjusting, a shaderOverlay.update() might need to be called. +But for the most part, the Tag() should take care of it - for instance, resizing the window. +See ZIM Tag() for more tips regarding update(). + +EXAMPLE +new Frame(FILL, 1024, 768, clear, clear, ready); +function ready() { + const fragment = ` + void main() { + vec2 uv = gl_FragCoord.xy / iResolution.xy; + vec3 color = 0.5 + 0.5 * cos(iTime + uv.xyx + vec3(0,2,4)); + gl_FragColor = vec4(vec3(color), 1.0); + } + `; + const shader = new zim.ShaderOverlay(W, H, fragment).center(); + shader.tag.style.zIndex = -50; // put shader beneath stage + + const list = new List({ + backdropColor:faint, + bgColor:white.toAlpha(.5), + rollBgColor:dark.toAlpha(.5), + selectedBgColor:dark.toAlpha(.8), + }).sca(1.5).center(); + F.on("resize", ()=>{list.center();}); +} // end ready +END EXAMPLE + +--*///+50.962 +zim.ShaderOverlay = function(width, height, fragment, uniforms, vertex, dynamic, preCall, postCall, rate, version, canvas, vertexPosition, strip, log, style, group, inherit) { + var sig = "width, height, fragment, uniforms, vertex, dynamic, preCall, postCall, rate, version, canvas, vertexPosition, strip, log, style, group, inherit"; + var duo; if (duo = zob(zim.ShaderOverlay, arguments, sig, this)) return duo; + z_d("50.962"); + this.group = group; + var DS = style===false?{}:zim.getStyle("ShaderOverlay", this.group, inherit); + var s = makeShader(DS, width, height, fragment, uniforms, vertex, dynamic, preCall, postCall, rate, version, canvas, vertexPosition, strip, log, this); + + this.zimTag_constructor(width, height); + this.type = "ShaderOverlay"; + + this.uniforms = s.uniforms; // now a Uniforms object - use uniforms.obj to see original + this.canvas = s.canvas; + this.gl = s.gl; + this.setUniform = s.setUniform; + this.update = s.update; + this.program = s.program; + this.fragment = s.fragment; + this.vertex = s.vertex; + this.ticker = s.ticker; + var that = this; + var _dynamic = s.dynamic; + + that.canvas.style.visibility = "visible"; + that.tag.appendChild(that.canvas); + + Object.defineProperty(this, 'dynamic', { + get: function() { + return _dynamic; + }, + set: function(value) { + if (value) { + zim.Ticker.add(this.ticker); + } else { + zim.Ticker.remove(this.ticker); + } + _dynamic = value?true:false; + } + }); + + if (style!==false) zim.styleTransforms(this, DS); + + this.clone = function() { + return that.cloneProps(new zim.Shader(width, height, fragment, uniforms, vertex, dynamic, preCall, postCall, rate, version, canvas, vertexPosition, strip, log, style, this.group, inherit)); + }; + + this.dispose = function(temp, b, disposing) { + if (this.ticker) zim.Ticker.remove(this.ticker); + if (this.uniforms) this.uniforms.dispose(); + this.canvas = null; + this.gl = null; + this.program = null; + this.ticker = null; + if (!disposing) this.zimTag_dispose(true); + return true; + } + +} +zim.extend(zim.ShaderOverlay, zim.Tag, ["clone", "dispose"], "zimTag", false); +//-50.962 + +/*-- +makeShader = function(DS, width, height, fragment, uniforms, vertex, dynamic, preCall, postCall, rate, version, canvas, vertexPosition, strip, log, tether) + +makeShader +function - used internally only + +DESCRIPTION +This is the general code to make a Shader in JS. It is used by Shader() and ShaderOverlay(). +It is included in the docs to more easily reference the code. +See Shader() and ShaderOverlay() for more information. + +The code is a compilation of MDN Shader instructions and organization by David Banks +https://developer.mozilla.org/en-US/docs/Web/API/WebGLShader +https://medium.com/@banksysan_10088/webgl-checkerboard-42e15490603c +With a portion in the middle of ZIM code to handle uniforms, etc. +primarily to match default uniforms at ShaderToy https://www.shadertoy.com/ +Introduced in ZIM 016 + +--*///+50.964 +function makeShader(DS, width, height, fragment, uniforms, vertex, dynamic, preCall, postCall, rate, version, canvas, vertexPosition, strip, log, tether) { + z_d("50.964"); + + if (zot(width)) width = DS.width!=null?DS.width:500; + if (zot(height)) height = DS.radius!=null?DS.height:500; + if (zot(rate)) rate = DS.rate!=null?DS.rate:1; + if (zot(version)) version = DS.version!=null?DS.version:"#version 300 es"; + var defaultV; + var defaultF; + if (version == "") defaultV = "attribute vec3 vertexPosition;\nvoid main() {\n\tgl_Position = vec4(vertexPosition, 1.0);\n}"; + else defaultV = "in vec3 vertexPosition;\nvoid main() {\n\tgl_Position = vec4(vertexPosition, 1.0);\n}"; + if (zot(vertex)) vertex = DS.vertex!=null?DS.vertex:defaultV; + if (version == "") defaultF = "void main() {\n\tgl_FragColor = vec4(0.0, 0.0, 0.0, 1.0);\n}"; + else defaultF = "out vec4 fragColor;\nvoid main() {\n\tfragColor = vec4(0.0, 0.0, 0.0, 1.0);\n}"; + if (zot(fragment)) fragment = DS.fragment!=null?DS.fragment:defaultF; + if (zot(uniforms)) uniforms = DS.uniforms!=null?DS.uniforms:null; + if (zot(dynamic)) dynamic = DS.dynamic!=null?DS.dynamic:true; + if (zot(preCall)) preCall = DS.preCall!=null?DS.preCall:null; + if (zot(postCall)) postCall = DS.postCall!=null?DS.postCall:null; + if (zot(canvas)) canvas = DS.canvas!=null?DS.canvas:null; + if (zot(vertexPosition)) vertexPosition = DS.vertexPosition!=null?DS.vertexPosition:"vertexPosition"; + if (zot(strip)) strip = DS.strip!=null?DS.strip:true; + if (zot(log)) log = DS.log!=null?DS.log:null; + + if (zot(canvas)) { + canvas = document.createElement("canvas"); + canvas.setAttribute("width", width); + canvas.setAttribute("height", height); + canvas.setAttribute("id", "shaderCanvas"); + } + + fragment = version + "\nprecision mediump float;\n\n// ZIM Default Uniforms\nuniform vec3 iResolution;\nuniform float iTime;\nuniform float iTimeDelta;\nuniform float iFrameRate;\nuniform float iFrame;\nuniform vec4 iMouse;\nuniform vec4 iDate;\nuniform float iChange;\n" + fragment; + vertex = version + "\nprecision mediump float;\n\n// ZIM Default Uniforms\nprecision mediump float;\nuniform vec3 iResolution;\nuniform float iTime;\nuniform float iTimeDelta;\nuniform float iFrameRate;\nuniform float iFrame;\nuniform vec4 iMouse;\nuniform vec4 iDate;\nuniform float iChange;\n\n" + vertex; + + // Replace ShaderToy mainImage + var matches = fragment.match(/(void |.*)mainImage\(\s*(out\s*vec4\s*([^,]*)),\s*in\s*(vec[234]\s*[^)\s]*)\s*\)/i); + fragment = fragment.replace(/(void |.*)mainImage\(\s*(out\s*vec4\s*[^,]*),\s*in\s*(vec[234]\s*[^)\s]*)\s*\)/i, "$2;\nvoid main()"); + if (matches && matches[4]) { + var append = "main() {\n\t"+matches[4]+" = gl_FragCoord.xy; // ZIM shadertoy adjust"; + fragment = fragment.replace(/main\(\)\s*{/i, append); + fragment = fragment.replace(/main\(\)\s*(\n|\r)\s*{/i, append); + if (matches && matches[3]) fragment = fragment.replace(/gl_FragColor/g, matches[3]); + } + if (log) { + zogd("VERTEX SHADER:\n\n" + vertex); + zogd("FRAGMENT SHADER:\n\n" + fragment); + } + + if (zot(canvas)) { + canvas = document.createElement("canvas"); + canvas.setAttribute(id, "shaderCanvas_" + zim.makeID()); + } + if (!zot(uniforms)) { + if (uniforms.type != "Uniforms") { + uniforms = new zim.Uniforms(uniforms); + } + } + + tether.dynamic = dynamic; + tether.rate = zik(rate); + tether.change = 0; + + // base code from https://medium.com/@banksysan_10088/webgl-checkerboard-42e15490603c + var gl = canvas.getContext('webgl2'); + gl.enable(gl.CULL_FACE); + gl.cullFace(gl.BACK); + gl.frontFace(gl.CCW); + gl.clearColor(1.0, 1.0, 1.0, 1.0); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + + var program = createProgram(gl, vertex, fragment); + if (preCall && typeof preCall=="function") preCall(program, gl, canvas); // ZIM + gl.useProgram(program); + var vertexData = []; + if (postCall && typeof postCall=="function") { + postCall(program, gl, canvas, vertexData); + } + + if (strip) { + if (vertexData.length == 0) vertexData = [1.0, 1.0, -1.0, 1.0, 1.0, -1.0, -1.0, -1.0]; // two triangles strips + } else { + if (vertexData.length == 0) vertexData = [-1.0, -1.0, 1.0, 1.0, -1.0, 1.0, 1.0, 1.0, -1.0, -1.0, 1.0, -1.0]; // two triangles + } + var vertexBuffer = gl.createBuffer(); + gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer); + gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertexData), gl.STATIC_DRAW); + var vertexAttributeLocation = gl.getAttribLocation(program, vertexPosition); + gl.vertexAttribPointer(vertexAttributeLocation, 2, gl.FLOAT, false, 2 * Float32Array.BYTES_PER_ELEMENT, 0); + gl.enableVertexAttribArray(vertexAttributeLocation); + + // ZIM + function setUniform(type, name, v1, v2, v3, v4) { + if (zot(type)) type = "1f"; + if (zot(name)) {zogf("uniform - must have name"); return;} + gl["uniform"+type](gl.getUniformLocation(program, name), v1, v2, v3, v4); + } + var frameNum = 0; + function update(e) { + // process uniforms + if (!zot(uniforms)) { + uniforms.update(); // takes from properties and updates obj + zim.loop(uniforms.obj, function(name,val) { + var n = 1; + if (Array.isArray(val)) n = val.length; + else val = [val]; + // if (name=="time") zog(val[0]) + setUniform(n+"f", name, val[0], val[1], val[2], val[3]); + }); + } + + // DEFAULT UNIFORMS + var date = new Date(); + setUniform("3f", "iResolution", width, height); + var time = zim.decimals(date.getHours()*60*60+date.getMinutes()*60+date.getSeconds()+date.getMilliseconds()/1000, 4); + setUniform("4f", "iDate", date.getFullYear(), date.getMonth(), time); + setUniform("1f", "iTime", e?zim.decimals(e.runTime/1000, 4):0); + setUniform("1f", "iChange", tether.rate); + setUniform("1f", "iChange", tether.change+=tether.rate/60); + setUniform("1f", "iTimeDelta", e?zim.decimals(e.delta/1000, 4):0); + setUniform("1f", "iFrame", e?frameNum++:0); + setUniform("1f", "iFrameRate", zim.decimals(createjs.Ticker.getMeasuredFPS())); + if (tether.stage) { + var mou = tether.globalToLocal(tether.stage.frame.mouseX, tether.stage.frame.mouseY); + setUniform("4f", "iMouse", mou.x, height-mou.y, downCheck?1:0, clickCheck?1:0); + clickCheck = false; + } + + if (strip) gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); + else gl.drawArrays(gl.TRIANGLES, 0, vertexData.length); + + } + var downCheck = false; + var clickCheck = false; + tether.on("mousedown", function() {downCheck = true;}); + tether.on("pressup", function() {downCheck = false;}); + tether.tap(function() {clickCheck = true;}); + update(); + var ticker = zim.Ticker.add(update); + if (!dynamic) { + zim.Ticker.remove(ticker); + setTimeout(update, 50); + if (tether.stage) tether.stage.update(); + } + return {dynamic:dynamic, fragment:fragment, vertex:vertex, program:program, uniforms:uniforms, gl:gl, canvas:canvas, update:update, setUniform:setUniform, ticker:ticker}; + + // private + function createProgram(webGlContext, vertexShaderText, fragmentShaderText, verify) { + if (zot(verify)) verify = true; + if (!webGlContext) { + console.error("This browser doesn't support WebGL"); + } + + webGlContext.clearColor(0.0, 0.0, 0.0, 0.0); + webGlContext.clear( + webGlContext.COLOR_BUFFER_BIT | webGlContext.DEPTH_BUFFER_BIT + ); + + var vertexShader = webGlContext.createShader(webGlContext.VERTEX_SHADER); + var fragmentShader = webGlContext.createShader( + webGlContext.FRAGMENT_SHADER + ); + + webGlContext.shaderSource(vertexShader, vertexShaderText); + webGlContext.shaderSource(fragmentShader, fragmentShaderText); + + webGlContext.compileShader(vertexShader); + webGlContext.compileShader(fragmentShader); + + var compileStatus = { + vertexStatus: + webGlContext.getShaderParameter( + vertexShader, + webGlContext.COMPILE_STATUS + ) || webGlContext.getShaderInfoLog(vertexShader), + fragmentStatus: + webGlContext.getShaderParameter( + fragmentShader, + webGlContext.COMPILE_STATUS + ) || webGlContext.getShaderInfoLog(fragmentShader), + }; + + if ( + compileStatus.vertexStatus !== true || + compileStatus.fragmentStatus !== true + ) { + throw new Error("Failed to compile. " + JSON.stringify(compileStatus, null, 2)); + } + + var program = webGlContext.createProgram(); + + webGlContext.attachShader(program, vertexShader); + webGlContext.attachShader(program, fragmentShader); + webGlContext.linkProgram(program); + + var linkingStatus = + webGlContext.getProgramParameter(program, webGlContext.LINK_STATUS) || + webGlContext.getProgramInfoLog(program); + + if (linkingStatus !== true) { + throw new Error("Linking filed:\n" + linkingStatus); + } + + if (verify) { + webGlContext.validateProgram(program); + var validationStatus = + webGlContext.getProgramParameter( + program, + webGlContext.VALIDATE_STATUS + ) || webGlContext.getProgramInfoLog(program); + + if (validationStatus !== true) { + throw new Error("Validation failed.\n" + validationStatus); + } + } + + return program; + } +} // end makeShader +//-50.964 + +/*-- +zim.Uniforms = function(obj) + +Uniforms +zim class - extends a zim.Container which extends a createjs.Container + +DESCRIPTION +Makes an object to hold uniforms for a ZIM Shader() or ShaderOverlay(). +(This is not to be used as a display object despite extending a Container) +Uniforms are variables to pass in to the shader code. +They are often arrays to make vectors and array values are hard to animate. +ZIM Uniforms stores each array element as an individual property. +Uniforms extends a Container giving it animate() and wiggle() methods. +This means we can animate or wiggle any of the Uniforms properties +and ZIM will update the shader if the shader's dynamic parameter or property is true. + +HOW IT WORKS +An object literal {} is passed into the Uniforms(obj). +This will have all the uniform names and values. +If the value is an array that is used to set vector uniforms, then ZIM splits this up +so that each array value has a property called the name_A, name_B, name_C, name_D as needed. +There are only ever 2, 3, or 4 component vectors. +If the value is a single value, then it is stored as the name alone. + +NOTE: as of ZIM 5.5.0 the zim namespace is no longer required (unless zns is set to true before running zim) + +EXAMPLE +const uniforms = new Uniforms({ + age:20, + color:[.5, .2, .8, 1] +}); + +// the the Uniforms object will have: +zog(uniforms.age); // 20 +zog(uniforms.color_A); // .5 +zog(uniforms.color_B); // .2 +zog(uniforms.color_C); // .8 +zog(uniforms.color_D); // 1 + +uniforms.wiggle("color_A", .5, .2, .4, 1, 2); // would wiggle the red + +// inside the shader they must be declared if they are to be used +// uniform float age; +// uniform vec4 color; + +const fragment = ` + uniform vec4 color; // declare our uniform - only using color, age will be ignored + void mainImage( out vec4 fragColor, in vec2 fragCoord ) { + fragColor = color; // gl_FragColor or fragColor is the default output + } +`; + +new Shader(500, 500, fragment, uniforms).center().drag(); // draggable animated color box +END EXAMPLE + +PARAMETERS +obj - the object literal {} with uniform properties and values + +METHODS +update() - update the uniforms as long as dynamic is true or the update() method of the shader is called + this is handled automatically by the shader update() so may as well call update() on shader + +PROPERTIES +type - holds the class name as a String +obj - the original object literal - its properties get updated as the uniforms properties are updated +*** +Each property in the ojbect literal +with arrays being split into a property for each element in the format +name_A, name_B, name_C, name_D as dicted by the number of elements in the value for the property +{year:2024, dimensions:[200,500]} +would get: +uniforms.year = 2024 +uniforms.dimensions_A = 200 +uniforms.dimensions_B = 500 +--*///+50.966 +zim.Uniforms = function(obj) { + // extends container to give wiggle() and animate() + // to use these on any uniform value + this.zimContainer_constructor(); + z_d("50.966"); + var that = this; + that.type = "Uniforms"; + zim.loop(obj, function(item, val) { + if (val) + that[obj] + }); + that.obj = obj; + var suf = ["A","B","C","D"]; + zim.loop(obj, function(name,val) { + // make props from obj + if (!Array.isArray(val)) val = [val]; + zim.loop(val, function(item, i, t) { + if (t==1) { + that[name] = val[0]; + } else { + that[name + "_" + suf[i]] = val[i]; + } + }); + }); + that.update = function() { + // put props back into obj + zim.loop(obj, function(name,val) { + if (!Array.isArray(val)) val = [val]; + zim.loop(val, function(item, i, t) { + if (t==1) { + val[0] = that[name]; + } else { + val[i] = that[name + "_" + suf[i]]; + } + }); + }); + } +} +zim.extend(zim.Uniforms, zim.Container, null, "zimContainer", false); +//-50.966 + // SUBSECTION ZIM SHAPES @@ -12188,7 +13641,7 @@ zim.extend(zim.CustomShape, zim.Container, null, "zimContainer", false); /*-- -zim.Circle = function(radius, color, borderColor, borderWidth, dashed, percent, percentClose, strokeObj, style, group, inherit) +zim.Circle = function(radius, color, borderColor, borderWidth, dashed, percent, percentClose, percentArc, strokeObj, style, group, inherit) Circle zim class - extends a zim.CustomShape which extends a zim.Container which extends a createjs.Container @@ -12238,6 +13691,16 @@ dashed - (default false) set to true for dashed border (if borderWidth or border eg. [20, 10] is 20 line and 10 space repeated and [20,100,50,10] is 20 line, 100 space, 50 line, 10 space, etc. percent - (default 100) set to a percentage of a circle (arc) - registration stays at radius center, bounds shrink to arc percentClose - (default true) set to false to not close the border of a circle with percent set +percentArc - (default false) set to a percent to make moon shapes - must have percent turned on + the value is the distance the arc-making circle is placed from the original circle's edge + this distance is given as a percentage of the original circle's radius + so if percentArc is set to 0 then the arc-making circle is at the radius (the edge) of the original circle + if the percentArc is set to 50 then the arc-making circle is half the radius outside the original radius and the arc is less + if the percentArc is set to -50 then the arc-making circle is half the radius inside the original radius and the arc is more + Note, due to canvas winding, the arc will not do very thin cresents as expected + instead once the inner arc is as wide as the outer arc, it makes a straight line + for thin crecents, overlap the circle with a circle that matches the background color + or if the background is an image, etc. then mask a clone of the background with the arc circle strokeObj - (default {caps:"butt", joints:"miter", miterLimit:10, ignoreScale:false}) set to adjust stroke properties // note, not all applicable to a Circle - perhaps just ignoreScale... caps options: "butt", "round", "square" or 0,1,2 @@ -12299,6 +13762,7 @@ radius - gets or sets the radius. percentage - get or set the percent of the circle (see percent parameter) NOTE not percent property which interferes with animate percent percentClose - get or set the percent close of the circle (see percentClose parameter) +percentArc - get or set the percent arc (see percentArc parameter) mouseChildren - set to false to avoid dragging the shape inside to drag or interact with objects inside then set mouseChildren to true veeObj - an object with ZIM VEE original parameters:value allowing the ZIM VEE values to be referenced @@ -12316,14 +13780,16 @@ EVENTS See the CreateJS Easel Docs for Container events such as: added, click, dblclick, mousedown, mouseout, mouseover, pressdown (ZIM), pressmove, pressup, removed, rollout, rollover --*///+51 - zim.Circle = function(radius, color, borderColor, borderWidth, dashed, percent, percentClose, strokeObj, style, group, inherit) { - var sig = "radius, color, borderColor, borderWidth, dashed, percent, percentClose, strokeObj, style, group, inherit"; + zim.Circle = function(radius, color, borderColor, borderWidth, dashed, percent, percentClose, percentArc, strokeObj, style, group, inherit) { + var sig = "radius, color, borderColor, borderWidth, dashed, percent, percentClose, percentArc, strokeObj, style, group, inherit"; var duo; if (duo = zob(zim.Circle, arguments, sig, this)) return duo; z_d("51"); this.zimCustomShape_constructor(null,null,null,null,false); this.type = "Circle"; this.group = group; - var DS = style===false?{}:zim.getStyle(this.type, this.group, inherit); + // var DS = style===false?{}:zim.getStyle(this.type, this.group, inherit); + // adjustments for groupOnly - to set style:false and still use group + var DS = style===false?group!=null?zim.getStyle(null,null,inherit,this.group):{}:zim.getStyle(this.type,this.group,inherit); if (zot(radius)) radius = DS.radius!=null?DS.radius:50; if (zot(dashed)) dashed = DS.dashed!=null?DS.dashed:false; if (zot(borderColor)) borderColor = DS.borderColor!=null?DS.borderColor:null; @@ -12333,8 +13799,10 @@ added, click, dblclick, mousedown, mouseout, mouseover, pressdown (ZIM), pressmo if (zot(color)) color = DS.color!=null?DS.color:(borderWidth>0?"rgba(0,0,0,0)":zim.black); if (zot(percent)) percent = DS.percent!=null?DS.percent:100; if (zot(percentClose)) percentClose = DS.percentClose!=null?DS.percentClose:true; + if (zot(percentArc)) percentArc = DS.percentArc!=null?DS.percentArc:null; if (zot(strokeObj)) strokeObj = DS.strokeObj!=null?DS.strokeObj:{}; + // PICK var oa = remember(radius, color, borderColor, borderWidth, percent); this.veeObj = {radius:oa[0], color:oa[1], borderColor:oa[2], borderWidth:oa[3], percent:oa[4]}; @@ -12375,8 +13843,16 @@ added, click, dblclick, mousedown, mouseout, mouseover, pressdown (ZIM), pressmo if (typeof percent == "number" && percent >= 0 && percent < 100) { var p = 360*percent/100/2; g.arc(0, 0, that._radius, (-p-90)*Math.PI/180, (p-90)*Math.PI/180, false); + + var dX = Math.sin(p*Math.PI/180)*that._radius; + var dY = Math.cos(p*Math.PI/180)*that._radius; + if (!zot(percentArc)) { + var r2 = Math.sqrt(Math.pow(dX,2) + Math.pow((that._radius+dY)+percentArc/100*that._radius,2)); + var a1 = Math.asin(dX/r2); + g.arc(0, that._radius+percentArc/100*that._radius, r2, (-90*RAD+a1), (-90*RAD-a1), true); + } if (percentClose) g.cp(); - h = that._radius-Math.cos(p*Math.PI/180)*that._radius; + h = that._radius-dY; } else { g.dc(0,0,that._radius); } @@ -12415,6 +13891,15 @@ added, click, dblclick, mousedown, mouseout, mouseover, pressdown (ZIM), pressmo } }); + Object.defineProperty(that, 'percentArc', { + get: function() { + return percentArc; + }, + set: function(value) { + percentArc = value; + that.drawShape(); + } + }); // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // NOTE: extends ZIM CustomShape for more properties and a few functions. @@ -12422,8 +13907,10 @@ added, click, dblclick, mousedown, mouseout, mouseover, pressdown (ZIM), pressmo if (style!==false) zim.styleTransforms(this, DS); // global function - would have put on DisplayObject if had access to it - this.clone = function(exact) { - var newShape = that.cloneProps(new zim.Circle((exact||!zim.isPick(oa[0]))?that.radius:oa[0], (exact||!zim.isPick(oa[1]))?that.color:oa[1], (exact||!zim.isPick(oa[2]))?that.borderColor:oa[2], (exact||!zim.isPick(oa[3]))?that.borderWidth:oa[3], that.dashed, (exact||!zim.isPick(oa[4]))?percent:oa[4], percentClose, strokeObj, style, this.group, inherit)); + this.clone = function(exact, useStyle) { + var cloneStyle = zot(useStyle)?style:useStyle; + if (exact) cloneStyle = false; + var newShape = that.cloneProps(new zim.Circle((exact||!zim.isPick(oa[0]))?that.radius:oa[0], (exact||!zim.isPick(oa[1]))?that.color:oa[1], (exact||!zim.isPick(oa[2]))?that.borderColor:oa[2], (exact||!zim.isPick(oa[3]))?that.borderWidth:oa[3], that.dashed, (exact||!zim.isPick(oa[4]))?percent:oa[4], percentClose, percentArc, strokeObj, cloneStyle, this.group, inherit)); if (that.linearGradientParams) newShape.linearGradient.apply(newShape, that.linearGradientParams); if (that.radialGradientParams) newShape.radialGradient.apply(newShape, that.radialGradientParams); return newShape; @@ -12629,10 +14116,11 @@ zim.Rectangle = function(width, height, color, borderColor, borderWidth, corner, if (Array.isArray(that._corner)) { var r = that._corner; - if (Array.isArray(r) && r.length==2) r = [r,r,r,r]; + if (r.length==2) r = [r,r,r,r]; var temp = []; var flag = false; - for (var i=0; i