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