Flat, round, designer-friendly pseudo-3D engine for canvas & SVG
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 222 lines 7.7 kB view raw
1/** 2 * Texture 3 */ 4( function( root, factory ) { 5 // module definition 6 if ( typeof module == 'object' && module.exports ) { 7 // CommonJS 8 module.exports = factory(require('./vector')); 9 } else { 10 // browser global 11 var Zdog = root.Zdog; 12 Zdog.Texture = factory(Zdog.Vector); 13 } 14 }( this, function factory(Vector) { 15 16 /** 17 * Calculates the inverse of the matrix: 18 * | x1 x2 x3 | 19 * | y1 y2 y3 | 20 * | 1 1 1 | 21 */ 22 function inverse(x1, y1, x2, y2, x3, y3) { 23 let tp = [ 24 y2 - y3, x3 - x2, x2*y3 - x3*y2, 25 y3 - y1, x1 - x3, x3*y1 - x1*y3, 26 y1 - y2, x2 - x1, x1*y2 - x2*y1]; 27 let det = tp[2] + tp[5] + tp[8]; 28 return tp.map(function(x) { return x / det;}); 29 } 30 31 function parsePointMap(size, map) { 32 if (!Array.isArray(map) || !map.length) { 33 map = [0, 0, size[0], size[1]]; 34 } 35 if (typeof(map[0]) == "number") { 36 if (map.length < 4) { 37 let tmp = map; 38 map = [0, 0, size[0], size[1]]; 39 for (let i = 0; i < tmp.length; i++) { 40 map[i] = tmp[i]; 41 } 42 } 43 return [ 44 new Vector({x:map[0], y:map[1], z:1}), 45 new Vector({x:map[0] + map[2], y:map[1], z:1}), 46 new Vector({x:map[0], y:map[1] + map[3], z:1}) 47 ]; 48 } else { 49 return [new Vector(map[0]), new Vector(map[1]), new Vector(map[2])]; 50 } 51 } 52 53 var idCounter = 0; 54 55 const optionKeys = [ 56 'img', 57 'linearGrad', 58 'radialGrad', 59 'colorStops', 60 'src', 61 'dst' 62 ] 63 64 /** 65 * Creates a tecture map. Possible options: 66 * img: Image object to be used as texture 67 * linearGrad: [x1, y1, x2, y2] Array defining the linear gradient 68 * radialGrad: [x0, y0, r0, x1, y1, r1] Array defining the radial gradient 69 * colorStops: [offset1, color1, offset2, color2...] Array defining the color 70 * stops for the gradient, offset must be in range [0, 1] 71 * 72 * src: <surface definition> Represents the surface for the texture. Above 73 * gradient definition should be represented in this coordinate space 74 * dst: <surface definition> Represents the surface of the object. This allows 75 * keeping the texture definition independent of the surface definition 76 * 77 * <surface definition> Can be represented in one of the following ways: 78 * [x, y, width, height] => We use 3 points top-left, top-right, bottom-left 79 * [x, y] => image/gradient size is used for width and height with the above rule 80 * [vector, vector, vector] => provided points are used 81 */ 82 function Texture(options) { 83 this.id = idCounter++; 84 this.isTexture = true; 85 86 options = options || { } 87 for (var key in options ) { 88 if (optionKeys.indexOf( key ) != -1 ) { 89 this[key] = options[key]; 90 } 91 } 92 93 var size; 94 if (options.img) { 95 size = [options.img.width, options.img.height]; 96 } else if (options.linearGrad) { 97 size = [Math.abs(options.linearGrad[2] - options.linearGrad[0]), Math.abs(options.linearGrad[3] - options.linearGrad[1])]; 98 } else if (options.radialGrad) { 99 size = [Math.abs(options.radialGrad[3] - options.radialGrad[0]), Math.abs(options.radialGrad[4] - options.radialGrad[1])]; 100 } else { 101 throw "One of [img, linearGrad, radialGrad] is required"; 102 } 103 if (size[0] == 0) size[0] = size[1]; 104 if (size[1] == 0) size[1] = size[0]; 105 106 this.src = parsePointMap(size, options.src); 107 this.dst = parsePointMap(size, options.dst); 108 109 this.srcInverse = inverse( 110 this.src[0].x, this.src[0].y, 111 this.src[1].x, this.src[1].y, 112 this.src[2].x, this.src[2].y); 113 this.p1 = new Vector(); 114 this.p2 = new Vector(); 115 this.p3 = new Vector(); 116 this.matrix = [0, 0, 0, 0, 0, 0]; 117 }; 118 119 Texture.prototype.getMatrix = function() { 120 let m = this.matrix; 121 let inverse = this.srcInverse; 122 m[0] = this.p1.x * inverse[0] + this.p2.x * inverse[3] + this.p3.x * inverse[6]; 123 m[1] = this.p1.y * inverse[0] + this.p2.y * inverse[3] + this.p3.y * inverse[6]; 124 m[2] = this.p1.x * inverse[1] + this.p2.x * inverse[4] + this.p3.x * inverse[7]; 125 m[3] = this.p1.y * inverse[1] + this.p2.y * inverse[4] + this.p3.y * inverse[7]; 126 m[4] = this.p1.x * inverse[2] + this.p2.x * inverse[5] + this.p3.x * inverse[8]; 127 m[5] = this.p1.y * inverse[2] + this.p2.y * inverse[5] + this.p3.y * inverse[8]; 128 return m; 129 } 130 131 Texture.prototype.getCanvasFill = function(ctx) { 132 if (!this.pattern) { 133 if (this.img) { 134 this.pattern = ctx.createPattern(this.img, "repeat"); 135 } else { 136 this.pattern = this.linearGrad 137 ? ctx.createLinearGradient.apply(ctx, this.linearGrad) 138 : ctx.createRadialGradient.apply(ctx, this.radialGrad); 139 if (this.colorStops) { 140 for (var i = 0; i < this.colorStops.length; i+=2) { 141 this.pattern.addColorStop(this.colorStops[i], this.colorStops[i+1]); 142 } 143 } 144 } 145 } 146 // pattern.setTransform is not supported in IE, 147 // so transform the context instead 148 ctx.transform.apply(ctx, this.getMatrix()); 149 return this.pattern; 150 }; 151 152 const svgURI = 'http://www.w3.org/2000/svg'; 153 Texture.prototype.getSvgFill = function(svg) { 154 if (!this.svgPattern) { 155 if (this.img) { 156 this.svgPattern = document.createElementNS( svgURI, 'pattern'); 157 this.svgPattern.setAttribute("width", this.img.width); 158 this.svgPattern.setAttribute("height", this.img.height); 159 this.svgPattern.setAttribute("patternUnits", "userSpaceOnUse"); 160 this.attrTransform = "patternTransform"; 161 162 let img = document.createElementNS( svgURI, 'image'); 163 img.setAttribute("href", this.img.src); 164 this.svgPattern.appendChild(img); 165 } else { 166 var type, vals, keys; 167 if (this.linearGrad) { 168 type = "linearGradient"; 169 vals = this.linearGrad; 170 keys = ["x1", "y1", "x2", "y2"] 171 } else { 172 type = "radialGradient"; 173 vals = this.radialGrad; 174 keys = ["fx", "fy", "fr", "cx", "cy", "r"] 175 } 176 this.svgPattern = document.createElementNS( svgURI, type); 177 for (var i = 0; i < keys.length; i++) { 178 this.svgPattern.setAttribute(keys[i], vals[i]); 179 } 180 181 if (this.colorStops) { 182 for (var i = 0; i < this.colorStops.length; i+=2) { 183 let colorStop = document.createElementNS(svgURI, 'stop' ); 184 colorStop.setAttribute("offset", this.colorStops[i]); 185 colorStop.setAttribute("style", "stop-color:" + this.colorStops[i+1]); 186 this.svgPattern.appendChild(colorStop); 187 } 188 } 189 this.svgPattern.setAttribute("gradientUnits", "userSpaceOnUse"); 190 this.attrTransform = "gradientTransform"; 191 } 192 this.svgPattern.setAttribute("id", "texture_" + this.id); 193 this._svgUrl = 'url(#texture_' + this.id + ')'; 194 195 this.defs = document.createElementNS(svgURI, 'defs' ); 196 this.defs.appendChild(this.svgPattern); 197 } 198 199 this.svgPattern.setAttribute(this.attrTransform, 'matrix(' + this.getMatrix().join(' ') + ')'); 200 svg.appendChild( this.defs ); 201 return this._svgUrl; 202 } 203 204 // ----- update ----- // 205 Texture.prototype.reset = function() { 206 this.p1.set(this.dst[0]); 207 this.p2.set(this.dst[1]); 208 this.p3.set(this.dst[2]); 209 }; 210 211 Texture.prototype.transform = function( translation, rotation, scale ) { 212 this.p1.transform(translation, rotation, scale); 213 this.p2.transform(translation, rotation, scale); 214 this.p3.transform(translation, rotation, scale); 215 }; 216 217 Texture.prototype.clone = function() { 218 return new Texture(this); 219 }; 220 221 return Texture; 222} ) );