]> git.lizzy.rs Git - minetest.git/commitdiff
Add model[] formspec element (#10320)
authorSmallJoker <SmallJoker@users.noreply.github.com>
Wed, 4 Nov 2020 20:46:18 +0000 (21:46 +0100)
committerGitHub <noreply@github.com>
Wed, 4 Nov 2020 20:46:18 +0000 (21:46 +0100)
Formspec element to display models, written by @kilbith, rebased and tweaked.

Co-authored-by: Jean-Patrick Guerrero <jeanpatrick.guerrero@gmail.com>
Co-authored-by: sfan5 <sfan5@live.de>
16 files changed:
doc/lua_api.txt
games/devtest/mods/testformspec/LICENSE.txt [new file with mode: 0644]
games/devtest/mods/testformspec/formspec.lua
games/devtest/mods/testformspec/models/testformspec_character.b3d [new file with mode: 0644]
games/devtest/mods/testformspec/models/testformspec_chest.obj [new file with mode: 0644]
games/devtest/mods/testformspec/textures/default_chest_front.png [new file with mode: 0644]
games/devtest/mods/testformspec/textures/default_chest_inside.png [new file with mode: 0644]
games/devtest/mods/testformspec/textures/default_chest_side.png [new file with mode: 0644]
games/devtest/mods/testformspec/textures/default_chest_top.png [new file with mode: 0644]
games/devtest/mods/testformspec/textures/testformspec_character.png [new file with mode: 0644]
src/gui/CMakeLists.txt
src/gui/guiFormSpecMenu.cpp
src/gui/guiFormSpecMenu.h
src/gui/guiScene.cpp [new file with mode: 0644]
src/gui/guiScene.h [new file with mode: 0644]
util/ci/clang-format-whitelist.txt

index daf0da3d2685b0e93d8fb1d1f67a8a3d31bd92e9..38fc3066ac4d12969aec5faa61d27bd23956c62c 100644 (file)
@@ -2272,6 +2272,18 @@ Elements
 * `frame duration`: Milliseconds between each frame. `0` means the frames don't advance.
 * `frame start` (Optional): The index of the frame to start on. Default `1`.
 
+### `model[<X>,<Y>;<W>,<H>;<name>;<mesh>;<textures>;<rotation X,Y>;<continuous>;<mouse control>]`
+
+* Show a mesh model.
+* `name`: Element name that can be used for styling
+* `mesh`: The mesh model to use.
+* `textures`: The mesh textures to use according to the mesh materials.
+   Texture names must be separated by commas.
+* `rotation {X,Y}` (Optional): Initial rotation of the camera.
+  The axes are euler angles in degrees.
+* `continuous` (Optional): Whether the rotation is continuous. Default `false`.
+* `mouse control` (Optional): Whether the model can be controlled with the mouse. Default `true`.
+
 ### `item_image[<X>,<Y>;<W>,<H>;<item name>]`
 
 * Show an inventory image of registered item/node
@@ -2842,6 +2854,10 @@ Some types may inherit styles from parent types.
     * font_size - Sets font size. See button `font_size` property for more information.
     * noclip - boolean, set to true to allow the element to exceed formspec bounds.
     * textcolor - color. Default white.
+* model
+    * bgcolor - color, sets background color.
+    * noclip - boolean, set to true to allow the element to exceed formspec bounds.
+        * Default to false in formspec_version version 3 or higher
 * image
     * noclip - boolean, set to true to allow the element to exceed formspec bounds.
         * Default to false in formspec_version version 3 or higher
diff --git a/games/devtest/mods/testformspec/LICENSE.txt b/games/devtest/mods/testformspec/LICENSE.txt
new file mode 100644 (file)
index 0000000..07696cc
--- /dev/null
@@ -0,0 +1,14 @@
+License of media files
+----------------------
+Content imported from minetest_game.
+
+
+BlockMen (CC BY-SA 3.0)
+  default_chest_front.png
+  default_chest_lock.png
+  default_chest_side.png
+  default_chest_top.png
+
+stujones11 (CC BY-SA 3.0)
+An0n3m0us (CC BY-SA 3.0)
+  testformspec_character.b3d
index 87a05fc96859ed8970ea8dae3cdb34e54fa19c73..5495896cef8fbf1fbdee621c00438aaeb8d7fb81 100644 (file)
@@ -327,6 +327,10 @@ Number]
                        animated_image[3,4.25;1,1;;testformspec_animation.png;4;0;3]
                        animated_image[5.5,0.5;5,2;;testformspec_animation.png;4;100]
                        animated_image[5.5,2.75;5,2;;testformspec_animation.jpg;4;100]
