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

          Line data    Source code
       1           0 : /*
       2             : 
       3             : OODefaultShaderSynthesizer.m
       4             : 
       5             : 
       6             : Copyright © 2011-2013 Jens Ayton
       7             : 
       8             : Permission is hereby granted, free of charge, to any person obtaining a copy
       9             : of this software and associated documentation files (the “Software”), to deal
      10             : in the Software without restriction, including without limitation the rights
      11             : to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
      12             : copies of the Software, and to permit persons to whom the Software is
      13             : furnished to do so, subject to the following conditions:
      14             : 
      15             : The above copyright notice and this permission notice shall be included in all
      16             : copies or substantial portions of the Software.
      17             : 
      18             : THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
      19             : IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
      20             : FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
      21             : AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
      22             : LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
      23             : OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
      24             : SOFTWARE.
      25             : 
      26             : */
      27             : 
      28             : #import "OODefaultShaderSynthesizer.h"
      29             : #import "OOMesh.h"
      30             : #import "OOTexture.h"
      31             : #import "OOColor.h"
      32             : 
      33             : #import "NSStringOOExtensions.h"
      34             : #import "OOCollectionExtractors.h"
      35             : #import "NSDictionaryOOExtensions.h"
      36             : #import "OOMaterialSpecifier.h"
      37             : #import "ResourceManager.h"
      38             : 
      39             : /* 
      40             :  * GNUstep 1.20.1 does not support NSIntegerHashCallBacks but uses 
      41             :  * NSIntHashCallBacks instead. NSIntHashCallBacks was deprecated in favor of
      42             :  * NSIntegerHashCallBacks in GNUstep versions later than 1.20.1. If we move to
      43             :  * a newer GNUstep version for Oolite the #define below may not be necessary
      44             :  * anymore but for now we need it to be able to build. - Nikos 20120208.
      45             : */
      46             : #if OOLITE_GNUSTEP
      47             : #define NSIntegerHashCallBacks  NSIntHashCallBacks
      48             : #endif
      49             : 
      50             : 
      51             : static NSDictionary *CanonicalizeMaterialSpecifier(NSDictionary *spec, NSString *materialKey);
      52             : 
      53             : static NSString *FormatFloat(double value);
      54             : 
      55             : 
      56           0 : @interface OODefaultShaderSynthesizer: NSObject
      57             : {
      58             : @private
      59           0 :         NSDictionary                            *_configuration;
      60           0 :         NSString                                        *_materialKey;
      61           0 :         NSString                                        *_entityName;
      62             :         
      63           0 :         NSString                                        *_vertexShader;
      64           0 :         NSString                                        *_fragmentShader;
      65           0 :         NSMutableArray                          *_textures;
      66           0 :         NSMutableDictionary                     *_uniforms;
      67             :         
      68           0 :         NSMutableString                         *_attributes;
      69           0 :         NSMutableString                         *_varyings;
      70           0 :         NSMutableString                         *_vertexUniforms;
      71           0 :         NSMutableString                         *_fragmentUniforms;
      72           0 :         NSMutableString                         *_vertexHelpers;
      73           0 :         NSMutableString                         *_fragmentHelpers;
      74           0 :         NSMutableString                         *_vertexBody;
      75           0 :         NSMutableString                         *_fragmentPreTextures;
      76           0 :         NSMutableString                         *_fragmentTextureLookups;
      77           0 :         NSMutableString                         *_fragmentBody;
      78             :         
      79             :         // _texturesByName: dictionary mapping texture file names to texture specifications.
      80           0 :         NSMutableDictionary                     *_texturesByName;
      81             :         // _textureIDs: dictionary mapping texture file names to numerical IDs used to name variables.
      82           0 :         NSMutableDictionary                     *_textureIDs;
      83             :         // _sampledTextures: hash of integer texture IDs for which we’ve set up a sample.
      84           0 :         NSHashTable                                     *_sampledTextures;
      85             :         
      86           0 :         NSMutableDictionary                     *_uniformBindingNames;
      87             :         
      88           0 :         NSUInteger                                      _usesNormalMap: 1,
      89           0 :                                                                 _usesDiffuseTerm: 1,
      90           0 :                                                                 _constZNormal: 1,
      91           0 :                                                                 _haveDiffuseLight: 1,
      92             :         
      93             :         // Completion flags for various generation stages.
      94           0 :                                                                 _completed_writeFinalColorComposite: 1,
      95           0 :                                                                 _completed_writeDiffuseColorTerm: 1,
      96           0 :                                                                 _completed_writeSpecularLighting: 1,
      97           0 :                                                                 _completed_writeLightMaps: 1,
      98           0 :                                                                 _completed_writeDiffuseLighting: 1,
      99           0 :                                                                 _completed_writeDiffuseColorTermIfNeeded: 1,
     100           0 :                                                                 _completed_writeVertexPosition: 1,
     101           0 :                                                                 _completed_writeNormalIfNeeded: 1,
     102             :                                                 //              _completedwriteNormal: 1,
     103           0 :                                                                 _completed_writeLightVector: 1,
     104           0 :                                                                 _completed_writeEyeVector: 1, 
     105           0 :                                                                 _completed_writeTotalColor: 1,
     106           0 :                                                                 _completed_writeTextureCoordRead: 1,
     107           0 :                                                                 _completed_writeVertexTangentBasis: 1;
     108             :         
     109             : #ifndef NDEBUG
     110           0 :         NSHashTable                                     *_stagesInProgress;
     111             : #endif
     112             : }
     113             : 
     114           0 : - (id) initWithMaterialConfiguration:(NSDictionary *)configuration
     115             :                                                  materialKey:(NSString *)materialKey
     116             :                                                   entityName:(NSString *)name;
     117             : 
     118           0 : - (BOOL) run;
     119             : 
     120           0 : - (NSString *) vertexShader;
     121           0 : - (NSString *) fragmentShader;
     122           0 : - (NSArray *) textureSpecifications;
     123           0 : - (NSDictionary *) uniformSpecifications;
     124             : 
     125           0 : - (NSString *) materialKey;
     126           0 : - (NSString *) entityName;
     127             : 
     128           0 : - (void) createTemporaries;
     129           0 : - (void) destroyTemporaries;
     130             : 
     131           0 : - (void) composeVertexShader;
     132           0 : - (void) composeFragmentShader;
     133             : 
     134             : // Write various types of declarations.
     135           0 : - (void) appendVariable:(NSString *)name ofType:(NSString *)type withPrefix:(NSString *)prefix to:(NSMutableString *)buffer;
     136           0 : - (void) addAttribute:(NSString *)name ofType:(NSString *)type;
     137           0 : - (void) addVarying:(NSString *)name ofType:(NSString *)type;
     138           0 : - (void) addVertexUniform:(NSString *)name ofType:(NSString *)type;
     139           0 : - (void) addFragmentUniform:(NSString *)name ofType:(NSString *)type;
     140             : 
     141             : // Create or retrieve a uniform variable name for a given binding.
     142           0 : - (NSString *) defineBindingUniform:(NSDictionary *)binding ofType:(NSString *)type;
     143             : 
     144           0 : - (NSString *) readRGBForTextureSpec:(NSDictionary *)textureSpec mapName:(NSString *)mapName;   // Generate a read for an RGB value, or a single channel splatted across RGB.
     145           0 : - (NSString *) readOneChannelForTextureSpec:(NSDictionary *)textureSpec mapName:(NSString *)mapName;    // Generate a read for a single channel.
     146             : 
     147             : // Details of texture setup; generally use -read*ForTextureSpec:mapName: instead.
     148           0 : - (NSUInteger) textureIDForSpec:(NSDictionary *)textureSpec;
     149           0 : - (void) setUpOneTexture:(NSDictionary *)textureSpec;
     150           0 : - (void) getSampleName:(NSString **)outSampleName andSwizzleOp:(NSString **)outSwizzleOp forTextureSpec:(NSDictionary *)textureSpec;
     151             : 
     152             : 
     153             : /*      Stages. These should only be called through the REQUIRE_STAGE macro to
     154             :         avoid duplicated code and ensure data depedencies are met.
     155             : */
     156             : 
     157             : 
     158             : /*      writeTextureCoordRead
     159             :         Generate vec2 texCoords.
     160             : */
     161           0 : - (void) writeTextureCoordRead;
     162             : 
     163             : /*      writeDiffuseColorTermIfNeeded
     164             :         Generates and populates the fragment shader value vec3 diffuseColor, unless
     165             :         the diffuse term is black. If a diffuseColor is generated, _usesDiffuseTerm
     166             :         is set. The value will be const if possible.
     167             :         See also: writeDiffuseColorTerm.
     168             : */
     169           0 : - (void) writeDiffuseColorTermIfNeeded;
     170             : 
     171             : /*      writeDiffuseColorTerm
     172             :         Generates vec3 diffuseColor unconditionally – that is, even if the diffuse
     173             :         term is black.
     174             :         See also: writeDiffuseColorTermIfNeeded.
     175             : */
     176           0 : - (void) writeDiffuseColorTerm;
     177             : 
     178             : /*      writeDiffuseLighting
     179             :         Generate the fragment variable vec3 diffuseLight and add Lambertian and
     180             :         ambient terms to it.
     181             : */
     182           0 : - (void) writeDiffuseLighting;
     183             : 
     184             : /*      writeLightVector
     185             :         Generate the fragment variable vec3 lightVector (unit vector) for temporary
     186             :         lighting. Calling this if lighting mode is kLightingUniform will cause an
     187             :         exception.
     188             : */
     189           0 : - (void) writeLightVector;
     190             : 
     191             : /*      writeEyeVector
     192             :         Generate vec3 lightVector, the normalized direction from the fragment to
     193             :         the light source.
     194             : */
     195           0 : - (void) writeEyeVector;
     196             : 
     197             : /*      writeVertexTangentBasis
     198             :         Generates tangent space basis matrix (TBN) in vertex shader, if in tangent-
     199             :         space lighting mode. If not, an exeception is raised.
     200             : */
     201           0 : - (void) writeVertexTangentBasis;
     202             : 
     203             : /*      writeNormalIfNeeded
     204             :         Writes fragment variable vec3 normal if necessary. Otherwise, it sets
     205             :         _constZNormal, indicating that the normal is always (0, 0, 1).
     206             :         
     207             :         See also: writeNormal.
     208             : */
     209           0 : - (void) writeNormalIfNeeded;
     210             : 
     211             : /*      writeNormal
     212             :         Generates vec3 normal unconditionally – if _constZNormal is set, normal will
     213             :         be const vec3 normal = vec3 (0.0, 0.0, 1.0).
     214             : */
     215           0 : - (void) writeNormal;
     216             : 
     217             : /*      writeSpecularLighting
     218             :         Calculate specular writing and add it to totalColor.
     219             : */
     220           0 : - (void) writeSpecularLighting;
     221             : 
     222             : /*      writeLightMaps
     223             :         Add emission and illumination maps to totalColor.
     224             : */
     225           0 : - (void) writeLightMaps;
     226             : 
     227             : /*      writeVertexPosition
     228             :         Calculate vertex position and write it to gl_Position.
     229             : */
     230           0 : - (void) writeVertexPosition;
     231             : 
     232             : /*      writeTotalColor
     233             :         Generate vec3 totalColor, the accumulator for output colour values.
     234             : */
     235           0 : - (void) writeTotalColor;
     236             : 
     237             : /*      writeFinalColorComposite
     238             :         This stage writes the final fragment shader. It also pulls in other stages
     239             :         through dependencies.
     240             : */
     241           0 : - (void) writeFinalColorComposite;
     242             : 
     243             : 
     244             : /*
     245             :         REQUIRE_STAGE(): pull in the required stage. A stage must have a
     246             :         zero-parameter method and a matching _completed_stage instance variable.
     247             :         
     248             :         In debug/testrelease builds, this dispatches through performStage: which
     249             :         checks for recursive calls.
     250             : */
     251             : #ifndef NDEBUG
     252           0 : #define REQUIRE_STAGE(NAME) if (!_completed_##NAME) { [self performStage:@selector(NAME)]; _completed_##NAME = YES; }
     253           0 : - (void) performStage:(SEL)stage;
     254             : #else
     255             : #define REQUIRE_STAGE(NAME) if (!_completed_##NAME) { [self NAME]; _completed_##NAME = YES; }
     256             : #endif
     257             : 
     258             : @end
     259             : 
     260             : 
     261           0 : static NSString *GetExtractMode(NSDictionary *textureSpecifier);
     262             : 
     263             : 
     264           0 : BOOL OOSynthesizeMaterialShader(NSDictionary *configuration, NSString *materialKey, NSString *entityName, NSString **outVertexShader, NSString **outFragmentShader, NSArray **outTextureSpecs, NSDictionary **outUniformSpecs)
     265             : {
     266             :         NSCParameterAssert(configuration != nil && outVertexShader != NULL && outFragmentShader != NULL && outTextureSpecs != NULL && outUniformSpecs != NULL);
     267             :         
     268             :         NSAutoreleasePool *pool = [NSAutoreleasePool new];
     269             :         
     270             :         OODefaultShaderSynthesizer *synthesizer = [[OODefaultShaderSynthesizer alloc]
     271             :                                                                                            initWithMaterialConfiguration:configuration
     272             :                                                                                                                                  materialKey:materialKey
     273             :                                                                                                                                   entityName:entityName];
     274             :         [synthesizer autorelease];
     275             :         
     276             :         BOOL OK = [synthesizer run];
     277             :         if (OK)
     278             :         {
     279             :                 *outVertexShader = [[synthesizer vertexShader] retain];
     280             :                 *outFragmentShader = [[synthesizer fragmentShader] retain];
     281             :                 *outTextureSpecs = [[synthesizer textureSpecifications] retain];
     282             :                 *outUniformSpecs = [[synthesizer uniformSpecifications] retain];
     283             :         }
     284             :         else
     285             :         {
     286             :                 *outVertexShader = nil;
     287             :                 *outFragmentShader = nil;
     288             :                 *outTextureSpecs = nil;
     289             :                 *outUniformSpecs = nil;
     290             :         }
     291             :         
     292             :         [pool release];
     293             :         
     294             :         [*outVertexShader autorelease];
     295             :         [*outFragmentShader autorelease];
     296             :         [*outTextureSpecs autorelease];
     297             :         [*outUniformSpecs autorelease];
     298             :         
     299             :         return YES;
     300             : }
     301             : 
     302             : 
     303             : @implementation OODefaultShaderSynthesizer
     304             : 
     305             : - (id) initWithMaterialConfiguration:(NSDictionary *)configuration
     306             :                                                  materialKey:(NSString *)materialKey
     307             :                                                   entityName:(NSString *)name
     308             : {
     309             :         if ((self = [super init]))
     310             :         {
     311             :                 _configuration = [CanonicalizeMaterialSpecifier(configuration, materialKey) retain];
     312             :                 _materialKey = [materialKey copy];
     313             :                 _entityName = [_entityName copy];
     314             :         }
     315             :         
     316             :         return self;
     317             : }
     318             : 
     319             : 
     320           0 : - (void) dealloc
     321             : {
     322             :         [self destroyTemporaries];
     323             :         DESTROY(_configuration);
     324             :         DESTROY(_materialKey);
     325             :         DESTROY(_entityName);
     326             :         DESTROY(_vertexShader);
     327             :         DESTROY(_fragmentShader);
     328             :         DESTROY(_textures);
     329             :         
     330             :     [super dealloc];
     331             : }
     332             : 
     333             : 
     334             : - (NSString *) vertexShader
     335             : {
     336             :         return _vertexShader;
     337             : }
     338             : 
     339             : 
     340             : - (NSString *) fragmentShader
     341             : {
     342             :         return _fragmentShader;
     343             : }
     344             : 
     345             : 
     346             : - (NSArray *) textureSpecifications
     347             : {
     348             : #ifndef NDEBUG
     349             :         return [NSArray arrayWithArray:_textures];
     350             : #else
     351             :         return _textures;
     352             : #endif
     353             : }
     354             : 
     355             : 
     356             : - (NSDictionary *) uniformSpecifications
     357             : {
     358             : #ifndef NDEBUG
     359             :         return [NSDictionary dictionaryWithDictionary:_uniforms];
     360             : #else
     361             :         return _uniforms;
     362             : #endif
     363             : }
     364             : 
     365             : 
     366             : - (BOOL) run
     367             : {
     368             :         [self createTemporaries];
     369             :         _uniforms = [[NSMutableDictionary alloc] init];
     370             :         [_vertexBody appendString:@"void main(void)\n{\n"];
     371             :         [_fragmentPreTextures appendString:@"void main(void)\n{\n"];
     372             :         
     373             :         @try
     374             :         {
     375             :                 REQUIRE_STAGE(writeFinalColorComposite);
     376             :                 
     377             :                 [self composeVertexShader];
     378             :                 [self composeFragmentShader];
     379             :         }
     380             :         @catch (NSException *exception)
     381             :         {
     382             :                 // Error should have been reported already.
     383             :                 return NO;
     384             :         }
     385             :         @finally
     386             :         {
     387             :                 [self destroyTemporaries];
     388             :         }
     389             :         
     390             :         return YES;
     391             : }
     392             : 
     393             : - (NSString *) materialKey
     394             : {
     395             :         return _materialKey;
     396             : }
     397             : 
     398             : 
     399             : - (NSString *) entityName
     400             : {
     401             :         return _entityName;
     402             : }
     403             : 
     404             : 
     405             : // MARK: - Utilities
     406             : 
     407           0 : static void AppendIfNotEmpty(NSMutableString *buffer, NSString *segment, NSString *name)
     408             : {
     409             :         if ([segment length] > 0)
     410             :         {
     411             :                 if ([buffer length] > 0)  [buffer appendString:@"\n\n"];
     412             :                 if ([name length] > 0)  [buffer appendFormat:@"// %@\n", name];
     413             :                 [buffer appendString:segment];
     414             :         }
     415             : }
     416             : 
     417             : 
     418           0 : static NSString *GetExtractMode(NSDictionary *textureSpecifier)
     419             : {
     420             :         NSString *result = nil;
     421             :         
     422             :         NSString *rawMode = [textureSpecifier oo_stringForKey:kOOTextureSpecifierSwizzleKey];
     423             :         if (rawMode != nil)
     424             :         {
     425             :                 NSUInteger length = [rawMode length];
     426             :                 if (1 <= length && length <= 4)
     427             :                 {
     428             :                         static NSCharacterSet *nonRGBACharset = nil;
     429             :                         if (nonRGBACharset == nil)
     430             :                         {
     431             :                                 nonRGBACharset = [[[NSCharacterSet characterSetWithCharactersInString:@"rgba"] invertedSet] retain];
     432             :                         }
     433             :                         
     434             :                         if ([rawMode rangeOfCharacterFromSet:nonRGBACharset].location == NSNotFound)
     435             :                         {
     436             :                                 result = rawMode;
     437             :                         }
     438             :                 }
     439             :         }
     440             :         
     441             :         return result;
     442             : }
     443             : 
     444             : 
     445             : - (void) appendVariable:(NSString *)name ofType:(NSString *)type withPrefix:(NSString *)prefix to:(NSMutableString *)buffer
     446             : {
     447             :         NSUInteger typeDeclLength = [prefix length] + [type length] + 1;
     448             :         NSUInteger padding = (typeDeclLength < 20) ? (23 - typeDeclLength) / 4 : 1;
     449             :         [buffer appendFormat:@"%@ %@%@%@;\n", prefix, type, OOTabString(padding), name];
     450             : }
     451             : 
     452             : 
     453             : - (void) addAttribute:(NSString *)name ofType:(NSString *)type
     454             : {
     455             :         [self appendVariable:name ofType:type withPrefix:@"attribute" to:_attributes];
     456             : }
     457             : 
     458             : 
     459             : - (void) addVarying:(NSString *)name ofType:(NSString *)type
     460             : {
     461             :         [self appendVariable:name ofType:type withPrefix:@"varying" to:_varyings];
     462             : }
     463             : 
     464             : 
     465             : - (void) addVertexUniform:(NSString *)name ofType:(NSString *)type
     466             : {
     467             :         [self appendVariable:name ofType:type withPrefix:@"uniform" to:_vertexUniforms];
     468             : }
     469             : 
     470             : 
     471             : - (void) addFragmentUniform:(NSString *)name ofType:(NSString *)type
     472             : {
     473             :         [self appendVariable:name ofType:type withPrefix:@"uniform" to:_fragmentUniforms];
     474             : }
     475             : 
     476             : 
     477             : - (NSString *) defineBindingUniform:(NSDictionary *)binding ofType:(NSString *)type
     478             : {
     479             :         NSString *name = [binding oo_stringForKey:@"binding"];
     480             :         NSParameterAssert([name length] > 0);
     481             :         
     482             :         NSMutableDictionary *bindingSpec = [[binding mutableCopy] autorelease];
     483             :         if ([bindingSpec oo_stringForKey:@"type"] == nil)  [bindingSpec setObject:@"binding" forKey:@"type"];
     484             :         
     485             :         // Use existing uniform if one is defined.
     486             :         NSString *uniformName = [_uniformBindingNames objectForKey:bindingSpec];
     487             :         if (uniformName != nil)  return uniformName;
     488             :         
     489             :         // Capitalize first char of name, and prepend u.
     490             :         unichar firstChar = toupper([name characterAtIndex:0]);
     491             :         NSString *baseName = [NSString stringWithFormat:@"u%C%@", firstChar, [name substringFromIndex:1]];
     492             :         
     493             :         // Ensure name is unique.
     494             :         name = baseName;
     495             :         unsigned idx = 1;
     496             :         while ([_uniforms objectForKey:name] != nil)
     497             :         {
     498             :                 name = [NSString stringWithFormat:@"%@%u", baseName, ++idx];
     499             :         }
     500             :         
     501             :         [self addFragmentUniform:name ofType:type];
     502             :         
     503             :         [_uniforms setObject:bindingSpec forKey:name];
     504             :         [_uniformBindingNames setObject:name forKey:bindingSpec];
     505             :         
     506             :         return name;
     507             : }
     508             : 
     509             : 
     510             : - (void) composeVertexShader
     511             : {
     512             :         while ([_vertexBody hasSuffix:@"\t\n"])
     513             :         {
     514             :                 [_vertexBody deleteCharactersInRange:(NSRange){ [_vertexBody length] - 2, 2 }];
     515             :         }
     516             :         [_vertexBody appendString:@"}"];
     517             :         
     518             :         NSMutableString *vertexShader = [NSMutableString string];
     519             :         AppendIfNotEmpty(vertexShader, _attributes, @"Attributes");
     520             :         AppendIfNotEmpty(vertexShader, _vertexUniforms, @"Uniforms");
     521             :         AppendIfNotEmpty(vertexShader, _varyings, @"Varyings");
     522             :         AppendIfNotEmpty(vertexShader, _vertexHelpers, @"Helper functions");
     523             :         AppendIfNotEmpty(vertexShader, _vertexBody, nil);
     524             :         
     525             : #ifndef NDEBUG
     526             :         _vertexShader = [vertexShader copy];
     527             : #else
     528             :         _vertexShader = [vertexShader retain];
     529             : #endif
     530             : }
     531             : 
     532             : 
     533             : - (void) composeFragmentShader
     534             : {
     535             :         while ([_fragmentBody hasSuffix:@"\t\n"])
     536             :         {
     537             :                 [_fragmentBody deleteCharactersInRange:(NSRange){ [_fragmentBody length] - 2, 2 }];
     538             :         }
     539             :         
     540             :         NSMutableString *fragmentShader = [NSMutableString string];
     541             :         AppendIfNotEmpty(fragmentShader, _fragmentUniforms, @"Uniforms");
     542             :         AppendIfNotEmpty(fragmentShader, _varyings, @"Varyings");
     543             :         AppendIfNotEmpty(fragmentShader, _fragmentHelpers, @"Helper functions");
     544             :         AppendIfNotEmpty(fragmentShader, _fragmentPreTextures, nil);
     545             :         if ([_fragmentTextureLookups length] > 0)
     546             :         {
     547             :                 [fragmentShader appendString:@"\t\n\t// Texture lookups\n"];
     548             :                 [fragmentShader appendString:_fragmentTextureLookups];
     549             :         }
     550             :         [fragmentShader appendString:@"\t\n"];
     551             :         [fragmentShader appendString:_fragmentBody];
     552             :         [fragmentShader appendString:@"}"];
     553             :         
     554             : #ifndef NDEBUG
     555             :         _fragmentShader = [fragmentShader copy];
     556             : #else
     557             :         _fragmentShader = [fragmentShader retain];
     558             : #endif
     559             : }
     560             : 
     561             : 
     562             : /*
     563             :         Build a key for a texture specifier, taking all texture configuration
     564             :         options into account and ignoring the other stuff that might be there.
     565             :         
     566             :         FIXME: efficiency and stuff.
     567             : */
     568           0 : static NSString *KeyFromTextureParameters(NSString *name, OOTextureFlags options, float anisotropy, float lodBias)
     569             : {
     570             : #ifndef NDEBUG
     571             :         options = OOApplyTextureOptionDefaults(options);
     572             : #endif
     573             :         
     574             :         // Extraction modes are ignored in synthesized shaders, since we use swizzling instead.
     575             :         options &= ~kOOTextureExtractChannelMask;
     576             :         
     577             :         return [NSString stringWithFormat:@"%@:%X:%g:%g", name, options, anisotropy, lodBias];
     578             : }
     579             : 
     580           0 : static NSString *KeyFromTextureSpec(NSDictionary *spec)
     581             : {
     582             :         NSString *texName = nil;
     583             :         OOTextureFlags texOptions;
     584             :         float anisotropy, lodBias;
     585             :         if (!OOInterpretTextureSpecifier(spec, &texName, &texOptions, &anisotropy, &lodBias, YES))
     586             :         {
     587             :                 // OOInterpretTextureSpecifier() will have logged something.
     588             :                 [NSException raise:NSGenericException format:@"Invalid texture specifier"];
     589             :         }
     590             :         
     591             :         return KeyFromTextureParameters(texName, texOptions, anisotropy, lodBias);
     592             : }
     593             : 
     594             : 
     595           0 : - (NSUInteger) assignIDForTexture:(NSDictionary *)spec
     596             : {
     597             :         NSParameterAssert(spec != nil);
     598             :         
     599             :         // extract_channel doesn't affect uniqueness, and we don't want OOTexture to do actual extraction.
     600             :         if ([spec objectForKey:kOOTextureSpecifierSwizzleKey] != nil)
     601             :         {
     602             :                 spec = [spec dictionaryByRemovingObjectForKey:kOOTextureSpecifierSwizzleKey];
     603             :         }
     604             :         
     605             :         NSString *texName = nil;
     606             :         OOTextureFlags texOptions;
     607             :         float anisotropy, lodBias;
     608             :         if (!OOInterpretTextureSpecifier(spec, &texName, &texOptions, &anisotropy, &lodBias, YES))
     609             :         {
     610             :                 // OOInterpretTextureSpecifier() will have logged something.
     611             :                 [NSException raise:NSGenericException format:@"Invalid texture specifier"];
     612             :         }
     613             :         
     614             :         if (texOptions & kOOTextureAllowCubeMap)
     615             :         {
     616             :                 // cube_map = true; fail regardless of whether actual texture qualifies.
     617             :                 OOLogERR(@"material.synthesis.error.cubeMap", @"The material \"%@\" of \"%@\" specifies a cube map texture, but doesn't have custom shaders. Cube map textures are not supported with the default shaders.", [self materialKey], [self entityName]);
     618             :                 [NSException raise:NSGenericException format:@"Invalid material"];
     619             :         }
     620             :         
     621             :         NSString *key = KeyFromTextureParameters(texName, texOptions, anisotropy, lodBias);
     622             :         NSUInteger texID;
     623             :         NSObject *existing = [_texturesByName objectForKey:key];
     624             :         if (existing == nil)
     625             :         {
     626             :                 texID = [_texturesByName count];
     627             :                 NSNumber        *texIDObj = [NSNumber numberWithUnsignedInteger:texID];
     628             :                 NSString        *texUniform = [NSString stringWithFormat:@"uTexture%lu", texID];
     629             :                 
     630             : #ifndef NDEBUG
     631             :                 BOOL useInternalFormat = NO;
     632             : #else
     633             :                 BOOL useInternalFormat = YES;
     634             : #endif
     635             :                 
     636             :                 [_textures addObject:OOMakeTextureSpecifier(texName, texOptions, anisotropy, lodBias, useInternalFormat)];
     637             :                 [_texturesByName setObject:spec forKey:key];
     638             :                 [_textureIDs setObject:texIDObj forKey:key];
     639             :                 [_uniforms setObject:[NSDictionary dictionaryWithObjectsAndKeys:@"texture", @"type", texIDObj, @"value", nil]
     640             :                                           forKey:texUniform];
     641             :                 
     642             :                 [self addFragmentUniform:texUniform ofType:@"sampler2D"];
     643             :         }
     644             :         else
     645             :         {
     646             :                 texID = [_textureIDs oo_unsignedIntegerForKey:texName];
     647             :         }
     648             :         
     649             :         return texID;
     650             : }
     651             : 
     652             : 
     653             : - (NSUInteger) textureIDForSpec:(NSDictionary *)textureSpec
     654             : {
     655             :         return [_textureIDs oo_unsignedIntegerForKey:KeyFromTextureSpec(textureSpec)];
     656             : }
     657             : 
     658             : 
     659             : - (void) setUpOneTexture:(NSDictionary *)textureSpec
     660             : {
     661             :         if (textureSpec == nil)  return;
     662             :         
     663             :         REQUIRE_STAGE(writeTextureCoordRead);
     664             :         
     665             :         NSUInteger texID = [self assignIDForTexture:textureSpec];
     666             :         if ((NSUInteger)NSHashGet(_sampledTextures, (const void *)(texID + 1)) == 0)
     667             :         {
     668             :                 NSHashInsertKnownAbsent(_sampledTextures, (const void *)(texID + 1));
     669             :                 [_fragmentTextureLookups appendFormat:@"\tvec4 tex%luSample = texture2D(uTexture%lu, texCoords);  // %@\n", texID, texID, [textureSpec oo_stringForKey:kOOTextureSpecifierNameKey]];
     670             :         }
     671             : }
     672             : 
     673             : 
     674             : - (void) getSampleName:(NSString **)outSampleName andSwizzleOp:(NSString **)outSwizzleOp forTextureSpec:(NSDictionary *)textureSpec
     675             : {
     676             :         NSParameterAssert(outSampleName != NULL && outSwizzleOp != NULL && textureSpec != nil);
     677             :         
     678             :         [self setUpOneTexture:textureSpec];
     679             :         NSUInteger      texID = [self textureIDForSpec:textureSpec];
     680             :         
     681             :         *outSampleName = [NSString stringWithFormat:@"tex%luSample", texID];
     682             :         *outSwizzleOp = GetExtractMode(textureSpec);
     683             : }
     684             : 
     685             : 
     686             : - (NSString *) readRGBForTextureSpec:(NSDictionary *)textureSpec mapName:(NSString *)mapName
     687             : {
     688             :         NSString *sample, *swizzle;
     689             :         [self getSampleName:&sample andSwizzleOp:&swizzle forTextureSpec:textureSpec];
     690             :         
     691             :         if (swizzle == nil)
     692             :         {
     693             :                 return [sample stringByAppendingString:@".rgb"];
     694             :         }
     695             :         
     696             :         NSUInteger channelCount = [swizzle length];
     697             :         
     698             :         if (channelCount == 1)
     699             :         {
     700             :                 return [NSString stringWithFormat:@"%@.%@%@%@", sample, swizzle, swizzle, swizzle];
     701             :         }
     702             :         else if (channelCount == 3)
     703             :         {
     704             :                 return [NSString stringWithFormat:@"%@.%@", sample, swizzle];
     705             :         }
     706             :         
     707             :         OOLogWARN(@"material.synthesis.warning.extractionMismatch", @"The %@ map for material \"%@\" of \"%@\" specifies %lu channels to extract, but only %@ may be used.", mapName, [self materialKey], [self entityName], channelCount, @"1 or 3");
     708             :         return nil;
     709             : }
     710             : 
     711             : 
     712             : - (NSString *) readOneChannelForTextureSpec:(NSDictionary *)textureSpec mapName:(NSString *)mapName
     713             : {
     714             :         NSString *sample, *swizzle;
     715             :         [self getSampleName:&sample andSwizzleOp:&swizzle forTextureSpec:textureSpec];
     716             :         
     717             :         if (swizzle == nil)
     718             :         {
     719             :                 return [sample stringByAppendingString:@".r"];
     720             :         }
     721             :         
     722             :         NSUInteger channelCount = [swizzle length];
     723             :         
     724             :         if (channelCount == 1)
     725             :         {
     726             :                 return [NSString stringWithFormat:@"%@.%@", sample, swizzle];
     727             :         }
     728             :         
     729             :         OOLogWARN(@"material.synthesis.warning.extractionMismatch", @"The %@ map for material \"%@\" of \"%@\" specifies %lu channels to extract, but only %@ may be used.", mapName, [self materialKey], [self entityName], channelCount, @"1");
     730             :         return nil;
     731             : }
     732             : 
     733             : 
     734             : #ifndef NDEBUG
     735             : - (void) performStage:(SEL)stage
     736             : {
     737             :         // Ensure that we aren’t recursing.
     738             :         if (NSHashGet(_stagesInProgress, stage) != NULL)
     739             :         {
     740             :                 OOLogERR(@"material.synthesis.error.recursion", @"Shader synthesis recursion for stage %@.", NSStringFromSelector(stage));
     741             :                 [NSException raise:NSInternalInconsistencyException format:@"stage recursion"];
     742             :         }
     743             :         
     744             :         NSHashInsertKnownAbsent(_stagesInProgress, stage);
     745             :         
     746             :         [self performSelector:stage];
     747             :         
     748             :         NSHashRemove(_stagesInProgress, stage);
     749             : }
     750             : #endif
     751             : 
     752             : 
     753             : - (void) createTemporaries
     754             : {
     755             :         _attributes = [[NSMutableString alloc] init];
     756             :         _varyings = [[NSMutableString alloc] init];
     757             :         _vertexUniforms = [[NSMutableString alloc] init];
     758             :         _fragmentUniforms = [[NSMutableString alloc] init];
     759             :         _vertexHelpers = [[NSMutableString alloc] init];
     760             :         _fragmentHelpers = [[NSMutableString alloc] init];
     761             :         _vertexBody = [[NSMutableString alloc] init];
     762             :         _fragmentPreTextures = [[NSMutableString alloc] init];
     763             :         _fragmentTextureLookups = [[NSMutableString alloc] init];
     764             :         _fragmentBody = [[NSMutableString alloc] init];
     765             :         
     766             :         _textures = [[NSMutableArray alloc] init];
     767             :         _texturesByName = [[NSMutableDictionary alloc] init];
     768             :         _textureIDs = [[NSMutableDictionary alloc] init];
     769             :         _sampledTextures = NSCreateHashTable(NSIntegerHashCallBacks, 0);
     770             :         
     771             :         _uniformBindingNames = [[NSMutableDictionary alloc] init];
     772             :         
     773             : #ifndef NDEBUG
     774             :         _stagesInProgress = NSCreateHashTable(NSNonOwnedPointerHashCallBacks, 0);
     775             : #endif
     776             : }
     777             : 
     778             : 
     779             : - (void) destroyTemporaries
     780             : {
     781             :         DESTROY(_attributes);
     782             :         DESTROY(_varyings);
     783             :         DESTROY(_vertexUniforms);
     784             :         DESTROY(_fragmentUniforms);
     785             :         DESTROY(_vertexHelpers);
     786             :         DESTROY(_fragmentHelpers);
     787             :         DESTROY(_vertexBody);
     788             :         DESTROY(_fragmentPreTextures);
     789             :         DESTROY(_fragmentTextureLookups);
     790             :         DESTROY(_fragmentBody);
     791             :         
     792             :         DESTROY(_texturesByName);
     793             :         DESTROY(_textureIDs);
     794             :         if (_sampledTextures != NULL)
     795             :         {
     796             :                 NSFreeHashTable(_sampledTextures);
     797             :                 _sampledTextures = NULL;
     798             :         }
     799             :         
     800             :         DESTROY(_uniformBindingNames);
     801             :         
     802             : #ifndef NDEBUG
     803             :         if (_stagesInProgress != NULL)
     804             :         {
     805             :                 NSFreeHashTable(_stagesInProgress);
     806             :                 _stagesInProgress = NULL;
     807             :         }
     808             : #endif
     809             : }
     810             : 
     811             : 
     812             : // MARK: - Synthesis stages
     813             : 
     814             : - (void) writeTextureCoordRead
     815             : {
     816             :         [self addVarying:@"vTexCoords" ofType:@"vec2"];
     817             :         [_vertexBody appendString:@"\tvTexCoords = gl_MultiTexCoord0.st;\n\t\n"];
     818             :         
     819             :         BOOL haveTexCoords = NO;
     820             :         NSDictionary *parallaxMap = [_configuration oo_parallaxMapSpecifier];
     821             :         
     822             :         if (parallaxMap != nil)
     823             :         {
     824             :                 float parallaxScale = [_configuration oo_parallaxScale];
     825             :                 if (parallaxScale != 0.0f)
     826             :                 {
     827             :                         /*
     828             :                                 We can’t call -getSampleName:... here because the standard
     829             :                                 texture loading mechanism has to occur after determining
     830             :                                 texture coordinates (duh).
     831             :                         */
     832             :                         NSString *swizzle = GetExtractMode(parallaxMap) ?: (NSString *)@"a";
     833             :                         NSUInteger channelCount = [swizzle length];
     834             :                         if (channelCount == 1)
     835             :                         {
     836             :                                 haveTexCoords = YES;
     837             :                                 
     838             :                                 REQUIRE_STAGE(writeEyeVector);
     839             :                                 
     840             :                                 [_fragmentPreTextures appendString:@"\t// Parallax mapping\n"];
     841             :                                 
     842             :                                 NSUInteger texID = [self assignIDForTexture:parallaxMap];
     843             :                                 [_fragmentPreTextures appendFormat:@"\tfloat parallax = texture2D(uTexture%lu, vTexCoords).%@;\n", texID, swizzle];
     844             :                                 
     845             :                                 if (parallaxScale != 1.0f)
     846             :                                 {
     847             :                                         [_fragmentPreTextures appendFormat:@"\tparallax *= %@;  // Parallax scale\n", FormatFloat(parallaxScale)];
     848             :                                 }
     849             :                                 
     850             :                                 float parallaxBias = [_configuration oo_parallaxBias];
     851             :                                 if (parallaxBias != 0.0)
     852             :                                 {
     853             :                                         [_fragmentPreTextures appendFormat:@"\tparallax += %@;  // Parallax bias\n", FormatFloat(parallaxBias)];
     854             :                                 }
     855             :                                 
     856             :                                 [_fragmentPreTextures appendString:@"\tvec2 texCoords = vTexCoords - parallax * eyeVector.xy * vec2(1.0, -1.0);\n"];
     857             :                         }
     858             :                         else
     859             :                         {
     860             :                                 OOLogWARN(@"material.synthesis.warning.extractionMismatch", @"The %@ map for material \"%@\" of \"%@\" specifies %lu channels to extract, but only %@ may be used.", @"parallax", [self materialKey], [self entityName], channelCount, @"1");
     861             :                         }
     862             :                 }
     863             :         }
     864             :         
     865             :         if (!haveTexCoords)
     866             :         {
     867             :                 [_fragmentPreTextures appendString:@"\tvec2 texCoords = vTexCoords;\n"];
     868             :         }
     869             : }
     870             : 
     871             : 
     872             : - (void) writeDiffuseColorTermIfNeeded
     873             : {
     874             :         NSDictionary            *diffuseMap = [_configuration oo_diffuseMapSpecifierWithDefaultName:[self materialKey]];
     875             :         OOColor                         *diffuseColor = [_configuration oo_diffuseColor] ?: [OOColor whiteColor];
     876             :         
     877             :         if ([diffuseColor isBlack])  return;
     878             :         _usesDiffuseTerm = YES;
     879             :         
     880             :         BOOL haveDiffuseColor = NO;
     881             :         if (diffuseMap != nil)
     882             :         {
     883             :                 NSString *readInstr = [self readRGBForTextureSpec:diffuseMap mapName:@"diffuse"];
     884             :                 if (EXPECT_NOT(readInstr == nil))
     885             :                 {
     886             :                         [_fragmentBody appendString:@"\t// INVALID EXTRACTION KEY\n\t\n"];
     887             :                 }
     888             :                 else
     889             :                 {
     890             :                         [_fragmentBody appendFormat:@"\tvec3 diffuseColor = %@;\n", readInstr];
     891             :                          haveDiffuseColor = YES;
     892             :                 }
     893             :         }
     894             :         
     895             :         if (!haveDiffuseColor || ![diffuseColor isWhite])
     896             :         {
     897             :                 float rgba[4];
     898             :                 [diffuseColor getRed:&rgba[0] green:&rgba[1] blue:&rgba[2] alpha:&rgba[3]];
     899             :                 NSString *format = nil;
     900             :                 if (haveDiffuseColor)
     901             :                 {
     902             :                         format = @"\tdiffuseColor *= vec3(%@, %@, %@);\n";
     903             :                 }
     904             :                 else
     905             :                 {
     906             :                         format = @"\tconst vec3 diffuseColor = vec3(%@, %@, %@);\n";
     907             :                         haveDiffuseColor = YES;
     908             :                 }
     909             :                 [_fragmentBody appendFormat:format, FormatFloat(rgba[0]), FormatFloat(rgba[1]), FormatFloat(rgba[2])];
     910             :         }
     911             :         
     912             :         (void) haveDiffuseColor;
     913             :         [_fragmentBody appendString:@"\t\n"];
     914             : }
     915             : 
     916             : 
     917             : - (void) writeDiffuseColorTerm
     918             : {
     919             :         REQUIRE_STAGE(writeDiffuseColorTermIfNeeded);
     920             :         
     921             :         if (!_usesDiffuseTerm)
     922             :         {
     923             :                 [_fragmentBody appendString:@"\tconst vec3 diffuseColor = vec3(0.0);  // Diffuse colour is black.\n\t\n"];
     924             :         }
     925             : }
     926             : 
     927             : 
     928             : - (void) writeDiffuseLighting
     929             : {
     930             :         REQUIRE_STAGE(writeDiffuseColorTermIfNeeded);
     931             :         if (!_usesDiffuseTerm)  return;
     932             :         
     933             :         REQUIRE_STAGE(writeTotalColor);
     934             :         REQUIRE_STAGE(writeVertexPosition);
     935             :         REQUIRE_STAGE(writeNormalIfNeeded);
     936             :         REQUIRE_STAGE(writeLightVector);
     937             :         
     938             :         // FIXME: currently uncoloured diffuse and ambient lighting.
     939             :         NSString *normalDotLight = _constZNormal ? @"lightVector.z" : @"dot(normal, lightVector)";
     940             :         
     941             :         [_fragmentBody appendFormat:
     942             :         @"\t// Diffuse (Lambertian) and ambient lighting\n"
     943             :          "\tvec3 diffuseLight = (gl_LightSource[1].diffuse * max(0.0, %@) + gl_LightModel.ambient).rgb;\n\t\n",
     944             :          normalDotLight];
     945             :         
     946             :         _haveDiffuseLight = YES;
     947             : }
     948             : 
     949             : 
     950             : - (void) writeLightVector
     951             : {
     952             :         REQUIRE_STAGE(writeVertexPosition);
     953             :         REQUIRE_STAGE(writeNormalIfNeeded);
     954             :         
     955             :         [self addVarying:@"vLightVector" ofType:@"vec3"];
     956             :         
     957             :         [_vertexBody appendString:
     958             :          @"\tvec3 lightVector = gl_LightSource[1].position.xyz;\n"
     959             :           "\tvLightVector = lightVector * TBN;\n\t\n"];
     960             :         [_fragmentBody appendFormat:@"\tvec3 lightVector = normalize(vLightVector);\n\t\n"];
     961             : }
     962             : 
     963             : 
     964             : - (void) writeEyeVector
     965             : {
     966             :         REQUIRE_STAGE(writeVertexPosition);
     967             :         REQUIRE_STAGE(writeVertexTangentBasis);
     968             :         
     969             :         [self addVarying:@"vEyeVector" ofType:@"vec3"];
     970             :         
     971             :         [_vertexBody appendString:@"\tvEyeVector = position.xyz * TBN;\n\t\n"];
     972             :         [_fragmentPreTextures appendString:@"\tvec3 eyeVector = normalize(vEyeVector);\n\t\n"];
     973             : }
     974             : 
     975             : 
     976             : - (void) writeVertexTangentBasis
     977             : {
     978             :         [self addAttribute:@"tangent" ofType:@"vec3"];
     979             :         
     980             :         [_vertexBody appendString:
     981             :          @"\t// Build tangent space basis\n"
     982             :           "\tvec3 n = gl_NormalMatrix * gl_Normal;\n"
     983             :           "\tvec3 t = gl_NormalMatrix * tangent;\n"
     984             :           "\tvec3 b = cross(n, t);\n"
     985             :           "\tmat3 TBN = mat3(t, b, n);\n\t\n"];
     986             : }
     987             : 
     988             : 
     989             : - (void) writeNormalIfNeeded
     990             : {
     991             :         REQUIRE_STAGE(writeVertexPosition);
     992             :         REQUIRE_STAGE(writeVertexTangentBasis);
     993             :         
     994             :         NSDictionary *normalMap = [_configuration oo_normalMapSpecifier];
     995             :         if (normalMap == nil)
     996             :         {
     997             :                 // FIXME: this stuff should be handled in OOMaterialSpecifier.m when synthesizer takes over the world. -- Ahruman 2012-02-08
     998             :                 normalMap = [_configuration oo_normalAndParallaxMapSpecifier];
     999             :         }
    1000             :         if (normalMap != nil)
    1001             :         {
    1002             :                 NSString *sample, *swizzle;
    1003             :                 [self getSampleName:&sample andSwizzleOp:&swizzle forTextureSpec:normalMap];
    1004             :                 if (swizzle == nil)  swizzle = @"rgb";
    1005             :                 if ([swizzle length] == 3)
    1006             :                 {
    1007             :                         [_fragmentBody appendFormat:@"\tvec3 normal = normalize(%@.%@ - 0.5);\n\t\n", sample, swizzle];
    1008             :                         _usesNormalMap = YES;
    1009             :                         return;
    1010             :                 }
    1011             :                 else
    1012             :                 {
    1013             :                         OOLogWARN(@"material.synthesis.warning.extractionMismatch", @"The %@ map for material \"%@\" of \"%@\" specifies %lu channels to extract, but only %@ may be used.", @"normal", [self materialKey], [self entityName], [swizzle length], @"3");
    1014             :                 }
    1015             :         }
    1016             :         _constZNormal = YES;
    1017             : }
    1018             : 
    1019             : 
    1020             : - (void) writeNormal
    1021             : {
    1022             :         REQUIRE_STAGE(writeNormalIfNeeded);
    1023             :         
    1024             :         if (_constZNormal)
    1025             :         {
    1026             :                 [_fragmentBody appendString:@"\tconst vec3 normal = vec3(0.0, 0.0, 1.0);\n\t\n"];
    1027             :         }
    1028             : }
    1029             : 
    1030             : 
    1031             : - (void) writeSpecularLighting
    1032             : {
    1033             :         float specularExponent = [_configuration oo_specularExponent];
    1034             :         if (specularExponent <= 0)  return;
    1035             :         
    1036             :         NSDictionary *specularColorMap = [_configuration oo_specularColorMapSpecifier];
    1037             :         NSDictionary *specularExponentMap = [_configuration oo_specularExponentMapSpecifier];
    1038             :         float scaleFactor = 1.0f;
    1039             :         
    1040             :         if (specularColorMap)
    1041             :         {
    1042             :                 scaleFactor = [specularColorMap oo_doubleForKey:kOOTextureSpecifierScaleFactorKey defaultValue:1.0f];
    1043             :         }
    1044             :         
    1045             :         OOColor *specularColor = nil;
    1046             :         if (specularColorMap == nil)
    1047             :         {
    1048             :                 specularColor = [_configuration oo_specularColor];
    1049             :         }
    1050             :         else
    1051             :         {
    1052             :                 specularColor = [_configuration oo_specularModulateColor];
    1053             :         }
    1054             :         
    1055             :         if ([specularColor isBlack])  return;
    1056             :         
    1057             :         BOOL modulateWithDiffuse = [specularColorMap oo_boolForKey:kOOTextureSpecifierSelfColorKey];
    1058             :         
    1059             :         REQUIRE_STAGE(writeTotalColor);
    1060             :         REQUIRE_STAGE(writeNormalIfNeeded);
    1061             :         REQUIRE_STAGE(writeEyeVector);
    1062             :         REQUIRE_STAGE(writeLightVector);
    1063             :         if (modulateWithDiffuse)
    1064             :         {
    1065             :                 REQUIRE_STAGE(writeDiffuseColorTerm);
    1066             :         }
    1067             :         
    1068             :         [_fragmentBody appendString:@"\t// Specular (Blinn-Phong) lighting\n"];
    1069             :         
    1070             :         BOOL haveSpecularColor = NO;
    1071             :         if (specularColorMap != nil)
    1072             :         {
    1073             :                 NSString *readInstr = [self readRGBForTextureSpec:specularColorMap mapName:@"specular colour"];
    1074             :                 if (EXPECT_NOT(readInstr == nil))
    1075             :                 {
    1076             :                         [_fragmentBody appendString:@"\t// INVALID EXTRACTION KEY\n\t\n"];
    1077             :                         return;
    1078             :                 }
    1079             :                 
    1080             :                 [_fragmentBody appendFormat:@"\tvec3 specularColor = %@;\n", readInstr];
    1081             :                 haveSpecularColor = YES;
    1082             :         }
    1083             :         
    1084             :         if (!haveSpecularColor || ![specularColor isWhite])
    1085             :         {
    1086             :                 float rgba[4];
    1087             :                 [specularColor getRed:&rgba[0] green:&rgba[1] blue:&rgba[2] alpha:&rgba[3]];
    1088             :                 
    1089             :                 NSString *comment = (scaleFactor == 1.0f) ? @"Constant colour" : @"Constant colour and scale factor";
    1090             :                 
    1091             :                 // Handle scale factor, colour, and colour alpha scaling as one multiply.
    1092             :                 scaleFactor *= rgba[3];
    1093             :                 rgba[0] *= scaleFactor;
    1094             :                 rgba[1] *= scaleFactor;
    1095             :                 rgba[2] *= scaleFactor;
    1096             :                 
    1097             :                 // Avoid reapplying scaleFactor below.
    1098             :                 scaleFactor = 1.0;
    1099             :                 
    1100             :                 NSString *format = nil;
    1101             :                 if (haveSpecularColor)
    1102             :                 {
    1103             :                         format = @"\tspecularColor *= vec3(%@, %@, %@);  // %@\n";
    1104             :                 }
    1105             :                 else
    1106             :                 {
    1107             :                         format = @"\tvec3 specularColor = vec3(%@, %@, %@);  // %@\n";
    1108             :                         haveSpecularColor = YES;
    1109             :                 }
    1110             :                 [_fragmentBody appendFormat:format, FormatFloat(rgba[0]), FormatFloat(rgba[1]), FormatFloat(rgba[2]), comment];
    1111             :         }
    1112             :         
    1113             :         // Handle scale_factor if no constant colour.
    1114             :         if (haveSpecularColor && scaleFactor != 1.0f)
    1115             :         {
    1116             :                 [_fragmentBody appendFormat:@"\tspecularColor *= %@;  // Scale factor\n", FormatFloat(scaleFactor)];
    1117             :         }
    1118             :         
    1119             :         // Handle self_color.
    1120             :         if (modulateWithDiffuse)
    1121             :         {
    1122             :                 [_fragmentBody appendString:@"\tspecularColor *= diffuseColor;  // Self-colouring\n"];
    1123             :         }
    1124             :         
    1125             :         // Specular exponent.
    1126             :         BOOL haveSpecularExponent = NO;
    1127             :         if (specularExponentMap != nil)
    1128             :         {
    1129             :                 NSString *readInstr = [self readOneChannelForTextureSpec:specularExponentMap mapName:@"specular exponent"];
    1130             :                 if (EXPECT_NOT(readInstr == nil))
    1131             :                 {
    1132             :                         [_fragmentBody appendString:@"\t// INVALID EXTRACTION KEY\n\t\n"];
    1133             :                         return;
    1134             :                 }
    1135             :                 
    1136             :                 [_fragmentBody appendFormat:@"\tfloat specularExponent = %@ * %.1f;\n", readInstr, specularExponent];
    1137             :                 haveSpecularExponent = YES;
    1138             :         }
    1139             :         if (!haveSpecularExponent)
    1140             :         {
    1141             :                 [_fragmentBody appendFormat:@"\tconst float specularExponent = %.1f;\n", specularExponent];
    1142             :         }
    1143             :         
    1144             :         if (_usesNormalMap)
    1145             :         {
    1146             :                 [_fragmentBody appendFormat:@"\tvec3 reflection = reflect(lightVector, normal);\n"];
    1147             :         }
    1148             :         else
    1149             :         {
    1150             :                 /*      reflect(I, N) is defined as I - 2 * dot(N, I) * N
    1151             :                         If N is (0,0,1), this becomes (I.x,I.y,-I.z).
    1152             :                 */
    1153             :                 [_fragmentBody appendFormat:@"\tvec3 reflection = vec3(lightVector.x, lightVector.y, -lightVector.z);  // Equivalent to reflect(lightVector, normal) since normal is known to be (0, 0, 1) in tangent space.\n"];
    1154             :         }
    1155             :         
    1156             :         [_fragmentBody appendFormat:
    1157             :         @"\tfloat specIntensity = dot(reflection, eyeVector);\n"
    1158             :          "\tspecIntensity = pow(max(0.0, specIntensity), specularExponent);\n"
    1159             :          "\ttotalColor += specIntensity * specularColor * gl_LightSource[1].specular.rgb;\n\t\n"];
    1160             : }
    1161             : 
    1162             : 
    1163             : - (void) writeLightMaps
    1164             : {
    1165             :         NSArray *lightMaps = [_configuration oo_arrayForKey:kOOMaterialLightMapsName];
    1166             :         NSUInteger idx, count = [lightMaps count];
    1167             :         if (count == 0)  return;
    1168             :         
    1169             :         REQUIRE_STAGE(writeTotalColor);
    1170             :         
    1171             :         // Check if we need the diffuse colour term.
    1172             :         for (idx = 0; idx < count; idx++)
    1173             :         {
    1174             :                 NSDictionary *lightMapSpec = [lightMaps oo_dictionaryAtIndex:idx];
    1175             :                 if ([lightMapSpec oo_boolForKey:kOOTextureSpecifierIlluminationModeKey])
    1176             :                 {
    1177             :                         REQUIRE_STAGE(writeDiffuseColorTerm);
    1178             :                         REQUIRE_STAGE(writeDiffuseLighting);
    1179             :                         break;
    1180             :                 }
    1181             :         }
    1182             :         
    1183             :         [_fragmentBody appendString:@"\tvec3 lightMapColor;\n"];
    1184             :         
    1185             :         for (idx = 0; idx < count; idx++)
    1186             :         {
    1187             :                 NSDictionary    *lightMapSpec = [lightMaps oo_dictionaryAtIndex:idx];
    1188             :                 NSDictionary    *textureSpec = OOTextureSpecFromObject(lightMapSpec, nil);
    1189             :                 NSArray                 *color = [lightMapSpec oo_arrayForKey:kOOTextureSpecifierModulateColorKey];
    1190             :                 float                   rgba[4] = { 1.0f, 1.0f, 1.0f, 1.0f };
    1191             :                 BOOL                    isIllumination = [lightMapSpec oo_boolForKey:kOOTextureSpecifierIlluminationModeKey];
    1192             :                 
    1193             :                 if (EXPECT_NOT(color == nil && textureSpec == nil))
    1194             :                 {
    1195             :                         [_fragmentBody appendString:@"\t// Light map with neither colour nor texture has no effect.\n\t\n"];
    1196             :                         continue;
    1197             :                 }
    1198             :                 
    1199             :                 if (color != nil)
    1200             :                 {
    1201             :                         NSUInteger idx, count = [color count];
    1202             :                         if (count > 4)  count = 4;
    1203             :                         for (idx = 0; idx < count; idx++)
    1204             :                         {
    1205             :                                 rgba[idx] = [color oo_doubleAtIndex:idx];
    1206             :                         }
    1207             :                         rgba[0] *= rgba[3]; rgba[1] *= rgba[3]; rgba[2] *= rgba[3];
    1208             :                 }
    1209             :                 
    1210             :                 if (EXPECT_NOT((rgba[0] == 0.0f && rgba[1] == 0.0f && rgba[2] == 0.0f) ||
    1211             :                                            (!_usesDiffuseTerm && isIllumination)))
    1212             :                 {
    1213             :                         [_fragmentBody appendString:@"\t// Light map tinted black has no effect.\n\t\n"];
    1214             :                         continue;
    1215             :                 }
    1216             :                 
    1217             :                 if (textureSpec != nil)
    1218             :                 {
    1219             :                         NSString *readInstr = [self readRGBForTextureSpec:textureSpec mapName:@"light"];
    1220             :                         if (EXPECT_NOT(readInstr == nil))
    1221             :                         {
    1222             :                                 [_fragmentBody appendString:@"\t// INVALID EXTRACTION KEY\n\n"];
    1223             :                                 continue;
    1224             :                         }
    1225             :                         
    1226             :                         [_fragmentBody appendFormat:@"\tlightMapColor = %@;\n", readInstr];
    1227             :                         
    1228             :                         if (rgba[0] != 1.0f || rgba[1] != 1.0f || rgba[2] != 1.0f)
    1229             :                         {
    1230             :                                 [_fragmentBody appendFormat:@"\tlightMapColor *= vec3(%@, %@, %@);\n", FormatFloat(rgba[0]), FormatFloat(rgba[1]), FormatFloat(rgba[2])];
    1231             :                         }
    1232             :                 }
    1233             :                 else
    1234             :                 {
    1235             :                         [_fragmentBody appendFormat:@"\tlightMapColor = vec3(%@, %@, %@);\n", FormatFloat(rgba[0]), FormatFloat(rgba[1]), FormatFloat(rgba[2])];
    1236             :                 }
    1237             :                 
    1238             :                 NSDictionary *binding = [textureSpec oo_dictionaryForKey:kOOTextureSpecifierBindingKey];
    1239             :                 if (binding != nil)
    1240             :                 {
    1241             :                         NSString *bindingName = [binding oo_stringForKey:@"binding"];
    1242             :                         NSDictionary *typeDict = [[ResourceManager shaderBindingTypesDictionary] oo_dictionaryForKey:@"player"];      // FIXME: select appropriate binding subset.
    1243             :                         NSString *bindingType = [typeDict oo_stringForKey:bindingName];
    1244             :                         NSString *glslType = nil;
    1245             :                         NSString *swizzle = @"";
    1246             :                         
    1247             :                         if ([bindingType isEqualToString:@"float"])
    1248             :                         {
    1249             :                                 glslType = @"float";
    1250             :                         }
    1251             :                         else if ([bindingType isEqualToString:@"vector"])
    1252             :                         {
    1253             :                                 glslType = @"vec3";
    1254             :                         }
    1255             :                         else if ([bindingType isEqualToString:@"color"])
    1256             :                         {
    1257             :                                 glslType = @"vec4";
    1258             :                                 swizzle = @".rgb";
    1259             :                         }
    1260             :                         
    1261             :                         if (glslType != nil)
    1262             :                         {
    1263             :                                 NSString *uniformName = [self defineBindingUniform:binding ofType:bindingType];
    1264             :                                 [_fragmentBody appendFormat:@"\tlightMapColor *= %@%@;\n", uniformName, swizzle];
    1265             :                         }
    1266             :                         else
    1267             :                         {
    1268             :                                 if (bindingType == nil)
    1269             :                                 {
    1270             :                                         OOLogERR(@"material.binding.error.unknown", @"Cannot bind light map to unknown attribute \"%@\".", bindingName);
    1271             :                                 }
    1272             :                                 else
    1273             :                                 {
    1274             :                                         OOLogERR(@"material.binding.error.badType", @"Cannot bind light map to attribute \"%@\" of type %@.", bindingName, bindingType);
    1275             :                                 }
    1276             :                                 [_fragmentBody appendString:@"\tlightMapColor = vec3(0.0);  // Bad binding, see log.\n"];
    1277             :                         }
    1278             :                 }
    1279             :                 
    1280             :                 if (!isIllumination)
    1281             :                 {
    1282             :                         [_fragmentBody appendString:@"\ttotalColor += lightMapColor;\n\t\n"];
    1283             :                 }
    1284             :                 else
    1285             :                 {
    1286             :                         [_fragmentBody appendString:@"\tdiffuseLight += lightMapColor;\n\t\n"];
    1287             :                 }
    1288             :         }
    1289             : }
    1290             : 
    1291             : 
    1292             : - (void) writeVertexPosition
    1293             : {
    1294             :         [_vertexBody appendString:
    1295             :         @"\tvec4 position = gl_ModelViewMatrix * gl_Vertex;\n"
    1296             :          "\tgl_Position = gl_ProjectionMatrix * position;\n\t\n"];
    1297             : }
    1298             : 
    1299             : 
    1300             : - (void) writeTotalColor
    1301             : {
    1302             :         [_fragmentPreTextures appendString:@"\tvec3 totalColor = vec3(0.0);\n\t\n"];
    1303             : }
    1304             : 
    1305             : 
    1306             : - (void) writeFinalColorComposite
    1307             : {
    1308             :         REQUIRE_STAGE(writeTotalColor); // Needed even if none of the following stages does anything.
    1309             :         REQUIRE_STAGE(writeDiffuseLighting);
    1310             :         REQUIRE_STAGE(writeSpecularLighting);
    1311             :         REQUIRE_STAGE(writeLightMaps);
    1312             :         
    1313             :         if (_haveDiffuseLight)
    1314             :         {
    1315             :                 [_fragmentBody appendString:@"\ttotalColor += diffuseColor * diffuseLight;\n"];
    1316             :         }
    1317             :         
    1318             :         [_fragmentBody appendString:@"\tgl_FragColor = vec4(totalColor, 1.0);\n\t\n"];
    1319             : }
    1320             : 
    1321             : @end
    1322             : 
    1323             : /*
    1324             :         Convert any legacy properties and simplified forms in a material specifier
    1325             :         to the standard form.
    1326             :         
    1327             :         FIXME: this should be done up front in OOShipRegistry. When doing that, it
    1328             :         also need to be done when materials are set on the fly through JS,
    1329             : */
    1330           0 : static NSDictionary *CanonicalizeMaterialSpecifier(NSDictionary *spec, NSString *materialKey)
    1331             : {
    1332             :         NSMutableDictionary             *result = [NSMutableDictionary dictionary];
    1333             :         OOColor                                 *col = nil;
    1334             :         id                                              texSpec = nil;
    1335             :         
    1336             :         // Colours.
    1337             :         col = [OOColor colorWithDescription:[spec objectForKey:kOOMaterialDiffuseColorName]];
    1338             :         if (col == nil)  col = [OOColor colorWithDescription:[spec objectForKey:kOOMaterialDiffuseColorLegacyName]];
    1339             :         if (col != nil)  [result setObject:[col normalizedArray] forKey:kOOMaterialDiffuseColorName];
    1340             :         
    1341             :         col = [OOColor colorWithDescription:[spec objectForKey:kOOMaterialAmbientColorName]];
    1342             :         if (col == nil)  col = [OOColor colorWithDescription:[spec objectForKey:kOOMaterialAmbientColorLegacyName]];
    1343             :         if (col != nil)  [result setObject:[col normalizedArray] forKey:kOOMaterialAmbientColorName];
    1344             :         
    1345             :         col = [OOColor colorWithDescription:[spec objectForKey:kOOMaterialSpecularColorName]];
    1346             :         if (col == nil)  col = [OOColor colorWithDescription:[spec objectForKey:kOOMaterialSpecularColorLegacyName]];
    1347             :         if (col != nil)  [result setObject:[col normalizedArray] forKey:kOOMaterialSpecularColorName];
    1348             :         
    1349             :         col = [OOColor colorWithDescription:[spec objectForKey:kOOMaterialSpecularModulateColorName]];
    1350             :         if (col != nil)  [result setObject:[col normalizedArray] forKey:kOOMaterialSpecularModulateColorName];
    1351             :         
    1352             :         col = [OOColor colorWithDescription:[spec objectForKey:kOOMaterialEmissionColorName]];
    1353             :         if (col == nil)  col = [OOColor colorWithDescription:[spec objectForKey:kOOMaterialEmissionColorLegacyName]];
    1354             :         if (col != nil)  [result setObject:[col normalizedArray] forKey:kOOMaterialEmissionColorName];
    1355             :         
    1356             :         // Diffuse map.
    1357             :         texSpec = [spec objectForKey:kOOMaterialDiffuseMapName];
    1358             :         if ([texSpec isKindOfClass:[NSString class]])
    1359             :         {
    1360             :                 if ([texSpec length] > 0)
    1361             :                 {
    1362             :                         texSpec = [NSDictionary dictionaryWithObject:texSpec forKey:kOOTextureSpecifierNameKey];
    1363             :                 }
    1364             :         }
    1365             :         else if ([texSpec isKindOfClass:[NSDictionary class]])
    1366             :         {
    1367             :                 /*      Special case for diffuse map: no name is changed to
    1368             :                         name = materialKey, while name = "" is changed to no name.
    1369             :                 */
    1370             :                 NSString *name = [texSpec objectForKey:kOOTextureSpecifierNameKey];
    1371             :                 if (name == nil)  texSpec = [texSpec dictionaryByAddingObject:materialKey forKey:kOOTextureSpecifierNameKey];
    1372             :                 else if ([name length] == 0)
    1373             :                 {
    1374             :                         texSpec = [texSpec dictionaryByRemovingObjectForKey:kOOTextureSpecifierNameKey];
    1375             :                 }
    1376             :         }
    1377             :         else
    1378             :         {
    1379             :                 // Special case for unspecified diffuse map.
    1380             :                 texSpec = [NSDictionary dictionaryWithObject:materialKey forKey:kOOTextureSpecifierNameKey];
    1381             :         }
    1382             :         [result setObject:texSpec forKey:kOOMaterialDiffuseMapName];
    1383             :         
    1384             :         // Specular maps.
    1385             :         {
    1386             :                 BOOL haveNewSpecular = NO;
    1387             :                 texSpec = [spec objectForKey:kOOMaterialSpecularColorMapName];
    1388             :                 if ([texSpec isKindOfClass:[NSString class]])
    1389             :                 {
    1390             :                         texSpec = [NSDictionary dictionaryWithObject:texSpec forKey:kOOTextureSpecifierNameKey];
    1391             :                 }
    1392             :                 else if (![texSpec isKindOfClass:[NSDictionary class]])
    1393             :                 {
    1394             :                         texSpec = nil;
    1395             :                 }
    1396             :                 if (texSpec != nil)
    1397             :                 {
    1398             :                         haveNewSpecular = YES;
    1399             :                         [result setObject:texSpec forKey:kOOMaterialSpecularColorMapName];
    1400             :                 }
    1401             :                 
    1402             :                 texSpec = [spec objectForKey:kOOMaterialSpecularExponentMapName];
    1403             :                 if ([texSpec isKindOfClass:[NSString class]])
    1404             :                 {
    1405             :                         texSpec = [NSDictionary dictionaryWithObject:texSpec forKey:kOOTextureSpecifierNameKey];
    1406             :                 }
    1407             :                 else if (![texSpec isKindOfClass:[NSDictionary class]])
    1408             :                 {
    1409             :                         texSpec = nil;
    1410             :                 }
    1411             :                 if (texSpec != nil)
    1412             :                 {
    1413             :                         haveNewSpecular = YES;
    1414             :                         [result setObject:texSpec forKey:kOOMaterialSpecularExponentMapName];
    1415             :                 }
    1416             :                 
    1417             :                 if (!haveNewSpecular)
    1418             :                 {
    1419             :                         // Fall back to legacy combined specular map if defined.
    1420             :                         texSpec = [spec objectForKey:kOOMaterialCombinedSpecularMapName];
    1421             :                         if ([texSpec isKindOfClass:[NSString class]])
    1422             :                         {
    1423             :                                 texSpec = [NSDictionary dictionaryWithObject:texSpec forKey:kOOTextureSpecifierNameKey];
    1424             :                         }
    1425             :                         else if (![texSpec isKindOfClass:[NSDictionary class]])
    1426             :                         {
    1427             :                                 texSpec = nil;
    1428             :                         }
    1429             :                         if (texSpec != nil)
    1430             :                         {
    1431             :                                 [result setObject:texSpec forKey:kOOMaterialSpecularColorMapName];
    1432             :                                 texSpec = [texSpec dictionaryByAddingObject:@"a" forKey:kOOTextureSpecifierSwizzleKey];
    1433             :                                 [result setObject:texSpec forKey:kOOMaterialSpecularExponentMapName];
    1434             :                         }
    1435             :                 }
    1436             :         }
    1437             :         
    1438             :         // Normal and parallax maps.
    1439             :         {
    1440             :                 BOOL haveParallax = NO;
    1441             :                 BOOL haveNewNormal = NO;
    1442             :                 texSpec = [spec objectForKey:kOOMaterialNormalMapName];
    1443             :                 if ([texSpec isKindOfClass:[NSString class]])
    1444             :                 {
    1445             :                         texSpec = [NSDictionary dictionaryWithObject:texSpec forKey:kOOTextureSpecifierNameKey];
    1446             :                 }
    1447             :                 else if (![texSpec isKindOfClass:[NSDictionary class]])
    1448             :                 {
    1449             :                         texSpec = nil;
    1450             :                 }
    1451             :                 if (texSpec != nil)
    1452             :                 {
    1453             :                         haveNewNormal = YES;
    1454             :                         [result setObject:texSpec forKey:kOOMaterialNormalMapName];
    1455             :                 }
    1456             :                 
    1457             :                 texSpec = [spec objectForKey:kOOMaterialParallaxMapName];
    1458             :                 if ([texSpec isKindOfClass:[NSString class]])
    1459             :                 {
    1460             :                         texSpec = [NSDictionary dictionaryWithObject:texSpec forKey:kOOTextureSpecifierNameKey];
    1461             :                 }
    1462             :                 else if (![texSpec isKindOfClass:[NSDictionary class]])
    1463             :                 {
    1464             :                         texSpec = nil;
    1465             :                 }
    1466             :                 if (texSpec != nil)
    1467             :                 {
    1468             :                         haveNewNormal = YES;
    1469             :                         haveParallax = YES;
    1470             :                         [result setObject:texSpec forKey:kOOMaterialParallaxMapName];
    1471             :                 }
    1472             :                 
    1473             :                 if (!haveNewNormal)
    1474             :                 {
    1475             :                         // Fall back to legacy combined normal and parallax map if defined.
    1476             :                         texSpec = [spec objectForKey:kOOMaterialNormalAndParallaxMapName];
    1477             :                         if ([texSpec isKindOfClass:[NSString class]])
    1478             :                         {
    1479             :                                 texSpec = [NSDictionary dictionaryWithObject:texSpec forKey:kOOTextureSpecifierNameKey];
    1480             :                         }
    1481             :                         else if (![texSpec isKindOfClass:[NSDictionary class]])
    1482             :                         {
    1483             :                                 texSpec = nil;
    1484             :                         }
    1485             :                         if (texSpec != nil)
    1486             :                         {
    1487             :                                 haveParallax = YES;
    1488             :                                 [result setObject:texSpec forKey:kOOMaterialNormalMapName];
    1489             :                                 texSpec = [texSpec dictionaryByAddingObject:@"a" forKey:kOOTextureSpecifierSwizzleKey];
    1490             :                                 [result setObject:texSpec forKey:kOOMaterialParallaxMapName];
    1491             :                         }
    1492             :                 }
    1493             :                 
    1494             :                 // Additional parallax parameters.
    1495             :                 if (haveParallax)
    1496             :                 {
    1497             :                         float parallaxScale = [spec oo_floatForKey:kOOMaterialParallaxScaleName defaultValue:kOOMaterialDefaultParallaxScale];
    1498             :                         [result oo_setFloat:parallaxScale forKey:kOOMaterialParallaxScaleName];
    1499             :                         
    1500             :                         float parallaxBias = [spec oo_floatForKey:kOOMaterialParallaxBiasName];
    1501             :                         [result oo_setFloat:parallaxBias forKey:kOOMaterialParallaxBiasName];
    1502             :                 }
    1503             :         }
    1504             :         
    1505             :         // Light maps.
    1506             :         {
    1507             :                 NSMutableArray *lightMaps = [NSMutableArray array];
    1508             :                 id lightMapSpecs = [spec objectForKey:kOOMaterialLightMapsName];
    1509             :                 if (lightMapSpecs != nil && ![lightMapSpecs isKindOfClass:[NSArray class]])
    1510             :                 {
    1511             :                         lightMapSpecs = [NSArray arrayWithObject:lightMapSpecs];
    1512             :                 }
    1513             :                 
    1514             :                 id lmSpec = nil;
    1515             :                 foreach (lmSpec, lightMapSpecs)
    1516             :                 {
    1517             :                         if ([lmSpec isKindOfClass:[NSString class]])
    1518             :                         {
    1519             :                                 lmSpec = [NSMutableDictionary dictionaryWithObject:lmSpec forKey:kOOTextureSpecifierNameKey];
    1520             :                         }
    1521             :                         else if ([lmSpec isKindOfClass:[NSDictionary class]])
    1522             :                         {
    1523             :                                 lmSpec = [[lmSpec mutableCopy] autorelease];
    1524             :                         }
    1525             :                         else
    1526             :                         {
    1527             :                                 continue;
    1528             :                         }
    1529             :                         
    1530             :                         id modulateColor = [lmSpec objectForKey:kOOTextureSpecifierModulateColorKey];
    1531             :                         if (modulateColor != nil && ![modulateColor isKindOfClass:[NSArray class]])
    1532             :                         {
    1533             :                                 // Don't convert arrays here, because we specifically don't want the behaviour of treating numbers greater than 1 as 0..255 components.
    1534             :                                 col = [OOColor colorWithDescription:modulateColor];
    1535             :                                 [lmSpec setObject:[col normalizedArray] forKey:kOOTextureSpecifierModulateColorKey];
    1536             :                         }
    1537             :                         
    1538             :                         id binding = [lmSpec objectForKey:kOOTextureSpecifierBindingKey];
    1539             :                         if (binding != nil)
    1540             :                         {
    1541             :                                 if ([binding isKindOfClass:[NSString class]])
    1542             :                                 {
    1543             :                                         NSDictionary *expandedBinding = [NSDictionary dictionaryWithObjectsAndKeys:@"binding", @"type", binding, @"binding", nil];
    1544             :                                         [lmSpec setObject:expandedBinding forKey:kOOTextureSpecifierBindingKey];
    1545             :                                 }
    1546             :                                 else if (![binding isKindOfClass:[NSDictionary class]] || [[binding oo_stringForKey:@"binding"] length] == 0)
    1547             :                                 {
    1548             :                                         [lmSpec removeObjectForKey:kOOTextureSpecifierBindingKey];
    1549             :                                 }
    1550             :                         }
    1551             :                         
    1552             :                         [lightMaps addObject:[[lmSpec copy] autorelease]];
    1553             :                 }
    1554             :                 
    1555             :                 if ([lightMaps count] == 0)
    1556             :                 {
    1557             :                         // If light_map isn't use, handle legacy emission_map, illumination_map and emission_and_illumination_map.
    1558             :                         id emissionSpec = [spec objectForKey:kOOMaterialEmissionMapName];
    1559             :                         id illuminationSpec = [spec objectForKey:kOOMaterialIlluminationMapName];
    1560             :                         
    1561             :                         if (emissionSpec == nil && illuminationSpec == nil)
    1562             :                         {
    1563             :                                 emissionSpec = [spec objectForKey:kOOMaterialEmissionAndIlluminationMapName];
    1564             :                                 if ([emissionSpec isKindOfClass:[NSString class]])
    1565             :                                 {
    1566             :                                         // Redundantish check required because we want to modify this as a dictionary to make illuminationSpec.
    1567             :                                         emissionSpec = [NSDictionary dictionaryWithObject:emissionSpec forKey:kOOTextureSpecifierNameKey];
    1568             :                                 }
    1569             :                                 else if (![emissionSpec isKindOfClass:[NSDictionary class]])
    1570             :                                 {
    1571             :                                         emissionSpec = nil;
    1572             :                                 }
    1573             :                                 
    1574             :                                 if (emissionSpec != nil)
    1575             :                                 {
    1576             :                                         illuminationSpec = [emissionSpec dictionaryByAddingObject:@"a" forKey:kOOTextureSpecifierSwizzleKey];
    1577             :                                 }
    1578             :                         }
    1579             :                         
    1580             :                         if (emissionSpec != nil)
    1581             :                         {
    1582             :                                 if ([emissionSpec isKindOfClass:[NSString class]])
    1583             :                                 {
    1584             :                                         emissionSpec = [NSDictionary dictionaryWithObject:emissionSpec forKey:kOOTextureSpecifierNameKey];
    1585             :                                 }
    1586             :                                 if ([emissionSpec isKindOfClass:[NSDictionary class]])
    1587             :                                 {
    1588             :                                         col = [OOColor colorWithDescription:[spec objectForKey:kOOMaterialEmissionModulateColorName]];
    1589             :                                         if (col != nil)  emissionSpec = [emissionSpec dictionaryByAddingObject:[col normalizedArray] forKey:kOOTextureSpecifierModulateColorKey];
    1590             :                                         
    1591             :                                         [lightMaps addObject:emissionSpec];
    1592             :                                 }
    1593             :                         }
    1594             :                         
    1595             :                         if (illuminationSpec != nil)
    1596             :                         {
    1597             :                                 if ([illuminationSpec isKindOfClass:[NSString class]])
    1598             :                                 {
    1599             :                                         illuminationSpec = [NSDictionary dictionaryWithObject:illuminationSpec forKey:kOOTextureSpecifierNameKey];
    1600             :                                 }
    1601             :                                 if ([illuminationSpec isKindOfClass:[NSDictionary class]])
    1602             :                                 {
    1603             :                                         col = [OOColor colorWithDescription:[spec objectForKey:kOOMaterialIlluminationModulateColorName]];
    1604             :                                         if (col != nil)  illuminationSpec = [illuminationSpec dictionaryByAddingObject:[col normalizedArray] forKey:kOOTextureSpecifierModulateColorKey];
    1605             :                                         
    1606             :                                         illuminationSpec = [illuminationSpec dictionaryByAddingObject:[NSNumber numberWithBool:YES] forKey:kOOTextureSpecifierIlluminationModeKey];
    1607             :                                         
    1608             :                                         [lightMaps addObject:illuminationSpec];
    1609             :                                 }
    1610             :                         }
    1611             :                 }
    1612             :                 
    1613             :                 [result setObject:lightMaps forKey:kOOMaterialLightMapsName];
    1614             :         }
    1615             :         
    1616             :         OOLog(@"material.canonicalForm", @"Canonicalized material %@:\nORIGINAL:\n%@\n\n@CANONICAL:\n%@", materialKey, spec, result);
    1617             :         
    1618             :         return result;
    1619             : }
    1620             : 
    1621             : 
    1622           0 : static NSString *FormatFloat(double value)
    1623             : {
    1624             :         long long intValue = value;
    1625             :         if (value == intValue)
    1626             :         {
    1627             :                 return [NSString stringWithFormat:@"%lli.0", intValue];
    1628             :         }
    1629             :         else
    1630             :         {
    1631             :                 return [NSString stringWithFormat:@"%g", value];
    1632             :         }
    1633             : }

Generated by: LCOV version 1.14