Incomplete support for general polygons in missile icons. (In case of splodey, setting the OLD_ICONS macro in HeadUpDisplay.m to 1 will revert to the old drawing code.)

git-svn-id: http://svn.berlios.de/svnroot/repos/oolite-linux/trunk@2661 127b21dd-08f5-0310-b4b7-95ae10353056
This commit is contained in:
Jens Ayton 2009-10-10 12:23:24 +00:00
parent e1ea90a110
commit bc3a113b0f
13 changed files with 690 additions and 23 deletions

View File

@ -139,7 +139,8 @@ OOLITE_GRAPHICS_MISC_FILES = \
OOOpenGLExtensionManager.m \
OOProbabilisticTextureManager.m \
OOSkyDrawable.m \
OpenGLSprite.m
OpenGLSprite.m \
OOPolygonSprite.m
OOLITE_MATHS_FILES = \
CollisionRegion.m \

View File

@ -564,6 +564,8 @@
1AB812900E90179D00A84923 /* TextureStore.h in Headers */ = {isa = PBXBuildFile; fileRef = 25161134099544390037C2E1 /* TextureStore.h */; };
1AB813090E90D8E500A84923 /* OOLogOutputHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 1AB813070E90D8E500A84923 /* OOLogOutputHandler.m */; };
1AB8130A0E90D8E500A84923 /* OOLogOutputHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = 1AB813080E90D8E500A84923 /* OOLogOutputHandler.h */; };
1AB9AE8B107F459B00B6F3CE /* OOPolygonSprite.h in Headers */ = {isa = PBXBuildFile; fileRef = 1AB9AE89107F459B00B6F3CE /* OOPolygonSprite.h */; };
1AB9AE8C107F459B00B6F3CE /* OOPolygonSprite.m in Sources */ = {isa = PBXBuildFile; fileRef = 1AB9AE8A107F459B00B6F3CE /* OOPolygonSprite.m */; };
1ABAD7310F350B3400FD2CBF /* OOShipGroup.m in Sources */ = {isa = PBXBuildFile; fileRef = 1ABAD72F0F350B3400FD2CBF /* OOShipGroup.m */; };
1ABAD7320F350B3400FD2CBF /* OOShipGroup.h in Headers */ = {isa = PBXBuildFile; fileRef = 1ABAD7300F350B3400FD2CBF /* OOShipGroup.h */; };
1ABB688C0D044306008BE96D /* OOLoggingExtended.h in Headers */ = {isa = PBXBuildFile; fileRef = 1ABB688B0D044306008BE96D /* OOLoggingExtended.h */; };
@ -1652,6 +1654,8 @@
1AB784F80D554F7B00517983 /* OOJSSoundSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OOJSSoundSource.m; sourceTree = "<group>"; };
1AB813070E90D8E500A84923 /* OOLogOutputHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OOLogOutputHandler.m; sourceTree = "<group>"; };
1AB813080E90D8E500A84923 /* OOLogOutputHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OOLogOutputHandler.h; sourceTree = "<group>"; };
1AB9AE89107F459B00B6F3CE /* OOPolygonSprite.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OOPolygonSprite.h; sourceTree = "<group>"; };
1AB9AE8A107F459B00B6F3CE /* OOPolygonSprite.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OOPolygonSprite.m; sourceTree = "<group>"; };
1ABAD72F0F350B3400FD2CBF /* OOShipGroup.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OOShipGroup.m; sourceTree = "<group>"; };
1ABAD7300F350B3400FD2CBF /* OOShipGroup.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OOShipGroup.h; sourceTree = "<group>"; };
1ABB688B0D044306008BE96D /* OOLoggingExtended.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OOLoggingExtended.h; sourceTree = "<group>"; };
@ -2224,6 +2228,8 @@
1A94D5B40D65A6B40072C805 /* OOCamera.m */,
25161100099544380037C2E1 /* OpenGLSprite.h */,
251610FF099544380037C2E1 /* OpenGLSprite.m */,
1AB9AE89107F459B00B6F3CE /* OOPolygonSprite.h */,
1AB9AE8A107F459B00B6F3CE /* OOPolygonSprite.m */,
1AED2D0A0C04586C004A1118 /* OOGraphicsResetManager.h */,
1AED2D0B0C04586C004A1118 /* OOGraphicsResetManager.m */,
1ADC3F850BFA1388000E0F89 /* Drawables */,
@ -3112,6 +3118,7 @@
1A817DA0106D3FF000AA2F97 /* OOPlasmaBurstEntity.h in Headers */,
1A817DC3106D443B00AA2F97 /* OOFlashEffectEntity.h in Headers */,
2B4CDFEC107B3D8400526C98 /* OOJSManifest.h in Headers */,
1AB9AE8B107F459B00B6F3CE /* OOPolygonSprite.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -3463,6 +3470,7 @@
1A817DA1106D3FF000AA2F97 /* OOPlasmaBurstEntity.m in Sources */,
1A817DC4106D443B00AA2F97 /* OOFlashEffectEntity.m in Sources */,
2B4CDFED107B3D8400526C98 /* OOJSManifest.m in Sources */,
1AB9AE8C107F459B00B6F3CE /* OOPolygonSprite.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View File

@ -731,6 +731,63 @@
-2, -3,
-2, 1
);
"oolite-default-missile-icon" =
(
0, 3,
2, 0,
1, 0,
1, -2,
-1, -2,
-1, 0,
-2, 0
);
// The following is a test of a complex, multi-contour polygon sprite
// with non-overlapping contours. It also happens to generate a mix of
// triangle strips and individual triangles. (The default icon generates
// a single triangle fan.)
// -- Ahruman 2009-10-10
/* (
(
-1, 2,
1, 2,
1, -2,
-1, -2
),
(
-3, 3,
3, 3,
3, -3,
-3, -3
)
);*/
// The following is a test of a complex, multi-contour polygon sprite
// which self-intersects.
// It currently doesn't work, because it requires a combine callback.
// -- Ahruman 2009-10-10
/* (
(
-1, 3,
1, 3,
1, -2,
-1, -2
),
(
-3, 1,
3, 1,
3, -3,
-3, -3
)
);
*/
"oolite-default-mine-icon" =
(
0, 2,
1, 1,
1, -1,
0, -2,
-1, -1,
-1, 1
);
// human surname namegen
"nom" =
(

View File

@ -76,6 +76,13 @@ mission.runMissionScreen = function (messageKey, backgroundImage, choiceKey, shi
};
// mission.addMessageTextKey(): load mission text from mission.plist and append to mission screen or info screen.
mission.addMessageTextKey = function(textKey)
{
mission.addMessageText(expandMissionText(textKey));
}
/* string.trim(): remove leading and trailing whitespace.
Implementation by Steve Leviathan, see:
http://blog.stevenlevithan.com/archives/faster-trim-javascript

View File

@ -312,6 +312,9 @@ GLuint tfan2[10] = { 33, 25, 26, 27, 28, 29, 30, 31, 32, 25 }; // final fan 6
OOGL(glDrawElements(GL_QUAD_STRIP, 18, GL_UNSIGNED_INT, qstrip3));
OOGL(glDrawElements(GL_TRIANGLE_FAN, 10, GL_UNSIGNED_INT, tfan2));
OOGL(glDisableClientState(GL_VERTEX_ARRAY));
OOGL(glDisableClientState(GL_COLOR_ARRAY));
glPopAttrib();
}

View File

@ -1179,7 +1179,6 @@ static float corona_blending;
OOGL(glNormalPointer(GL_FLOAT, 0, vertexdata.normal_array));
OOGL(glCallList(displayListNames[subdivideLevel]));
}
else
{
@ -1311,6 +1310,11 @@ static float corona_blending;
{
GLDebugWireframeModeOff();
}
OOGL(glDisableClientState(GL_VERTEX_ARRAY));
OOGL(glDisableClientState(GL_NORMAL_ARRAY));
OOGL(glDisableClientState(GL_COLOR_ARRAY));
OOGL(glDisableClientState(GL_TEXTURE_COORD_ARRAY));
break;
case PLANET_TYPE_SUN:

View File

@ -142,7 +142,10 @@ Ringdata ringentity;
OOGL(glDisableClientState(GL_TEXTURE_COORD_ARRAY));
OOGL(glDisableClientState(GL_EDGE_FLAG_ARRAY));
OOGL(glDrawElements( GL_TRIANGLES, 3 * 64, GL_UNSIGNED_INT, ringentity.triangle_index_array));
OOGL(glDrawElements(GL_TRIANGLES, 3 * 64, GL_UNSIGNED_INT, ringentity.triangle_index_array));
OOGL(glDisableClientState(GL_VERTEX_ARRAY));
OOGL(glDisableClientState(GL_COLOR_ARRAY));
}
else
{

View File

@ -207,7 +207,7 @@ MA 02110-1301, USA.
AI *shipAI; // ship's AI system
NSString *name; // descriptive name
NSString *displayName; // name shown on screen
NSString *displayName; // name shown on screen
OORoleSet *roleSet; // Roles a ship can take, eg. trader, hunter, police, pirate, scavenger &c.
NSString *primaryRole; // "Main" role of the ship.

View File

@ -33,6 +33,7 @@ MA 02110-1301, USA.
#import "GuiDisplayGen.h"
#import "OOTexture.h"
#import "OpenGLSprite.h"
#import "OOPolygonSprite.h"
#import "OOCollectionExtractors.h"
#import "OOEncodingConverter.h"
#import "OOCrosshairs.h"
@ -41,6 +42,9 @@ MA 02110-1301, USA.
#import "JoystickHandler.h"
#define OLD_ICONS 0
#define kOOLogUnconvertedNSLog @"unclassified.HeadUpDisplay"
@ -58,7 +62,9 @@ static void hudDrawMarkerAt(GLfloat x, GLfloat y, GLfloat z, NSSize siz, double
static void hudDrawBarAt(GLfloat x, GLfloat y, GLfloat z, NSSize siz, double amount);
static void hudDrawSurroundAt(GLfloat x, GLfloat y, GLfloat z, NSSize siz);
static void hudDrawSpecialIconAt(NSArray* ptsArray, int x, int y, int z, NSSize siz);
#if OLD_ICONS
static void hudDrawMineIconAt(int x, int y, int z, NSSize siz);
#endif
static void hudDrawMissileIconAt(int x, int y, int z, NSSize siz);
static void hudDrawStatusIconAt(int x, int y, int z, NSSize siz);
static void hudDrawReticleOnTarget(Entity* target, PlayerEntity* player1, GLfloat z1, GLfloat overallAlpha, BOOL reticleTargetSensitive);
@ -371,6 +377,8 @@ OOINLINE void GLColorWithOverallAlpha(GLfloat *color, GLfloat alpha)
- (void) renderHUD
{
glShadeModel(GL_FLAT);
OOGL(glLineWidth(_crosshairWidth * line_width));
[self drawCrosshairs];
@ -1505,6 +1513,103 @@ OOINLINE void SetCompassBlipColor(GLfloat relativeZ, GLfloat alpha)
}
#if !OLD_ICONS
static NSString * const kDefaultMissileIconKey = @"oolite-default-missile-icon";
static NSString * const kDefaultMineIconKey = @"oolite-default-mine-icon";
static OOPolygonSprite *IconForMissileRole(NSString *role)
{
static NSMutableDictionary *sIcons = nil;
OOPolygonSprite *result = nil;
result = [sIcons objectForKey:role];
if (result == nil)
{
NSString *key = role;
NSArray *iconDef = [[UNIVERSE descriptions] oo_arrayForKey:key];
if (iconDef != nil) result = [[OOPolygonSprite alloc] initWithDataArray:iconDef outlineWidth:0.5f name:key];
if (result == nil) // No custom icon or bad data
{
/* Backwards compatibility note:
The old implementation used suffixes "MISSILE" and "MINE" (without
the underscore), and didn't draw anything if neither was found. I
believe any difference in practical behavour due to the change here
will be positive.
-- Ahruman 2009-10-09
*/
if ([role hasSuffix:@"_MISSILE"]) key = kDefaultMissileIconKey;
else key = kDefaultMineIconKey;
iconDef = [[UNIVERSE descriptions] oo_arrayForKey:key];
result = [[OOPolygonSprite alloc] initWithDataArray:iconDef outlineWidth:0.5f name:key];
}
if (result != nil)
{
if (sIcons == nil) sIcons = [[NSMutableDictionary alloc] init];
[sIcons setObject:result forKey:role];
[result release]; // Balance alloc
}
}
return result;
}
- (void) drawIconForMissile:(ShipEntity *)missile
selected:(BOOL)selected
status:(OOMissileStatus)status
x:(int)x y:(int)y
width:(GLfloat)width height:(GLfloat)height
{
OOPolygonSprite *sprite = IconForMissileRole([missile primaryRole]);
if (selected)
{
// Draw yellow outline.
OOGL(glPushMatrix());
OOGL(glTranslatef(x - width * 2.0f, y - height * 2.0f, z1));
OOGL(glScalef(width + 1.0f, height + 1.0f, 1.0f));
GLColorWithOverallAlpha(yellow_color, overallAlpha);
[sprite drawFilled];
OOGL(glPopMatrix());
// Draw black backing, so outline colour isnt blended into missile colour.
OOGL(glPushMatrix());
OOGL(glTranslatef(x - width * 2.0f, y - height * 2.0f, z1));
OOGL(glScalef(width, height, 1.0f));
GLColorWithOverallAlpha(black_color, overallAlpha);
[sprite drawFilled];
OOGL(glPopMatrix());
switch (status)
{
case MISSILE_STATUS_SAFE :
GLColorWithOverallAlpha(green_color, overallAlpha); break;
case MISSILE_STATUS_ARMED :
GLColorWithOverallAlpha(yellow_color, overallAlpha); break;
case MISSILE_STATUS_TARGET_LOCKED :
GLColorWithOverallAlpha(red_color, overallAlpha); break;
}
}
else
{
if ([missile primaryTarget] == nil) GLColorWithOverallAlpha(green_color, overallAlpha);
else GLColorWithOverallAlpha(red_color, overallAlpha);
}
OOGL(glPushMatrix());
OOGL(glTranslatef(x - width * 2.0f, y - height * 2.0f, z1));
OOGL(glScalef(width, height, 1.0f));
[sprite drawFilled];
OOGL(glPopMatrix());
}
#endif
- (void) drawMissileDisplay:(NSDictionary *) info
{
PlayerEntity *player = [PlayerEntity sharedPlayer];
@ -1521,14 +1626,23 @@ OOINLINE void SetCompassBlipColor(GLfloat relativeZ, GLfloat alpha)
if (![player dialIdentEngaged])
{
OOMissileStatus status = [player dialMissileStatus];
unsigned n_mis = [player dialMaxMissiles];
unsigned i;
for (i = 0; i < n_mis; i++)
{
if ([player missileForStation:i])
ShipEntity *missile = [player missileForStation:i];
if (missile)
{
#if !OLD_ICONS
[self drawIconForMissile:missile
selected:i == [player activeMissile]
status:status
x:x + (int)i * sp + 2 y:y
width:siz.width *0.25f height:siz.height *0.25f];
#else
// TODO: copy icon data into missile object instead of looking it up each time. Possibly make weapon stores a ShipEntity subclass?
NSString *miss_roles = [[player missileForStation:i] primaryRole];
NSString *miss_roles = [missile primaryRole];
NSArray *miss_icon = [[UNIVERSE descriptions] oo_arrayForKey:miss_roles];
if (i == [player activeMissile])
{
@ -1575,7 +1689,7 @@ OOINLINE void SetCompassBlipColor(GLfloat relativeZ, GLfloat alpha)
}
else
{
if ([[player missileForStation:i] primaryTarget])
if ([missile primaryTarget])
GLColorWithOverallAlpha(red_color, overallAlpha);
else
GLColorWithOverallAlpha(green_color, overallAlpha);
@ -1610,9 +1724,10 @@ OOINLINE void SetCompassBlipColor(GLfloat relativeZ, GLfloat alpha)
}
OOGLEND();
}
#endif
}
else
{
{
GLColorWithOverallAlpha(lightgray_color, overallAlpha);
OOGLBEGIN(GL_LINE_LOOP);
hudDrawMissileIconAt(x + i * sp, y, z1, siz);
@ -2123,9 +2238,9 @@ static void hudDrawSpecialIconAt(NSArray* ptsArray, int x, int y, int z, NSSize
int ox = x - siz.width / 2.0;
int oy = y - siz.height / 2.0;
int w = siz.width / 4.0;
int h = siz.height / 4.0;
int h = siz.height / 4.0;
int i = 0;
int npts = [ptsArray count] & 0xfffe; // make sure it's an even number
int npts = [ptsArray count] & ~1; // make sure it's an even number
while (i < npts)
{
int x = [ptsArray oo_intAtIndex:i++];
@ -2142,16 +2257,17 @@ static void hudDrawMissileIconAt(int x, int y, int z, NSSize siz)
int w = siz.width / 4.0;
int h = siz.height / 4.0;
glVertex3i(ox, oy + 3 * h, z);
glVertex3i(ox + 2 * w, oy, z);
glVertex3i(ox + w, oy, z);
glVertex3i(ox + w, oy - 2 * h, z);
glVertex3i(ox - w, oy - 2 * h, z);
glVertex3i(ox - w, oy, z);
glVertex3i(ox - 2 * w, oy, z);
glVertex3i(ox + 0 * w, oy + 3 * h, z);
glVertex3i(ox + 2 * w, oy + 0 * h, z);
glVertex3i(ox + 1 * w, oy + 0 * h, z);
glVertex3i(ox + 1 * w, oy - 2 * h, z);
glVertex3i(ox - 1 * w, oy - 2 * h, z);
glVertex3i(ox - 1 * w, oy + 0 * h, z);
glVertex3i(ox - 2 * w, oy + 0 * h, z);
}
#if OLD_ICONS
static void hudDrawMineIconAt(int x, int y, int z, NSSize siz)
{
int ox = x - siz.width / 2.0;
@ -2159,13 +2275,14 @@ static void hudDrawMineIconAt(int x, int y, int z, NSSize siz)
int w = siz.width / 4.0;
int h = siz.height / 4.0;
glVertex3i(ox, oy + 2 * h, z);
glVertex3i(ox + w, oy + h, z);
glVertex3i(ox + w, oy - h, z);
glVertex3i(ox, oy - 2 * h, z);
glVertex3i(ox - w, oy - h, z);
glVertex3i(ox - w, oy + h, z);
glVertex3i(ox + 0 * w, oy + 2 * h, z);
glVertex3i(ox + 1 * w, oy + 1 * h, z);
glVertex3i(ox + 1 * w, oy - 1 * h, z);
glVertex3i(ox + 0 * w, oy - 2 * h, z);
glVertex3i(ox - 1 * w, oy - 1 * h, z);
glVertex3i(ox - 1 * w, oy + 1 * h, z);
}
#endif
static void hudDrawStatusIconAt(int x, int y, int z, NSSize siz)

View File

@ -78,6 +78,9 @@
OOGL(glDrawArrays(GL_LINES, 0, _count * 2));
OOGL(glDisableClientState(GL_VERTEX_ARRAY));
OOGL(glDisableClientState(GL_COLOR_ARRAY));
OOGL(glPopMatrix());
OOGL(glPopAttrib());
}

View File

@ -306,6 +306,10 @@ shaderBindingTarget:(id<OOWeakReferenceSupport>)object
else [localException raise]; // pass these on
NS_ENDHANDLER
OOGL(glDisableClientState(GL_VERTEX_ARRAY));
OOGL(glDisableClientState(GL_NORMAL_ARRAY));
OOGL(glDisableClientState(GL_TEXTURE_COORD_ARRAY));
#ifndef NDEBUG
if (gDebugFlags & DEBUG_DRAW_NORMALS) [self debugDrawNormals];
#endif

View File

@ -0,0 +1,57 @@
/*
OOPolygonSprite.h
Oolite
Two-dimensional polygon object for UI things such as missile icons.
Copyright (C) 2009 Jens Ayton
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#import "OOCocoa.h"
#import "OOOpenGL.h"
@interface OOPolygonSprite: NSObject
{
@private
GLfloat *_solidData;
size_t _solidCount;
#ifndef NDEBUG
NSString *_name;
#endif
}
/* DataArray is either an array of pairs of numbers, or an array of such
arrays (representing one or more contours).
OutlineWidth is the width of the tesselated outline, in the same scale as
the vertices.
Name is used for debugging only.
*/
- (id) initWithDataArray:(NSArray *)dataArray outlineWidth:(GLfloat)outlineWidth name:(NSString *)name;
- (void) drawFilled;
@end

403
src/Core/OOPolygonSprite.m Normal file
View File

@ -0,0 +1,403 @@
/*
OOPolygonSprite.m
Oolite
Copyright (C) 2009 Jens Ayton
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
/* Implementation note:
The polygon data is tesselated (on object creation) using a GLU tesselator.
Although GLU produces a mix of triangles, triangle fans and triangle
strips, we convert those to a single triangle soup since the primitives are
unlikely to be large enough that using multiple primitives would be a win.
Uniquing vertices and using indices, and using the same vertex array for
outline and filled mode, would in principle be more efficient, but not
worth the added complexity in the preprocessing given the simplicity of
the icons we're likely to encounter.
*/
#undef OO_CHECK_GL_HEAVY
#define OO_CHECK_GL_HEAVY 0
#import "OOPolygonSprite.h"
#import "OOCollectionExtractors.h"
#import "OOMacroOpenGL.h"
#define TESS_TOLERANCE 0.05 // Feature merging factor: higher values merge more features with greater chance of distortion.
@interface OOPolygonSprite (Private)
- (BOOL) loadPolygons:(NSArray *)dataArray;
@end
typedef struct
{
GLfloat *data;
size_t count; // Number of vertices in use, i.e. half of number of data elements used.
size_t capacity; // Half of number of floats there is space for in data.
GLenum mode; // Current primitive mode.
size_t vCount; // Number of vertices so far in primitive.
NSPoint pending0, pending1; // Used for splitting GL_TRIANGLE_STRIP/GL_TRIANGLE_FAN primitives.
BOOL OK; // Set to false to indicate error.
#ifndef NDEBUG
NSString *name;
#endif
} TessPolygonData;
static BOOL GrowTessPolygonData(TessPolygonData *data, size_t capacityHint); // Returns true if capacity grew by at least one.
static BOOL AppendVertex(TessPolygonData *data, NSPoint vertex);
static void SolidBeginCallback(GLenum type, void *polygonData);
static void SolidVertexCallback(void *vertexData, void *polygonData);
static void SolidEndCallback(void *polygonData);
static void ErrorCallback(GLenum error, void *polygonData);
@implementation OOPolygonSprite
- (id) initWithDataArray:(NSArray *)dataArray outlineWidth:(GLfloat)outlineWidth name:(NSString *)name
{
if ((self = [super init]))
{
#ifndef NDEBUG
_name = [name copy];
#endif
if ([dataArray count] == 0)
{
[self release];
return nil;
}
// Normalize data to array-of-arrays form.
if (![[dataArray objectAtIndex:0] isKindOfClass:[NSArray class]])
{
dataArray = [NSArray arrayWithObject:dataArray];
}
if (![self loadPolygons:dataArray])
{
[self release];
return nil;
}
}
return self;
}
- (void) dealloc
{
#ifndef NDEBUG
DESTROY(_name);
#endif
free(_solidData);
[super dealloc];
}
#ifndef NDEBUG
- (NSString *) descriptionComponents
{
return _name;
}
#endif
- (void) drawFilled
{
OO_ENTER_OPENGL();
OOGL(glEnableClientState(GL_VERTEX_ARRAY));
OOGL(glVertexPointer(2, GL_FLOAT, 0, _solidData));
OOGL(glDrawArrays(GL_TRIANGLES, 0, _solidCount));
OOGL(glDisableClientState(GL_VERTEX_ARRAY));
}
- (BOOL) loadPolygons:(NSArray *)dataArray
{
NSParameterAssert(dataArray != nil);
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
GLUtesselator *tesselator = NULL;
TessPolygonData polygonData;
memset(&polygonData, 0, sizeof polygonData);
polygonData.OK = YES;
#ifndef NDEBUG
polygonData.name = _name;
#endif
#if !OO_DEBUG
// For efficiency, grow to more than big enough for most cases to avoid regrowing.
if (!GrowTessPolygonData(&polygonData, 100))
{
polygonData.OK = NO;
goto END;
}
#endif
tesselator = gluNewTess();
if (tesselator == NULL)
{
polygonData.OK = NO;
goto END;
}
gluTessCallback(tesselator, GLU_TESS_BEGIN_DATA, SolidBeginCallback);
gluTessCallback(tesselator, GLU_TESS_VERTEX_DATA, SolidVertexCallback);
gluTessCallback(tesselator, GLU_TESS_END_DATA, SolidEndCallback);
gluTessCallback(tesselator, GLU_TESS_ERROR_DATA, ErrorCallback);
gluTessProperty(tesselator, GLU_TESS_TOLERANCE, TESS_TOLERANCE);
gluTessBeginPolygon(tesselator, &polygonData);
OOUInteger contourCount = [dataArray count], contourIndex;
for (contourIndex = 0; contourIndex < contourCount && polygonData.OK; contourIndex++)
{
NSArray *contour = [dataArray oo_arrayAtIndex:contourIndex];
if (contour == nil)
{
polygonData.OK = NO;
break;
}
OOUInteger vertexCount = [contour count] / 2, vertexIndex;
if (vertexCount > 2)
{
gluTessBeginContour(tesselator);
for (vertexIndex = 0; vertexIndex < vertexCount && polygonData.OK; vertexIndex++)
{
GLdouble vert[3] =
{
[contour oo_doubleAtIndex:vertexIndex * 2],
[contour oo_doubleAtIndex:vertexIndex * 2 + 1],
0.0
};
/* The third parameter to gluTessVertex() is the data
actually passed to our vertex callback. Since the vertex
callback isn't called until later, each vertex's data needs
to have an independent existence. We pack them into
NSValues here and let NSAutoReleasepool clean up
afterwards.
*/
NSPoint p = { vert[0], vert[1] };
NSValue *vertValue = [NSValue valueWithPoint:p];
gluTessVertex(tesselator, vert, vertValue);
}
gluTessEndContour(tesselator);
}
}
gluTessEndPolygon(tesselator);
if (polygonData.OK)
{
_solidCount = polygonData.count;
_solidData = realloc(polygonData.data, polygonData.count * sizeof (GLfloat) * 2);
if (_solidData != NULL) polygonData.data = NULL;
else
{
_solidData = polygonData.data;
if (_solidData == NULL) polygonData.OK = NO;
}
}
END:
free(polygonData.data);
gluDeleteTess(tesselator);
[pool release];
return polygonData.OK;
}
@end
static BOOL GrowTessPolygonData(TessPolygonData *data, size_t capacityHint)
{
NSCParameterAssert(data != NULL);
size_t minCapacity = data->capacity + 1;
size_t desiredCapacity = MAX(capacityHint, minCapacity);
size_t newCapacity = 0;
GLfloat *newData = realloc(data->data, desiredCapacity * sizeof (GLfloat) * 2);
if (newData != NULL)
{
newCapacity = desiredCapacity;
}
else
{
desiredCapacity = minCapacity;
newData = realloc(data->data, desiredCapacity * sizeof (GLfloat) * 2);
if (newData != NULL) newCapacity = desiredCapacity;
}
if (newData == NULL) return NO;
NSCAssert(newCapacity > data->capacity, @"Buffer regrow logic error");
data->data = newData;
data->capacity = newCapacity;
return YES;
}
static BOOL AppendVertex(TessPolygonData *data, NSPoint vertex)
{
NSCParameterAssert(data != NULL);
if (data->capacity == data->count && !GrowTessPolygonData(data, data->capacity * 2)) return NO;
data->data[data->count * 2] = vertex.x;
data->data[data->count * 2 + 1] = vertex.y;
data->count++;
return YES;
}
static void SolidBeginCallback(GLenum type, void *polygonData)
{
TessPolygonData *data = polygonData;
NSCParameterAssert(data != NULL);
data->mode = type;
data->vCount = 0;
}
static void SolidVertexCallback(void *vertexData, void *polygonData)
{
TessPolygonData *data = polygonData;
NSValue *vertValue = vertexData;
NSCParameterAssert(vertValue != NULL && data != NULL);
if (!data->OK) return;
NSPoint p = [vertValue pointValue];
NSPoint vertex = { p.x, p.y };
size_t vCount = data->vCount++;
switch (data->mode)
{
case GL_TRIANGLES:
data->OK = AppendVertex(data, vertex);
OOLog(@"tesselate.vertex.tri", @"%u: %@", vCount, NSStringFromPoint(vertex));
break;
case GL_TRIANGLE_FAN:
if (vCount == 0) data->pending0 = vertex;
else if (vCount == 1) data->pending1 = vertex;
else
{
data->OK = AppendVertex(data, data->pending0) &&
AppendVertex(data, data->pending1) &&
AppendVertex(data, vertex);
OOLog(@"tesselate.vertex.fan", @"%u: (%@ %@) %@", vCount, NSStringFromPoint(data->pending0), NSStringFromPoint(data->pending1), NSStringFromPoint(vertex));
data->pending1 = vertex;
}
break;
case GL_TRIANGLE_STRIP:
if (vCount == 0) data->pending0 = vertex;
else if (vCount == 1) data->pending1 = vertex;
else
{
/* In order to produce consistent winding, the vertex->triangle
order for GL_TRIANGLE_STRIP is:
0, 1, 2
2, 1, 3
2, 3, 4
4, 3, 5
4, 5, 6
6, 5, 7
6, 7, 8
Vertices 0 and 1 are special-cased above, and the first
time we get here it's time for the first triangle, which
is pending0, pending1, v. v (i.e., vertex 2) then goes into
pending0.
For the second triangle, the triangle is again pending0,
pending1, v, and we then put v (i.e., vertex 3) into
pending1.
The third triangle follows the same pattern as the first,
and the fourthe the same as the second.
In other words, after storing each triangle, v goes into
pending0 for even vertex indicies, and pending1 for odd
vertex indices.
*/
data->OK = AppendVertex(data, data->pending0) &&
AppendVertex(data, data->pending1) &&
AppendVertex(data, vertex);
OOLog(@"tesselate.vertex.strip", @"%u: (%@ %@) %@", vCount, NSStringFromPoint(data->pending0), NSStringFromPoint(data->pending1), NSStringFromPoint(vertex));
if ((vCount % 2) == 0) data->pending0 = vertex;
else data->pending1 = vertex;
}
break;
default:
#ifndef NDEBUG
OOLog(@"polygonSprite.tesselate.error", @"Unexpected tesselator primitive mode %u.", data->mode);
#endif
data->OK = NO;
}
}
static void SolidEndCallback(void *polygonData)
{
TessPolygonData *data = polygonData;
NSCParameterAssert(data != NULL);
data->mode = 0;
data->vCount = 0;
}
static void ErrorCallback(GLenum error, void *polygonData)
{
TessPolygonData *data = polygonData;
NSCParameterAssert(data != NULL);
NSString *name = @"";
#ifndef NDEBUG
name = [NSString stringWithFormat:@" \"%@\"", data->name];
#endif
char *errStr = (char *)gluErrorString(error);
OOLog(@"polygonSprite.tesselate.error", @"Error %s (%u) while tesselating polygon%@.", errStr, error, name);
data->OK = NO;
}