Oolite 1.91.0.7645-241119-222d325
Loading...
Searching...
No Matches
OODefaultShaderSynthesizer.m
Go to the documentation of this file.
1/*
2
3OODefaultShaderSynthesizer.m
4
5
6Copyright © 2011-2013 Jens Ayton
7
8Permission is hereby granted, free of charge, to any person obtaining a copy
9of this software and associated documentation files (the “Software”), to deal
10in the Software without restriction, including without limitation the rights
11to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12copies of the Software, and to permit persons to whom the Software is
13furnished to do so, subject to the following conditions:
14
15The above copyright notice and this permission notice shall be included in all
16copies or substantial portions of the Software.
17
18THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24SOFTWARE.
25
26*/
27
29#import "OOMesh.h"
30#import "OOTexture.h"
31#import "OOColor.h"
32
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
51static NSDictionary *CanonicalizeMaterialSpecifier(NSDictionary *spec, NSString *materialKey);
52
53static NSString *FormatFloat(double value);
54
55
56@interface OODefaultShaderSynthesizer: NSObject
57{
58@private
59 NSDictionary *_configuration;
60 NSString *_materialKey;
61 NSString *_entityName;
62
63 NSString *_vertexShader;
64 NSString *_fragmentShader;
65 NSMutableArray *_textures;
66 NSMutableDictionary *_uniforms;
67
68 NSMutableString *_attributes;
69 NSMutableString *_varyings;
70 NSMutableString *_vertexUniforms;
71 NSMutableString *_fragmentUniforms;
72 NSMutableString *_vertexHelpers;
73 NSMutableString *_fragmentHelpers;
74 NSMutableString *_vertexBody;
75 NSMutableString *_fragmentPreTextures;
76 NSMutableString *_fragmentTextureLookups;
77 NSMutableString *_fragmentBody;
78
79 // _texturesByName: dictionary mapping texture file names to texture specifications.
80 NSMutableDictionary *_texturesByName;
81 // _textureIDs: dictionary mapping texture file names to numerical IDs used to name variables.
82 NSMutableDictionary *_textureIDs;
83 // _sampledTextures: hash of integer texture IDs for which we’ve set up a sample.
84 NSHashTable *_sampledTextures;
85
86 NSMutableDictionary *_uniformBindingNames;
87
88 NSUInteger _usesNormalMap: 1,
92
93 // Completion flags for various generation stages.
102 // _completedwriteNormal: 1,
108
109#ifndef NDEBUG
110 NSHashTable *_stagesInProgress;
111#endif
112}
113
114- (id) initWithMaterialConfiguration:(NSDictionary *)configuration
115 materialKey:(NSString *)materialKey
116 entityName:(NSString *)name;
117
118- (BOOL) run;
119
120- (NSString *) vertexShader;
121- (NSString *) fragmentShader;
122- (NSArray *) textureSpecifications;
123- (NSDictionary *) uniformSpecifications;
124
125- (NSString *) materialKey;
126- (NSString *) entityName;
127
128- (void) createTemporaries;
129- (void) destroyTemporaries;
130
131- (void) composeVertexShader;
132- (void) composeFragmentShader;
133
134// Write various types of declarations.
135- (void) appendVariable:(NSString *)name ofType:(NSString *)type withPrefix:(NSString *)prefix to:(NSMutableString *)buffer;
136- (void) addAttribute:(NSString *)name ofType:(NSString *)type;
137- (void) addVarying:(NSString *)name ofType:(NSString *)type;
138- (void) addVertexUniform:(NSString *)name ofType:(NSString *)type;
139- (void) addFragmentUniform:(NSString *)name ofType:(NSString *)type;
140
141// Create or retrieve a uniform variable name for a given binding.
142- (NSString *) defineBindingUniform:(NSDictionary *)binding ofType:(NSString *)type;
143
144- (NSString *) readRGBForTextureSpec:(NSDictionary *)textureSpec mapName:(NSString *)mapName; // Generate a read for an RGB value, or a single channel splatted across RGB.
145- (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- (NSUInteger) textureIDForSpec:(NSDictionary *)textureSpec;
149- (void) setUpOneTexture:(NSDictionary *)textureSpec;
150- (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- (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- (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- (void) writeDiffuseColorTerm;
177
178/* writeDiffuseLighting
179 Generate the fragment variable vec3 diffuseLight and add Lambertian and
180 ambient terms to it.
181*/
182- (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- (void) writeLightVector;
190
191/* writeEyeVector
192 Generate vec3 lightVector, the normalized direction from the fragment to
193 the light source.
194*/
195- (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- (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- (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- (void) writeNormal;
216
217/* writeSpecularLighting
218 Calculate specular writing and add it to totalColor.
219*/
220- (void) writeSpecularLighting;
221
222/* writeLightMaps
223 Add emission and illumination maps to totalColor.
224*/
225- (void) writeLightMaps;
226
227/* writeVertexPosition
228 Calculate vertex position and write it to gl_Position.
229*/
230- (void) writeVertexPosition;
231
232/* writeTotalColor
233 Generate vec3 totalColor, the accumulator for output colour values.
234*/
235- (void) writeTotalColor;
236
237/* writeFinalColorComposite
238 This stage writes the final fragment shader. It also pulls in other stages
239 through dependencies.
240*/
241- (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#define REQUIRE_STAGE(NAME) if (!_completed_##NAME) { [self performStage:@selector(NAME)]; _completed_##NAME = YES; }
253- (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
261static NSString *GetExtractMode(NSDictionary *textureSpecifier);
262
263
264BOOL 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
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- (void) dealloc
321{
322 [self destroyTemporaries];
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 {
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
407static 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
418static 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];
522 AppendIfNotEmpty(vertexShader, _vertexHelpers, @"Helper functions");
524
525#ifndef NDEBUG
526 _vertexShader = [vertexShader copy];
527#else
528 _vertexShader = [vertexShader retain];
529#endif
530}
531
532
534{
535 while ([_fragmentBody hasSuffix:@"\t\n"])
536 {
537 [_fragmentBody deleteCharactersInRange:(NSRange){ [_fragmentBody length] - 2, 2 }];
538 }
539
540 NSMutableString *fragmentShader = [NSMutableString string];
543 AppendIfNotEmpty(fragmentShader, _fragmentHelpers, @"Helper functions");
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*/
568static 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
580static 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- (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
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{
791
794 if (_sampledTextures != NULL)
795 {
796 NSFreeHashTable(_sampledTextures);
797 _sampledTextures = NULL;
798 }
799
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
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
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
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
918{
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
929{
931 if (!_usesDiffuseTerm) return;
932
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{
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{
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
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{
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{
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
1063 if (modulateWithDiffuse)
1064 {
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
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 {
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
1307{
1308 REQUIRE_STAGE(writeTotalColor); // Needed even if none of the following stages does anything.
1312
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*/
1330static 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
1622static 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}
#define DESTROY(x)
Definition OOCocoa.h:77
static NSString * GetExtractMode(NSDictionary *textureSpecifier)
static NSString * FormatFloat(double value)
#define REQUIRE_STAGE(NAME)
static NSDictionary * CanonicalizeMaterialSpecifier(NSDictionary *spec, NSString *materialKey)
BOOL OOSynthesizeMaterialShader(NSDictionary *configuration, NSString *materialKey, NSString *entityName, NSString **outVertexShader, NSString **outFragmentShader, NSArray **outTextureSpecs, NSDictionary **outUniformSpecs)
#define EXPECT_NOT(x)
#define OOLogWARN(class, format,...)
Definition OOLogging.h:113
#define OOLogERR(class, format,...)
Definition OOLogging.h:112
#define OOLog(class, format,...)
Definition OOLogging.h:88
unsigned count
return nil
OOTextureFlags OOApplyTextureOptionDefaults(OOTextureFlags options)
Definition OOTexture.m:855
NSDictionary * OOTextureSpecFromObject(id object, NSString *defaultName)
Definition OOTexture.m:647
NSString *const kOOTextureSpecifierIlluminationModeKey
Definition OOTexture.m:58
@ kOOTextureAllowCubeMap
Definition OOTexture.h:61
uint32_t OOTextureFlags
Definition OOTexture.h:98
BOOL OOInterpretTextureSpecifier(id specifier, NSString **outName, OOTextureFlags *outOptions, float *outAnisotropy, float *outLODBias, BOOL ignoreExtract)
Definition OOTexture.m:694
NSString *const kOOTextureSpecifierSwizzleKey
Definition OOTexture.m:46
OOColor * colorWithDescription:(id description)
Definition OOColor.m:127
void getRed:green:blue:alpha:(float *red,[green] float *green,[blue] float *blue,[alpha] float *alpha)
Definition OOColor.m:368
NSArray * normalizedArray()
Definition OOColor.m:511
OOColor * whiteColor()
Definition OOColor.m:256
static void AppendIfNotEmpty(NSMutableString *buffer, NSString *segment, NSString *name)
static NSString * KeyFromTextureSpec(NSDictionary *spec)
NSMutableDictionary * _uniformBindingNames
static NSString * GetExtractMode(NSDictionary *textureSpecifier)
static NSString * KeyFromTextureParameters(NSString *name, OOTextureFlags options, float anisotropy, float lodBias)
NSDictionary * shaderBindingTypesDictionary()