Line data Source code
1 0 : /*
2 :
3 : OOPolygonSprite.m
4 : Oolite
5 :
6 :
7 : Copyright (C) 2009-2013 Jens Ayton
8 :
9 : Permission is hereby granted, free of charge, to any person obtaining a copy
10 : of this software and associated documentation files (the "Software"), to deal
11 : in the Software without restriction, including without limitation the rights
12 : to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 : copies of the Software, and to permit persons to whom the Software is
14 : furnished to do so, subject to the following conditions:
15 :
16 : The above copyright notice and this permission notice shall be included in all
17 : copies or substantial portions of the Software.
18 :
19 : THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 : IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 : FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 : AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 : LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 : OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25 : SOFTWARE.
26 :
27 : */
28 :
29 : /* Implementation note:
30 :
31 : The polygon data is tesselated (on object creation) using a GLU tesselator.
32 : Although GLU produces a mix of triangles, triangle fans and triangle
33 : strips, we convert those to a single triangle soup since the polygons are
34 : unlikely to be large enough that using multiple primitives would be a win.
35 :
36 : Uniquing vertices and using indices, and using the same vertex array for
37 : outline and filled mode, would in principle be more efficient, but not
38 : worth the added complexity in the preprocessing given the simplicity of
39 : the icons we're likely to encounter.
40 : */
41 :
42 : #import "OOPolygonSprite.h"
43 : #import "OOCollectionExtractors.h"
44 : #import "OOMacroOpenGL.h"
45 : #import "OOMaths.h"
46 : #import "OOPointMaths.h"
47 : #import "OOGraphicsResetManager.h"
48 :
49 :
50 : #ifndef APIENTRY
51 0 : #define APIENTRY
52 : #endif
53 :
54 :
55 0 : #define kCosMitreLimit 0.866f // Approximately cos(30 deg)
56 :
57 :
58 : @interface OOPolygonSprite (Private) <OOGraphicsResetClient>
59 :
60 0 : - (BOOL) loadPolygons:(NSArray *)dataArray outlineWidth:(float)outlineWidth;
61 :
62 : @end
63 :
64 :
65 0 : typedef struct
66 : {
67 0 : GLfloat *data;
68 0 : size_t count; // Number of vertices in use, i.e. half of number of data elements used.
69 0 : size_t capacity; // Half of number of floats there is space for in data.
70 0 : GLenum mode; // Current primitive mode.
71 0 : size_t vCount; // Number of vertices so far in primitive.
72 0 : NSPoint pending0, pending1; // Used for splitting GL_TRIANGLE_STRIP/GL_TRIANGLE_FAN primitives.
73 0 : BOOL OK; // Set to false to indicate error.
74 : #ifndef NDEBUG
75 0 : BOOL generatingOutline;
76 0 : unsigned svgID;
77 0 : NSString *name;
78 0 : NSMutableString *debugSVG;
79 : #endif
80 : } TessPolygonData;
81 :
82 :
83 : static NSArray *DataArrayToPoints(TessPolygonData *data, NSArray *dataArray);
84 : static NSArray *BuildOutlineContour(NSArray *dataArray, GLfloat width, BOOL inner);
85 :
86 : static void SubmitVertices(GLUtesselator *tesselator, TessPolygonData *polygonData, NSArray *contour);
87 :
88 : static BOOL GrowTessPolygonData(TessPolygonData *data, size_t capacityHint); // Returns true if capacity grew by at least one.
89 : static BOOL AppendVertex(TessPolygonData *data, NSPoint vertex);
90 :
91 : #ifndef NDEBUG
92 : static void SVGDumpBegin(TessPolygonData *data);
93 : static void SVGDumpEnd(TessPolygonData *data);
94 : static void SVGDumpBeginGroup(TessPolygonData *data, NSString *name);
95 : static void SVGDumpEndGroup(TessPolygonData *data);
96 : static void SVGDumpAppendBaseContour(TessPolygonData *data, NSArray *points);
97 : static void SVGDumpBeginPrimitive(TessPolygonData *data);
98 : static void SVGDumpEndPrimitive(TessPolygonData *data);
99 : static void SVGDumpAppendTriangle(TessPolygonData *data, NSPoint v0, NSPoint v1, NSPoint v2);
100 : #else
101 : #define SVGDumpEnd(data) do {} while (0)
102 : #define SVGDumpBeginGroup(data, name) do {} while (0)
103 : #define SVGDumpEndGroup(data) do {} while (0)
104 : #define SVGDumpAppendBaseContour(data, points) do {} while (0)
105 : #define SVGDumpBeginPrimitive(data) do {} while (0)
106 : #define SVGDumpEndPrimitive(data) do {} while (0)
107 : #define SVGDumpAppendTriangle(data, v0, v1, v2) do {} while (0)
108 : #endif
109 :
110 :
111 : static void APIENTRY TessBeginCallback(GLenum type, void *polygonData);
112 : static void APIENTRY TessVertexCallback(void *vertexData, void *polygonData);
113 : static void APIENTRY TessCombineCallback(GLdouble coords[3], void *vertexData[4], GLfloat weight[4], void **outData, void *polygonData);
114 : static void APIENTRY TessEndCallback(void *polygonData);
115 :
116 : static void APIENTRY ErrorCallback(GLenum error, void *polygonData);
117 :
118 : // this is needed to maintain compatibility with GCC 14+
119 0 : typedef GLvoid (*TessFuncPtr)();
120 :
121 :
122 : @implementation OOPolygonSprite
123 :
124 : - (id) initWithDataArray:(NSArray *)dataArray outlineWidth:(GLfloat)outlineWidth name:(NSString *)name
125 : {
126 : if ((self = [super init]))
127 : {
128 : #ifndef NDEBUG
129 : _name = [name copy];
130 : #endif
131 :
132 : if ([dataArray count] == 0)
133 : {
134 : [self release];
135 : return nil;
136 : }
137 :
138 : // Normalize data to array-of-arrays form.
139 : if (![[dataArray objectAtIndex:0] isKindOfClass:[NSArray class]])
140 : {
141 : dataArray = [NSArray arrayWithObject:dataArray];
142 : }
143 : if (![self loadPolygons:dataArray outlineWidth:outlineWidth])
144 : {
145 : [self release];
146 : return nil;
147 : }
148 :
149 : [[OOGraphicsResetManager sharedManager] registerClient:self];
150 : }
151 :
152 : return self;
153 : }
154 :
155 :
156 0 : - (void) dealloc
157 : {
158 : [[OOGraphicsResetManager sharedManager] unregisterClient:self];
159 :
160 : #ifndef NDEBUG
161 : DESTROY(_name);
162 : #endif
163 : free(_solidData);
164 : free(_outlineData);
165 :
166 : [super dealloc];
167 : }
168 :
169 :
170 : #ifndef NDEBUG
171 0 : - (NSString *) descriptionComponents
172 : {
173 : return _name;
174 : }
175 : #endif
176 :
177 :
178 0 : - (void) drawWithData:(GLfloat *)data count:(size_t)count VBO:(GLuint *)vbo
179 : {
180 : if (count == 0) return;
181 : NSParameterAssert(vbo != NULL && data != NULL);
182 :
183 : OO_ENTER_OPENGL();
184 : OOSetOpenGLState(OPENGL_STATE_OVERLAY);
185 :
186 : #if OO_USE_VBO
187 : BOOL useVBO = [[OOOpenGLExtensionManager sharedManager] vboSupported];
188 :
189 : if (useVBO)
190 : {
191 : if (*vbo == 0)
192 : {
193 : OOGL(glGenBuffersARB(1, vbo));
194 : if (*vbo != 0)
195 : {
196 : OOGL(glBindBufferARB(GL_ARRAY_BUFFER, *vbo));
197 : OOGL(glBufferDataARB(GL_ARRAY_BUFFER, sizeof (GLfloat) * count * 2, data, GL_STATIC_DRAW));
198 : }
199 : }
200 : else
201 : {
202 : OOGL(glBindBufferARB(GL_ARRAY_BUFFER, *vbo));
203 : }
204 : if (*vbo != 0) data = NULL; // Must pass NULL pointer to glVertexPointer to use VBO.
205 : }
206 : #endif
207 :
208 : OOGL(glEnableClientState(GL_VERTEX_ARRAY));
209 : OOGL(glVertexPointer(2, GL_FLOAT, 0, data));
210 : OOGL(glDrawArrays(GL_TRIANGLES, 0, count));
211 : OOGL(glDisableClientState(GL_VERTEX_ARRAY));
212 :
213 : #if OO_USE_VBO
214 : if (useVBO) OOGL(glBindBufferARB(GL_ARRAY_BUFFER, 0));
215 : #endif
216 :
217 : OOVerifyOpenGLState();
218 : OOCheckOpenGLErrors(@"OOPolygonSprite after rendering %@", self);
219 : }
220 :
221 :
222 : - (void) drawFilled
223 : {
224 : #if !OO_USE_VBO
225 : GLuint _solidVBO; // Unusued
226 : #endif
227 :
228 : [self drawWithData:_solidData count:_solidCount VBO:&_solidVBO];
229 : }
230 :
231 :
232 : - (void) drawOutline
233 : {
234 : #if !OO_USE_VBO
235 : GLuint _outlineVBO; // Unusued
236 : #endif
237 :
238 : [self drawWithData:_outlineData count:_outlineCount VBO:&_outlineVBO];
239 : }
240 :
241 :
242 0 : - (void)resetGraphicsState
243 : {
244 : #if OO_USE_VBO
245 : OO_ENTER_OPENGL();
246 :
247 : if (_solidVBO != 0) glDeleteBuffersARB(1, &_solidVBO);
248 : if (_outlineVBO != 0) glDeleteBuffersARB(1, &_outlineVBO);
249 :
250 : _solidVBO = 0;
251 : _outlineVBO = 0;
252 : #endif
253 : }
254 :
255 :
256 : // FIXME: this method is absolutely horrible.
257 0 : - (BOOL) loadPolygons:(NSArray *)dataArray outlineWidth:(float)outlineWidth
258 : {
259 : NSParameterAssert(dataArray != nil);
260 :
261 : NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
262 : GLUtesselator *tesselator = NULL;
263 :
264 : TessPolygonData polygonData;
265 : memset(&polygonData, 0, sizeof polygonData);
266 : polygonData.OK = YES;
267 : #ifndef NDEBUG
268 : polygonData.name = _name;
269 : if ([[NSUserDefaults standardUserDefaults] boolForKey:@"polygon-sprite-dump-svg"]) SVGDumpBegin(&polygonData);
270 : #endif
271 :
272 : // For efficiency, grow to more than big enough for most cases to avoid regrowing.
273 : if (!GrowTessPolygonData(&polygonData, 100))
274 : {
275 : polygonData.OK = NO;
276 : goto END;
277 : }
278 :
279 : tesselator = gluNewTess();
280 : if (tesselator == NULL)
281 : {
282 : polygonData.OK = NO;
283 : goto END;
284 : }
285 :
286 : dataArray = DataArrayToPoints(&polygonData, dataArray);
287 :
288 : /*** Tesselate polygon fill ***/
289 : gluTessCallback(tesselator, GLU_TESS_BEGIN_DATA, (TessFuncPtr)TessBeginCallback);
290 : gluTessCallback(tesselator, GLU_TESS_VERTEX_DATA, (TessFuncPtr)TessVertexCallback);
291 : gluTessCallback(tesselator, GLU_TESS_END_DATA, (TessFuncPtr)TessEndCallback);
292 : gluTessCallback(tesselator, GLU_TESS_ERROR_DATA, (TessFuncPtr)ErrorCallback);
293 : gluTessCallback(tesselator, GLU_TESS_COMBINE_DATA, (TessFuncPtr)TessCombineCallback);
294 :
295 : gluTessBeginPolygon(tesselator, &polygonData);
296 : SVGDumpBeginGroup(&polygonData, @"Fill");
297 :
298 : NSUInteger contourCount = [dataArray count], contourIndex;
299 : for (contourIndex = 0; contourIndex < contourCount && polygonData.OK; contourIndex++)
300 : {
301 : NSArray *contour = [dataArray oo_arrayAtIndex:contourIndex];
302 : if (contour == nil)
303 : {
304 : polygonData.OK = NO;
305 : break;
306 : }
307 :
308 : SubmitVertices(tesselator, &polygonData, contour);
309 : }
310 :
311 : gluTessEndPolygon(tesselator);
312 : SVGDumpEndGroup(&polygonData);
313 :
314 : if (polygonData.OK)
315 : {
316 : _solidCount = polygonData.count;
317 :
318 : if (_solidCount != 0)
319 : {
320 : _solidData = realloc(polygonData.data, polygonData.count * sizeof (GLfloat) * 2);
321 : if (_solidData != NULL) polygonData.data = NULL; // realloc succeded.
322 : else
323 : {
324 : // Unlikely, but legal: realloc failed to shrink buffer.
325 : _solidData = polygonData.data;
326 : if (_solidData == NULL) polygonData.OK = NO;
327 : }
328 : }
329 : else
330 : {
331 : // Empty polygon.
332 : _solidData = NULL;
333 : }
334 : }
335 : if (!polygonData.OK) goto END;
336 :
337 : /*** Tesselate polygon outline ***/
338 : gluDeleteTess(tesselator);
339 : tesselator = gluNewTess();
340 : if (tesselator == NULL)
341 : {
342 : polygonData.OK = NO;
343 : goto END;
344 : }
345 :
346 : polygonData.count = 0;
347 : polygonData.capacity = 0;
348 : if (!GrowTessPolygonData(&polygonData, 100))
349 : {
350 : polygonData.OK = NO;
351 : goto END;
352 : }
353 : #ifndef NDEBUG
354 : polygonData.generatingOutline = YES;
355 : #endif
356 :
357 : gluTessCallback(tesselator, GLU_TESS_BEGIN_DATA, (TessFuncPtr)TessBeginCallback);
358 : gluTessCallback(tesselator, GLU_TESS_VERTEX_DATA, (TessFuncPtr)TessVertexCallback);
359 : gluTessCallback(tesselator, GLU_TESS_END_DATA, (TessFuncPtr)TessEndCallback);
360 : gluTessCallback(tesselator, GLU_TESS_ERROR_DATA, (TessFuncPtr)ErrorCallback);
361 : gluTessCallback(tesselator, GLU_TESS_COMBINE_DATA, (TessFuncPtr)TessCombineCallback);
362 : gluTessProperty(tesselator, GLU_TESS_WINDING_RULE, GLU_TESS_WINDING_POSITIVE);
363 :
364 : gluTessBeginPolygon(tesselator, &polygonData);
365 : SVGDumpBeginGroup(&polygonData, @"Outline");
366 :
367 : outlineWidth *= 0.5f; // Half the width in, half the width out.
368 : contourCount = [dataArray count];
369 : for (contourIndex = 0; contourIndex < contourCount && polygonData.OK; contourIndex++)
370 : {
371 : NSArray *contour = [dataArray oo_arrayAtIndex:contourIndex];
372 : if (contour == nil)
373 : {
374 : polygonData.OK = NO;
375 : break;
376 : }
377 :
378 : SubmitVertices(tesselator, &polygonData, BuildOutlineContour(contour, outlineWidth, NO));
379 : SubmitVertices(tesselator, &polygonData, BuildOutlineContour(contour, outlineWidth, YES));
380 : }
381 :
382 : gluTessEndPolygon(tesselator);
383 : SVGDumpEndGroup(&polygonData);
384 :
385 : if (polygonData.OK)
386 : {
387 : if (polygonData.count != 0)
388 : {
389 : _outlineCount = polygonData.count;
390 : _outlineData = realloc(polygonData.data, polygonData.count * sizeof (GLfloat) * 2);
391 : if (_outlineData != NULL) polygonData.data = NULL; // realloc succeded.
392 : else
393 : {
394 : // Unlikely, but legal: realloc failed to shrink buffer.
395 : _outlineData = polygonData.data;
396 : if (_outlineData == NULL) polygonData.OK = NO;
397 : }
398 : }
399 : else
400 : {
401 : // Empty polygon.
402 : _outlineCount = 0;
403 : _outlineData = NULL;
404 : }
405 : }
406 :
407 : END:
408 : SVGDumpEnd(&polygonData);
409 : free(polygonData.data);
410 : gluDeleteTess(tesselator);
411 : [pool release];
412 : #ifndef NDEBUG
413 : DESTROY(polygonData.debugSVG);
414 : #endif
415 : return polygonData.OK;
416 : }
417 :
418 : @end
419 :
420 :
421 0 : static void SubmitVertices(GLUtesselator *tesselator, TessPolygonData *polygonData, NSArray *contour)
422 : {
423 : NSUInteger vertexCount = [contour count], vertexIndex;
424 : if (vertexCount > 2)
425 : {
426 : gluTessBeginContour(tesselator);
427 :
428 : for (vertexIndex = 0; vertexIndex < vertexCount && polygonData->OK; vertexIndex++)
429 : {
430 : NSValue *pointValue = [contour objectAtIndex:vertexIndex];
431 : NSPoint p = [pointValue pointValue];
432 : GLdouble vert[3] = { p.x, p.y, 0.0 };
433 :
434 : gluTessVertex(tesselator, vert, pointValue);
435 : }
436 :
437 : gluTessEndContour(tesselator);
438 : }
439 : }
440 :
441 :
442 0 : static NSArray *DataArrayToPoints(TessPolygonData *data, NSArray *dataArray)
443 : {
444 : /* This converts an icon definition in the form of an array of array of
445 : numbers to internal data in the form of an array of arrays of NSValues
446 : containing NSPoint data. In addition to repacking the data, it performs
447 : the following data processing:
448 : * Sequences of duplicate vertices are removed (including across the
449 : beginning and end, in case of manually closed contours).
450 : * Vertices containing nans or infinities are skipped, Just In Case.
451 : * The signed area of each contour is calculated; if it is negative,
452 : the contour is clockwise, and we need to flip it.
453 : */
454 :
455 : SVGDumpBeginGroup(data, @"Base contours");
456 :
457 : NSUInteger polyIter, polyCount = [dataArray count];
458 : NSArray *subArrays[polyCount];
459 :
460 : for (polyIter = 0; polyIter < polyCount; polyIter++)
461 : {
462 : NSArray *polyDef = [dataArray objectAtIndex:polyIter];
463 : NSUInteger vertIter, vertCount = [polyDef count] / 2;
464 : NSMutableArray *newPolyDef = [NSMutableArray arrayWithCapacity:vertCount];
465 : CGFloat area = 0;
466 :
467 : CGFloat oldX = [polyDef oo_doubleAtIndex:(vertCount -1) * 2];
468 : CGFloat oldY = [polyDef oo_doubleAtIndex:(vertCount -1) * 2 + 1];
469 :
470 : for (vertIter = 0; vertIter < vertCount; vertIter++)
471 : {
472 : CGFloat x = [polyDef oo_doubleAtIndex:vertIter * 2];
473 : CGFloat y = [polyDef oo_doubleAtIndex:vertIter * 2 + 1];
474 :
475 : // Skip bad or duplicate vertices.
476 : if (x == oldX && y == oldY) continue;
477 : if (isnan(x) || isnan(y)) continue;
478 : if (!isfinite(x) || !isfinite(y)) continue;
479 :
480 : area += x * oldY - oldX * y;
481 :
482 : oldX = x;
483 : oldY = y;
484 :
485 : [newPolyDef addObject:[NSValue valueWithPoint:NSMakePoint(x, y)]];
486 : }
487 :
488 : // Eliminate duplicates at ends - the initialization of oldX and oldY will catch one pair, but not extra-silly cases.
489 : while ([newPolyDef count] > 1 && [[newPolyDef objectAtIndex:0] isEqual:[newPolyDef lastObject]])
490 : {
491 : [newPolyDef removeLastObject];
492 : }
493 :
494 : if (area >= 0)
495 : {
496 : subArrays[polyIter] = newPolyDef;
497 : }
498 : else
499 : {
500 : subArrays[polyIter] = [[newPolyDef reverseObjectEnumerator] allObjects];
501 : }
502 :
503 : SVGDumpAppendBaseContour(data, subArrays[polyIter]);
504 : }
505 :
506 : SVGDumpEndGroup(data);
507 : return [NSArray arrayWithObjects:subArrays count:polyCount];
508 : }
509 :
510 :
511 0 : static NSArray *BuildOutlineContour(NSArray *dataArray, GLfloat width, BOOL inner)
512 : {
513 : NSUInteger i, count = [dataArray count];
514 : if (count < 2) return dataArray;
515 :
516 : /*
517 : Generate inner or outer boundary for a contour, offset by the specified
518 : width inwards/outwards from the line. At anticlockwise (convex) corners
519 : sharper than acos(kCosMitreLimit), the corner is mitred, i.e. an
520 : additional line segment is generated so the outline doesn't protrude
521 : arbitratrily far.
522 :
523 : Overview of the maths:
524 : For each vertex, we consider a normalized vector A from the previous
525 : vertex and a normalized vector B to the next vertex. (These are always
526 : defined since the polygons are closed.)
527 :
528 : The dot product of A and B is the cosine of the angle, which we compare
529 : to kCosMitreLimit to determine mitreing. If the dot product is exactly
530 : 1, the vectors are antiparallel and we have a cap; the mitreing case
531 : handles this implicitly. (The non-mitreing case would result in a
532 : divide-by-zero.)
533 :
534 : Non-mitreing case:
535 : To position the vertex, we need a vector N normal to the corner.
536 : We observe that A + B is tangent to the corner, and a 90 degree
537 : rotation in 2D is trivial. The offset along this line is
538 : proportional to the secant of the angle between N and the 90 degree
539 : rotation of A (or B; the angle is by definition the same), or
540 : width / (N dot rA).
541 : Since both N and rA are rotated by ninety degrees in the same
542 : direction, we can cut out both rotations (i.e., using the tangent
543 : and A) and get the same result.
544 :
545 : Mitreing case:
546 : The two new vertices are the original vertex offset by scale along
547 : the ninety-degree rotation of A and B respectively.
548 : */
549 :
550 : NSPoint prev, current, next;
551 : if (inner)
552 : {
553 : prev = [[dataArray objectAtIndex:0] pointValue];
554 : current = [[dataArray objectAtIndex:count -1] pointValue];
555 : next = [[dataArray objectAtIndex:count - 2] pointValue];
556 : }
557 : else
558 : {
559 : prev = [[dataArray objectAtIndex:count - 1] pointValue];
560 : current = [[dataArray objectAtIndex:0] pointValue];
561 : next = [[dataArray objectAtIndex:1] pointValue];
562 : }
563 :
564 : NSMutableArray *result = [NSMutableArray arrayWithCapacity:count];
565 :
566 : for (i = 0; i < count; i++)
567 : {
568 : NSPoint a = PtNormal(PtSub(current, prev));
569 : NSPoint b = PtNormal(PtSub(next, current));
570 :
571 : CGFloat dot = PtDot(a, b);
572 : BOOL clockwise = PtCross(a, b) < 0.0f;
573 :
574 : if (-dot < kCosMitreLimit || !clockwise)
575 : {
576 : // Non-mitreing case.
577 : NSPoint t = PtNormal(PtAdd(a, b));
578 : NSPoint v = PtScale(PtRotACW(t), width / PtDot(t, a));
579 :
580 : if (!isnan(v.x) && !isnan(v.y))
581 : {
582 : [result addObject:[NSValue valueWithPoint:PtAdd(v, current)]];
583 : }
584 : }
585 : else
586 : {
587 : // Mitreing case.
588 : NSPoint v1 = PtScale(PtAdd(PtRotACW(a), a), width);
589 : NSPoint v2 = PtScale(PtSub(PtRotACW(b), b), width);
590 :
591 : if (!isnan(v1.x) && !isnan(v1.y))
592 : {
593 : [result addObject:[NSValue valueWithPoint:PtAdd(v1, current)]];
594 : }
595 : if (!isnan(v2.x) && !isnan(v2.y))
596 : {
597 : [result addObject:[NSValue valueWithPoint:PtAdd(v2, current)]];
598 : }
599 : }
600 :
601 : prev = current;
602 : current = next;
603 :
604 : if (inner)
605 : {
606 : next = [[dataArray objectAtIndex:(count * 2 - 3 - i) % count] pointValue];
607 : }
608 : else
609 : {
610 : next = [[dataArray objectAtIndex:(i + 2) % count] pointValue];
611 : }
612 : }
613 :
614 : return result;
615 : }
616 :
617 :
618 0 : static BOOL GrowTessPolygonData(TessPolygonData *data, size_t capacityHint)
619 : {
620 : NSCParameterAssert(data != NULL);
621 :
622 : size_t minCapacity = data->capacity + 1;
623 : size_t desiredCapacity = MAX(capacityHint, minCapacity);
624 : size_t newCapacity = 0;
625 : GLfloat *newData = realloc(data->data, desiredCapacity * sizeof (GLfloat) * 2);
626 : if (newData != NULL)
627 : {
628 : newCapacity = desiredCapacity;
629 : }
630 : else
631 : {
632 : desiredCapacity = minCapacity;
633 : newData = realloc(data->data, desiredCapacity * sizeof (GLfloat) * 2);
634 : if (newData != NULL) newCapacity = desiredCapacity;
635 : }
636 :
637 : if (newData == NULL) return NO;
638 :
639 : NSCAssert(newCapacity > data->capacity, @"Buffer regrow logic error");
640 :
641 : data->data = newData;
642 : data->capacity = newCapacity;
643 : return YES;
644 : }
645 :
646 :
647 0 : static BOOL AppendVertex(TessPolygonData *data, NSPoint vertex)
648 : {
649 : NSCParameterAssert(data != NULL);
650 :
651 : if (data->capacity == data->count && !GrowTessPolygonData(data, data->capacity * 2)) return NO;
652 :
653 : data->data[data->count * 2] = vertex.x;
654 : data->data[data->count * 2 + 1] = vertex.y;
655 : data->count++;
656 : return YES;
657 : }
658 :
659 :
660 0 : static void APIENTRY TessBeginCallback(GLenum type, void *polygonData)
661 : {
662 : TessPolygonData *data = polygonData;
663 : NSCParameterAssert(data != NULL);
664 :
665 : data->mode = type;
666 : data->vCount = 0;
667 :
668 : SVGDumpBeginPrimitive(data);
669 : }
670 :
671 :
672 0 : static void APIENTRY TessVertexCallback(void *vertexData, void *polygonData)
673 : {
674 : TessPolygonData *data = polygonData;
675 : NSValue *vertValue = vertexData;
676 : NSCParameterAssert(vertValue != NULL && data != NULL);
677 : if (!data->OK) return;
678 :
679 : NSPoint p = [vertValue pointValue];
680 : NSPoint vertex = { p.x, p.y };
681 : size_t vCount = data->vCount++;
682 :
683 : switch (data->mode)
684 : {
685 : case GL_TRIANGLES:
686 : data->OK = AppendVertex(data, vertex);
687 : #ifndef NDEBUG
688 : switch (vCount % 3)
689 : {
690 : case 0:
691 : data->pending0 = vertex;
692 : break;
693 :
694 : case 1:
695 : data->pending1 = vertex;
696 : break;
697 :
698 : case 2:
699 : SVGDumpAppendTriangle(data, data->pending0, data->pending1, vertex);
700 : }
701 : #endif
702 : break;
703 :
704 : case GL_TRIANGLE_FAN:
705 : if (vCount == 0) data->pending0 = vertex;
706 : else if (vCount == 1) data->pending1 = vertex;
707 : else
708 : {
709 : data->OK = AppendVertex(data, data->pending0) &&
710 : AppendVertex(data, data->pending1) &&
711 : AppendVertex(data, vertex);
712 : SVGDumpAppendTriangle(data, data->pending0, data->pending1, vertex);
713 : data->pending1 = vertex;
714 : }
715 : break;
716 :
717 : case GL_TRIANGLE_STRIP:
718 : if (vCount == 0) data->pending0 = vertex;
719 : else if (vCount == 1) data->pending1 = vertex;
720 : else
721 : {
722 : /* In order to produce consistent winding, the vertex->triangle
723 : order for GL_TRIANGLE_STRIP is:
724 : 0, 1, 2
725 : 2, 1, 3
726 : 2, 3, 4
727 : 4, 3, 5
728 : 4, 5, 6
729 : 6, 5, 7
730 : 6, 7, 8
731 :
732 : Vertices 0 and 1 are special-cased above, and the first
733 : time we get here it's time for the first triangle, which
734 : is pending0, pending1, v. v (i.e., vertex 2) then goes into
735 : pending0.
736 : For the second triangle, the triangle is again pending0,
737 : pending1, v, and we then put v (i.e., vertex 3) into
738 : pending1.
739 : The third triangle follows the same pattern as the first,
740 : and the fourthe the same as the second.
741 : In other words, after storing each triangle, v goes into
742 : pending0 for even vertex indicies, and pending1 for odd
743 : vertex indices.
744 : */
745 : data->OK = AppendVertex(data, data->pending0) &&
746 : AppendVertex(data, data->pending1) &&
747 : AppendVertex(data, vertex);
748 : SVGDumpAppendTriangle(data, data->pending0, data->pending1, vertex);
749 : if ((vCount % 2) == 0) data->pending0 = vertex;
750 : else data->pending1 = vertex;
751 : }
752 : break;
753 :
754 : default:
755 : OOLog(@"polygonSprite.tesselate.error", @"Unexpected tesselator primitive mode %u.", data->mode);
756 : data->OK = NO;
757 : }
758 : }
759 :
760 :
761 0 : static void APIENTRY TessCombineCallback(GLdouble coords[3], void *vertexData[4], GLfloat weight[4], void **outData, void *polygonData)
762 : {
763 : NSPoint point = { coords[0], coords[1] };
764 : *outData = [NSValue valueWithPoint:point];
765 : }
766 :
767 :
768 0 : static void APIENTRY TessEndCallback(void *polygonData)
769 : {
770 : TessPolygonData *data = polygonData;
771 : NSCParameterAssert(data != NULL);
772 :
773 : data->mode = 0;
774 : data->vCount = 0;
775 :
776 : SVGDumpEndPrimitive(data);
777 : }
778 :
779 :
780 0 : static void APIENTRY ErrorCallback(GLenum error, void *polygonData)
781 : {
782 : TessPolygonData *data = polygonData;
783 : NSCParameterAssert(data != NULL);
784 :
785 : NSString *name = @"";
786 : #ifndef NDEBUG
787 : name = [NSString stringWithFormat:@" \"%@\"", data->name];
788 : #endif
789 :
790 : char *errStr = (char *)gluErrorString(error);
791 :
792 : OOLog(@"polygonSprite.tesselate.error", @"Error %s (%u) while tesselating polygon%@.", errStr, error, name);
793 : data->OK = NO;
794 : }
795 :
796 : #ifndef NDEBUG
797 : #import "ResourceManager.h"
798 : #import "legacy_random.h"
799 :
800 0 : static void SVGDumpBegin(TessPolygonData *data)
801 : {
802 : DESTROY(data->debugSVG);
803 : data->debugSVG = [[NSMutableString alloc] initWithString:
804 : @"<?xml version=\"1.0\" standalone=\"no\"?>\n"
805 : "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n"
806 : "<svg viewBox=\"-5 -5 10 10\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\">\n"
807 : "\t<desc>Oolite polygon sprite debug dump.</desc>\n"
808 : "\t\n"
809 : ];
810 : }
811 :
812 :
813 0 : static void SVGDumpEnd(TessPolygonData *data)
814 : {
815 : if (data->debugSVG == nil) return;
816 :
817 : [data->debugSVG appendString:@"</svg>\n"];
818 : [ResourceManager writeDiagnosticString:data->debugSVG toFileNamed:[NSString stringWithFormat:@"Polygon Sprites/%@.svg", data->name]];
819 : DESTROY(data->debugSVG);
820 : }
821 :
822 :
823 0 : static void SVGDumpBeginGroup(TessPolygonData *data, NSString *name)
824 : {
825 : if (data->debugSVG == nil) return;
826 :
827 : [data->debugSVG appendFormat:@"\t<g id=\"%@ %u\">\n", name, data->svgID++];
828 : }
829 :
830 :
831 0 : static void SVGDumpEndGroup(TessPolygonData *data)
832 : {
833 : if (data->debugSVG == nil) return;
834 : [data->debugSVG appendString:@"\t</g>\n"];
835 : }
836 :
837 :
838 0 : static void SVGDumpAppendBaseContour(TessPolygonData *data, NSArray *points)
839 : {
840 : if (data->debugSVG == nil) return;
841 :
842 : NSString *groupName = [NSString stringWithFormat:@"contour %u", data->svgID++];
843 : [data->debugSVG appendFormat:@"\t\t<g id=\"%@\" stroke=\"#BBB\" fill=\"none\">\n\t\t<path stroke-width=\"0.05\" d=\"", groupName];
844 :
845 : NSUInteger i, count = [points count];
846 : for (i = 0; i < count; i++)
847 : {
848 : NSPoint p = [[points objectAtIndex:i] pointValue];
849 : [data->debugSVG appendFormat:@"%c %f %f ", (i == 0) ? 'M' : 'L', p.x, -p.y];
850 : }
851 :
852 : // Close and add a circle at the first vertex. (SVG has support for end markers, but this isn’t reliable across implementations.)
853 : NSPoint p = [[points objectAtIndex:0] pointValue];
854 : [data->debugSVG appendFormat:@"z\"/>\n\t\t\t<circle cx=\"%f\" cy=\"%f\" r=\"0.1\" fill=\"#BBB\" stroke=\"none\"/>\n\t\t</g>\n", p.x, -p.y];
855 : }
856 :
857 :
858 0 : static void SVGDumpBeginPrimitive(TessPolygonData *data)
859 : {
860 : if (data->debugSVG == nil) return;
861 :
862 : NSString *groupName = @"Unknown primitive";
863 : switch (data->mode)
864 : {
865 : case GL_TRIANGLES:
866 : groupName = @"Triangle soup";
867 : break;
868 :
869 : case GL_TRIANGLE_FAN:
870 : groupName = @"Triangle fan";
871 : break;
872 :
873 : case GL_TRIANGLE_STRIP:
874 : groupName = @"Triangle strip";
875 : break;
876 : }
877 : groupName = [groupName stringByAppendingFormat:@" %u", data->svgID++];
878 :
879 : // Pick random colour for the primitive.
880 : uint8_t red = (Ranrot() & 0x3F) + 0x20;
881 : uint8_t green = (Ranrot() & 0x3F) + 0x20;
882 : uint8_t blue = (Ranrot() & 0x3F) + 0x20;
883 : if (!data->generatingOutline)
884 : {
885 : red += 0x80;
886 : green += 0x80;
887 : blue += 0x80;
888 : }
889 :
890 : [data->debugSVG appendFormat:@"\t\t<g id=\"%@\" fill=\"#%2X%2X%2X\" fill-opacity=\"0.3\" stroke=\"%@\" stroke-width=\"0.01\">\n", groupName, red, green, blue, data->generatingOutline ? @"#060" : @"#008"];
891 : }
892 :
893 :
894 0 : static void SVGDumpEndPrimitive(TessPolygonData *data)
895 : {
896 : if (data->debugSVG == nil) return;
897 : [data->debugSVG appendString:@"\t\t</g>\n"];
898 : }
899 :
900 :
901 0 : static void SVGDumpAppendTriangle(TessPolygonData *data, NSPoint v0, NSPoint v1, NSPoint v2)
902 : {
903 : if (data->debugSVG == nil) return;
904 : [data->debugSVG appendFormat:@"\t\t\t<path d=\"M %f %f L %f %f L %f %f z\"/>\n", v0.x, -v0.y, v1.x, -v1.y, v2.x, -v2.y];
905 : }
906 : #endif
|