LCOV - code coverage report
Current view: top level - Core - OOPolygonSprite.m (source / functions) Hit Total Coverage
Test: coverxygen.info Lines: 0 40 0.0 %
Date: 2025-05-28 07:50:54 Functions: 0 0 -

          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

Generated by: LCOV version 1.14