]> git.lizzy.rs Git - minetest.git/blob - src/wieldmesh.cpp
Revert "Add support for using arbitrary meshes as items"
[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 "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 16
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         scene::IMesh *newmesh = createForsythOptimizedMesh(mesh);
118         mesh->drop();
119         return newmesh;
120 }
121
122 /*
123         Caches extrusion meshes so that only one of them per resolution
124         is needed. Also caches one cube (for convenience).
125
126         E.g. there is a single extrusion mesh that is used for all
127         16x16 px images, another for all 256x256 px images, and so on.
128
129         WARNING: Not thread safe. This should not be a problem since
130         rendering related classes (such as WieldMeshSceneNode) will be
131         used from the rendering thread only.
132 */
133 class ExtrusionMeshCache: public IReferenceCounted
134 {
135 public:
136         // Constructor
137         ExtrusionMeshCache()
138         {
139                 for (int resolution = MIN_EXTRUSION_MESH_RESOLUTION;
140                                 resolution <= MAX_EXTRUSION_MESH_RESOLUTION;
141                                 resolution *= 2) {
142                         m_extrusion_meshes[resolution] =
143                                 createExtrusionMesh(resolution, resolution);
144                 }
145                 m_cube = createCubeMesh(v3f(1.0, 1.0, 1.0));
146         }
147         // Destructor
148         virtual ~ExtrusionMeshCache()
149         {
150                 for (std::map<int, scene::IMesh*>::iterator
151                                 it = m_extrusion_meshes.begin();
152                                 it != m_extrusion_meshes.end(); ++it) {
153                         it->second->drop();
154                 }
155                 m_cube->drop();
156         }
157         // Get closest extrusion mesh for given image dimensions
158         // Caller must drop the returned pointer
159         scene::IMesh* create(core::dimension2d<u32> dim)
160         {
161                 // handle non-power of two textures inefficiently without cache
162                 if (!is_power_of_two(dim.Width) || !is_power_of_two(dim.Height)) {
163                         return createExtrusionMesh(dim.Width, dim.Height);
164                 }
165
166                 int maxdim = MYMAX(dim.Width, dim.Height);
167
168                 std::map<int, scene::IMesh*>::iterator
169                         it = m_extrusion_meshes.lower_bound(maxdim);
170
171                 if (it == m_extrusion_meshes.end()) {
172                         // no viable resolution found; use largest one
173                         it = m_extrusion_meshes.find(MAX_EXTRUSION_MESH_RESOLUTION);
174                         sanity_check(it != m_extrusion_meshes.end());
175                 }
176
177                 scene::IMesh *mesh = it->second;
178                 mesh->grab();
179                 return mesh;
180         }
181         // Returns a 1x1x1 cube mesh with one meshbuffer (material) per face
182         // Caller must drop the returned pointer
183         scene::IMesh* createCube()
184         {
185                 m_cube->grab();
186                 return m_cube;
187         }
188
189 private:
190         std::map<int, scene::IMesh*> m_extrusion_meshes;
191         scene::IMesh *m_cube;
192 };
193
194 ExtrusionMeshCache *g_extrusion_mesh_cache = NULL;
195
196
197 WieldMeshSceneNode::WieldMeshSceneNode(
198                 scene::ISceneNode *parent,
199                 scene::ISceneManager *mgr,
200                 s32 id,
201                 bool lighting
202 ):
203         scene::ISceneNode(parent, mgr, id),
204         m_meshnode(NULL),
205         m_material_type(video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF),
206         m_lighting(lighting),
207         m_bounding_box(0.0, 0.0, 0.0, 0.0, 0.0, 0.0)
208 {
209         m_enable_shaders = g_settings->getBool("enable_shaders");
210         m_anisotropic_filter = g_settings->getBool("anisotropic_filter");
211         m_bilinear_filter = g_settings->getBool("bilinear_filter");
212         m_trilinear_filter = g_settings->getBool("trilinear_filter");
213
214         // If this is the first wield mesh scene node, create a cache
215         // for extrusion meshes (and a cube mesh), otherwise reuse it
216         if (g_extrusion_mesh_cache == NULL)
217                 g_extrusion_mesh_cache = new ExtrusionMeshCache();
218         else
219                 g_extrusion_mesh_cache->grab();
220
221         // Disable bounding box culling for this scene node
222         // since we won't calculate the bounding box.
223         setAutomaticCulling(scene::EAC_OFF);
224
225         // Create the child scene node
226         scene::IMesh *dummymesh = g_extrusion_mesh_cache->createCube();
227         m_meshnode = SceneManager->addMeshSceneNode(dummymesh, this, -1);
228         m_meshnode->setReadOnlyMaterials(false);
229         m_meshnode->setVisible(false);
230         dummymesh->drop(); // m_meshnode grabbed it
231 }
232
233 WieldMeshSceneNode::~WieldMeshSceneNode()
234 {
235         sanity_check(g_extrusion_mesh_cache);
236         if (g_extrusion_mesh_cache->drop())
237                 g_extrusion_mesh_cache = NULL;
238 }
239
240 void WieldMeshSceneNode::setCube(const TileSpec tiles[6],
241                         v3f wield_scale, ITextureSource *tsrc)
242 {
243         scene::IMesh *cubemesh = g_extrusion_mesh_cache->createCube();
244         changeToMesh(cubemesh);
245         cubemesh->drop();
246
247         m_meshnode->setScale(wield_scale * WIELD_SCALE_FACTOR);
248
249         // Customize materials
250         for (u32 i = 0; i < m_meshnode->getMaterialCount(); ++i) {
251                 assert(i < 6);
252                 video::SMaterial &material = m_meshnode->getMaterial(i);
253                 if (tiles[i].animation_frame_count == 1) {
254                         material.setTexture(0, tiles[i].texture);
255                 } else {
256                         FrameSpec animation_frame = tiles[i].frames[0];
257                         material.setTexture(0, animation_frame.texture);
258                 }
259                 tiles[i].applyMaterialOptions(material);
260         }
261 }
262
263 void WieldMeshSceneNode::setExtruded(const std::string &imagename,
264                 v3f wield_scale, ITextureSource *tsrc, u8 num_frames)
265 {
266         video::ITexture *texture = tsrc->getTexture(imagename);
267         if (!texture) {
268                 changeToMesh(NULL);
269                 return;
270         }
271
272         core::dimension2d<u32> dim = texture->getSize();
273         // Detect animation texture and pull off top frame instead of using entire thing
274         if (num_frames > 1) {
275                 u32 frame_height = dim.Height / num_frames;
276                 dim = core::dimension2d<u32>(dim.Width, frame_height);
277         }
278         scene::IMesh *mesh = g_extrusion_mesh_cache->create(dim);
279         changeToMesh(mesh);
280         mesh->drop();
281
282         m_meshnode->setScale(wield_scale * WIELD_SCALE_FACTOR_EXTRUDED);
283
284         // Customize material
285         video::SMaterial &material = m_meshnode->getMaterial(0);
286         material.setTexture(0, tsrc->getTextureForMesh(imagename));
287         material.TextureLayer[0].TextureWrapU = video::ETC_CLAMP_TO_EDGE;
288         material.TextureLayer[0].TextureWrapV = video::ETC_CLAMP_TO_EDGE; 
289         material.MaterialType = m_material_type;
290         material.setFlag(video::EMF_BACK_FACE_CULLING, true);
291         // Enable bi/trilinear filtering only for high resolution textures
292         if (dim.Width > 32) {
293                 material.setFlag(video::EMF_BILINEAR_FILTER, m_bilinear_filter);
294                 material.setFlag(video::EMF_TRILINEAR_FILTER, m_trilinear_filter);
295         } else {
296                 material.setFlag(video::EMF_BILINEAR_FILTER, false);
297                 material.setFlag(video::EMF_TRILINEAR_FILTER, false);
298         }
299         material.setFlag(video::EMF_ANISOTROPIC_FILTER, m_anisotropic_filter);
300         // mipmaps cause "thin black line" artifacts
301 #if (IRRLICHT_VERSION_MAJOR >= 1 && IRRLICHT_VERSION_MINOR >= 8) || IRRLICHT_VERSION_MAJOR >= 2
302         material.setFlag(video::EMF_USE_MIP_MAPS, false);
303 #endif
304         if (m_enable_shaders) {
305                 material.setTexture(2, tsrc->getShaderFlagsTexture(false));
306         }
307 }
308
309 void WieldMeshSceneNode::setItem(const ItemStack &item, IGameDef *gamedef)
310 {
311         ITextureSource *tsrc = gamedef->getTextureSource();
312         IItemDefManager *idef = gamedef->getItemDefManager();
313         IShaderSource *shdrsrc = gamedef->getShaderSource();
314         INodeDefManager *ndef = gamedef->getNodeDefManager();
315         const ItemDefinition &def = item.getDefinition(idef);
316         const ContentFeatures &f = ndef->get(def.name);
317         content_t id = ndef->getId(def.name);
318
319         if (m_enable_shaders) {
320                 u32 shader_id = shdrsrc->getShader("wielded_shader", TILE_MATERIAL_BASIC, NDT_NORMAL);
321                 m_material_type = shdrsrc->getShaderInfo(shader_id).material;
322         }
323
324         // If wield_image is defined, it overrides everything else
325         if (def.wield_image != "") {
326                 setExtruded(def.wield_image, def.wield_scale, tsrc, 1);
327                 return;
328         }
329         // Handle nodes
330         // See also CItemDefManager::createClientCached()
331         else if (def.type == ITEM_NODE) {
332                 if (f.mesh_ptr[0]) {
333                         // e.g. mesh nodes and nodeboxes
334                         changeToMesh(f.mesh_ptr[0]);
335                         // mesh_ptr[0] is pre-scaled by BS * f->visual_scale
336                         m_meshnode->setScale(
337                                         def.wield_scale * WIELD_SCALE_FACTOR
338                                         / (BS * f.visual_scale));
339                 } else if (f.drawtype == NDT_AIRLIKE) {
340                         changeToMesh(NULL);
341                 } else if (f.drawtype == NDT_PLANTLIKE) {
342                         setExtruded(tsrc->getTextureName(f.tiles[0].texture_id), def.wield_scale, tsrc, f.tiles[0].animation_frame_count);
343                 } else if (f.drawtype == NDT_NORMAL || f.drawtype == NDT_ALLFACES) {
344                         setCube(f.tiles, def.wield_scale, tsrc);
345                 } else {
346                         MeshMakeData mesh_make_data(gamedef, false);
347                         MapNode mesh_make_node(id, 255, 0);
348                         mesh_make_data.fillSingleNode(&mesh_make_node);
349                         MapBlockMesh mapblock_mesh(&mesh_make_data, v3s16(0, 0, 0));
350                         changeToMesh(mapblock_mesh.getMesh());
351                         translateMesh(m_meshnode->getMesh(), v3f(-BS, -BS, -BS));
352                         m_meshnode->setScale(
353                                         def.wield_scale * WIELD_SCALE_FACTOR
354                                         / (BS * f.visual_scale));
355                 }
356                 u32 material_count = m_meshnode->getMaterialCount();
357                 if (material_count > 6) {
358                         errorstream << "WieldMeshSceneNode::setItem: Invalid material "
359                                 "count " << material_count << ", truncating to 6" << std::endl;
360                         material_count = 6;
361                 }
362                 for (u32 i = 0; i < material_count; ++i) {
363                         video::SMaterial &material = m_meshnode->getMaterial(i);
364                         material.setFlag(video::EMF_BACK_FACE_CULLING, true);
365                         material.setFlag(video::EMF_BILINEAR_FILTER, m_bilinear_filter);
366                         material.setFlag(video::EMF_TRILINEAR_FILTER, m_trilinear_filter);
367                         bool animated = (f.tiles[i].animation_frame_count > 1);
368                         if (animated) {
369                                 FrameSpec animation_frame = f.tiles[i].frames[0];
370                                 material.setTexture(0, animation_frame.texture);
371                         } else {
372                                 material.setTexture(0, f.tiles[i].texture);
373                         }
374                         material.MaterialType = m_material_type;
375                         if (m_enable_shaders) {
376                                 if (f.tiles[i].normal_texture) {
377                                         if (animated) {
378                                                 FrameSpec animation_frame = f.tiles[i].frames[0];
379                                                 material.setTexture(1, animation_frame.normal_texture);
380                                         } else {
381                                                 material.setTexture(1, f.tiles[i].normal_texture);
382                                         }
383                                 }
384                                 material.setTexture(2, f.tiles[i].flags_texture);
385                         }
386                 }
387                 return;
388         }
389         else if (def.inventory_image != "") {
390                 setExtruded(def.inventory_image, def.wield_scale, tsrc, 1);
391                 return;
392         }
393
394         // no wield mesh found
395         changeToMesh(NULL);
396 }
397
398 void WieldMeshSceneNode::setColor(video::SColor color)
399 {
400         assert(!m_lighting);
401         setMeshColor(m_meshnode->getMesh(), color);
402         shadeMeshFaces(m_meshnode->getMesh());
403 }
404
405 void WieldMeshSceneNode::render()
406 {
407         // note: if this method is changed to actually do something,
408         // you probably should implement OnRegisterSceneNode as well
409 }
410
411 void WieldMeshSceneNode::changeToMesh(scene::IMesh *mesh)
412 {
413         if (mesh == NULL) {
414                 scene::IMesh *dummymesh = g_extrusion_mesh_cache->createCube();
415                 m_meshnode->setVisible(false);
416                 m_meshnode->setMesh(dummymesh);
417                 dummymesh->drop();  // m_meshnode grabbed it
418         } else {
419                 if (m_lighting) {
420                         m_meshnode->setMesh(mesh);
421                 } else {
422                         /*
423                                 Lighting is disabled, this means the caller can (and probably will)
424                                 call setColor later. We therefore need to clone the mesh so that
425                                 setColor will only modify this scene node's mesh, not others'.
426                         */
427                         scene::IMeshManipulator *meshmanip = SceneManager->getMeshManipulator();
428                         scene::IMesh *new_mesh = meshmanip->createMeshCopy(mesh);
429                         m_meshnode->setMesh(new_mesh);
430                         new_mesh->drop();  // m_meshnode grabbed it
431                 }
432         }
433
434         m_meshnode->setMaterialFlag(video::EMF_LIGHTING, m_lighting);
435         // need to normalize normals when lighting is enabled (because of setScale())
436         m_meshnode->setMaterialFlag(video::EMF_NORMALIZE_NORMALS, m_lighting);
437         m_meshnode->setVisible(true);
438 }