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 : }
|