Oolite 1.91.0.7645-241119-222d325
Loading...
Searching...
No Matches
OOPolygonSprite.m
Go to the documentation of this file.
1/*
2
3OOPolygonSprite.m
4Oolite
5
6
7Copyright (C) 2009-2013 Jens Ayton
8
9Permission is hereby granted, free of charge, to any person obtaining a copy
10of this software and associated documentation files (the "Software"), to deal
11in the Software without restriction, including without limitation the rights
12to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13copies of the Software, and to permit persons to whom the Software is
14furnished to do so, subject to the following conditions:
15
16The above copyright notice and this permission notice shall be included in all
17copies or substantial portions of the Software.
18
19THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25SOFTWARE.
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"
44#import "OOMacroOpenGL.h"
45#import "OOMaths.h"
46#import "OOPointMaths.h"
48
49
50#ifndef APIENTRY
51#define APIENTRY
52#endif
53
54
55#define kCosMitreLimit 0.866f // Approximately cos(30 deg)
56
57
58@interface OOPolygonSprite (Private) <OOGraphicsResetClient>
59
60- (BOOL) loadPolygons:(NSArray *)dataArray outlineWidth:(float)outlineWidth;
61
62@end
63
64
65typedef struct
66{
67 GLfloat *data;
68 size_t count; // Number of vertices in use, i.e. half of number of data elements used.
69 size_t capacity; // Half of number of floats there is space for in data.
70 GLenum mode; // Current primitive mode.
71 size_t vCount; // Number of vertices so far in primitive.
72 NSPoint pending0, pending1; // Used for splitting GL_TRIANGLE_STRIP/GL_TRIANGLE_FAN primitives.
73 BOOL OK; // Set to false to indicate error.
74#ifndef NDEBUG
76 unsigned svgID;
77 NSString *name;
78 NSMutableString *debugSVG;
79#endif
81
82
83static NSArray *DataArrayToPoints(TessPolygonData *data, NSArray *dataArray);
84static NSArray *BuildOutlineContour(NSArray *dataArray, GLfloat width, BOOL inner);
85
86static void SubmitVertices(GLUtesselator *tesselator, TessPolygonData *polygonData, NSArray *contour);
87
88static BOOL GrowTessPolygonData(TessPolygonData *data, size_t capacityHint); // Returns true if capacity grew by at least one.
89static BOOL AppendVertex(TessPolygonData *data, NSPoint vertex);
90
91#ifndef NDEBUG
92static void SVGDumpBegin(TessPolygonData *data);
93static void SVGDumpEnd(TessPolygonData *data);
94static void SVGDumpBeginGroup(TessPolygonData *data, NSString *name);
95static void SVGDumpEndGroup(TessPolygonData *data);
96static void SVGDumpAppendBaseContour(TessPolygonData *data, NSArray *points);
97static void SVGDumpBeginPrimitive(TessPolygonData *data);
98static void SVGDumpEndPrimitive(TessPolygonData *data);
99static 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
111static void APIENTRY TessBeginCallback(GLenum type, void *polygonData);
112static void APIENTRY TessVertexCallback(void *vertexData, void *polygonData);
113static void APIENTRY TessCombineCallback(GLdouble coords[3], void *vertexData[4], GLfloat weight[4], void **outData, void *polygonData);
114static void APIENTRY TessEndCallback(void *polygonData);
115
116static void APIENTRY ErrorCallback(GLenum error, void *polygonData);
117
118// this is needed to maintain compatibility with GCC 14+
119typedef 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
150 }
151
152 return self;
153}
154
155
156- (void) dealloc
157{
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- (NSString *) descriptionComponents
172{
173 return _name;
174}
175#endif
176
177
178- (void) drawWithData:(GLfloat *)data count:(size_t)count VBO:(GLuint *)vbo
179{
180 if (count == 0) return;
181 NSParameterAssert(vbo != NULL && data != NULL);
182
185
186#if OO_USE_VBO
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
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- (void)resetGraphicsState
243{
244#if OO_USE_VBO
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- (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
407END:
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
421static 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
442static 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
511static 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
618static 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
647static 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
660static 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
669}
670
671
672static 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
761static 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
768static void APIENTRY TessEndCallback(void *polygonData)
769{
770 TessPolygonData *data = polygonData;
771 NSCParameterAssert(data != NULL);
772
773 data->mode = 0;
774 data->vCount = 0;
775
777}
778
779
780static 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
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
813static 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
823static 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
832{
833 if (data->debugSVG == nil) return;
834 [data->debugSVG appendString:@"\t</g>\n"];
835}
836
837
838static 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
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
895{
896 if (data->debugSVG == nil) return;
897 [data->debugSVG appendString:@"\t\t</g>\n"];
898}
899
900
901static 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
#define DESTROY(x)
Definition OOCocoa.h:77
#define OOLog(class, format,...)
Definition OOLogging.h:88
#define OO_ENTER_OPENGL()
#define MAX(A, B)
Definition OOMaths.h:114
@ OPENGL_STATE_OVERLAY
Definition OOOpenGL.h:126
#define OOVerifyOpenGLState()
Definition OOOpenGL.h:136
BOOL OOCheckOpenGLErrors(NSString *format,...)
Definition OOOpenGL.m:39
#define OOSetOpenGLState(STATE)
Definition OOOpenGL.h:135
#define OOGL(statement)
Definition OOOpenGL.h:251
unsigned count
OOINLINE CGFloat PtDot(NSPoint a, NSPoint b)
OOINLINE NSPoint PtScale(NSPoint p, CGFloat scale)
OOINLINE NSPoint PtSub(NSPoint a, NSPoint b)
OOINLINE NSPoint PtRotACW(NSPoint p)
OOINLINE NSPoint PtNormal(NSPoint p)
OOINLINE CGFloat PtCross(NSPoint a, NSPoint b)
OOINLINE NSPoint PtAdd(NSPoint a, NSPoint b)
Definition OOPointMaths.h:6
static void APIENTRY TessVertexCallback(void *vertexData, void *polygonData)
static void APIENTRY TessEndCallback(void *polygonData)
static BOOL AppendVertex(TessPolygonData *data, NSPoint vertex)
static void SVGDumpEndPrimitive(TessPolygonData *data)
static void APIENTRY TessBeginCallback(GLenum type, void *polygonData)
static void SVGDumpBeginGroup(TessPolygonData *data, NSString *name)
static void SVGDumpAppendTriangle(TessPolygonData *data, NSPoint v0, NSPoint v1, NSPoint v2)
static NSArray * DataArrayToPoints(TessPolygonData *data, NSArray *dataArray)
#define APIENTRY
GLvoid(* TessFuncPtr)()
static void SubmitVertices(GLUtesselator *tesselator, TessPolygonData *polygonData, NSArray *contour)
static void APIENTRY ErrorCallback(GLenum error, void *polygonData)
static void SVGDumpBeginPrimitive(TessPolygonData *data)
static void APIENTRY TessCombineCallback(GLdouble coords[3], void *vertexData[4], GLfloat weight[4], void **outData, void *polygonData)
static void SVGDumpAppendBaseContour(TessPolygonData *data, NSArray *points)
static NSArray * BuildOutlineContour(NSArray *dataArray, GLfloat width, BOOL inner)
static void SVGDumpBegin(TessPolygonData *data)
static void SVGDumpEndGroup(TessPolygonData *data)
static void SVGDumpEnd(TessPolygonData *data)
static BOOL GrowTessPolygonData(TessPolygonData *data, size_t capacityHint)
#define kCosMitreLimit
return nil
float y
float x
void registerClient:(id< OOGraphicsResetClient > client)
void unregisterClient:(id< OOGraphicsResetClient > client)
OOGraphicsResetManager * sharedManager()
OOOpenGLExtensionManager * sharedManager()
BOOL writeDiagnosticString:toFileNamed:(NSString *string,[toFileNamed] NSString *name)
unsigned Ranrot(void)
NSMutableString * debugSVG