+
+                       style[m1;bgcolor=black]
+                       model[0.5,6;4,4;m1;testformspec_character.b3d;testformspec_character.png]
+                       model[5,6;4,4;m2;testformspec_chest.obj;default_chest_top.png,default_chest_top.png,default_chest_side.png,default_chest_side.png,default_chest_front.png,default_chest_inside.png;30,1;true;true]
                ]],
 
        -- Scroll containers
diff --git a/games/devtest/mods/testformspec/models/testformspec_character.b3d b/games/devtest/mods/testformspec/models/testformspec_character.b3d
new file mode 100644 (file)
index 0000000..8edbaf6
Binary files /dev/null and b/games/devtest/mods/testformspec/models/testformspec_character.b3d differ
diff --git a/games/devtest/mods/testformspec/models/testformspec_chest.obj b/games/devtest/mods/testformspec/models/testformspec_chest.obj
new file mode 100644 (file)
index 0000000..72ba175
--- /dev/null
@@ -0,0 +1,79 @@
+# Blender v2.78 (sub 0) OBJ File: 'chest-open.blend'
+# www.blender.org
+o Top_Cube.002_None_Top_Cube.002_None_bottom
+v -0.500000 0.408471 0.720970
+v -0.500000 1.115578 0.013863
+v -0.500000 0.894607 -0.207108
+v -0.500000 0.187501 0.499999
+v 0.500000 1.115578 0.013863
+v 0.500000 0.408471 0.720970
+v 0.500000 0.187501 0.499999
+v 0.500000 0.894607 -0.207108
+v -0.500000 0.187500 -0.500000
+v -0.500000 -0.500000 -0.500000
+v -0.500000 -0.500000 0.500000
+v 0.500000 0.187500 -0.500000
+v 0.500000 -0.500000 0.500000
+v 0.500000 -0.500000 -0.500000
+vt 0.0000 1.0000
+vt 0.0000 0.0000
+vt 1.0000 0.0000
+vt 1.0000 1.0000
+vt 1.0000 0.0000
+vt 1.0000 1.0000
+vt 0.0000 1.0000
+vt 0.0000 0.0000
+vt 0.0000 1.0000
+vt 1.0000 1.0000
+vt 1.0000 0.6875
+vt 0.0000 0.6875
+vt 1.0000 1.0000
+vt 0.0000 0.6875
+vt 1.0000 0.6875
+vt 1.0000 0.6875
+vt 1.0000 0.0000
+vt 0.0000 0.0000
+vt 1.0000 0.6875
+vt 1.0000 0.0000
+vt 1.0000 1.0000
+vt 1.0000 0.6875
+vt 1.0000 0.0000
+vt 0.0000 1.0000
+vt 0.0000 0.6875
+vt 0.0000 0.6875
+vt 0.0000 0.0000
+vt 1.0000 0.5000
+vt 1.0000 1.0000
+vt 0.0000 1.0000
+vt 0.0000 0.5000
+vt 0.0000 0.0000
+vt 1.0000 0.0000
+vn 0.0000 0.7071 0.7071
+vn -0.0000 -1.0000 -0.0000
+vn -1.0000 0.0000 0.0000
+vn 1.0000 0.0000 -0.0000
+vn 0.0000 -0.7071 0.7071
+vn 0.0000 0.0000 1.0000
+vn -0.0000 0.7071 -0.7071
+vn -0.0000 0.0000 -1.0000
+vn -0.0000 -0.7071 -0.7071
+vn -0.0000 1.0000 -0.0000
+g Top_Cube.002_None_Top_Cube.002_None_bottom_Top_Cube.002_None_Top_Cube.002_None_bottom_Top
+s off
+f 6/1/1 5/2/1 2/3/1 1/4/1
+g Top_Cube.002_None_Top_Cube.002_None_bottom_Top_Cube.002_None_Top_Cube.002_None_bottom_Bottom
+f 11/5/2 10/6/2 14/7/2 13/8/2
+g Top_Cube.002_None_Top_Cube.002_None_bottom_Top_Cube.002_None_Top_Cube.002_None_bottom_Right-Left
+f 1/9/3 2/10/3 3/11/3 4/12/3
+f 5/13/4 6/1/4 7/14/4 8/15/4
+f 4/12/3 9/16/3 10/17/3 11/18/3
+f 12/19/4 7/14/4 13/8/4 14/20/4
+g Top_Cube.002_None_Top_Cube.002_None_bottom_Top_Cube.002_None_Top_Cube.002_None_bottom_Back
+f 6/21/5 1/9/5 4/12/5 7/22/5
+f 7/22/6 4/12/6 11/18/6 13/23/6
+g Top_Cube.002_None_Top_Cube.002_None_bottom_Top_Cube.002_None_Top_Cube.002_None_bottom_Front
+f 2/10/7 5/24/7 8/25/7 3/11/7
+f 9/16/8 12/26/8 14/27/8 10/17/8
+g Top_Cube.002_None_Top_Cube.002_None_bottom_Top_Cube.002_None_Top_Cube.002_None_bottom_Inside
+f 4/28/9 3/29/9 8/30/9 7/31/9
+f 7/31/10 12/32/10 9/33/10 4/28/10
diff --git a/games/devtest/mods/testformspec/textures/default_chest_front.png b/games/devtest/mods/testformspec/textures/default_chest_front.png
new file mode 100644 (file)
index 0000000..85227d8
Binary files /dev/null and b/games/devtest/mods/testformspec/textures/default_chest_front.png differ
diff --git a/games/devtest/mods/testformspec/textures/default_chest_inside.png b/games/devtest/mods/testformspec/textures/default_chest_inside.png
new file mode 100644 (file)
index 0000000..5f7b6b1
Binary files /dev/null and b/games/devtest/mods/testformspec/textures/default_chest_inside.png differ
diff --git a/games/devtest/mods/testformspec/textures/default_chest_side.png b/games/devtest/mods/testformspec/textures/default_chest_side.png
new file mode 100644 (file)
index 0000000..44a65a4
Binary files /dev/null and b/games/devtest/mods/testformspec/textures/default_chest_side.png differ
diff --git a/games/devtest/mods/testformspec/textures/default_chest_top.png b/games/devtest/mods/testformspec/textures/default_chest_top.png
new file mode 100644 (file)
index 0000000..f4a92ee
Binary files /dev/null and b/games/devtest/mods/testformspec/textures/default_chest_top.png differ
diff --git a/games/devtest/mods/testformspec/textures/testformspec_character.png b/games/devtest/mods/testformspec/textures/testformspec_character.png
new file mode 100644 (file)
index 0000000..0502178
Binary files /dev/null and b/games/devtest/mods/testformspec/textures/testformspec_character.png differ
index 147f445f4dcbc418216974be6f686d9872a7bd9a..5305e7ad3a9220371d3c7df3a0f9d46986a9e28a 100644 (file)
@@ -15,6 +15,7 @@ set(gui_SRCS
        ${CMAKE_CURRENT_SOURCE_DIR}/guiKeyChangeMenu.cpp
        ${CMAKE_CURRENT_SOURCE_DIR}/guiPasswordChange.cpp
        ${CMAKE_CURRENT_SOURCE_DIR}/guiPathSelectMenu.cpp
+       ${CMAKE_CURRENT_SOURCE_DIR}/guiScene.cpp
        ${CMAKE_CURRENT_SOURCE_DIR}/guiScrollBar.cpp
        ${CMAKE_CURRENT_SOURCE_DIR}/guiScrollContainer.cpp
        ${CMAKE_CURRENT_SOURCE_DIR}/guiSkin.cpp
index 7e3ad3b15119cfe23a4acbf7b8ec8dce2bc34441..039b28e7935110e7244d2d8bacfb5e93b692b882 100644 (file)
@@ -65,6 +65,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "guiScrollContainer.h"
 #include "intlGUIEditBox.h"
 #include "guiHyperText.h"
+#include "guiScene.h"
 
 #define MY_CHECKPOS(a,b)                                                                                                       \
        if (v_pos.size() != 2) {                                                                                                \
@@ -2695,6 +2696,86 @@ void GUIFormSpecMenu::parseSetFocus(const std::string &element)
                << "'" << std::endl;
 }
 
+void GUIFormSpecMenu::parseModel(parserData *data, const std::string &element)
+{
+       std::vector<std::string> parts = split(element, ';');
+
+       if (parts.size() < 5 || (parts.size() > 8 &&
+                       m_formspec_version <= FORMSPEC_API_VERSION)) {
+               errorstream << "Invalid model element (" << parts.size() << "): '" << element
+                       << "'" << std::endl;
+               return;
+       }
+
+       // Avoid length checks by resizing
+       if (parts.size() < 8)
+               parts.resize(8);
+
+       std::vector<std::string> v_pos = split(parts[0], ',');
+       std::vector<std::string> v_geom = split(parts[1], ',');
+       std::string name = unescape_string(parts[2]);
+       std::string meshstr = unescape_string(parts[3]);
+       std::vector<std::string> textures = split(parts[4], ',');
+       std::vector<std::string> vec_rot = split(parts[5], ',');
+       bool inf_rotation = is_yes(parts[6]);
+       bool mousectrl = is_yes(parts[7]) || parts[7].empty(); // default true
+
+       MY_CHECKPOS("model", 0);
+       MY_CHECKGEOM("model", 1);
+
+       v2s32 pos;
+       v2s32 geom;
+
+       if (data->real_coordinates) {
+               pos = getRealCoordinateBasePos(v_pos);
+               geom = getRealCoordinateGeometry(v_geom);
+       } else {
+               pos = getElementBasePos(&v_pos);
+               geom.X = stof(v_geom[0]) * (float)imgsize.X;
+               geom.Y = stof(v_geom[1]) * (float)imgsize.Y;
+       }
+
+       if (!data->explicit_size)
+               warningstream << "invalid use of model without a size[] element" << std::endl;
+
+       scene::IAnimatedMesh *mesh = m_client->getMesh(meshstr);
+
+       if (!mesh) {
+               errorstream << "Invalid model element: Unable to load mesh:"
+                               << std::endl << "\t" << meshstr << std::endl;
+               return;
+       }
+
+       FieldSpec spec(
+               name,
+               L"",
+               L"",
+               258 + m_fields.size()
+       );
+
+       core::rect<s32> rect(pos, pos + geom);
+
+       GUIScene *e = new GUIScene(Environment, RenderingEngine::get_scene_manager(),
+                       data->current_parent, rect, spec.fid);
+
+       auto meshnode = e->setMesh(mesh);
+
+       for (u32 i = 0; i < textures.size() && i < meshnode->getMaterialCount(); ++i)
+               e->setTexture(i, m_tsrc->getTexture(textures[i]));
+
+       if (vec_rot.size() >= 2)
+               e->setRotation(v2f(stof(vec_rot[0]), stof(vec_rot[1])));
+
+       e->enableContinuousRotation(inf_rotation);
+       e->enableMouseControl(mousectrl);
+
+       auto style = getStyleForElement("model", spec.fname);
+       e->setStyles(style);
+       e->drop();
+
+       m_fields.push_back(spec);
+}
+
 void GUIFormSpecMenu::parseElement(parserData* data, const std::string &element)
 {
        //some prechecks
@@ -2891,6 +2972,11 @@ void GUIFormSpecMenu::parseElement(parserData* data, const std::string &element)
                return;
        }
 
+       if (type == "model") {
+               parseModel(data, description);
+               return;
+       }
+
        // Ignore others
        infostream << "Unknown DrawSpec: type=" << type << ", data=\"" << description << "\""
                        << std::endl;
index 613acaa043fb926656ae2555bbb19afa35e29485..c5d662a69b53e382465cbc64a84f56a8225fa813 100644 (file)
@@ -38,7 +38,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 class InventoryManager;
 class ISimpleTextureSource;
 class Client;
-class TexturePool;
 class GUIScrollContainer;
 
 typedef enum {
@@ -444,6 +443,7 @@ class GUIFormSpecMenu : public GUIModalMenu
        void parseAnchor(parserData *data, const std::string &element);
        bool parseStyle(parserData *data, const std::string &element, bool style_type);
        void parseSetFocus(const std::string &element);
+       void parseModel(parserData *data, const std::string &element);
 
        void tryClose();
 
diff --git a/src/gui/guiScene.cpp b/src/gui/guiScene.cpp
new file mode 100644 (file)
index 0000000..08f119e
--- /dev/null
@@ -0,0 +1,257 @@
+/*
+Minetest
+Copyright (C) 2020 Jean-Patrick Guerrero <jeanpatrick.guerrero@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "guiScene.h"
+
+#include <SViewFrustum.h>
+#include <IAnimatedMeshSceneNode.h>
+#include <ILightSceneNode.h>
+#include "porting.h"
+
+GUIScene::GUIScene(gui::IGUIEnvironment *env, scene::ISceneManager *smgr,
+                  gui::IGUIElement *parent, core::recti rect, s32 id)
+       : IGUIElement(gui::EGUIET_ELEMENT, env, parent, id, rect)
+{
+       m_driver = env->getVideoDriver();
+       m_smgr = smgr->createNewSceneManager(false);
+
+       m_cam = m_smgr->addCameraSceneNode(0, v3f(0.f, 0.f, -100.f), v3f(0.f));
+       m_cam->setFOV(30.f * core::DEGTORAD);
+
+       scene::ILightSceneNode *light = m_smgr->addLightSceneNode(m_cam);
+       light->setRadius(1000.f);
+
+       m_smgr->getParameters()->setAttribute(scene::ALLOW_ZWRITE_ON_TRANSPARENT, true);
+}
+
+GUIScene::~GUIScene()
+{
+       setMesh(nullptr);
+
+       m_smgr->drop();
+}
+
+scene::IAnimatedMeshSceneNode *GUIScene::setMesh(scene::IAnimatedMesh *mesh)
+{
+       if (m_mesh) {
+               m_mesh->remove();
+               m_mesh = nullptr;
+       }
+
+       if (!mesh)
+               return nullptr;
+
+       m_mesh = m_smgr->addAnimatedMeshSceneNode(mesh);
+       m_mesh->setPosition(-m_mesh->getBoundingBox().getCenter());
+       m_mesh->animateJoints();
+       return m_mesh;
+}
+
+void GUIScene::setTexture(u32 idx, video::ITexture *texture)
+{
+       video::SMaterial &material = m_mesh->getMaterial(idx);
+       material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
+       material.MaterialTypeParam = 0.5f;
+       material.TextureLayer[0].Texture = texture;
+       material.setFlag(video::EMF_LIGHTING, false);
+       material.setFlag(video::EMF_FOG_ENABLE, true);
+       material.setFlag(video::EMF_BILINEAR_FILTER, false);
+       material.setFlag(video::EMF_BACK_FACE_CULLING, false);
+}
+
+void GUIScene::draw()
+{
+       // Control rotation speed based on time
+       u64 new_time = porting::getTimeMs();
+       u64 dtime_ms = 0;
+       if (m_last_time != 0)
+               dtime_ms = porting::getDeltaMs(m_last_time, new_time);
+       m_last_time = new_time;
+
+       core::rect<s32> oldViewPort = m_driver->getViewPort();
+       m_driver->setViewPort(getAbsoluteClippingRect());
+       core::recti borderRect = Environment->getRootGUIElement()->getAbsoluteClippingRect();
+
+       if (m_bgcolor != 0) {
+               Environment->getSkin()->draw3DSunkenPane(
+                       this, m_bgcolor, false, true, borderRect, 0);
+       }
+
+       core::dimension2d<s32> size = getAbsoluteClippingRect().getSize();
+       m_smgr->getActiveCamera()->setAspectRatio((f32)size.Width / (f32)size.Height);
+
+       if (!m_target) {
+               updateCamera(m_smgr->addEmptySceneNode());
+               rotateCamera(v3f(0.f));
+               m_cam->bindTargetAndRotation(true);
+       }
+
+       cameraLoop();
+
+       // Continuous rotation
+       if (m_inf_rot)
+               rotateCamera(v3f(0.f, -0.03f * (float)dtime_ms, 0.f));
+
+       m_smgr->drawAll();
+
+       if (m_initial_rotation && m_mesh) {
+               rotateCamera(v3f(m_custom_rot.X, m_custom_rot.Y, 0.f));
+               calcOptimalDistance();
+
+               m_initial_rotation = false;
+       }
+
+       m_driver->setViewPort(oldViewPort);
+}
+
+bool GUIScene::OnEvent(const SEvent &event)
+{
+       if (m_mouse_ctrl && event.EventType == EET_MOUSE_INPUT_EVENT) {
+               if (event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN) {
+                       m_last_pos = v2f((f32)event.MouseInput.X, (f32)event.MouseInput.Y);
+                       return true;
+               } else if (event.MouseInput.Event == EMIE_MOUSE_MOVED) {
+                       if (event.MouseInput.isLeftPressed()) {
+                               m_curr_pos = v2f((f32)event.MouseInput.X, (f32)event.MouseInput.Y);
+
+                               rotateCamera(v3f(
+                                       m_last_pos.Y - m_curr_pos.Y,
+                                       m_curr_pos.X - m_last_pos.X, 0.f));
+
+                               m_last_pos = m_curr_pos;
+                               return true;
+                       }
+               }
+       }
+
+       return gui::IGUIElement::OnEvent(event);
+}
+
+void GUIScene::setStyles(const std::array<StyleSpec, StyleSpec::NUM_STATES> &styles)
+{
+       StyleSpec::State state = StyleSpec::STATE_DEFAULT;
+       StyleSpec style = StyleSpec::getStyleFromStatePropagation(styles, state);
+
+       setNotClipped(style.getBool(StyleSpec::NOCLIP, false));
+       setBackgroundColor(style.getColor(StyleSpec::BGCOLOR, m_bgcolor));
+}
+
+/* Camera control functions */
+
+inline void GUIScene::calcOptimalDistance()
+{
+       core::aabbox3df box = m_mesh->getBoundingBox();
+       f32 width  = box.MaxEdge.X - box.MinEdge.X;
+       f32 height = box.MaxEdge.Y - box.MinEdge.Y;
+       f32 depth  = box.MaxEdge.Z - box.MinEdge.Z;
+       f32 max_width = width > depth ? width : depth;
+
+       const scene::SViewFrustum *f = m_cam->getViewFrustum();
+       f32 cam_far = m_cam->getFarValue();
+       f32 far_width = core::line3df(f->getFarLeftUp(), f->getFarRightUp()).getLength();
+       f32 far_height = core::line3df(f->getFarLeftUp(), f->getFarLeftDown()).getLength();
+
+       core::recti rect = getAbsolutePosition();
+       f32 zoomX = rect.getWidth() / max_width;
+       f32 zoomY = rect.getHeight() / height;
+       f32 dist;
+
+       if (zoomX < zoomY)
+               dist = (max_width / (far_width / cam_far)) + (0.5f * max_width);
+       else
+               dist = (height / (far_height / cam_far)) + (0.5f * max_width);
+
+       m_cam_distance = dist;
+       m_update_cam = true;
+}
+
+void GUIScene::updateCamera(scene::ISceneNode *target)
+{
+       m_target = target;
+       updateTargetPos();
+
+       m_last_target_pos = m_target_pos;
+       updateCameraPos();
+
+       m_update_cam = true;
+}
+
+void GUIScene::updateTargetPos()
+{
+       m_last_target_pos = m_target_pos;
+       m_target->updateAbsolutePosition();
+       m_target_pos = m_target->getAbsolutePosition();
+}
+
+void GUIScene::setCameraRotation(v3f rot)
+{
+       correctBounds(rot);
+
+       core::matrix4 mat;
+       mat.setRotationDegrees(rot);
+
+       m_cam_pos = v3f(0.f, 0.f, m_cam_distance);
+       mat.rotateVect(m_cam_pos);
+
+       m_cam_pos += m_target_pos;
+       m_cam->setPosition(m_cam_pos);
+       m_update_cam = false;
+}
+
+bool GUIScene::correctBounds(v3f &rot)
+{
+       const float ROTATION_MAX_1 = 60.0f;
+       const float ROTATION_MAX_2 = 300.0f;
+
+       // Limit and correct the rotation when needed
+       if (rot.X < 90.f) {
+               if (rot.X > ROTATION_MAX_1) {
+                       rot.X = ROTATION_MAX_1;
+                       return true;
+               }
+       } else if (rot.X < ROTATION_MAX_2) {
+               rot.X = ROTATION_MAX_2;
+               return true;
+       }
+
+       // Not modified
+       return false;
+}
+
+void GUIScene::cameraLoop()
+{
+       updateCameraPos();
+       updateTargetPos();
+
+       if (m_target_pos != m_last_target_pos)
+               m_update_cam = true;
+
+       if (m_update_cam) {
+               m_cam_pos = m_target_pos + (m_cam_pos - m_target_pos).normalize() * m_cam_distance;
+
+               v3f rot = getCameraRotation();
+               if (correctBounds(rot))
+                       setCameraRotation(rot);
+
+               m_cam->setPosition(m_cam_pos);
+               m_cam->setTarget(m_target_pos);
+
+               m_update_cam = false;
+       }
+}
diff --git a/src/gui/guiScene.h b/src/gui/guiScene.h
new file mode 100644 (file)
index 0000000..707e6f6
--- /dev/null
@@ -0,0 +1,85 @@
+/*
+Minetest
+Copyright (C) 2020 Jean-Patrick Guerrero <jeanpatrick.guerrero@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#pragma once
+
+#include "irrlichttypes_extrabloated.h"
+#include "ICameraSceneNode.h"
+#include "StyleSpec.h"
+
+using namespace irr;
+
+class GUIScene : public gui::IGUIElement
+{
+public:
+       GUIScene(gui::IGUIEnvironment *env, scene::ISceneManager *smgr,
+                gui::IGUIElement *parent, core::recti rect, s32 id = -1);
+
+       ~GUIScene();
+
+       scene::IAnimatedMeshSceneNode *setMesh(scene::IAnimatedMesh *mesh = nullptr);
+       void setTexture(u32 idx, video::ITexture *texture);
+       void setBackgroundColor(const video::SColor &color) noexcept { m_bgcolor = color; };
+       void enableMouseControl(bool enable) noexcept { m_mouse_ctrl = enable; };
+       void setRotation(v2f rot) noexcept { m_custom_rot = rot; };
+       void enableContinuousRotation(bool enable) noexcept { m_inf_rot = enable; };
+       void setStyles(const std::array<StyleSpec, StyleSpec::NUM_STATES> &styles);
+
+       virtual void draw();
+       virtual bool OnEvent(const SEvent &event);
+
+private:
+       void calcOptimalDistance();
+       void updateTargetPos();
+       void updateCamera(scene::ISceneNode *target);
+       void setCameraRotation(v3f rot);
+       /// @return true indicates that the rotation was corrected
+       bool correctBounds(v3f &rot);
+       void cameraLoop();
+
+       void updateCameraPos() { m_cam_pos = m_cam->getPosition(); };
+       v3f getCameraRotation() const { return (m_cam_pos - m_target_pos).getHorizontalAngle(); };
+       void rotateCamera(const v3f &delta) { setCameraRotation(getCameraRotation() + delta); };
+
+       scene::ISceneManager *m_smgr;
+       video::IVideoDriver *m_driver;
+       scene::ICameraSceneNode *m_cam;
+       scene::ISceneNode *m_target = nullptr;
+       scene::IAnimatedMeshSceneNode *m_mesh = nullptr;
+
+       f32 m_cam_distance = 50.f;
+
+       u64 m_last_time = 0;
+
+       v3f m_cam_pos;
+       v3f m_target_pos;
+       v3f m_last_target_pos;
+       // Cursor positions
+       v2f m_curr_pos;
+       v2f m_last_pos;
+       // Initial rotation
+       v2f m_custom_rot;
+
+       bool m_mouse_ctrl = true;
+       bool m_update_cam = false;
+       bool m_inf_rot    = false;
+       bool m_initial_rotation = true;
+
+       video::SColor m_bgcolor = 0;
+};
index 3334257ae8729acc67dfef5285e48c8f1d30e17a..75d99f4cde2504a508a2c1fa8a706ea0990d84bb 100644 (file)
@@ -183,6 +183,8 @@ src/gui/guiMainMenu.h
 src/gui/guiPasswordChange.cpp
 src/gui/guiPathSelectMenu.cpp
 src/gui/guiPathSelectMenu.h
+src/gui/guiScene.cpp
+src/gui/guiScene.h
 src/gui/guiScrollBar.cpp
 src/gui/guiSkin.cpp
 src/gui/guiSkin.h