1 module gfm.assimp.assimp;
2 
3 import std.conv,
4        std.string,
5        std.array : join;
6 
7 import std.logger;
8 
9 import gfm.core.text;
10 
11 import derelict.assimp3.assimp,
12        derelict.util.exception;
13 
14 /// The one exception type thrown in this wrapper.
15 /// A failing ASSIMP function should <b>always</b> throw an AssimpException.
16 class AssimpException : Exception
17 {
18     public
19     {
20         this(string msg)
21         {
22             super(msg);
23         }
24     }
25 }
26 
27 /// Create one to use the ASSIMP libary.
28 /// Owns both the loader and logging redirection.
29 /// This object is passed around to other ASSIMP wrapper objects
30 /// to ensure library loading.
31 final class Assimp
32 {
33     public
34     {
35         /// Load ASSIMP library, redirect logging to our logger.
36         /// You can pass a null logger if you don't want logging.
37         /// Throws: AssimpException on error.
38         this(Logger logger)
39         {
40             _logger = logger is null ? new NullLogger() : logger;
41 
42             try
43             {
44                 DerelictASSIMP3.load();
45             }
46             catch(DerelictException e)
47             {
48                 throw new AssimpException(e.msg);
49             }
50 
51             _libInitialized = true;
52 
53             _logger.infof("Assimp %s initialized.", getVersion());
54             _logger.infof("%s.", getLegalString());
55 
56             // enable verbose logging in debug-mode
57             debug
58                 aiEnableVerboseLogging(AI_TRUE);
59             else
60                 aiEnableVerboseLogging(AI_FALSE);
61 
62             // route Assimp logging to our own
63             _logStream.callback = &loggingCallbackAssimp;
64             _logStream.user = cast(char*)(cast(void*)this);
65             aiAttachLogStream(&_logStream);
66         }
67 
68         ~this()
69         {
70             close();
71         }
72 
73         /// Releases the ASSIMP library and all resources.
74         /// All resources should have been released at this point,
75         /// since you won't be able to call any ASSIMP function afterwards.
76         void close()
77         {
78             if (_libInitialized)
79             {
80                 aiDetachLogStream(&_logStream);
81                 DerelictASSIMP3.unload();
82                 _libInitialized = false;
83             }
84         }
85 
86         /// Returns: ASSIMP version string as returned by the dynamic library.
87         string getVersion()
88         {
89             string compileFlags()
90             {
91                 string[] res;
92                 uint flags = aiGetCompileFlags();
93                 if ((flags & ASSIMP_CFLAGS_SHARED) != 0)
94                     res ~= "shared";
95                 if ((flags & ASSIMP_CFLAGS_STLPORT) != 0)
96                     res ~= "stl-port";
97                 if ((flags & ASSIMP_CFLAGS_DEBUG) != 0)
98                     res ~= "debug";
99                 if ((flags & ASSIMP_CFLAGS_NOBOOST) != 0)
100                     res ~= "no-boost";
101                 if ((flags & ASSIMP_CFLAGS_SINGLETHREADED) != 0)
102                     res ~= "single-threaded";
103                 return join(res, ", ");
104             }
105             return format("v%s.%s_r%s (%s)", aiGetVersionMajor(), aiGetVersionMinor(), aiGetVersionRevision(), compileFlags());
106         }
107 
108         /// Returns: A string with legal copyright and licensing information about Assimp. 
109         string getLegalString()
110         {
111             const(char)* legalZ = aiGetLegalString();
112             return sanitizeUTF8(legalZ, _logger, "Assimp legal string");
113         }
114     }
115 
116     package
117     {
118         Logger _logger;
119 
120         // exception mechanism that shall be used by every module here
121         void throwAssimpException(string callThatFailed)
122         {
123             string message = format("%s failed: %s", callThatFailed, getErrorString());
124             throw new AssimpException(message);
125         }
126 
127         string getErrorString()
128         {
129             const(char)* errorZ = aiGetErrorString();
130             return sanitizeUTF8(errorZ, _logger, "Assimp error string");
131         }
132     }
133 
134     private
135     {
136         bool _libInitialized;
137         aiLogStream _logStream;
138     }
139 }
140 
141 extern (C) private 
142 {
143     void loggingCallbackAssimp(const(char)* message, char* user) nothrow
144     {
145         Assimp assimp = cast(Assimp)user;
146         try
147         {
148             Logger logger = assimp._logger;
149             logger.infof("assimp: %s", sanitizeUTF8(message, log, "Assimp logging"));
150         }
151         catch(Exception e)
152         {
153             // ignoring IO exceptions, format errors, etc... to be nothrow
154             // making the whole Log interface nothrow is not that trivial
155         }
156     }
157 }