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.
21 #include "wieldmesh.h"
22 #include "inventory.h"
27 #include "mapblock_mesh.h"
28 #include "client/tile.h"
30 #include "util/numeric.h"
32 #include <IMeshManipulator.h>
34 #define WIELD_SCALE_FACTOR 30.0
35 #define WIELD_SCALE_FACTOR_EXTRUDED 40.0
37 #define MIN_EXTRUSION_MESH_RESOLUTION 16
38 #define MAX_EXTRUSION_MESH_RESOLUTION 512
40 static scene::IMesh *createExtrusionMesh(int resolution_x, int resolution_y)
44 scene::IMeshBuffer *buf = new scene::SMeshBuffer();
45 video::SColor c(255,255,255,255);
46 v3f scale(1.0, 1.0, 0.1);
50 video::S3DVertex vertices[8] = {
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),
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),
62 u16 indices[12] = {0,1,2,2,3,0,4,5,6,6,7,4};
63 buf->append(vertices, 8, indices, 12);
66 f32 pixelsize_x = 1 / (f32) resolution_x;
67 f32 pixelsize_y = 1 / (f32) resolution_y;
69 for (int i = 0; i < resolution_x; ++i) {
70 f32 pixelpos_x = i * pixelsize_x - 0.5;
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] = {
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),
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),
87 u16 indices[12] = {0,1,2,2,3,0,4,5,6,6,7,4};
88 buf->append(vertices, 8, indices, 12);
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;
94 f32 tex0 = (i + 0.1) * pixelsize_y;
95 f32 tex1 = (i + 0.9) * pixelsize_y;
96 video::S3DVertex vertices[8] = {
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),
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),
108 u16 indices[12] = {0,1,2,2,3,0,4,5,6,6,7,4};
109 buf->append(vertices, 8, indices, 12);
112 // Create mesh object
113 scene::SMesh *mesh = new scene::SMesh();
114 mesh->addMeshBuffer(buf);
116 scaleMesh(mesh, scale); // also recalculates bounding box
117 scene::IMesh *newmesh = createForsythOptimizedMesh(mesh);
123 Caches extrusion meshes so that only one of them per resolution
124 is needed. Also caches one cube (for convenience).
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.
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.
133 class ExtrusionMeshCache: public IReferenceCounted
139 for (int resolution = MIN_EXTRUSION_MESH_RESOLUTION;
140 resolution <= MAX_EXTRUSION_MESH_RESOLUTION;
142 m_extrusion_meshes[resolution] =
143 createExtrusionMesh(resolution, resolution);
145 m_cube = createCubeMesh(v3f(1.0, 1.0, 1.0));
148 virtual ~ExtrusionMeshCache()
150 for (std::map<int, scene::IMesh*>::iterator
151 it = m_extrusion_meshes.begin();
152 it != m_extrusion_meshes.end(); ++it) {
157 // Get closest extrusion mesh for given image dimensions
158 // Caller must drop the returned pointer
159 scene::IMesh* create(core::dimension2d<u32> dim)
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);
166 int maxdim = MYMAX(dim.Width, dim.Height);
168 std::map<int, scene::IMesh*>::iterator
169 it = m_extrusion_meshes.lower_bound(maxdim);
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());
177 scene::IMesh *mesh = it->second;
181 // Returns a 1x1x1 cube mesh with one meshbuffer (material) per face
182 // Caller must drop the returned pointer
183 scene::IMesh* createCube()
190 std::map<int, scene::IMesh*> m_extrusion_meshes;
191 scene::IMesh *m_cube;
194 ExtrusionMeshCache *g_extrusion_mesh_cache = NULL;
197 WieldMeshSceneNode::WieldMeshSceneNode(
198 scene::ISceneNode *parent,
199 scene::ISceneManager *mgr,
203 scene::ISceneNode(parent, mgr, id),
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)
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");
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();
219 g_extrusion_mesh_cache->grab();
221 // Disable bounding box culling for this scene node
222 // since we won't calculate the bounding box.
223 setAutomaticCulling(scene::EAC_OFF);
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
233 WieldMeshSceneNode::~WieldMeshSceneNode()
235 sanity_check(g_extrusion_mesh_cache);
236 if (g_extrusion_mesh_cache->drop())
237 g_extrusion_mesh_cache = NULL;
240 void WieldMeshSceneNode::setCube(const TileSpec tiles[6],
241 v3f wield_scale, ITextureSource *tsrc)
243 scene::IMesh *cubemesh = g_extrusion_mesh_cache->createCube();
244 changeToMesh(cubemesh);
247 m_meshnode->setScale(wield_scale * WIELD_SCALE_FACTOR);
249 // Customize materials
250 for (u32 i = 0; i < m_meshnode->getMaterialCount(); ++i) {
252 video::SMaterial &material = m_meshnode->getMaterial(i);
253 if (tiles[i].animation_frame_count == 1) {
254 material.setTexture(0, tiles[i].texture);
256 FrameSpec animation_frame = tiles[i].frames[0];
257 material.setTexture(0, animation_frame.texture);
259 tiles[i].applyMaterialOptions(material);
263 void WieldMeshSceneNode::setExtruded(const std::string &imagename,
264 v3f wield_scale, ITextureSource *tsrc, u8 num_frames)
266 video::ITexture *texture = tsrc->getTexture(imagename);
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);
278 scene::IMesh *mesh = g_extrusion_mesh_cache->create(dim);
282 m_meshnode->setScale(wield_scale * WIELD_SCALE_FACTOR_EXTRUDED);
284 // Customize material
285 video::SMaterial &material = m_meshnode->getMaterial(0);
286 material.setTexture(0, tsrc->getTexture(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);
296 material.setFlag(video::EMF_BILINEAR_FILTER, false);
297 material.setFlag(video::EMF_TRILINEAR_FILTER, false);
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);
304 if (m_enable_shaders) {
305 material.setTexture(2, tsrc->getShaderFlagsTexture(false, true, true));
309 void WieldMeshSceneNode::setItem(const ItemStack &item, IGameDef *gamedef)
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);
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;
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);
330 // See also CItemDefManager::createClientCached()
331 else if (def.type == ITEM_NODE) {
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) {
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);
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));
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;
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);
369 FrameSpec animation_frame = f.tiles[i].frames[0];
370 material.setTexture(0, animation_frame.texture);
372 material.setTexture(0, f.tiles[i].texture);
374 material.MaterialType = m_material_type;
375 if (m_enable_shaders) {
376 if (f.tiles[i].normal_texture) {
378 FrameSpec animation_frame = f.tiles[i].frames[0];
379 material.setTexture(1, animation_frame.normal_texture);
381 material.setTexture(1, f.tiles[i].normal_texture);
384 material.setTexture(2, f.tiles[i].flags_texture);
389 else if (def.inventory_image != "") {
390 setExtruded(def.inventory_image, def.wield_scale, tsrc, 1);
394 // no wield mesh found
398 void WieldMeshSceneNode::setColor(video::SColor color)
401 setMeshColor(m_meshnode->getMesh(), color);
402 shadeMeshFaces(m_meshnode->getMesh());
405 void WieldMeshSceneNode::render()
407 // note: if this method is changed to actually do something,
408 // you probably should implement OnRegisterSceneNode as well
411 void WieldMeshSceneNode::changeToMesh(scene::IMesh *mesh)
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
420 m_meshnode->setMesh(mesh);
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'.
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
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);