]> git.lizzy.rs Git - minetest.git/blob - src/wieldmesh.cpp
Implement WieldMeshSceneNode which improves wield mesh rendering
[minetest.git] / src / wieldmesh.cpp
1 /*
2 Minetest
3 Copyright (C) 2010-2014 celeron55, Perttu Ahola <celeron55@gmail.com>
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as published by
7 the Free Software Foundation; either version 2.1 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU Lesser General Public License for more details.
14
15 You should have received a copy of the GNU Lesser General Public License along
16 with this program; if not, write to the Free Software Foundation, Inc.,
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20 #include "wieldmesh.h"
21 #include "inventory.h"
22 #include "gamedef.h"
23 #include "itemdef.h"
24 #include "nodedef.h"
25 #include "mesh.h"
26 #include "tile.h"
27 #include "log.h"
28 #include "util/numeric.h"
29 #include <map>
30 #include <IMeshManipulator.h>
31
32 #define WIELD_SCALE_FACTOR 30.0
33 #define WIELD_SCALE_FACTOR_EXTRUDED 40.0
34
35 #define MIN_EXTRUSION_MESH_RESOLUTION 32   // not 16: causes too many "holes"
36 #define MAX_EXTRUSION_MESH_RESOLUTION 512
37
38 static scene::IMesh* createExtrusionMesh(int resolution_x, int resolution_y)
39 {
40         const f32 r = 0.5;
41
42         scene::IMeshBuffer *buf = new scene::SMeshBuffer();
43         video::SColor c(255,255,255,255);
44         v3f scale(1.0, 1.0, 0.1);
45
46         // Front and back
47         {
48                 video::S3DVertex vertices[8] = {
49                         // z-
50                         video::S3DVertex(-r,+r,-r, 0,0,-1, c, 0,0),
51                         video::S3DVertex(+r,+r,-r, 0,0,-1, c, 1,0),
52                         video::S3DVertex(+r,-r,-r, 0,0,-1, c, 1,1),
53                         video::S3DVertex(-r,-r,-r, 0,0,-1, c, 0,1),
54                         // z+
55                         video::S3DVertex(-r,+r,+r, 0,0,+1, c, 0,0),
56                         video::S3DVertex(-r,-r,+r, 0,0,+1, c, 0,1),
57                         video::S3DVertex(+r,-r,+r, 0,0,+1, c, 1,1),
58                         video::S3DVertex(+r,+r,+r, 0,0,+1, c, 1,0),
59                 };
60                 u16 indices[12] = {0,1,2,2,3,0,4,5,6,6,7,4};
61                 buf->append(vertices, 8, indices, 12);
62         }
63
64         f32 pixelsize_x = 1 / (f32) resolution_x;
65         f32 pixelsize_y = 1 / (f32) resolution_y;
66
67         for (int i = 0; i < resolution_x; ++i) {
68                 f32 pixelpos_x = i * pixelsize_x - 0.5;
69                 f32 x0 = pixelpos_x;
70                 f32 x1 = pixelpos_x + pixelsize_x;
71                 f32 tex0 = (i + 0.1) * pixelsize_x;
72                 f32 tex1 = (i + 0.9) * pixelsize_x;
73                 video::S3DVertex vertices[8] = {
74                         // x-
75                         video::S3DVertex(x0,-r,-r, -1,0,0, c, tex0,1),
76                         video::S3DVertex(x0,-r,+r, -1,0,0, c, tex1,1),
77                         video::S3DVertex(x0,+r,+r, -1,0,0, c, tex1,0),
78                         video::S3DVertex(x0,+r,-r, -1,0,0, c, tex0,0),
79                         // x+
80                         video::S3DVertex(x1,-r,-r, +1,0,0, c, tex0,1),
81                         video::S3DVertex(x1,+r,-r, +1,0,0, c, tex0,0),
82                         video::S3DVertex(x1,+r,+r, +1,0,0, c, tex1,0),
83                         video::S3DVertex(x1,-r,+r, +1,0,0, c, tex1,1),
84                 };
85                 u16 indices[12] = {0,1,2,2,3,0,4,5,6,6,7,4};
86                 buf->append(vertices, 8, indices, 12);
87         }
88         for (int i = 0; i < resolution_y; ++i) {
89                 f32 pixelpos_y = i * pixelsize_y - 0.5;
90                 f32 y0 = -pixelpos_y - pixelsize_y;
91                 f32 y1 = -pixelpos_y;
92                 f32 tex0 = (i + 0.1) * pixelsize_y;
93                 f32 tex1 = (i + 0.9) * pixelsize_y;
94                 video::S3DVertex vertices[8] = {
95                         // y-
96                         video::S3DVertex(-r,y0,-r, 0,-1,0, c, 0,tex0),
97                         video::S3DVertex(+r,y0,-r, 0,-1,0, c, 1,tex0),
98                         video::S3DVertex(+r,y0,+r, 0,-1,0, c, 1,tex1),
99                         video::S3DVertex(-r,y0,+r, 0,-1,0, c, 0,tex1),
100                         // y+
101                         video::S3DVertex(-r,y1,-r, 0,+1,0, c, 0,tex0),
102                         video::S3DVertex(-r,y1,+r, 0,+1,0, c, 0,tex1),
103                         video::S3DVertex(+r,y1,+r, 0,+1,0, c, 1,tex1),
104                         video::S3DVertex(+r,y1,-r, 0,+1,0, c, 1,tex0),
105                 };
106                 u16 indices[12] = {0,1,2,2,3,0,4,5,6,6,7,4};
107                 buf->append(vertices, 8, indices, 12);
108         }
109
110         // Define default material
111         video::SMaterial *material = &buf->getMaterial();
112         material->MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF;
113         material->BackfaceCulling = true;
114         material->setFlag(video::EMF_LIGHTING, false);
115         material->setFlag(video::EMF_BILINEAR_FILTER, false);
116         material->setFlag(video::EMF_TRILINEAR_FILTER, false);
117         // anisotropic filtering removes "thin black line" artifacts
118         material->setFlag(video::EMF_ANISOTROPIC_FILTER, true);
119         material->setFlag(video::EMF_TEXTURE_WRAP, false);
120
121         // Create mesh object
122         scene::SMesh *mesh = new scene::SMesh();
123         mesh->addMeshBuffer(buf);
124         buf->drop();
125         scaleMesh(mesh, scale);  // also recalculates bounding box
126         return mesh;
127 }
128
129 /*
130         Caches extrusion meshes so that only one of them per resolution
131         is needed. Also caches one cube (for convenience).
132
133         E.g. there is a single extrusion mesh that is used for all
134         16x16 px images, another for all 256x256 px images, and so on.
135
136         WARNING: Not thread safe. This should not be a problem since
137         rendering related classes (such as WieldMeshSceneNode) will be
138         used from the rendering thread only.
139 */
140 class ExtrusionMeshCache: public IReferenceCounted
141 {
142 public:
143         // Constructor
144         ExtrusionMeshCache()
145         {
146                 for (int resolution = MIN_EXTRUSION_MESH_RESOLUTION;
147                                 resolution <= MAX_EXTRUSION_MESH_RESOLUTION;
148                                 resolution *= 2) {
149                         m_extrusion_meshes[resolution] =
150                                 createExtrusionMesh(resolution, resolution);
151                 }
152                 m_cube = createCubeMesh(v3f(1.0, 1.0, 1.0));
153         }
154         // Destructor
155         virtual ~ExtrusionMeshCache()
156         {
157                 for (std::map<int, scene::IMesh*>::iterator
158                                 it = m_extrusion_meshes.begin();
159                                 it != m_extrusion_meshes.end(); ++it) {
160                         it->second->drop();
161                 }
162                 m_cube->drop();
163         }
164         // Get closest extrusion mesh for given image dimensions
165         // Caller must drop the returned pointer
166         scene::IMesh* create(core::dimension2d<u32> dim)
167         {
168                 // handle non-power of two textures inefficiently without cache
169                 if (!is_power_of_two(dim.Width) || !is_power_of_two(dim.Height)) {
170                         return createExtrusionMesh(dim.Width, dim.Height);
171                 }
172
173                 int maxdim = MYMAX(dim.Width, dim.Height);
174
175                 std::map<int, scene::IMesh*>::iterator
176                         it = m_extrusion_meshes.lower_bound(maxdim);
177
178                 if (it == m_extrusion_meshes.end()) {
179                         // no viable resolution found; use largest one
180                         it = m_extrusion_meshes.find(MAX_EXTRUSION_MESH_RESOLUTION);
181                         assert(it != m_extrusion_meshes.end());
182                 }
183
184                 scene::IMesh *mesh = it->second;
185                 mesh->grab();
186                 return mesh;
187         }
188         // Returns a 1x1x1 cube mesh with one meshbuffer (material) per face
189         // Caller must drop the returned pointer
190         scene::IMesh* createCube()
191         {
192                 m_cube->grab();
193                 return m_cube;
194         }
195
196 private:
197         std::map<int, scene::IMesh*> m_extrusion_meshes;
198         scene::IMesh *m_cube;
199 };
200
201 ExtrusionMeshCache *g_extrusion_mesh_cache = NULL;
202
203
204 WieldMeshSceneNode::WieldMeshSceneNode(
205                 scene::ISceneNode *parent,
206                 scene::ISceneManager *mgr,
207                 s32 id,
208                 bool lighting
209 ):
210         scene::ISceneNode(parent, mgr, id),
211         m_meshnode(NULL),
212         m_lighting(lighting),
213         m_bounding_box(0.0, 0.0, 0.0, 0.0, 0.0, 0.0)
214 {
215         // If this is the first wield mesh scene node, create a cache
216         // for extrusion meshes (and a cube mesh), otherwise reuse it
217         if (g_extrusion_mesh_cache == NULL)
218                 g_extrusion_mesh_cache = new ExtrusionMeshCache();
219         else
220                 g_extrusion_mesh_cache->grab();
221
222         // Disable bounding box culling for this scene node
223         // since we won't calculate the bounding box.
224         setAutomaticCulling(scene::EAC_OFF);
225
226         // Create the child scene node
227         scene::IMesh *dummymesh = g_extrusion_mesh_cache->createCube();
228         m_meshnode = SceneManager->addMeshSceneNode(dummymesh, this, -1);
229         m_meshnode->setReadOnlyMaterials(false);
230         m_meshnode->setVisible(false);
231         dummymesh->drop(); // m_meshnode grabbed it
232 }
233
234 WieldMeshSceneNode::~WieldMeshSceneNode()
235 {
236         assert(g_extrusion_mesh_cache);
237         if (g_extrusion_mesh_cache->drop())
238                 g_extrusion_mesh_cache = NULL;
239 }
240
241 void WieldMeshSceneNode::setCube(const TileSpec tiles[6],
242                         v3f wield_scale, ITextureSource *tsrc)
243 {
244         scene::IMesh *cubemesh = g_extrusion_mesh_cache->createCube();
245         changeToMesh(cubemesh);
246         cubemesh->drop();
247
248         m_meshnode->setScale(wield_scale * WIELD_SCALE_FACTOR);
249
250         // Customize materials
251         for (u32 i = 0; i < m_meshnode->getMaterialCount(); ++i) {
252                 assert(i < 6);
253                 video::SMaterial &material = m_meshnode->getMaterial(i);
254                 material.setTexture(0, tiles[i].texture);
255                 tiles[i].applyMaterialOptions(material);
256         }
257 }
258
259 void WieldMeshSceneNode::setExtruded(const std::string &imagename,
260                 v3f wield_scale, ITextureSource *tsrc)
261 {
262         video::ITexture *texture = tsrc->getTexture(imagename);
263         if (!texture) {
264                 changeToMesh(NULL);
265                 return;
266         }
267
268         scene::IMesh *mesh = g_extrusion_mesh_cache->create(texture->getSize());
269         changeToMesh(mesh);
270         mesh->drop();
271
272         m_meshnode->setScale(wield_scale * WIELD_SCALE_FACTOR_EXTRUDED);
273
274         // Customize material
275         assert(m_meshnode->getMaterialCount() == 1);
276         video::SMaterial &material = m_meshnode->getMaterial(0);
277         material.setTexture(0, texture);
278 }
279
280 void WieldMeshSceneNode::setItem(const ItemStack &item, IGameDef *gamedef)
281 {
282         ITextureSource *tsrc = gamedef->getTextureSource();
283         IItemDefManager *idef = gamedef->getItemDefManager();
284
285         const ItemDefinition &def = item.getDefinition(idef);
286
287         // If wield_image is defined, it overrides everything else
288         if (def.wield_image != "") {
289                 setExtruded(def.wield_image, def.wield_scale, tsrc);
290                 return;
291         }
292
293         // Handle nodes
294         // See also CItemDefManager::createClientCached()
295         if (def.type == ITEM_NODE) {
296                 INodeDefManager *ndef = gamedef->getNodeDefManager();
297                 const ContentFeatures &f = ndef->get(def.name);
298                 if (f.mesh_ptr[0]) {
299                         // e.g. mesh nodes and nodeboxes
300                         changeToMesh(f.mesh_ptr[0]);
301                         // mesh_ptr[0] is pre-scaled by BS * f->visual_scale
302                         m_meshnode->setScale(
303                                         def.wield_scale * WIELD_SCALE_FACTOR
304                                         / (BS * f.visual_scale));
305                         // Customize materials
306                         for (u32 i = 0; i < m_meshnode->getMaterialCount(); ++i) {
307                                 assert(i < 6);
308                                 video::SMaterial &material = m_meshnode->getMaterial(i);
309                                 material.setTexture(0, f.tiles[i].texture);
310                                 f.tiles[i].applyMaterialOptions(material);
311                         }
312                         return;
313                 } else if (f.drawtype == NDT_NORMAL || f.drawtype == NDT_ALLFACES) {
314                         setCube(f.tiles, def.wield_scale, tsrc);
315                         return;
316                 } else if (f.drawtype == NDT_AIRLIKE) {
317                         changeToMesh(NULL);
318                         return;
319                 }
320
321                 // If none of the above standard cases worked, use the wield mesh from ClientCached
322                 scene::IMesh *mesh = idef->getWieldMesh(item.name, gamedef);
323                 if (mesh) {
324                         changeToMesh(mesh);
325                         m_meshnode->setScale(def.wield_scale * WIELD_SCALE_FACTOR);
326                         return;
327                 }
328         }
329
330         // default to inventory_image
331         if (def.inventory_image != "") {
332                 setExtruded(def.inventory_image, def.wield_scale, tsrc);
333                 return;
334         }
335
336         // no wield mesh found
337         changeToMesh(NULL);
338 }
339
340 void WieldMeshSceneNode::setColor(video::SColor color)
341 {
342         assert(!m_lighting);
343         setMeshColor(m_meshnode->getMesh(), color);
344 }
345
346 void WieldMeshSceneNode::render()
347 {
348         // note: if this method is changed to actually do something,
349         // you probably should implement OnRegisterSceneNode as well
350 }
351
352 void WieldMeshSceneNode::changeToMesh(scene::IMesh *mesh)
353 {
354         if (mesh == NULL) {
355                 scene::IMesh *dummymesh = g_extrusion_mesh_cache->createCube();
356                 m_meshnode->setVisible(false);
357                 m_meshnode->setMesh(dummymesh);
358                 dummymesh->drop();  // m_meshnode grabbed it
359         }
360
361         if (m_lighting) {
362                 m_meshnode->setMesh(mesh);
363         } else {
364                 /*
365                         Lighting is disabled, this means the caller can (and probably will)
366                         call setColor later. We therefore need to clone the mesh so that
367                         setColor will only modify this scene node's mesh, not others'.
368                 */
369                 scene::IMeshManipulator *meshmanip = SceneManager->getMeshManipulator();
370                 scene::IMesh *new_mesh = meshmanip->createMeshCopy(mesh);
371                 m_meshnode->setMesh(new_mesh);
372                 new_mesh->drop();  // m_meshnode grabbed it
373         }
374
375         m_meshnode->setMaterialFlag(video::EMF_LIGHTING, m_lighting);
376         // need to normalize normals when lighting is enabled (because of setScale())
377         m_meshnode->setMaterialFlag(video::EMF_NORMALIZE_NORMALS, m_lighting);
378         m_meshnode->setVisible(true);
379
380 }