Wielded fixes. Add shaders support.
[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 "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                         assert(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_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         assert(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.find(0)->second;
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)
263 {
264         video::ITexture *texture = tsrc->getTexture(imagename);
265         if (!texture) {
266                 changeToMesh(NULL);
267                 return;
268         }
269         core::dimension2d<u32> dim = texture->getSize();
270         scene::IMesh *mesh = g_extrusion_mesh_cache->create(dim);
271         changeToMesh(mesh);
272         mesh->drop();
273
274         m_meshnode->setScale(wield_scale * WIELD_SCALE_FACTOR_EXTRUDED);
275
276         // Customize material
277         video::SMaterial &material = m_meshnode->getMaterial(0);
278         material.setTexture(0, texture);
279         material.MaterialType = m_material_type;
280         material.setFlag(video::EMF_BACK_FACE_CULLING, true);
281         // Enable filtering only for high resolution texures
282         if (dim.Width > 32) {
283                 material.setFlag(video::EMF_BILINEAR_FILTER, m_bilinear_filter);
284                 material.setFlag(video::EMF_TRILINEAR_FILTER, m_trilinear_filter);
285         } else {
286                 material.setFlag(video::EMF_BILINEAR_FILTER, false);
287                 material.setFlag(video::EMF_TRILINEAR_FILTER, false);
288         }
289         // anisotropic filtering removes "thin black line" artifacts
290         material.setFlag(video::EMF_ANISOTROPIC_FILTER, true);
291         if (m_enable_shaders) 
292                 material.setTexture(2, tsrc->getTexture("disable_img.png"));
293 }
294
295 void WieldMeshSceneNode::setItem(const ItemStack &item, IGameDef *gamedef)
296 {
297         ITextureSource *tsrc = gamedef->getTextureSource();
298         IItemDefManager *idef = gamedef->getItemDefManager();
299         IShaderSource *shdrsrc = gamedef->getShaderSource();
300         INodeDefManager *ndef = gamedef->getNodeDefManager();
301         const ItemDefinition &def = item.getDefinition(idef);
302         const ContentFeatures &f = ndef->get(def.name);
303         content_t id = ndef->getId(def.name);
304
305         if (m_enable_shaders) {
306                 u32 shader_id = shdrsrc->getShader("nodes_shader", TILE_MATERIAL_BASIC, NDT_NORMAL);
307                 m_material_type = shdrsrc->getShaderInfo(shader_id).material;
308         }
309
310         // If wield_image is defined, it overrides everything else
311         if (def.wield_image != "") {
312                 setExtruded(def.wield_image, def.wield_scale, tsrc);
313                 return;
314         }
315         // Handle nodes
316         // See also CItemDefManager::createClientCached()
317         else if (def.type == ITEM_NODE) {
318                 if (f.mesh_ptr[0]) {
319                         // e.g. mesh nodes and nodeboxes
320                         changeToMesh(f.mesh_ptr[0]);
321                         // mesh_ptr[0] is pre-scaled by BS * f->visual_scale
322                         m_meshnode->setScale(
323                                         def.wield_scale * WIELD_SCALE_FACTOR
324                                         / (BS * f.visual_scale));
325                 } else if (f.drawtype == NDT_AIRLIKE) {
326                         changeToMesh(NULL);
327                 } else if (f.drawtype == NDT_PLANTLIKE) {
328                         setExtruded(tsrc->getTextureName(f.tiles[0].texture_id), def.wield_scale, tsrc);
329                 } else if (f.drawtype == NDT_NORMAL || f.drawtype == NDT_ALLFACES) {
330                         setCube(f.tiles, def.wield_scale, tsrc);
331                 } else {
332                         MeshMakeData mesh_make_data(gamedef);
333                         MapNode mesh_make_node(id, 255, 0);
334                         mesh_make_data.fillSingleNode(&mesh_make_node);
335                         MapBlockMesh mapblock_mesh(&mesh_make_data, v3s16(0, 0, 0));
336                         changeToMesh(mapblock_mesh.getMesh());
337                         translateMesh(m_meshnode->getMesh(), v3f(-BS, -BS, -BS));
338                         m_meshnode->setScale(
339                                         def.wield_scale * WIELD_SCALE_FACTOR
340                                         / (BS * f.visual_scale));       
341                 }
342                 for (u32 i = 0; i < m_meshnode->getMaterialCount(); ++i) {
343                         assert(i < 6);
344                         video::SMaterial &material = m_meshnode->getMaterial(i);
345                         material.setFlag(video::EMF_BACK_FACE_CULLING, true);
346                         material.setFlag(video::EMF_BILINEAR_FILTER, m_bilinear_filter);
347                         material.setFlag(video::EMF_TRILINEAR_FILTER, m_trilinear_filter);
348                         bool animated = (f.tiles[i].animation_frame_count > 1);
349                         if (animated) {
350                                 FrameSpec animation_frame = f.tiles[i].frames.find(0)->second;
351                                 material.setTexture(0, animation_frame.texture);
352                         } else {
353                                 material.setTexture(0, f.tiles[i].texture);
354                         }
355                         material.MaterialType = m_material_type;
356                         if (m_enable_shaders) {
357                                 if (f.tiles[i].normal_texture) {
358                                         if (animated) {
359                                                 FrameSpec animation_frame = f.tiles[i].frames.find(0)->second;
360                                                 material.setTexture(1, animation_frame.normal_texture);
361                                         } else {
362                                                 material.setTexture(1, f.tiles[i].normal_texture);
363                                         }
364                                         material.setTexture(2, tsrc->getTexture("enable_img.png"));
365                                 } else {
366                                         material.setTexture(2, tsrc->getTexture("disable_img.png"));
367                                 }
368                         }
369                 }
370                 return;
371         }
372         else if (def.inventory_image != "") {
373                 setExtruded(def.inventory_image, def.wield_scale, tsrc);
374                 return;
375         }
376
377         // no wield mesh found
378         changeToMesh(NULL);
379 }
380
381 void WieldMeshSceneNode::setColor(video::SColor color)
382 {
383         assert(!m_lighting);
384         setMeshColor(m_meshnode->getMesh(), color);
385 }
386
387 void WieldMeshSceneNode::render()
388 {
389         // note: if this method is changed to actually do something,
390         // you probably should implement OnRegisterSceneNode as well
391 }
392
393 void WieldMeshSceneNode::changeToMesh(scene::IMesh *mesh)
394 {
395         if (mesh == NULL) {
396                 scene::IMesh *dummymesh = g_extrusion_mesh_cache->createCube();
397                 m_meshnode->setVisible(false);
398                 m_meshnode->setMesh(dummymesh);
399                 dummymesh->drop();  // m_meshnode grabbed it
400         }
401
402         if (m_lighting) {
403                 m_meshnode->setMesh(mesh);
404         } else {
405                 /*
406                         Lighting is disabled, this means the caller can (and probably will)
407                         call setColor later. We therefore need to clone the mesh so that
408                         setColor will only modify this scene node's mesh, not others'.
409                 */
410                 scene::IMeshManipulator *meshmanip = SceneManager->getMeshManipulator();
411                 scene::IMesh *new_mesh = meshmanip->createMeshCopy(mesh);
412                 m_meshnode->setMesh(new_mesh);
413                 new_mesh->drop();  // m_meshnode grabbed it
414         }
415
416         m_meshnode->setMaterialFlag(video::EMF_LIGHTING, m_lighting);
417         // need to normalize normals when lighting is enabled (because of setScale())
418         m_meshnode->setMaterialFlag(video::EMF_NORMALIZE_NORMALS, m_lighting);
419         m_meshnode->setVisible(true);
420 }