1 /*
2     This file is part of Magnum.
3 
4     Original authors — credit is appreciated but not required:
5 
6         2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 —
7             Vladimír Vondruš <mosra@centrum.cz>
8 
9     This is free and unencumbered software released into the public domain.
10 
11     Anyone is free to copy, modify, publish, use, compile, sell, or distribute
12     this software, either in source code form or as a compiled binary, for any
13     purpose, commercial or non-commercial, and by any means.
14 
15     In jurisdictions that recognize copyright laws, the author or authors of
16     this software dedicate any and all copyright interest in the software to
17     the public domain. We make this dedication for the benefit of the public
18     at large and to the detriment of our heirs and successors. We intend this
19     dedication to be an overt act of relinquishment in perpetuity of all
20     present and future rights to this software under copyright law.
21 
22     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23     IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24     FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
25     THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
26     IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
27     CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28 */
29 
30 #include <Corrade/Containers/Array.h>
31 #include <Corrade/Containers/Optional.h>
32 #include <Corrade/PluginManager/Manager.h>
33 #include <Corrade/Utility/Arguments.h>
34 #include <Corrade/Utility/DebugStl.h>
35 #include <Magnum/ImageView.h>
36 #include <Magnum/Mesh.h>
37 #include <Magnum/PixelFormat.h>
38 #include <Magnum/GL/DefaultFramebuffer.h>
39 #include <Magnum/GL/Mesh.h>
40 #include <Magnum/GL/Renderer.h>
41 #include <Magnum/GL/Texture.h>
42 #include <Magnum/GL/TextureFormat.h>
43 #include <Magnum/MeshTools/Compile.h>
44 #include <Magnum/Platform/Sdl2Application.h>
45 #include <Magnum/SceneGraph/Camera.h>
46 #include <Magnum/SceneGraph/Drawable.h>
47 #include <Magnum/SceneGraph/MatrixTransformation3D.h>
48 #include <Magnum/SceneGraph/Scene.h>
49 #include <Magnum/Shaders/Phong.h>
50 #include <Magnum/Trade/AbstractImporter.h>
51 #include <Magnum/Trade/ImageData.h>
52 #include <Magnum/Trade/MeshData3D.h>
53 #include <Magnum/Trade/MeshObjectData3D.h>
54 #include <Magnum/Trade/PhongMaterialData.h>
55 #include <Magnum/Trade/SceneData.h>
56 #include <Magnum/Trade/TextureData.h>
57 
58 namespace Magnum { namespace Examples {
59 
60 using namespace Math::Literals;
61 
62 typedef SceneGraph::Object<SceneGraph::MatrixTransformation3D> Object3D;
63 typedef SceneGraph::Scene<SceneGraph::MatrixTransformation3D> Scene3D;
64 
65 class ViewerExample: public Platform::Application {
66     public:
67         explicit ViewerExample(const Arguments& arguments);
68 
69     private:
70         void drawEvent() override;
71         void viewportEvent(ViewportEvent& event) override;
72         void mousePressEvent(MouseEvent& event) override;
73         void mouseReleaseEvent(MouseEvent& event) override;
74         void mouseMoveEvent(MouseMoveEvent& event) override;
75         void mouseScrollEvent(MouseScrollEvent& event) override;
76 
77         Vector3 positionOnSphere(const Vector2i& position) const;
78 
79         void addObject(Trade::AbstractImporter& importer, Containers::ArrayView<const Containers::Optional<Trade::PhongMaterialData>> materials, Object3D& parent, UnsignedInt i);
80 
81         Shaders::Phong _coloredShader,
82             _texturedShader{Shaders::Phong::Flag::DiffuseTexture};
83         Containers::Array<Containers::Optional<GL::Mesh>> _meshes;
84         Containers::Array<Containers::Optional<GL::Texture2D>> _textures;
85 
86         Scene3D _scene;
87         Object3D _manipulator, _cameraObject;
88         SceneGraph::Camera3D* _camera;
89         SceneGraph::DrawableGroup3D _drawables;
90         Vector3 _previousPosition;
91 };
92 
93 class ColoredDrawable: public SceneGraph::Drawable3D {
94     public:
ColoredDrawable(Object3D & object,Shaders::Phong & shader,GL::Mesh & mesh,const Color4 & color,SceneGraph::DrawableGroup3D & group)95         explicit ColoredDrawable(Object3D& object, Shaders::Phong& shader, GL::Mesh& mesh, const Color4& color, SceneGraph::DrawableGroup3D& group): SceneGraph::Drawable3D{object, &group}, _shader(shader), _mesh(mesh), _color{color} {}
96 
97     private:
98         void draw(const Matrix4& transformationMatrix, SceneGraph::Camera3D& camera) override;
99 
100         Shaders::Phong& _shader;
101         GL::Mesh& _mesh;
102         Color4 _color;
103 };
104 
105 class TexturedDrawable: public SceneGraph::Drawable3D {
106     public:
TexturedDrawable(Object3D & object,Shaders::Phong & shader,GL::Mesh & mesh,GL::Texture2D & texture,SceneGraph::DrawableGroup3D & group)107         explicit TexturedDrawable(Object3D& object, Shaders::Phong& shader, GL::Mesh& mesh, GL::Texture2D& texture, SceneGraph::DrawableGroup3D& group): SceneGraph::Drawable3D{object, &group}, _shader(shader), _mesh(mesh), _texture(texture) {}
108 
109     private:
110         void draw(const Matrix4& transformationMatrix, SceneGraph::Camera3D& camera) override;
111 
112         Shaders::Phong& _shader;
113         GL::Mesh& _mesh;
114         GL::Texture2D& _texture;
115 };
116 
ViewerExample(const Arguments & arguments)117 ViewerExample::ViewerExample(const Arguments& arguments):
118     Platform::Application{arguments, Configuration{}
119         .setTitle("Magnum Viewer Example")
120         .setWindowFlags(Configuration::WindowFlag::Resizable)}
121 {
122     Utility::Arguments args;
123     args.addArgument("file").setHelp("file", "file to load")
124         .addOption("importer", "AnySceneImporter").setHelp("importer", "importer plugin to use")
125         .addSkippedPrefix("magnum", "engine-specific options")
126         .setGlobalHelp("Displays a 3D scene file provided on command line.")
127         .parse(arguments.argc, arguments.argv);
128 
129     /* Every scene needs a camera */
130     _cameraObject
131         .setParent(&_scene)
132         .translate(Vector3::zAxis(5.0f));
133     (*(_camera = new SceneGraph::Camera3D{_cameraObject}))
134         .setAspectRatioPolicy(SceneGraph::AspectRatioPolicy::Extend)
135         .setProjectionMatrix(Matrix4::perspectiveProjection(35.0_degf, 1.0f, 0.01f, 1000.0f))
136         .setViewport(GL::defaultFramebuffer.viewport().size());
137 
138     /* Base object, parent of all (for easy manipulation) */
139     _manipulator.setParent(&_scene);
140 
141     /* Setup renderer and shader defaults */
142     GL::Renderer::enable(GL::Renderer::Feature::DepthTest);
143     GL::Renderer::enable(GL::Renderer::Feature::FaceCulling);
144     _coloredShader
145         .setAmbientColor(0x111111_rgbf)
146         .setSpecularColor(0xffffff_rgbf)
147         .setShininess(80.0f);
148     _texturedShader
149         .setAmbientColor(0x111111_rgbf)
150         .setSpecularColor(0x111111_rgbf)
151         .setShininess(80.0f);
152 
153     /* Load a scene importer plugin */
154     PluginManager::Manager<Trade::AbstractImporter> manager;
155     Containers::Pointer<Trade::AbstractImporter> importer = manager.loadAndInstantiate(args.value("importer"));
156     if(!importer) std::exit(1);
157 
158     Debug{} << "Opening file" << args.value("file");
159 
160     /* Load file */
161     if(!importer->openFile(args.value("file")))
162         std::exit(4);
163 
164     /* Load all textures. Textures that fail to load will be NullOpt. */
165     _textures = Containers::Array<Containers::Optional<GL::Texture2D>>{importer->textureCount()};
166     for(UnsignedInt i = 0; i != importer->textureCount(); ++i) {
167         Debug{} << "Importing texture" << i << importer->textureName(i);
168 
169         Containers::Optional<Trade::TextureData> textureData = importer->texture(i);
170         if(!textureData || textureData->type() != Trade::TextureData::Type::Texture2D) {
171             Warning{} << "Cannot load texture properties, skipping";
172             continue;
173         }
174 
175         Debug{} << "Importing image" << textureData->image() << importer->image2DName(textureData->image());
176 
177         Containers::Optional<Trade::ImageData2D> imageData = importer->image2D(textureData->image());
178         GL::TextureFormat format;
179         if(imageData && imageData->format() == PixelFormat::RGB8Unorm)
180             format = GL::TextureFormat::RGB8;
181         else if(imageData && imageData->format() == PixelFormat::RGBA8Unorm)
182             format = GL::TextureFormat::RGBA8;
183         else {
184             Warning{} << "Cannot load texture image, skipping";
185             continue;
186         }
187 
188         /* Configure the texture */
189         GL::Texture2D texture;
190         texture
191             .setMagnificationFilter(textureData->magnificationFilter())
192             .setMinificationFilter(textureData->minificationFilter(), textureData->mipmapFilter())
193             .setWrapping(textureData->wrapping().xy())
194             .setStorage(Math::log2(imageData->size().max()) + 1, format, imageData->size())
195             .setSubImage(0, {}, *imageData)
196             .generateMipmap();
197 
198         _textures[i] = std::move(texture);
199     }
200 
201     /* Load all materials. Materials that fail to load will be NullOpt. The
202        data will be stored directly in objects later, so save them only
203        temporarily. */
204     Containers::Array<Containers::Optional<Trade::PhongMaterialData>> materials{importer->materialCount()};
205     for(UnsignedInt i = 0; i != importer->materialCount(); ++i) {
206         Debug{} << "Importing material" << i << importer->materialName(i);
207 
208         Containers::Pointer<Trade::AbstractMaterialData> materialData = importer->material(i);
209         if(!materialData || materialData->type() != Trade::MaterialType::Phong) {
210             Warning{} << "Cannot load material, skipping";
211             continue;
212         }
213 
214         materials[i] = std::move(static_cast<Trade::PhongMaterialData&>(*materialData));
215     }
216 
217     /* Load all meshes. Meshes that fail to load will be NullOpt. */
218     _meshes = Containers::Array<Containers::Optional<GL::Mesh>>{importer->mesh3DCount()};
219     for(UnsignedInt i = 0; i != importer->mesh3DCount(); ++i) {
220         Debug{} << "Importing mesh" << i << importer->mesh3DName(i);
221 
222         Containers::Optional<Trade::MeshData3D> meshData = importer->mesh3D(i);
223         if(!meshData || !meshData->hasNormals() || meshData->primitive() != MeshPrimitive::Triangles) {
224             Warning{} << "Cannot load the mesh, skipping";
225             continue;
226         }
227 
228         /* Compile the mesh */
229         _meshes[i] = MeshTools::compile(*meshData);
230     }
231 
232     /* Load the scene */
233     if(importer->defaultScene() != -1) {
234         Debug{} << "Adding default scene" << importer->sceneName(importer->defaultScene());
235 
236         Containers::Optional<Trade::SceneData> sceneData = importer->scene(importer->defaultScene());
237         if(!sceneData) {
238             Error{} << "Cannot load scene, exiting";
239             return;
240         }
241 
242         /* Recursively add all children */
243         for(UnsignedInt objectId: sceneData->children3D())
244             addObject(*importer, materials, _manipulator, objectId);
245 
246     /* The format has no scene support, display just the first loaded mesh with
247        a default material and be done with it */
248     } else if(!_meshes.empty() && _meshes[0])
249         new ColoredDrawable{_manipulator, _coloredShader, *_meshes[0], 0xffffff_rgbf, _drawables};
250 }
251 
addObject(Trade::AbstractImporter & importer,Containers::ArrayView<const Containers::Optional<Trade::PhongMaterialData>> materials,Object3D & parent,UnsignedInt i)252 void ViewerExample::addObject(Trade::AbstractImporter& importer, Containers::ArrayView<const Containers::Optional<Trade::PhongMaterialData>> materials, Object3D& parent, UnsignedInt i) {
253     Debug{} << "Importing object" << i << importer.object3DName(i);
254     Containers::Pointer<Trade::ObjectData3D> objectData = importer.object3D(i);
255     if(!objectData) {
256         Error{} << "Cannot import object, skipping";
257         return;
258     }
259 
260     /* Add the object to the scene and set its transformation */
261     auto* object = new Object3D{&parent};
262     object->setTransformation(objectData->transformation());
263 
264     /* Add a drawable if the object has a mesh and the mesh is loaded */
265     if(objectData->instanceType() == Trade::ObjectInstanceType3D::Mesh && objectData->instance() != -1 && _meshes[objectData->instance()]) {
266         const Int materialId = static_cast<Trade::MeshObjectData3D*>(objectData.get())->material();
267 
268         /* Material not available / not loaded, use a default material */
269         if(materialId == -1 || !materials[materialId]) {
270             new ColoredDrawable{*object, _coloredShader, *_meshes[objectData->instance()], 0xffffff_rgbf, _drawables};
271 
272         /* Textured material. If the texture failed to load, again just use a
273            default colored material. */
274         } else if(materials[materialId]->flags() & Trade::PhongMaterialData::Flag::DiffuseTexture) {
275             Containers::Optional<GL::Texture2D>& texture = _textures[materials[materialId]->diffuseTexture()];
276             if(texture)
277                 new TexturedDrawable{*object, _texturedShader, *_meshes[objectData->instance()], *texture, _drawables};
278             else
279                 new ColoredDrawable{*object, _coloredShader, *_meshes[objectData->instance()], 0xffffff_rgbf, _drawables};
280 
281         /* Color-only material */
282         } else {
283             new ColoredDrawable{*object, _coloredShader, *_meshes[objectData->instance()], materials[materialId]->diffuseColor(), _drawables};
284         }
285     }
286 
287     /* Recursively add children */
288     for(std::size_t id: objectData->children())
289         addObject(importer, materials, *object, id);
290 }
291 
draw(const Matrix4 & transformationMatrix,SceneGraph::Camera3D & camera)292 void ColoredDrawable::draw(const Matrix4& transformationMatrix, SceneGraph::Camera3D& camera) {
293     _shader
294         .setDiffuseColor(_color)
295         .setLightPosition(camera.cameraMatrix().transformPoint({-3.0f, 10.0f, 10.0f}))
296         .setTransformationMatrix(transformationMatrix)
297         .setNormalMatrix(transformationMatrix.normalMatrix())
298         .setProjectionMatrix(camera.projectionMatrix());
299 
300     _mesh.draw(_shader);
301 }
302 
draw(const Matrix4 & transformationMatrix,SceneGraph::Camera3D & camera)303 void TexturedDrawable::draw(const Matrix4& transformationMatrix, SceneGraph::Camera3D& camera) {
304     _shader
305         .setLightPosition(camera.cameraMatrix().transformPoint({-3.0f, 10.0f, 10.0f}))
306         .setTransformationMatrix(transformationMatrix)
307         .setNormalMatrix(transformationMatrix.normalMatrix())
308         .setProjectionMatrix(camera.projectionMatrix())
309         .bindDiffuseTexture(_texture);
310 
311     _mesh.draw(_shader);
312 }
313 
drawEvent()314 void ViewerExample::drawEvent() {
315     GL::defaultFramebuffer.clear(GL::FramebufferClear::Color|GL::FramebufferClear::Depth);
316 
317     _camera->draw(_drawables);
318 
319     swapBuffers();
320 }
321 
viewportEvent(ViewportEvent & event)322 void ViewerExample::viewportEvent(ViewportEvent& event) {
323     GL::defaultFramebuffer.setViewport({{}, event.framebufferSize()});
324     _camera->setViewport(event.windowSize());
325 }
326 
mousePressEvent(MouseEvent & event)327 void ViewerExample::mousePressEvent(MouseEvent& event) {
328     if(event.button() == MouseEvent::Button::Left)
329         _previousPosition = positionOnSphere(event.position());
330 }
331 
mouseReleaseEvent(MouseEvent & event)332 void ViewerExample::mouseReleaseEvent(MouseEvent& event) {
333     if(event.button() == MouseEvent::Button::Left)
334         _previousPosition = Vector3();
335 }
336 
mouseScrollEvent(MouseScrollEvent & event)337 void ViewerExample::mouseScrollEvent(MouseScrollEvent& event) {
338     if(!event.offset().y()) return;
339 
340     /* Distance to origin */
341     const Float distance = _cameraObject.transformation().translation().z();
342 
343     /* Move 15% of the distance back or forward */
344     _cameraObject.translate(Vector3::zAxis(
345         distance*(1.0f - (event.offset().y() > 0 ? 1/0.85f : 0.85f))));
346 
347     redraw();
348 }
349 
positionOnSphere(const Vector2i & position) const350 Vector3 ViewerExample::positionOnSphere(const Vector2i& position) const {
351     const Vector2 positionNormalized = Vector2{position}/Vector2{_camera->viewport()} - Vector2{0.5f};
352     const Float length = positionNormalized.length();
353     const Vector3 result(length > 1.0f ? Vector3(positionNormalized, 0.0f) : Vector3(positionNormalized, 1.0f - length));
354     return (result*Vector3::yScale(-1.0f)).normalized();
355 }
356 
mouseMoveEvent(MouseMoveEvent & event)357 void ViewerExample::mouseMoveEvent(MouseMoveEvent& event) {
358     if(!(event.buttons() & MouseMoveEvent::Button::Left)) return;
359 
360     const Vector3 currentPosition = positionOnSphere(event.position());
361     const Vector3 axis = Math::cross(_previousPosition, currentPosition);
362 
363     if(_previousPosition.length() < 0.001f || axis.length() < 0.001f) return;
364 
365     _manipulator.rotate(Math::angle(_previousPosition, currentPosition), axis.normalized());
366     _previousPosition = currentPosition;
367 
368     redraw();
369 }
370 
371 }}
372 
373 MAGNUM_APPLICATION_MAIN(Magnum::Examples::ViewerExample)
374