3 Copyright (C) 2010-2014 celeron55, Perttu Ahola <celeron55@gmail.com>
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.
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.
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.
22 #include "wieldmesh.h"
23 #include "inventory.h"
28 #include "mapblock_mesh.h"
31 #include "util/numeric.h"
33 #include <IMeshManipulator.h>
35 #define WIELD_SCALE_FACTOR 30.0
36 #define WIELD_SCALE_FACTOR_EXTRUDED 40.0
38 #define MIN_EXTRUSION_MESH_RESOLUTION 32 // not 16: causes too many "holes"
39 #define MAX_EXTRUSION_MESH_RESOLUTION 512
41 static scene::IMesh* createExtrusionMesh(int resolution_x, int resolution_y)
45 scene::IMeshBuffer *buf = new scene::SMeshBuffer();
46 video::SColor c(255,255,255,255);
47 v3f scale(1.0, 1.0, 0.1);
51 video::S3DVertex vertices[8] = {
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),
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),
63 u16 indices[12] = {0,1,2,2,3,0,4,5,6,6,7,4};
64 buf->append(vertices, 8, indices, 12);
67 f32 pixelsize_x = 1 / (f32) resolution_x;
68 f32 pixelsize_y = 1 / (f32) resolution_y;
70 for (int i = 0; i < resolution_x; ++i) {
71 f32 pixelpos_x = i * pixelsize_x - 0.5;
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] = {
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),
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),
88 u16 indices[12] = {0,1,2,2,3,0,4,5,6,6,7,4};
89 buf->append(vertices, 8, indices, 12);
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;
95 f32 tex0 = (i + 0.1) * pixelsize_y;
96 f32 tex1 = (i + 0.9) * pixelsize_y;
97 video::S3DVertex vertices[8] = {
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),
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),
109 u16 indices[12] = {0,1,2,2,3,0,4,5,6,6,7,4};
110 buf->append(vertices, 8, indices, 12);
113 // Create mesh object
114 scene::SMesh *mesh = new scene::SMesh();
115 mesh->addMeshBuffer(buf);
117 scaleMesh(mesh, scale); // also recalculates bounding box
122 Caches extrusion meshes so that only one of them per resolution
123 is needed. Also caches one cube (for convenience).
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.
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.
132 class ExtrusionMeshCache: public IReferenceCounted
138 for (int resolution = MIN_EXTRUSION_MESH_RESOLUTION;
139 resolution <= MAX_EXTRUSION_MESH_RESOLUTION;
141 m_extrusion_meshes[resolution] =
142 createExtrusionMesh(resolution, resolution);
144 m_cube = createCubeMesh(v3f(1.0, 1.0, 1.0));
147 virtual ~ExtrusionMeshCache()
149 for (std::map<int, scene::IMesh*>::iterator
150 it = m_extrusion_meshes.begin();
151 it != m_extrusion_meshes.end(); ++it) {
156 // Get closest extrusion mesh for given image dimensions
157 // Caller must drop the returned pointer
158 scene::IMesh* create(core::dimension2d<u32> dim)
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);
165 int maxdim = MYMAX(dim.Width, dim.Height);
167 std::map<int, scene::IMesh*>::iterator
168 it = m_extrusion_meshes.lower_bound(maxdim);
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());
176 scene::IMesh *mesh = it->second;
180 // Returns a 1x1x1 cube mesh with one meshbuffer (material) per face
181 // Caller must drop the returned pointer
182 scene::IMesh* createCube()
189 std::map<int, scene::IMesh*> m_extrusion_meshes;
190 scene::IMesh *m_cube;
193 ExtrusionMeshCache *g_extrusion_mesh_cache = NULL;
196 WieldMeshSceneNode::WieldMeshSceneNode(
197 scene::ISceneNode *parent,
198 scene::ISceneManager *mgr,
202 scene::ISceneNode(parent, mgr, id),
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)
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");
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();
218 g_extrusion_mesh_cache->grab();
220 // Disable bounding box culling for this scene node
221 // since we won't calculate the bounding box.
222 setAutomaticCulling(scene::EAC_OFF);
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
232 WieldMeshSceneNode::~WieldMeshSceneNode()
234 assert(g_extrusion_mesh_cache);
235 if (g_extrusion_mesh_cache->drop())
236 g_extrusion_mesh_cache = NULL;
239 void WieldMeshSceneNode::setCube(const TileSpec tiles[6],
240 v3f wield_scale, ITextureSource *tsrc)
242 scene::IMesh *cubemesh = g_extrusion_mesh_cache->createCube();
243 changeToMesh(cubemesh);
246 m_meshnode->setScale(wield_scale * WIELD_SCALE_FACTOR);
248 // Customize materials
249 for (u32 i = 0; i < m_meshnode->getMaterialCount(); ++i) {
251 video::SMaterial &material = m_meshnode->getMaterial(i);
252 if (tiles[i].animation_frame_count == 1) {
253 material.setTexture(0, tiles[i].texture);
255 FrameSpec animation_frame = tiles[i].frames[0];
256 material.setTexture(0, animation_frame.texture);
258 tiles[i].applyMaterialOptions(material);
262 void WieldMeshSceneNode::setExtruded(const std::string &imagename,
263 v3f wield_scale, ITextureSource *tsrc)
265 video::ITexture *texture = tsrc->getTexture(imagename);
270 core::dimension2d<u32> dim = texture->getSize();
271 scene::IMesh *mesh = g_extrusion_mesh_cache->create(dim);
275 m_meshnode->setScale(wield_scale * WIELD_SCALE_FACTOR_EXTRUDED);
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);
287 material.setFlag(video::EMF_BILINEAR_FILTER, false);
288 material.setFlag(video::EMF_TRILINEAR_FILTER, false);
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);
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"));
303 void WieldMeshSceneNode::setItem(const ItemStack &item, IGameDef *gamedef)
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);
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;
321 // If wield_image is defined, it overrides everything else
322 if (def.wield_image != "") {
323 setExtruded(def.wield_image, def.wield_scale, tsrc);
327 // See also CItemDefManager::createClientCached()
328 else if (def.type == ITEM_NODE) {
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) {
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);
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));
355 for (u32 i = 0; i < m_meshnode->getMaterialCount(); ++i) {
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);
363 FrameSpec animation_frame = f.tiles[i].frames[0];
364 material.setTexture(0, animation_frame.texture);
366 material.setTexture(0, f.tiles[i].texture);
368 material.MaterialType = m_material_type;
370 //// TODO(RealBadAngel): Reactivate when shader is added for wield items
371 if (m_enable_shaders) {
372 if (f.tiles[i].normal_texture) {
374 FrameSpec animation_frame = f.tiles[i].frames[0];
375 material.setTexture(1, animation_frame.normal_texture);
377 material.setTexture(1, f.tiles[i].normal_texture);
379 material.setTexture(2, tsrc->getTexture("enable_img.png"));
381 material.setTexture(2, tsrc->getTexture("disable_img.png"));
388 else if (def.inventory_image != "") {
389 setExtruded(def.inventory_image, def.wield_scale, tsrc);
393 // no wield mesh found
397 void WieldMeshSceneNode::setColor(video::SColor color)
400 setMeshColor(m_meshnode->getMesh(), color);
403 void WieldMeshSceneNode::render()
405 // note: if this method is changed to actually do something,
406 // you probably should implement OnRegisterSceneNode as well
409 void WieldMeshSceneNode::changeToMesh(scene::IMesh *mesh)
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
418 m_meshnode->setMesh(mesh);
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'.
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
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);