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