257 changes: 257 additions & 0 deletions src/gui/guiScene.cpp
Original file line number Diff line number Diff line change
@@ -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;
}
}
85 changes: 85 additions & 0 deletions src/gui/guiScene.h
Original file line number Diff line number Diff line change
@@ -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;
};
2 changes: 2 additions & 0 deletions util/ci/clang-format-whitelist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down