For usages of assert() that are meant to persist in Release builds (when NDEBUG is...
[oweals/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 "main.h"
21 #include "settings.h"
22 #include "wieldmesh.h"
23 #include "inventory.h"
24 #include "gamedef.h"
25 #include "itemdef.h"
26 #include "nodedef.h"
27 #include "mesh.h"
28 #include "mapblock_mesh.h"
29 #include "client/tile.h"
30 #include "log.h"
31 #include "util/numeric.h"
32 #include <map>
33 #include <IMeshManipulator.h>
34
35 #define WIELD_SCALE_FACTOR 30.0
36 #define WIELD_SCALE_FACTOR_EXTRUDED 40.0
37
38 #define MIN_EXTRUSION_MESH_RESOLUTION 32   // not 16: causes too many "holes"
39 #define MAX_EXTRUSION_MESH_RESOLUTION 512
40
41 static scene::IMesh* createExtrusionMesh(int resolution_x, int resolution_y)
42 {
43         const f32 r = 0.5;
44
45         scene::IMeshBuffer *buf = new scene::SMeshBuffer();
46         video::SColor c(255,255,255,255);
47         v3f scale(1.0, 1.0, 0.1);
48
49         // Front and back
50         {
51                 video::S3DVertex vertices[8] = {
52                         // z-
53                         video::S3DVertex(-r,+r,-r, 0,0,-1, c, 0,0),
54                         video::S3DVertex(+r,+r,-r, 0,0,-1, c, 1,0),
55                         video::S3DVertex(+r,-r,-r, 0,0,-1, c, 1,1),
56                         video::S3DVertex(-r,-r,-r, 0,0,-1, c, 0,1),
57                         // z+
58                         video::S3DVertex(-r,+r,+r, 0,0,+1, c, 0,0),
59                         video::S3DVertex(-r,-r,+r, 0,0,+1, c, 0,1),
60                         video::S3DVertex(+r,-r,+r, 0,0,+1, c, 1,1),
61                         video::S3DVertex(+r,+r,+r, 0,0,+1, c, 1,0),
62                 };
63                 u16 indices[12] = {0,1,2,2,3,0,4,5,6,6,7,4};
64                 buf->append(vertices, 8, indices, 12);
65         }
66
67         f32 pixelsize_x = 1 / (f32) resolution_x;
68         f32 pixelsize_y = 1 / (f32) resolution_y;
69
70         for (int i = 0; i < resolution_x; ++i) {
71                 f32 pixelpos_x = i * pixelsize_x - 0.5;
72                 f32 x0 = pixelpos_x;
73                 f32 x1 = pixelpos_x + pixelsize_x;
74                 f32 tex0 = (i + 0.1) * pixelsize_x;
75                 f32 tex1 = (i + 0.9) * pixelsize_x;
76                 video::S3DVertex vertices[8] = {
77                         // x-
78                         video::S3DVertex(x0,-r,-r, -1,0,0, c, tex0,1),
79                         video::S3DVertex(x0,-r,+r, -1,0,0, c, tex1,1),
80                         video::S3DVertex(x0,+r,+r, -1,0,0, c, tex1,0),
81                         video::S3DVertex(x0,+r,-r, -1,0,0, c, tex0,0),
82                         // x+
83                         video::S3DVertex(x1,-r,-r, +1,0,0, c, tex0,1),
84                         video::S3DVertex(x1,+r,-r, +1,0,0, c, tex0,0),
85                         video::S3DVertex(x1,+r,+r, +1,0,0, c, tex1,0),
86                         video::S3DVertex(x1,-r,+r, +1,0,0, c, tex1,1),
87                 };
88                 u16 indices[12] = {0,1,2,2,3,0,4,5,6,6,7,4};
89                 buf->append(vertices, 8, indices, 12);
90         }
91         for (int i = 0; i < resolution_y; ++i) {
92                 f32 pixelpos_y = i * pixelsize_y - 0.5;
93                 f32 y0 = -pixelpos_y - pixelsize_y;
94                 f32 y1 = -pixelpos_y;
95                 f32 tex0 = (i + 0.1) * pixelsize_y;
96                 f32 tex1 = (i + 0.9) * pixelsize_y;
97                 video::S3DVertex vertices[8] = {
98                         // y-
99                         video::S3DVertex(-r,y0,-r, 0,-1,0, c, 0,tex0),
100                         video::S3DVertex(+r,y0,-r, 0,-1,0, c, 1,tex0),
101                         video::S3DVertex(+r,y0,+r, 0,-1,0, c, 1,tex1),
102                         video::S3DVertex(-r,y0,+r, 0,-1,0, c, 0,tex1),
103                         // y+
104                         video::S3DVertex(-r,y1,-r, 0,+1,0, c, 0,tex0),
105                         video::S3DVertex(-r,y1,+r, 0,+1,0, c, 0,tex1),
106                         video::S3DVertex(+r,y1,+r, 0,+1,0, c, 1,tex1),
107                         video::S3DVertex(+r,y1,-r, 0,+1,0, c, 1,tex0),
108                 };
109                 u16 indices[12] = {0,1,2,2,3,0,4,5,6,6,7,4};
110                 buf->append(vertices, 8, indices, 12);
111         }
112
113         // Create mesh object
114         scene::SMesh *mesh = new scene::SMesh();
115         mesh->addMeshBuffer(buf);
116         buf->drop();
117         scaleMesh(mesh, scale);  // also recalculates bounding box
118         return mesh;
119 }
120
121 /*
122         Caches extrusion meshes so that only one of them per resolution
123         is needed. Also caches one cube (for convenience).
124
125         E.g. there is a single extrusion mesh that is used for all
126         16x16 px images, another for all 256x256 px images, and so on.
127
128         WARNING: Not thread safe. This should not be a problem since
129         rendering related classes (such as WieldMeshSceneNode) will be
130         used from the rendering thread only.
131 */
132 class ExtrusionMeshCache: public IReferenceCounted
133 {
134 public:
135         // Constructor
136         ExtrusionMeshCache()
137         {
138                 for (int resolution = MIN_EXTRUSION_MESH_RESOLUTION;
139                                 resolution <= MAX_EXTRUSION_MESH_RESOLUTION;
140                                 resolution *= 2) {
141                         m_extrusion_meshes[resolution] =
142                                 createExtrusionMesh(resolution, resolution);
143                 }
144                 m_cube = createCubeMesh(v3f(1.0, 1.0, 1.0));
145         }
146         // Destructor
147         virtual ~ExtrusionMeshCache()
148         {
149                 for (std::map<int, scene::IMesh*>::iterator
150                                 it = m_extrusion_meshes.begin();
151                                 it != m_extrusion_meshes.end(); ++it) {
152                         it->second->drop();
153                 }
154                 m_cube->drop();
155         }
156         // Get closest extrusion mesh for given image dimensions
157         // Caller must drop the returned pointer
158         scene::IMesh* create(core::dimension2d<u32> dim)
159         {
160                 // handle non-power of two textures inefficiently without cache
161                 if (!is_power_of_two(dim.Width) || !is_power_of_two(dim.Height)) {
162                         return createExtrusionMesh(dim.Width, dim.Height);
163                 }
164
165                 int maxdim = MYMAX(dim.Width, dim.Height);
166
167                 std::map<int, scene::IMesh*>::iterator
168                         it = m_extrusion_meshes.lower_bound(maxdim);
169
170                 if (it == m_extrusion_meshes.end()) {
171                         // no viable resolution found; use largest one
172                         it = m_extrusion_meshes.find(MAX_EXTRUSION_MESH_RESOLUTION);
173                         sanity_check(it != m_extrusion_meshes.end());
174                 }
175
176                 scene::IMesh *mesh = it->second;
177                 mesh->grab();
178                 return mesh;
179         }
180         // Returns a 1x1x1 cube mesh with one meshbuffer (material) per face
181         // Caller must drop the returned pointer
182         scene::IMesh* createCube()
183         {
184                 m_cube->grab();
185                 return m_cube;
186         }
187
188 private:
189         std::map<int, scene::IMesh*> m_extrusion_meshes;
190         scene::IMesh *m_cube;
191 };
192
193 ExtrusionMeshCache *g_extrusion_mesh_cache = NULL;
194
195
196 WieldMeshSceneNode::WieldMeshSceneNode(
197                 scene::ISceneNode *parent,
198                 scene::ISceneManager *mgr,
199                 s32 id,
200                 bool lighting
201 ):
202         scene::ISceneNode(parent, mgr, id),
203         m_meshnode(NULL),
204         m_material_type(video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF),
205         m_lighting(lighting),
206         m_bounding_box(0.0, 0.0, 0.0, 0.0, 0.0, 0.0)
207 {
208         m_enable_shaders = g_settings->getBool("enable_shaders");
209         m_anisotropic_filter = g_settings->getBool("anisotropic_filter");
210         m_bilinear_filter = g_settings->getBool("bilinear_filter");
211         m_trilinear_filter = g_settings->getBool("trilinear_filter");
212
213         // If this is the first wield mesh scene node, create a cache
214         // for extrusion meshes (and a cube mesh), otherwise reuse it
215         if (g_extrusion_mesh_cache == NULL)
216                 g_extrusion_mesh_cache = new ExtrusionMeshCache();
217         else
218                 g_extrusion_mesh_cache->grab();
219
220         // Disable bounding box culling for this scene node
221         // since we won't calculate the bounding box.
222         setAutomaticCulling(scene::EAC_OFF);
223
224         // Create the child scene node
225         scene::IMesh *dummymesh = g_extrusion_mesh_cache->createCube();
226         m_meshnode = SceneManager->addMeshSceneNode(dummymesh, this, -1);
227         m_meshnode->setReadOnlyMaterials(false);
228         m_meshnode->setVisible(false);
229         dummymesh->drop(); // m_meshnode grabbed it
230 }
231
232 WieldMeshSceneNode::~WieldMeshSceneNode()
233 {
234         sanity_check(g_extrusion_mesh_cache);
235         if (g_extrusion_mesh_cache->drop())
236                 g_extrusion_mesh_cache = NULL;
237 }
238
239 void WieldMeshSceneNode::setCube(const TileSpec tiles[6],
240                         v3f wield_scale, ITextureSource *tsrc)
241 {
242         scene::IMesh *cubemesh = g_extrusion_mesh_cache->createCube();
243         changeToMesh(cubemesh);
244         cubemesh->drop();
245
246         m_meshnode->setScale(wield_scale * WIELD_SCALE_FACTOR);
247
248         // Customize materials
249         for (u32 i = 0; i < m_meshnode->getMaterialCount(); ++i) {
250                 assert(i < 6);
251                 video::SMaterial &material = m_meshnode->getMaterial(i);
252                 if (tiles[i].animation_frame_count == 1) {
253                         material.setTexture(0, tiles[i].texture);
254                 } else {
255                         FrameSpec animation_frame = tiles[i].frames[0];
256                         material.setTexture(0, animation_frame.texture);
257                 }
258                 tiles[i].applyMaterialOptions(material);
259         }
260 }
261
262 void WieldMeshSceneNode::setExtruded(const std::string &imagename,
263                 v3f wield_scale, ITextureSource *tsrc)
264 {
265         video::ITexture *texture = tsrc->getTexture(imagename);
266         if (!texture) {
267                 changeToMesh(NULL);
268                 return;
269         }
270         core::dimension2d<u32> dim = texture->getSize();
271         scene::IMesh *mesh = g_extrusion_mesh_cache->create(dim);
272         changeToMesh(mesh);
273         mesh->drop();
274
275         m_meshnode->setScale(wield_scale * WIELD_SCALE_FACTOR_EXTRUDED);
276
277         // Customize material
278         video::SMaterial &material = m_meshnode->getMaterial(0);
279         material.setTexture(0, texture);
280         material.MaterialType = m_material_type;
281         material.setFlag(video::EMF_BACK_FACE_CULLING, true);
282         // Enable bi/trilinear filtering only for high resolution textures
283         if (dim.Width > 32) {
284                 material.setFlag(video::EMF_BILINEAR_FILTER, m_bilinear_filter);
285                 material.setFlag(video::EMF_TRILINEAR_FILTER, m_trilinear_filter);
286         } else {
287                 material.setFlag(video::EMF_BILINEAR_FILTER, false);
288                 material.setFlag(video::EMF_TRILINEAR_FILTER, false);
289         }
290         material.setFlag(video::EMF_ANISOTROPIC_FILTER, m_anisotropic_filter);
291         // mipmaps cause "thin black line" artifacts
292 #if (IRRLICHT_VERSION_MAJOR >= 1 && IRRLICHT_VERSION_MINOR >= 8) || IRRLICHT_VERSION_MAJOR >= 2
293         material.setFlag(video::EMF_USE_MIP_MAPS, false);
294 #endif
295
296 #if 0
297 //// TODO(RealBadAngel): Reactivate when shader is added for wield items
298         if (m_enable_shaders)
299                 material.setTexture(2, tsrc->getTexture("disable_img.png"));
300 #endif
301 }
302
303 void WieldMeshSceneNode::setItem(const ItemStack &item, IGameDef *gamedef)
304 {
305         ITextureSource *tsrc = gamedef->getTextureSource();
306         IItemDefManager *idef = gamedef->getItemDefManager();
307         //IShaderSource *shdrsrc = gamedef->getShaderSource();
308         INodeDefManager *ndef = gamedef->getNodeDefManager();
309         const ItemDefinition &def = item.getDefinition(idef);
310         const ContentFeatures &f = ndef->get(def.name);
311         content_t id = ndef->getId(def.name);
312
313 #if 0
314 //// TODO(RealBadAngel): Reactivate when shader is added for wield items
315         if (m_enable_shaders) {
316                 u32 shader_id = shdrsrc->getShader("nodes_shader", TILE_MATERIAL_BASIC, NDT_NORMAL);
317                 m_material_type = shdrsrc->getShaderInfo(shader_id).material;
318         }
319 #endif
320
321         // If wield_image is defined, it overrides everything else
322         if (def.wield_image != "") {
323                 setExtruded(def.wield_image, def.wield_scale, tsrc);
324                 return;
325         }
326         // Handle nodes
327         // See also CItemDefManager::createClientCached()
328         else if (def.type == ITEM_NODE) {
329                 if (f.mesh_ptr[0]) {
330                         // e.g. mesh nodes and nodeboxes
331                         changeToMesh(f.mesh_ptr[0]);
332                         // mesh_ptr[0] is pre-scaled by BS * f->visual_scale
333                         m_meshnode->setScale(
334                                         def.wield_scale * WIELD_SCALE_FACTOR
335                                         / (BS * f.visual_scale));
336                 } else if (f.drawtype == NDT_AIRLIKE) {
337                         changeToMesh(NULL);
338                 } else if (f.drawtype == NDT_PLANTLIKE) {
339                         setExtruded(tsrc->getTextureName(f.tiles[0].texture_id), def.wield_scale, tsrc);
340                 } else if (f.drawtype == NDT_NORMAL || f.drawtype == NDT_ALLFACES) {
341                         setCube(f.tiles, def.wield_scale, tsrc);
342                 } else {
343                         //// TODO: Change false in the following constructor args to
344                         //// appropriate value when shader is added for wield items (if applicable)
345                         MeshMakeData mesh_make_data(gamedef, false);
346                         MapNode mesh_make_node(id, 255, 0);
347                         mesh_make_data.fillSingleNode(&mesh_make_node);
348                         MapBlockMesh mapblock_mesh(&mesh_make_data, v3s16(0, 0, 0));
349                         changeToMesh(mapblock_mesh.getMesh());
350                         translateMesh(m_meshnode->getMesh(), v3f(-BS, -BS, -BS));
351                         m_meshnode->setScale(
352                                         def.wield_scale * WIELD_SCALE_FACTOR
353                                         / (BS * f.visual_scale));
354                 }
355                 for (u32 i = 0; i < m_meshnode->getMaterialCount(); ++i) {
356                         assert(i < 6);
357                         video::SMaterial &material = m_meshnode->getMaterial(i);
358                         material.setFlag(video::EMF_BACK_FACE_CULLING, true);
359                         material.setFlag(video::EMF_BILINEAR_FILTER, m_bilinear_filter);
360                         material.setFlag(video::EMF_TRILINEAR_FILTER, m_trilinear_filter);
361                         bool animated = (f.tiles[i].animation_frame_count > 1);
362                         if (animated) {
363                                 FrameSpec animation_frame = f.tiles[i].frames[0];
364                                 material.setTexture(0, animation_frame.texture);
365                         } else {
366                                 material.setTexture(0, f.tiles[i].texture);
367                         }
368                         material.MaterialType = m_material_type;
369 #if 0
370 //// TODO(RealBadAngel): Reactivate when shader is added for wield items
371                         if (m_enable_shaders) {
372                                 if (f.tiles[i].normal_texture) {
373                                         if (animated) {
374                                                 FrameSpec animation_frame = f.tiles[i].frames[0];
375                                                 material.setTexture(1, animation_frame.normal_texture);
376                                         } else {
377                                                 material.setTexture(1, f.tiles[i].normal_texture);
378                                         }
379                                         material.setTexture(2, tsrc->getTexture("enable_img.png"));
380                                 } else {
381                                         material.setTexture(2, tsrc->getTexture("disable_img.png"));
382                                 }
383                         }
384 #endif
385                 }
386                 return;
387         }
388         else if (def.inventory_image != "") {
389                 setExtruded(def.inventory_image, def.wield_scale, tsrc);
390                 return;
391         }
392
393         // no wield mesh found
394         changeToMesh(NULL);
395 }
396
397 void WieldMeshSceneNode::setColor(video::SColor color)
398 {
399         assert(!m_lighting);
400         setMeshColor(m_meshnode->getMesh(), color);
401 }
402
403 void WieldMeshSceneNode::render()
404 {
405         // note: if this method is changed to actually do something,
406         // you probably should implement OnRegisterSceneNode as well
407 }
408
409 void WieldMeshSceneNode::changeToMesh(scene::IMesh *mesh)
410 {
411         if (mesh == NULL) {
412                 scene::IMesh *dummymesh = g_extrusion_mesh_cache->createCube();
413                 m_meshnode->setVisible(false);
414                 m_meshnode->setMesh(dummymesh);
415                 dummymesh->drop();  // m_meshnode grabbed it
416         } else {
417                 if (m_lighting) {
418                         m_meshnode->setMesh(mesh);
419                 } else {
420                         /*
421                                 Lighting is disabled, this means the caller can (and probably will)
422                                 call setColor later. We therefore need to clone the mesh so that
423                                 setColor will only modify this scene node's mesh, not others'.
424                         */
425                         scene::IMeshManipulator *meshmanip = SceneManager->getMeshManipulator();
426                         scene::IMesh *new_mesh = meshmanip->createMeshCopy(mesh);
427                         m_meshnode->setMesh(new_mesh);
428                         new_mesh->drop();  // m_meshnode grabbed it
429                 }
430         }
431
432         m_meshnode->setMaterialFlag(video::EMF_LIGHTING, m_lighting);
433         // need to normalize normals when lighting is enabled (because of setScale())
434         m_meshnode->setMaterialFlag(video::EMF_NORMALIZE_NORMALS, m_lighting);
435         m_meshnode->setVisible(true);
436 }