1 /**
  2  * Copyright (C) 2009-2012 Klaus Reimer <k@ailis.de>
  3  * See LICENSE.txt for licensing information
  4  * 
  5  * @require threedee.js
  6  * @require threedee/Vector.js
  7  */
  8 
  9 /**
 10  * Constructs a new render polygon.
 11  *
 12  * @param {!threedee.Model} model
 13  *             The original model this polygon is connected to.
 14  * @param {!threedee.Polygon} polygon
 15  *             The original polygon.
 16  * @param {!Array.<!threedee.Vector>} vertices
 17  *             The vertices array.
 18  *             
 19  * @constructor
 20  * @class A render polygon is a wrapper around a polygon for internal usage
 21  * while rendering. 
 22  */
 23 threedee.RenderPolygon = function(model, polygon, vertices)
 24 {
 25     var i, max;
 26     
 27     this.model = model;
 28     this.material = polygon.getMaterial();
 29     this.vertices = [];
 30     this.size = polygon.countVertices();
 31     this.origSize = this.size;
 32     for (i = 0, max = this.size; i < max; i++)
 33         this.vertices.push(vertices[polygon.getVertex(i)]);
 34     
 35     this.polygon = [];
 36     this.prevPolygon = [];
 37 };
 38 
 39 /** 
 40  * Speed-optimization vector cache. 
 41  * @private 
 42  * @type {!threedee.Vector} 
 43  */
 44 threedee.RenderPolygon.V1 = new threedee.Vector();
 45 
 46 /** 
 47  * Speed-optimization vector cache. 
 48  * @private 
 49  * @type {!threedee.Vector} 
 50  */
 51 threedee.RenderPolygon.V2 = new threedee.Vector();
 52 
 53 /** 
 54  * The array with the used vertices.
 55  * @private 
 56  * @type {!Array.<!threedee.Vector>} 
 57  */
 58 threedee.RenderPolygon.prototype.vertices;
 59 
 60 /** 
 61  * The current number of used vertices. 
 62  * @private 
 63  * @type {number} 
 64  */
 65 threedee.RenderPolygon.prototype.size = 0;
 66 
 67 /** 
 68  * The original number of vertices. 
 69  * @private 
 70  * @type {number} 
 71  */
 72 threedee.RenderPolygon.prototype.origSize = 0;
 73 
 74 /** 
 75  * The extra vectors added to the original ones.
 76  * @private 
 77  * @type {number} 
 78  */
 79 threedee.RenderPolygon.prototype.extras = 0;
 80 
 81 /** 
 82  * The vertex indices that forms the polygon. 
 83  * @private 
 84  * @type {!Array.<number>} 
 85  */
 86 threedee.RenderPolygon.prototype.polygon;
 87 
 88 /** 
 89  * The previous vertex indices that forms the polygon. 
 90  * @private 
 91  * @type {!Array.<number>} 
 92  */
 93 threedee.RenderPolygon.prototype.prevPolygon;
 94 
 95 /** 
 96  * The normal vector cache. 
 97  * @private 
 98  * @type {threedee.Vector} 
 99  */
