Add model[] formspec element (#10320)

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>
This commit is contained in:
SmallJoker 2020-11-04 21:46:18 +01:00 committed by GitHub
parent e3bd6704a0
commit 3356da0151
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 545 additions and 1 deletions

@ -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

@ -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

@ -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

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 423 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 375 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 423 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

@ -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

@ -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;

@ -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 @@ private:
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();

257
src/gui/guiScene.cpp Normal file

@ -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
src/gui/guiScene.h Normal file

@ -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;
};

@ -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