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