100 threedee.RenderPolygon.prototype.normal = null;
101 
102 /** 
103  * The center vector cache. 
104  * @private 
105  * @type {threedee.Vector} 
106  */
107 threedee.RenderPolygon.prototype.center = null;
108 
109 /** 
110  * The referenced vertices. 
111  * @private 
112  * @type {!Array.<!threedee.Vector>} 
113  */
114 threedee.RenderPolygon.prototype.vertices;
115 
116 /** 
117  * The average Z position of the polygon. 
118  * @private 
119  * @type {number} 
120  */
121 threedee.RenderPolygon.prototype.averageZ = 0;
122 
123 /** 
124  * The polygon material. 
125  * @private 
126  * @type {?threedee.Material} 
127  */
128 threedee.RenderPolygon.prototype.material = null;
129 
130 /** 
131  * The model this polygon is connected to. 
132  * @private 
133  * @type {!threedee.Model} 
134  */
135 threedee.RenderPolygon.prototype.model;
136 
137 /**
138  * Initializes the polygon for the next rendering. This simply resets the
139  * vertex indices to the original order and resets the counter of extra
140  * vertices.
141  */
142 threedee.RenderPolygon.prototype.init = function()
143 {
144     var i;
145     
146     this.size = this.origSize;
147     for (i = this.size - 1; i >= 0; i--) this.polygon[i] = i;
148     this.extras = 0;
149 };
150 
151 /**
152  * Clips this polygon against the specified view plane.
153  * 
154  * @param {threedee.Plane} plane
155  *            The clipping plane
156  * @return {boolean}
157  *            True if polygon was clipped completely away, false if it is
158  *            still visible (even if clipped)
159  */
160 threedee.RenderPolygon.prototype.clip = function(plane)
161 {
162     var max, j, i, a, b, c, tmp, planeNormal, planeDistance, distanceA,
163         distanceB, s, va, vb;
164     
165     // Swap the polygon arrays
166     tmp = this.prevPolygon;
167     this.prevPolygon = this.polygon;
168     this.polygon = tmp;
169     
170     max = this.size;    
171     j = 0;
172     planeNormal = plane.getNormal();
173     planeDistance = plane.getDistance();
174     for (i = 0; i < max; i++)
175     {
176         // Get the two vertex indices of the next line
177         a = this.prevPolygon[i];
178         b = this.prevPolygon[(i + 1) % max];
179 
180         // Get the corresponding vertices and calculate the distances
181         va = this.vertices[a];
182         vb = this.vertices[b];
183         distanceA = va.dot(planeNormal) - planeDistance;
184         distanceB = vb.dot(planeNormal) - planeDistance;
185 
186         // If both points are on the back-side of the plane then it is
187         // completely clipped away, so return null
188         if (distanceA < 0 && distanceB < 0) continue;
189 
190         // If both points are on the front-side of the plane then use the line
191         // as it is (Point B is omitted because it is the starting point of
192         // the next line in the polygon)
193         if (distanceA >= 0 && distanceB >= 0)
194         {
195             this.polygon[j++] = a;
196         }
197         
198         // Otherwise calculate the intersection and use a clipped line
199         else
200         {    
201             // Calculate the intersection point
202             s = distanceA / (distanceA - distanceB);
203             c = this.origSize + this.extras++;
204             if (!this.vertices[c]) this.vertices[c] = new threedee.Vector();
205             this.vertices[c].set(va.x + s * (vb.x - va.x), va.y + s
206                 * (vb.y - va.y), va.z + s * (vb.z - va.z));
207     
208             // If point A is on the back-side of the plane then use a line
209             // from the intersection point to point B (Where point B is
210             // omitted because it is the original starting  point of the next
211             // line in the polygon
212             if (distanceA < 0)
213             {
214                 this.polygon[j++] = c;
215             }
216 
217             // Otherwise point B is on the back-side so use a line from point A
218             // to intersection point
219             else
220             {
221                 this.polygon[j++] = a;
222                 this.polygon[j++] = c;
223             }
224         }
225     }
226 
227     this.size = j;
228     return !j;
229 };
230 
231 /**
232  * Returns the number of used vertices.
233  * 
234  * @return {number} The number of used vertices
235  */
236 threedee.RenderPolygon.prototype.countVertices = function()
237 {
238     return this.size;
239 };
240 
241 /**
242  * Returns the vertex with the specified index.
243  * 
244  * @param {number} index
245  *            The vertex index
246  * @return {!threedee.Vector} The vertex
247  */
248 threedee.RenderPolygon.prototype.getVertex = function(index)
249 {
250     return this.vertices[this.polygon[index]];
251 };
252 
253 /**
254  * Returns the center of the polygon.
255  * 
256  * Speed-optimization: The calculated center vector is cached in an instance
257  * scope variable to prevent creation of vector objects on reach frame render.
258  * WARNING: If you want to use the returned center vector for a longer time
259  * (and not only for a single frame) then you should copy the vector because
260  * otherwise the content may change when the polygon changes.
261  * 
262  * @return {!threedee.Vector} The center of the polygon
263  */
264 threedee.RenderPolygon.prototype.getCenter = function()
265 {
266     var vertexCount, ax, ay, az, i, v;
267     
268     vertexCount = this.size;
269     ax = 0;
270     ay = 0;
271     az = 0;
272 
273     for (i = 0; i < vertexCount; i++)
274     {
275         v = this.vertices[this.polygon[i]];
276         ax += v.x;
277         ay += v.y;
278         az += v.z;
279     }
280     ax /= vertexCount;
281     ay /= vertexCount;
282     az /= vertexCount;
283 
284     if (!this.center) this.center = new threedee.Vector();
285     return this.center.set(ax, ay, az);
286 };
287 
288 /**
289  * Calculates the normal of the polygon. May return null if polygon has
290  * fewer then three vertices.
291  * 
292  * Speed-optimization: For calculating the normal we need three additional
293  * vectors. Two of them (V1 and V2) are only temporary and therefore are
294  * shared in static scope. The third one is polygon-specific and
295  * is shared in instance scope. WARNING: If you want to use the returned normal
296  * vector for a longer time (and not only for a single frame) then you should
297  * copy the vector because otherwise the content may change when the polygon
298  * changes.
299  * 
300  * @return {?threedee.Vector}
301  *            The normal or null if polygon has fewer then three vertices
302  */
303 threedee.RenderPolygon.prototype.getNormal = function()
304 {
305     var a, b, c, v1, v2;
306 
307     // If polygon has fewer then three vertices then it has no normal
308     if (this.polygon.length < 3) return null;
309 
310     a = this.vertices[this.polygon[0]];
311     b = this.vertices[this.polygon[1]];
312     c = this.vertices[this.polygon[2]];
313     
314     v1 = b.copy(threedee.RenderPolygon.V1).sub(a);
315     v2 = a.copy(threedee.RenderPolygon.V2).sub(c);
316     this.normal = v1.cross(v2).toUnit().copy(this.normal);
317     return this.normal;
318 };
319 
320 /**
321  * Updates the average Z value.
322  */
323 threedee.RenderPolygon.prototype.updateAverageZ = function()
324 {
325     var averageZ, vertexCount, v;
326     
327     averageZ = 0;
328     vertexCount = this.size;
329     for (v = 0; v < vertexCount; v++)
330     {
331         averageZ += this.vertices[this.polygon[v]].z;
332     }
333     averageZ /= vertexCount;
334     this.averageZ = averageZ;
335 };
336 
337 /**
338  * Compares two polygons according to their average Z value. This is used
339  * to sort polygons.
340  *
341  * @param {!threedee.RenderPolygon} a
342  *             The first polygon to compare
343  * @param {!threedee.RenderPolygon} b
344  *             The second polygon to compare
345  * @return {number}
346  *             Lower than 0 if z-value of second polygon is lower then z-value
347  *             of first polygon. Greater than 0 if the other way around. 0
348  *             means z-values are equal 
349  */
350 threedee.RenderPolygon.compare = function(a, b)
351 {
352     return b.averageZ - a.averageZ;
353 };
354 
355 /**
356  * Checks if currently the backface of the polygon is visible.
357  * 
358  * @return {boolean}
359  *            True if backface is visible, false if front-side.
360  */
361 threedee.RenderPolygon.prototype.isBackface = function()
362 {
363     return this.getNormal().dot(this.getVertex(0)) > 0;
364 };
365 
366 /**
367  * Returns the polygon material. If it has a specific material then this one
368  * is returned. Otherwise the global modal material is returned.
369  * 
370  * @return {threedee.Material} The material to be used for this polygon
371  */
372 threedee.RenderPolygon.prototype.getMaterial = function()
373 {
374     if (this.material) return this.material;
375     return this.model.getMaterial();
376 };
377