1 /**
  2  * Compositor object.
  3  *
  4  * @author Andrew J. Baker
  5  */
  6 
  7 /** @namespace Steppe */
  8 var Steppe = (function(Steppe) {
  9     /** @class */
 10     Steppe.Compositor = function(undefined) {
 11         var _heightmap = [],
 12             _outOfBoundsHeightmap = [],
 13             _textureArray = [];
 14 
 15         return {
 16             /**
 17              * Add a texture to the texture-array.
 18              *
 19              * @param {number} height The 'height' at which to apply the
 20              *                        texture.
 21              * @param {Image} textureImage The image to use as a texture.
 22              * @return {Compositor} This (fluent interface).
 23              */
 24             addTexture: function(height, textureImage) {
 25                 if ( !(textureImage instanceof HTMLImageElement)) {
 26                     throw('Invalid textureImage: not an instance of ' +
 27                         'HTMLImageElement');
 28                 }
 29 
 30                 if (height < 0 || height > 255) {
 31                     throw('Invalid height; must be in the range 0..255');
 32                 }
 33 
 34                 if (textureImage.width != 256 ||
 35                     textureImage.height != 256) {
 36                     throw('Invalid texture dimensions; must be 256x256');
 37                 }
 38 
 39                 var textureCanvas    = document.createElement('canvas');
 40                 textureCanvas.width  = textureImage.width;
 41                 textureCanvas.height = textureImage.height;
 42 
 43                 var textureContext = textureCanvas.getContext('2d');
 44 
 45                 textureContext.drawImage(textureImage, 0, 0,
 46                     textureCanvas.width, textureCanvas.height);
 47 
 48                 _textureArray[height] = {
 49                     data:   textureContext.getImageData(0, 0,
 50                                 textureCanvas.width,
 51                                 textureCanvas.height).data,
 52                     height: textureCanvas.height,
 53                     width:  textureCanvas.width
 54                 };
 55 
 56                 return this;
 57             },
 58 
 59             /**
 60              * Create a composite texturemap from the heightmap and
 61              * texture-array images.
 62              *
 63              * @param {HTMLCanvasElement} texturemapCanvas The texturemap
 64              *                            canvas to which the heightmap and
 65              *                            texture-array images are
 66              *                            composited.
 67              */
 68             composite: function(texturemapCanvas) {
 69                 if ( !(texturemapCanvas instanceof HTMLCanvasElement)) {
 70                     throw('Invalid texturemapCanvas: not an instance of ' +
 71                         'HTMLCanvasElement');
 72                 }
 73 
 74                 var texturemapContext = texturemapCanvas.getContext('2d');
 75 
 76                 var textureCanvas = document.createElement('canvas');
 77                 textureCanvas.width = 256;
 78                 textureCanvas.height = 256;
 79 
 80                 var textureContext = textureCanvas.getContext('2d');
 81 
 82                 if (_textureArray[255] === undefined) {
 83                     throw('No texture added at height 255; unable to ' +
 84                         'composite');
 85                 }
 86                 for (var i = 254; i >= 0; --i) {
 87                     if (_textureArray[i] === undefined) {
 88                         _textureArray[i] = _textureArray[i + 1];
 89                     }
 90                 }
 91 
 92                 for (var y = 0; y < 1024; ++y) {
 93                     for (var x = 0; x < 1024; ++x) {
 94                         var height = _heightmap[(y << 10) + x];
 95 
 96                         var index = ((y & 255) << 10) + ((x & 255) << 2);
 97 
 98                         texturemapContext.fillStyle = 'rgb(' +
 99                             _textureArray[height].data[index] + ',' +
100                             _textureArray[height].data[index + 1] + ',' +
101                             _textureArray[height].data[index + 2] + ')';
102 
103                         texturemapContext.fillRect(x, y, 1, 1);
104                     }
105                 }
106             },
107 
108             /**
109              * Get the heightmap as an array.
110              *
111              * @return {array} The heightmap canvas converted to an array.
112              */
113             getHeightmap: function() {
114                 return _heightmap;
115             },
116 
117             /**
118              * Get the out-of-bounds heightmap as an array.
119              *
120              * @return {array} The out-of-bounds heightmap canvas converted to
121              *                 an array.
122              */
123             getOutOfBoundsHeightmap: function() {
124                 return _outOfBoundsHeightmap;
125             },
126 
127             /**
128              * Put a mask (a 2.5D sprite's heightmap).
129              *
130              * @param {HTMLImageElement} mask The 2.5D sprite's heightmap;
131              *                                should contain a greyscale
132              *                                image.
133              * @param {number} x The x-ordinate.
134              * @param {number} y The y-ordinate.
135              * @param {number} scaleFactor The scale factor to multiply each
136              *                             value in the mask by.
137              * @return {Compositor} This (fluent interface).
138              */
139             putMask: function(mask, x, y, scaleFactor) {
140                 var maskCanvas = document.createElement('canvas');
141                 maskCanvas.width  = mask.width;
142                 maskCanvas.height = mask.height;
143 
144                 var maskContext = maskCanvas.getContext('2d');
145 
146                 maskContext.drawImage(mask, 0, 0);
147 
148                 var data = maskContext.getImageData(0, 0, maskCanvas.width,
149                     maskCanvas.height).data;
150 
151                 for (var y2 = 0; y2 < maskCanvas.height; ++y2) {
152                     for (var x2 = 0; x2 < maskCanvas.width; ++x2) {
153                         var index = y2 * maskCanvas.width + x2;
154 
155                         if (data[index * 4 + 3]) {
156                             var index = ((y2 + y) << 10) + (x2 + x);
157 
158                             _heightmap[index] = 192 +
159                                 data[(y2 * maskCanvas.width + x2) * 4] *
160                                 scaleFactor;
161                         }
162                     }
163                 }
164 
165                 return this;
166             },
167 
168             /**
169              * Set the heightmap to use for compositing [and
170              * out-of-bounds].
171              *
172              * @param {HTMLCanvasElement} heightmapCanvas The heightmap
173              *                                            canvas; should
174              *                                            contain a
175              *                                            greyscale image.
176              * @return {Compositor} This (fluent interface).
177              */
178             setHeightmap: function(heightmapCanvas) {
179                 var data = heightmapCanvas.getContext('2d')
180                     .getImageData(0, 0, 1024, 1024).data;
181 
182                 for (var y = 0; y < 1024; ++y) {
183                     for (var x = 0; x < 1024; ++x) {
184                         var index = (y << 10) + x;
185 
186                         _heightmap[index] = data[index << 2];
187                         _outOfBoundsHeightmap[index] = _heightmap[index];
188                     }
189                 }
190 
191                 return this;
192             }
193         };
194     };
195 
196     return Steppe;
197 } (Steppe || { }) );
198