material.c 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. #include "material.h"
  2. #include "texture.h"
  3. #include "cache.h"
  4. #include "collection.h"
  5. #include "common.h"
  6. enum TokenType {
  7. Token_Skip,
  8. Token_String,
  9. Token_CurlyOpen,
  10. Token_CurlyClose,
  11. Token_Error,
  12. Token_End
  13. };
  14. struct TokenContext {
  15. const char *start;
  16. int length;
  17. const char *cursor;
  18. };
  19. static enum TokenType getNextToken(struct TokenContext *tok) {
  20. enum TokenType type = Token_Skip;
  21. const char *c = tok->cursor;
  22. while (type == Token_Skip) {
  23. while(*c != '\0' && isspace(*c)) ++c;
  24. if (*c == '\0') return Token_End;
  25. tok->start = c;
  26. tok->length = 0;
  27. switch(*c) {
  28. case '\"':
  29. tok->start = ++c;
  30. while(*c != '\0' && *c != '\"') ++c;
  31. type = (*c == '\"') ? Token_String : Token_Error;
  32. break;
  33. case '{': type = Token_CurlyOpen; break;
  34. case '}': type = Token_CurlyClose; break;
  35. case '/':
  36. if (*++c == '/') {
  37. while(*c != '\0' && *c != '\n') ++c;
  38. type = Token_Skip;
  39. } else
  40. type = Token_Error;
  41. break;
  42. default:
  43. while (*c != '\0' && isgraph(*c)) ++c;
  44. type = (c == tok->start) ? Token_Error : Token_String;
  45. } /* switch(*c) */
  46. } /* while skip */
  47. tok->length = c - tok->start;
  48. tok->cursor = c + 1;
  49. return type;
  50. }
  51. struct MaterialContext {
  52. struct Material *mat;
  53. struct TokenContext tok;
  54. const char *key;
  55. int key_length;
  56. char value[128];
  57. };
  58. enum KeyValueResult {KeyValue_Read, KeyValue_Error, KeyValue_End};
  59. static enum KeyValueResult getNextKeyValue(struct MaterialContext *ctx) {
  60. for (;;) {
  61. enum TokenType type = getNextToken(&ctx->tok);
  62. if (type == Token_End || type == Token_CurlyClose) return KeyValue_End;
  63. if (type != Token_String) return KeyValue_Error;
  64. ctx->key = ctx->tok.start;
  65. ctx->key_length = ctx->tok.length;
  66. type = getNextToken(&ctx->tok);
  67. if (type == Token_CurlyOpen) {
  68. /* skip unsupported proxies and DX-level specifics */
  69. PRINTF("Skipping section %.*s", ctx->key_length, ctx->key);
  70. int depth = 1;
  71. for (;;) {
  72. type = getNextToken(&ctx->tok);
  73. if (type == Token_CurlyClose) {
  74. if (--depth == 0)
  75. break;
  76. } else if (type == Token_CurlyOpen) {
  77. ++depth;
  78. } else if (type != Token_String)
  79. return KeyValue_Error;
  80. }
  81. } else if (type != Token_String) {
  82. return KeyValue_Error;
  83. } else {
  84. if (ctx->tok.length > (int)sizeof(ctx->value) - 1) {
  85. PRINTF("Value is too long: %d", ctx->tok.length);
  86. return KeyValue_Error;
  87. }
  88. memcpy(ctx->value, ctx->tok.start, ctx->tok.length);
  89. ctx->value[ctx->tok.length] = '\0';
  90. return KeyValue_Read;
  91. }
  92. } /* loop until key value pair found */
  93. } /* getNextKeyValue */
  94. static const char * const ignore_params[] = {
  95. "$surfaceprop", "$surfaceprop2", "$tooltexture",
  96. "%tooltexture", "%keywords", "%compilewater", "%detailtype",
  97. "%compilenolight", "%compilepassbullets",
  98. 0
  99. };
  100. static int materialLoad(struct IFile *file, struct ICollection *coll, struct Material *output, struct Stack *tmp) {
  101. char buffer[8192]; /* most vmts are < 300, a few are almost 1000, max seen ~3200 */
  102. if (file->size > sizeof(buffer) - 1) {
  103. PRINTF("VMT is too large: %zu", file->size);
  104. return 0;
  105. }
  106. if (file->size != file->read(file, 0, file->size, buffer)) return 0;
  107. buffer[file->size] = '\0';
  108. struct MaterialContext ctx;
  109. ctx.mat = output;
  110. ctx.tok.cursor = buffer;
  111. #define EXPECT_TOKEN(type) \
  112. if (getNextToken(&ctx.tok) != type) { \
  113. PRINTF("Unexpected token at position %zd, expecting %d; left: %s", ctx.tok.cursor - buffer, type, ctx.tok.cursor); \
  114. return 0; \
  115. }
  116. EXPECT_TOKEN(Token_String);
  117. const char *shader = ctx.tok.start;
  118. const int shader_length = ctx.tok.length;
  119. EXPECT_TOKEN(Token_CurlyOpen);
  120. for (;;) {
  121. const enum KeyValueResult result = getNextKeyValue(&ctx);
  122. if (result == KeyValue_End) break;
  123. if (result != KeyValue_Read) goto error;
  124. int skip = 0;
  125. for (const char *const *ignore = ignore_params; ignore[0] != 0; ++ignore)
  126. if (strncasecmp(ignore[0], ctx.key, ctx.key_length) == 0) {
  127. skip = 1;
  128. break;
  129. }
  130. if (skip) continue;
  131. if (strncasecmp("$basetexture", ctx.key, ctx.key_length) == 0) {
  132. output->base_texture[0] = textureGet(ctx.value, coll, tmp);
  133. } else if (strncasecmp("$basetexture2", ctx.key, ctx.key_length) == 0) {
  134. output->base_texture[1] = textureGet(ctx.value, coll, tmp);
  135. } else if (strncasecmp("$basetexturetransform", ctx.key, ctx.key_length) == 0) {
  136. } else if (strncasecmp("$basetexturetransform2", ctx.key, ctx.key_length) == 0) {
  137. } else if (strncasecmp("$detail", ctx.key, ctx.key_length) == 0) {
  138. output->detail = textureGet(ctx.value, coll, tmp);
  139. } else if (strncasecmp("$detailscale", ctx.key, ctx.key_length) == 0) {
  140. } else if (strncasecmp("$detailblendfactor", ctx.key, ctx.key_length) == 0) {
  141. } else if (strncasecmp("$detailblendmode", ctx.key, ctx.key_length) == 0) {
  142. } else if (strncasecmp("$parallaxmap", ctx.key, ctx.key_length) == 0) {
  143. } else if (strncasecmp("$parallaxmapscale", ctx.key, ctx.key_length) == 0) {
  144. } else if (strncasecmp("$bumpmap", ctx.key, ctx.key_length) == 0) {
  145. output->bump = textureGet(ctx.value, coll, tmp);
  146. } else if (strncasecmp("$envmap", ctx.key, ctx.key_length) == 0) {
  147. output->envmap = textureGet(ctx.value, coll, tmp);
  148. } else if (strncasecmp("$fogenable", ctx.key, ctx.key_length) == 0) {
  149. } else if (strncasecmp("$fogcolor", ctx.key, ctx.key_length) == 0) {
  150. } else if (strncasecmp("$alphatest", ctx.key, ctx.key_length) == 0) {
  151. } else if (strncasecmp("$translucent", ctx.key, ctx.key_length) == 0) {
  152. } else {
  153. PRINTF("Material shader:%.*s, unknown param %.*s = %s",
  154. shader_length, shader, ctx.key_length, ctx.key, ctx.value);
  155. }
  156. } /* for all properties */
  157. return 1;
  158. error:
  159. PRINTF("Error parsing material with shader %.*s: %s", shader_length, shader, ctx.tok.cursor);
  160. return 0;
  161. }
  162. const struct Material *materialGet(const char *name, struct ICollection *collection, struct Stack *tmp) {
  163. const struct Material *mat = cacheGetMaterial(name);
  164. if (mat) return mat;
  165. struct IFile *matfile;
  166. if (CollectionOpen_Success != collectionChainOpen(collection, name, File_Material, &matfile)) {
  167. PRINTF("Material \"%s\" not found", name);
  168. return cacheGetMaterial("opensource/placeholder");
  169. }
  170. struct Material localmat;
  171. memset(&localmat, 0, sizeof localmat);
  172. if (materialLoad(matfile, collection, &localmat, tmp) == 0) {
  173. PRINTF("Material \"%s\" found, but could not be loaded", name);
  174. } else {
  175. cachePutMaterial(name, &localmat);
  176. mat = cacheGetMaterial(name);
  177. }
  178. matfile->close(matfile);
  179. return mat ? mat : cacheGetMaterial("opensource/placeholder");
  180. }