From 639b40bbb624c0b791b8d63ae785062270d05b45 Mon Sep 17 00:00:00 2001 From: Armin Burgmeier Date: Mon, 6 Jul 2009 23:45:44 +0200 Subject: [PATCH] Added StdMeshMaterial --- Makefile.am | 2 + standard/inc/StdBuf.h | 10 + standard/inc/StdMeshMaterial.h | 129 ++++++++++ standard/src/StdMeshMaterial.cpp | 405 +++++++++++++++++++++++++++++++ 4 files changed, 546 insertions(+) create mode 100644 standard/inc/StdMeshMaterial.h create mode 100644 standard/src/StdMeshMaterial.cpp diff --git a/Makefile.am b/Makefile.am index 2da972a24..eb57372f2 100644 --- a/Makefile.am +++ b/Makefile.am @@ -432,6 +432,7 @@ libstandard_a_SOURCES = \ standard/src/StdGL.cpp \ standard/src/StdGLCtx.cpp \ standard/src/StdMarkup.cpp \ + standard/src/StdMeshMaterial.cpp \ standard/src/StdNoGfx.cpp \ standard/src/StdPNG.cpp \ standard/src/StdRegistry.cpp \ @@ -462,6 +463,7 @@ libstandard_a_SOURCES = \ standard/inc/StdFont.h \ standard/inc/StdGL.h \ standard/inc/StdMarkup.h \ + standard/inc/StdMeshMaterial.h \ standard/inc/StdNoGfx.h \ standard/inc/StdPNG.h \ standard/inc/StdRandom.h \ diff --git a/standard/inc/StdBuf.h b/standard/inc/StdBuf.h index e54e854d1..4333ca23c 100644 --- a/standard/inc/StdBuf.h +++ b/standard/inc/StdBuf.h @@ -676,6 +676,16 @@ public: }; +#if 0 +// const char* + StdStrBuf +inline StdStrBuf operator + (const char* szString, const StdStrBuf& Buf2) +{ + StdStrBuf Buf(szString); + Buf.Append(Buf2); + return Buf; +} +#endif + // Wrappers extern StdStrBuf FormatString(const char *szFmt, ...) GNUC_FORMAT_ATTRIBUTE; extern StdStrBuf FormatStringV(const char *szFmt, va_list args); diff --git a/standard/inc/StdMeshMaterial.h b/standard/inc/StdMeshMaterial.h new file mode 100644 index 000000000..216ff46f3 --- /dev/null +++ b/standard/inc/StdMeshMaterial.h @@ -0,0 +1,129 @@ +/* + * OpenClonk, http://www.openclonk.org + * + * Copyright (c) 2009 Armin Burgmeier + * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de + * + * Portions might be copyrighted by other authors who have contributed + * to OpenClonk. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * See isc_license.txt for full license and disclaimer. + * + * "Clonk" is a registered trademark of Matthes Bender. + * See clonk_trademark_license.txt for full license. + */ + +#ifndef INC_StdMeshMaterial +#define INC_StdMeshMaterial + +#include +#include +#include +#include + +#include +#include + +// TODO: Support more features of OGRE material scripts +// Refer to http://www.ogre3d.org/docs/manual/manual_14.html + +class StdMeshMaterialParserCtx; + +class StdMeshMaterialError: public std::exception +{ +public: + StdMeshMaterialError(const StdStrBuf& message, const char* file, unsigned int line); + virtual ~StdMeshMaterialError() throw() {} + + virtual const char* what() const throw() { return Buf.getData(); } + +protected: + StdStrBuf Buf; +}; + +// Interface to load textures. Given a texture filename occuring in the +// material script, this should load the texture from wherever the material +// script is actually loaded, for example from a C4Group. +class StdMeshMaterialTextureLoader +{ +public: + virtual bool operator()(const char* filename, CPNGFile& dest) = 0; +}; + +class StdMeshMaterialTextureUnit +{ +public: + StdMeshMaterialTextureUnit(); + ~StdMeshMaterialTextureUnit(); + + void Load(StdMeshMaterialParserCtx& ctx); + + CTexRef* Texture; +}; + +class StdMeshMaterialPass +{ +public: + StdMeshMaterialPass(); + void Load(StdMeshMaterialParserCtx& ctx); + + std::vector TextureUnits; + + float Ambient[4]; + float Diffuse[4]; + float Specular[4]; + float Emissive[4]; + float Shininess; +}; + +class StdMeshMaterialTechnique +{ +public: + void Load(StdMeshMaterialParserCtx& ctx); + + std::vector Passes; +}; + +class StdMeshMaterial +{ +public: + StdMeshMaterial(); + void Load(StdMeshMaterialParserCtx& ctx); + + // Location the Material was loaded from + StdStrBuf FileName; + unsigned int Line; + + // Material name + StdStrBuf Name; + + // Not currently used in Clonk, but don't fail when we see this in a + // Material script: + bool ReceiveShadows; + + // Available techniques + std::vector Techniques; +}; + +class StdMeshMatManager +{ +public: + // Parse a material script file, and add the materials to the manager. + // filename may be NULL if the source is not a file. It will only be used + // for error messages. + // Throws StdMeshMaterialError. + void Parse(const char* mat_script, const char* filename, StdMeshMaterialTextureLoader& tex_loader); + + // Get material by name. NULL if there is no such material with this name. + const StdMeshMaterial* GetMaterial(const char* material_name) const; + +private: + std::map Materials; +}; + +#endif + +// vim: et ts=2 sw=2 diff --git a/standard/src/StdMeshMaterial.cpp b/standard/src/StdMeshMaterial.cpp new file mode 100644 index 000000000..6c0b10f9a --- /dev/null +++ b/standard/src/StdMeshMaterial.cpp @@ -0,0 +1,405 @@ +/* + * OpenClonk, http://www.openclonk.org + * + * Copyright (c) 2009 Armin Burgmeier + * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de + * + * Portions might be copyrighted by other authors who have contributed + * to OpenClonk. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * See isc_license.txt for full license and disclaimer. + * + * "Clonk" is a registered trademark of Matthes Bender. + * See clonk_trademark_license.txt for full license. + */ + +#include + +#include + +StdMeshMaterialError::StdMeshMaterialError(const StdStrBuf& message, const char* file, unsigned int line) +{ + Buf.Format("%s[%u]: %s", file, line, message.getData()); +} + +enum Token +{ + TOKEN_IDTF, + TOKEN_BRACE_OPEN, + TOKEN_BRACE_CLOSE, + TOKEN_COLON, + TOKEN_EOF +}; + +class StdMeshMaterialParserCtx +{ +public: + StdMeshMaterialParserCtx(const char* mat_script, const char* filename, StdMeshMaterialTextureLoader& tex_loader); + + void SkipWhitespace(); + Token Peek(StdStrBuf& name); + Token Advance(StdStrBuf& name); + Token AdvanceNonEOF(StdStrBuf& name); + Token AdvanceRequired(StdStrBuf& name, Token expect); + Token AdvanceRequired(StdStrBuf& name, Token expect1, Token expect2); + float AdvanceFloat(); + bool AdvanceFloatOptional(float& value); + bool AdvanceBoolean(); + void Error(const StdStrBuf& message); + void ErrorUnexpectedIdentifier(const StdStrBuf& identifier); + + // Current parsing data + unsigned int Line; + const char* Script; + + StdStrBuf FileName; + StdMeshMaterialTextureLoader& TextureLoader; +}; + +StdMeshMaterialParserCtx::StdMeshMaterialParserCtx(const char* mat_script, const char* filename, StdMeshMaterialTextureLoader& tex_loader): + Line(0), Script(mat_script), FileName(filename), TextureLoader(tex_loader) +{ +} + +void StdMeshMaterialParserCtx::SkipWhitespace() +{ + while(isspace(*Script)) + { + if(*Script == '\n') ++Line; + ++Script; + } +} + +Token StdMeshMaterialParserCtx::Peek(StdStrBuf& name) +{ + SkipWhitespace(); + + const char* before = Script; + Token tok = Advance(name); + Script = before; + return tok; +} + +Token StdMeshMaterialParserCtx::Advance(StdStrBuf& name) +{ + SkipWhitespace(); + + switch(*Script) + { + case '\0': + name.Clear(); + return TOKEN_EOF; + case '{': + ++Script; + name = "{"; + return TOKEN_BRACE_OPEN; + case '}': + ++Script; + name = "}"; + return TOKEN_BRACE_CLOSE; + case ':': + ++Script; + name = ":"; + return TOKEN_COLON; + default: + const char* begin = Script; + // Advance to next whitespace + do { ++Script; } while(!isspace(*Script) && *Script != '{' && *Script != '}' && *Script != ':'); + name.Copy(begin, Script - begin); + return TOKEN_IDTF; + } +} + +Token StdMeshMaterialParserCtx::AdvanceNonEOF(StdStrBuf& name) +{ + Token token = Advance(name); + if(token == TOKEN_EOF) Error(StdStrBuf("Unexpected end of file")); + return token; +} + +Token StdMeshMaterialParserCtx::AdvanceRequired(StdStrBuf& name, Token expect) +{ + Token token = AdvanceNonEOF(name); + // TODO: Explain what was actually expected + if(token != expect) Error(StdStrBuf("'") + name + "' unexpected"); + return token; +} + +Token StdMeshMaterialParserCtx::AdvanceRequired(StdStrBuf& name, Token expect1, Token expect2) +{ + Token token = AdvanceNonEOF(name); + // TODO: Explain what was actually expected + if(token != expect1 && token != expect2) + Error(StdStrBuf("'") + name + "' unexpected"); + return token; +} + +float StdMeshMaterialParserCtx::AdvanceFloat() +{ + StdStrBuf buf; + AdvanceRequired(buf, TOKEN_IDTF); + char* end; + float f = strtof(buf.getData(), &end); + if(*end != '\0') Error(StdStrBuf("Floating point value expected")); + return f; +} + +bool StdMeshMaterialParserCtx::AdvanceFloatOptional(float& value) +{ + StdStrBuf buf; + Token tok = Peek(buf); + + if(tok == TOKEN_IDTF && isdigit(buf[0])) + { + value = AdvanceFloat(); + return true; + } + + return false; +} + +bool StdMeshMaterialParserCtx::AdvanceBoolean() +{ + StdStrBuf buf; + AdvanceRequired(buf, TOKEN_IDTF); + if(buf == "on") return true; + if(buf == "off") return false; + Error(StdStrBuf("Expected either 'on' or 'off', but not '") + buf + "'"); + return false; // Never reached +} + +void StdMeshMaterialParserCtx::Error(const StdStrBuf& message) +{ + throw StdMeshMaterialError(message, FileName.getData(), Line); +} + +void StdMeshMaterialParserCtx::ErrorUnexpectedIdentifier(const StdStrBuf& identifier) +{ + Error(StdStrBuf("Unexpected identifier: '") + identifier + "'"); +} + +StdMeshMaterialTextureUnit::StdMeshMaterialTextureUnit(): Texture(NULL) {} +StdMeshMaterialTextureUnit::~StdMeshMaterialTextureUnit() { delete Texture; } + +void StdMeshMaterialTextureUnit::Load(StdMeshMaterialParserCtx& ctx) +{ + Token token; + StdStrBuf token_name; + while((token = ctx.AdvanceNonEOF(token_name)) == TOKEN_IDTF) + { + if(token_name == "texture") + { + ctx.AdvanceRequired(token_name, TOKEN_IDTF); + + CPNGFile png; + if(!ctx.TextureLoader(token_name.getData(), png)) + ctx.Error(StdStrBuf("Could not load texture '") + token_name + "'"); + + if(png.iWdt != png.iHgt) + ctx.Error(StdStrBuf("Texture '") + token_name + "' is not quadratic"); + + Texture = new CTexRef(png.iWdt, false); + Texture->Lock(); + for(unsigned int y = 0; y < png.iHgt; ++y) + for(unsigned int x = 0; x < png.iWdt; ++x) + Texture->SetPix4(x, y, png.GetPix(x, y)); + Texture->Unlock(); + } + else + ctx.ErrorUnexpectedIdentifier(token_name); + } + + if(token != TOKEN_BRACE_CLOSE) + ctx.Error(StdStrBuf("'") + token_name.getData() + "' unexpected"); +} + +StdMeshMaterialPass::StdMeshMaterialPass() +{ + Ambient[0] = Ambient[1] = Ambient[2] = Ambient[3] = 1.0f; + Diffuse[0] = Diffuse[1] = Diffuse[2] = Diffuse[3] = 1.0f; + Specular[0] = Specular[1] = Specular[2] = Specular[3] = 0.0f; + Emissive[0] = Emissive[1] = Emissive[2] = Emissive[3] = 0.0f; + Shininess = 0.0f; +} + +void StdMeshMaterialPass::Load(StdMeshMaterialParserCtx& ctx) +{ + Token token; + StdStrBuf token_name; + while((token = ctx.AdvanceNonEOF(token_name)) == TOKEN_IDTF) + { + if(token_name == "texture_unit") + { + // TODO: Can there be an optional name? + ctx.AdvanceRequired(token_name, TOKEN_BRACE_OPEN); + TextureUnits.push_back(StdMeshMaterialTextureUnit()); + TextureUnits.back().Load(ctx); + } + else if(token_name == "ambient") + { + Ambient[0] = ctx.AdvanceFloat(); + Ambient[1] = ctx.AdvanceFloat(); + Ambient[2] = ctx.AdvanceFloat(); + ctx.AdvanceFloatOptional(Ambient[3]); + } + else if(token_name == "diffuse") + { + Diffuse[0] = ctx.AdvanceFloat(); + Diffuse[1] = ctx.AdvanceFloat(); + Diffuse[2] = ctx.AdvanceFloat(); + ctx.AdvanceFloatOptional(Diffuse[3]); + } + else if(token_name == "specular") + { + Diffuse[0] = ctx.AdvanceFloat(); + Diffuse[1] = ctx.AdvanceFloat(); + Diffuse[2] = ctx.AdvanceFloat(); + + // The fourth argument is optional, not the fifth: + float diffuse3 = ctx.AdvanceFloat(); + + float shininess; + if(ctx.AdvanceFloatOptional(shininess)) + { + Diffuse[3] = diffuse3; + Shininess = shininess; + } + else + { + Shininess = diffuse3; + } + } + else if(token_name == "emissive") + { + Emissive[0] = ctx.AdvanceFloat(); + Emissive[1] = ctx.AdvanceFloat(); + Emissive[2] = ctx.AdvanceFloat(); + ctx.AdvanceFloatOptional(Emissive[3]); + } + else + ctx.ErrorUnexpectedIdentifier(token_name); + } + + if(token != TOKEN_BRACE_CLOSE) + ctx.Error(StdStrBuf("'") + token_name.getData() + "' unexpected"); +} + +void StdMeshMaterialTechnique::Load(StdMeshMaterialParserCtx& ctx) +{ + Token token; + StdStrBuf token_name; + while((token = ctx.AdvanceNonEOF(token_name)) == TOKEN_IDTF) + { + if(token_name == "pass") + { + // TODO: Can there be an optional name? + ctx.AdvanceRequired(token_name, TOKEN_BRACE_OPEN); + Passes.push_back(StdMeshMaterialPass()); + Passes.back().Load(ctx); + } + else + ctx.ErrorUnexpectedIdentifier(token_name); + } + + if(token != TOKEN_BRACE_CLOSE) + ctx.Error(StdStrBuf("'") + token_name.getData() + "' unexpected"); +} + +StdMeshMaterial::StdMeshMaterial(): + Line(0), ReceiveShadows(true) +{ +} + +void StdMeshMaterial::Load(StdMeshMaterialParserCtx& ctx) +{ + Token token; + StdStrBuf token_name; + while((token = ctx.AdvanceNonEOF(token_name)) == TOKEN_IDTF) + { + if(token_name == "technique") + { + // TODO: Can there be an optional name? + ctx.AdvanceRequired(token_name, TOKEN_BRACE_OPEN); + Techniques.push_back(StdMeshMaterialTechnique()); + Techniques.back().Load(ctx); + } + else if(token_name == "receive_shadows") + { + ReceiveShadows = ctx.AdvanceBoolean(); + } + else + ctx.ErrorUnexpectedIdentifier(token_name); + } + + if(token != TOKEN_BRACE_CLOSE) + ctx.Error(StdStrBuf("'") + token_name.getData() + "' unexpected"); +} + +void StdMeshMatManager::Parse(const char* mat_script, const char* filename, StdMeshMaterialTextureLoader& tex_loader) +{ + StdMeshMaterialParserCtx ctx(mat_script, filename, tex_loader); + + Token token; + StdStrBuf token_name; + while((token = ctx.AdvanceNonEOF(token_name)) == TOKEN_IDTF) + { + if(token_name == "material") + { + // Read name + StdStrBuf material_name; + ctx.AdvanceRequired(material_name, TOKEN_IDTF); + + // Check for uniqueness + std::map::iterator iter = Materials.find(material_name); + if(iter != Materials.end()) + ctx.Error(FormatString("Material with name '%s' is already defined in %s:%u", material_name.getData(), iter->second.FileName.getData(), iter->second.Line)); + + // Check if there is a parent given + Token next = ctx.AdvanceRequired(token_name, TOKEN_BRACE_OPEN, TOKEN_COLON); + // Read parent name, if any + StdMeshMaterial* parent = NULL; + if(next == TOKEN_COLON) + { + // Note that if there is a parent, then it needs to be loaded + // already. This currently makes only sense when its defined above + // in the same material script file or in a parent definition. + // We could later support material scripts in the System.c4g. + StdStrBuf parent_name; + ctx.AdvanceRequired(parent_name, TOKEN_IDTF); + ctx.AdvanceRequired(token_name, TOKEN_BRACE_OPEN); + + iter = Materials.find(parent_name); + if(iter == Materials.end()) + ctx.Error(StdStrBuf("Parent material '") + parent_name + "' does not exist (or is not yet loaded)"); + parent = &iter->second; + } + + // Copy properties from parent if one is given, otherwise + // default-construct the material. + StdMeshMaterial& mat = Materials.insert(std::make_pair(material_name, parent ? StdMeshMaterial(*parent) : StdMeshMaterial())).first->second; + // Set/Overwrite source and name + mat.Name = material_name; + mat.FileName = ctx.FileName; + mat.Line = ctx.Line; + + mat.Load(ctx); + } + else + ctx.ErrorUnexpectedIdentifier(token_name); + } + + if(token != TOKEN_EOF) + ctx.Error(StdStrBuf("'") + token_name.getData() + "' unexpected"); +} + +const StdMeshMaterial* StdMeshMatManager::GetMaterial(const char* material_name) const +{ + std::map::const_iterator iter = Materials.find(StdStrBuf(material_name)); + if(iter == Materials.end()) return NULL; + return &iter->second; +} + +// vim: et ts=2 sw=2