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
121 Caches extrusion meshes so that only one of them per resolution
122 is needed. Also caches one cube (for convenience).
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.
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.
131 class ExtrusionMeshCache: public IReferenceCounted
137 for (int resolution = MIN_EXTRUSION_MESH_RESOLUTION;
138 resolution <= MAX_EXTRUSION_MESH_RESOLUTION;
140 m_extrusion_meshes[resolution] =
141 createExtrusionMesh(resolution, resolution);
143 m_cube = createCubeMesh(v3f(1.0, 1.0, 1.0));
146 virtual ~ExtrusionMeshCache()
148 for (std::map<int, scene::IMesh*>::iterator
149 it = m_extrusion_meshes.begin();
150 it != m_extrusion_meshes.end(); ++it) {
155 // Get closest extrusion mesh for given image dimensions
156 // Caller must drop the returned pointer
157 scene::IMesh* create(core::dimension2d<u32> dim)
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);
164 int maxdim = MYMAX(dim.Width, dim.Height);
166 std::map<int, scene::IMesh*>::iterator
167 it = m_extrusion_meshes.lower_bound(maxdim);
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());
175 scene::IMesh *mesh = it->second;
179 // Returns a 1x1x1 cube mesh with one meshbuffer (material) per face
180 // Caller must drop the returned pointer
181 scene::IMesh* createCube()
188 std::map<int, scene::IMesh*> m_extrusion_meshes;
189 scene::IMesh *m_cube;
192 ExtrusionMeshCache *g_extrusion_mesh_cache = NULL;
195 WieldMeshSceneNode::WieldMeshSceneNode(
196 scene::ISceneNode *parent,
197 scene::ISceneManager *mgr,
201 scene::ISceneNode(parent, mgr, id),
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)
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");
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();
217 g_extrusion_mesh_cache->grab();
219 // Disable bounding box culling for this scene node
220 // since we won't calculate the bounding box.
221 setAutomaticCulling(scene::EAC_OFF);
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
231 WieldMeshSceneNode::~WieldMeshSceneNode()
233 sanity_check(g_extrusion_mesh_cache);
234 if (g_extrusion_mesh_cache->drop())
235 g_extrusion_mesh_cache = NULL;
238 void WieldMeshSceneNode::setCube(const TileSpec tiles[6],
239 v3f wield_scale, ITextureSource *tsrc)
241 scene::IMesh *cubemesh = g_extrusion_mesh_cache->createCube();
242 changeToMesh(cubemesh);
245 m_meshnode->setScale(wield_scale * WIELD_SCALE_FACTOR);
247 // Customize materials
248 for (u32 i = 0; i < m_meshnode->getMaterialCount(); ++i) {
250 video::SMaterial &material = m_meshnode->getMaterial(i);
251 if (tiles[i].animation_frame_count == 1) {
252 material.setTexture(0, tiles[i].texture);
254 FrameSpec animation_frame = tiles[i].frames[0];
255 material.setTexture(0, animation_frame.texture);
257 tiles[i].applyMaterialOptions(material);
261 void WieldMeshSceneNode::setExtruded(const std::string &imagename,
262 v3f wield_scale, ITextureSource *tsrc, u8 num_frames)
264 video::ITexture *texture = tsrc->getTexture(imagename);
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);
276 scene::IMesh *mesh = g_extrusion_mesh_cache->create(dim);
280 m_meshnode->setScale(wield_scale * WIELD_SCALE_FACTOR_EXTRUDED);
282 // Customize material
283 video::SMaterial &material = m_meshnode->getMaterial(0);
284 material.setTexture(0, tsrc->getTextureForMesh(imagename));
285 material.TextureLayer[0].TextureWrapU = video::ETC_CLAMP_TO_EDGE;
286 material.TextureLayer[0].TextureWrapV = video::ETC_CLAMP_TO_EDGE;
287 material.MaterialType = m_material_type;
288 material.setFlag(video::EMF_BACK_FACE_CULLING, true);
289 // Enable bi/trilinear filtering only for high resolution textures
290 if (dim.Width > 32) {
291 material.setFlag(video::EMF_BILINEAR_FILTER, m_bilinear_filter);
292 material.setFlag(video::EMF_TRILINEAR_FILTER, m_trilinear_filter);
294 material.setFlag(video::EMF_BILINEAR_FILTER, false);
295 material.setFlag(video::EMF_TRILINEAR_FILTER, false);
297 material.setFlag(video::EMF_ANISOTROPIC_FILTER, m_anisotropic_filter);
298 // mipmaps cause "thin black line" artifacts
299 #if (IRRLICHT_VERSION_MAJOR >= 1 && IRRLICHT_VERSION_MINOR >= 8) || IRRLICHT_VERSION_MAJOR >= 2
300 material.setFlag(video::EMF_USE_MIP_MAPS, false);
302 if (m_enable_shaders) {
303 material.setTexture(2, tsrc->getShaderFlagsTexture(false));
307 void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client)
309 ITextureSource *tsrc = client->getTextureSource();
310 IItemDefManager *idef = client->getItemDefManager();
311 IShaderSource *shdrsrc = client->getShaderSource();
312 INodeDefManager *ndef = client->getNodeDefManager();
313 const ItemDefinition &def = item.getDefinition(idef);
314 const ContentFeatures &f = ndef->get(def.name);
315 content_t id = ndef->getId(def.name);
317 if (m_enable_shaders) {
318 u32 shader_id = shdrsrc->getShader("wielded_shader", TILE_MATERIAL_BASIC, NDT_NORMAL);
319 m_material_type = shdrsrc->getShaderInfo(shader_id).material;
324 video::SColor basecolor = idef->getItemstackColor(item, client);
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 m_colors.push_back(basecolor);
333 // See also CItemDefManager::createClientCached()
334 else if (def.type == ITEM_NODE) {
336 // e.g. mesh nodes and nodeboxes
337 changeToMesh(f.mesh_ptr[0]);
338 // mesh_ptr[0] is pre-scaled by BS * f->visual_scale
339 m_meshnode->setScale(
340 def.wield_scale * WIELD_SCALE_FACTOR
341 / (BS * f.visual_scale));
342 } else if (f.drawtype == NDT_AIRLIKE) {
344 } else if (f.drawtype == NDT_PLANTLIKE) {
345 setExtruded(tsrc->getTextureName(f.tiles[0].texture_id), def.wield_scale, tsrc, f.tiles[0].animation_frame_count);
346 } else if (f.drawtype == NDT_NORMAL || f.drawtype == NDT_ALLFACES) {
347 setCube(f.tiles, def.wield_scale, tsrc);
349 MeshMakeData mesh_make_data(client, false);
350 MapNode mesh_make_node(id, 255, 0);
351 mesh_make_data.fillSingleNode(&mesh_make_node);
352 MapBlockMesh mapblock_mesh(&mesh_make_data, v3s16(0, 0, 0));
353 changeToMesh(mapblock_mesh.getMesh());
354 translateMesh(m_meshnode->getMesh(), v3f(-BS, -BS, -BS));
355 m_meshnode->setScale(
356 def.wield_scale * WIELD_SCALE_FACTOR
357 / (BS * f.visual_scale));
359 u32 material_count = m_meshnode->getMaterialCount();
360 if (material_count > 6) {
361 errorstream << "WieldMeshSceneNode::setItem: Invalid material "
362 "count " << material_count << ", truncating to 6" << std::endl;
365 for (u32 i = 0; i < material_count; ++i) {
366 const TileSpec *tile = &(f.tiles[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 = (tile->animation_frame_count > 1);
373 FrameSpec animation_frame = tile->frames[0];
374 material.setTexture(0, animation_frame.texture);
376 material.setTexture(0, tile->texture);
378 m_colors.push_back(tile->has_color ? tile->color : basecolor);
379 material.MaterialType = m_material_type;
380 if (m_enable_shaders) {
381 if (tile->normal_texture) {
383 FrameSpec animation_frame = tile->frames[0];
384 material.setTexture(1, animation_frame.normal_texture);
386 material.setTexture(1, tile->normal_texture);
389 material.setTexture(2, tile->flags_texture);
394 else if (def.inventory_image != "") {
395 setExtruded(def.inventory_image, def.wield_scale, tsrc, 1);
396 m_colors.push_back(basecolor);
400 // no wield mesh found
404 void WieldMeshSceneNode::setColor(video::SColor c)
407 scene::IMesh *mesh=m_meshnode->getMesh();
412 u8 green = c.getGreen();
413 u8 blue = c.getBlue();
414 u32 mc = mesh->getMeshBufferCount();
415 for (u32 j = 0; j < mc; j++) {
416 video::SColor bc(0xFFFFFFFF);
417 if (m_colors.size() > j)
419 video::SColor buffercolor(255,
420 bc.getRed() * red / 255,
421 bc.getGreen() * green / 255,
422 bc.getBlue() * blue / 255);
423 scene::IMeshBuffer *buf = mesh->getMeshBuffer(j);
424 colorizeMeshBuffer(buf, &buffercolor);
428 void WieldMeshSceneNode::render()
430 // note: if this method is changed to actually do something,
431 // you probably should implement OnRegisterSceneNode as well
434 void WieldMeshSceneNode::changeToMesh(scene::IMesh *mesh)
437 scene::IMesh *dummymesh = g_extrusion_mesh_cache->createCube();
438 m_meshnode->setVisible(false);
439 m_meshnode->setMesh(dummymesh);
440 dummymesh->drop(); // m_meshnode grabbed it
443 m_meshnode->setMesh(mesh);
446 Lighting is disabled, this means the caller can (and probably will)
447 call setColor later. We therefore need to clone the mesh so that
448 setColor will only modify this scene node's mesh, not others'.
450 scene::IMeshManipulator *meshmanip = SceneManager->getMeshManipulator();
451 scene::IMesh *new_mesh = meshmanip->createMeshCopy(mesh);
452 m_meshnode->setMesh(new_mesh);
453 new_mesh->drop(); // m_meshnode grabbed it
457 m_meshnode->setMaterialFlag(video::EMF_LIGHTING, m_lighting);
458 // need to normalize normals when lighting is enabled (because of setScale())
459 m_meshnode->setMaterialFlag(video::EMF_NORMALIZE_NORMALS, m_lighting);
460 m_meshnode->setVisible(true);
463 void getItemMesh(Client *client, const ItemStack &item, ItemMesh *result)
465 ITextureSource *tsrc = client->getTextureSource();
466 IItemDefManager *idef = client->getItemDefManager();
467 INodeDefManager *ndef = client->getNodeDefManager();
468 const ItemDefinition &def = item.getDefinition(idef);
469 const ContentFeatures &f = ndef->get(def.name);
470 content_t id = ndef->getId(def.name);
472 if (!g_extrusion_mesh_cache) {
473 g_extrusion_mesh_cache = new ExtrusionMeshCache();
475 g_extrusion_mesh_cache->grab();
480 // If inventory_image is defined, it overrides everything else
481 if (def.inventory_image != "") {
482 mesh = getExtrudedMesh(tsrc, def.inventory_image);
484 result->buffer_colors.push_back(
485 std::pair<bool, video::SColor>(false, video::SColor(0xFFFFFFFF)));
486 } else if (def.type == ITEM_NODE) {
488 mesh = cloneMesh(f.mesh_ptr[0]);
489 scaleMesh(mesh, v3f(0.12, 0.12, 0.12));
490 } else if (f.drawtype == NDT_PLANTLIKE) {
491 mesh = getExtrudedMesh(tsrc,
492 tsrc->getTextureName(f.tiles[0].texture_id));
493 } else if (f.drawtype == NDT_NORMAL || f.drawtype == NDT_ALLFACES
494 || f.drawtype == NDT_LIQUID || f.drawtype == NDT_FLOWINGLIQUID) {
495 mesh = cloneMesh(g_extrusion_mesh_cache->createCube());
496 scaleMesh(mesh, v3f(1.2, 1.2, 1.2));
498 MeshMakeData mesh_make_data(client, false);
499 MapNode mesh_make_node(id, 255, 0);
500 mesh_make_data.fillSingleNode(&mesh_make_node);
501 MapBlockMesh mapblock_mesh(&mesh_make_data, v3s16(0, 0, 0));
502 mesh = cloneMesh(mapblock_mesh.getMesh());
503 translateMesh(mesh, v3f(-BS, -BS, -BS));
504 scaleMesh(mesh, v3f(0.12, 0.12, 0.12));
506 u32 mc = mesh->getMeshBufferCount();
507 for (u32 i = 0; i < mc; ++i) {
508 video::SMaterial &material1 =
509 mesh->getMeshBuffer(i)->getMaterial();
510 video::SMaterial &material2 =
511 mapblock_mesh.getMesh()->getMeshBuffer(i)->getMaterial();
512 material1.setTexture(0, material2.getTexture(0));
513 material1.setTexture(1, material2.getTexture(1));
514 material1.setTexture(2, material2.getTexture(2));
515 material1.setTexture(3, material2.getTexture(3));
516 material1.MaterialType = material2.MaterialType;
520 u32 mc = mesh->getMeshBufferCount();
521 for (u32 i = 0; i < mc; ++i) {
522 const TileSpec *tile = &(f.tiles[i]);
523 scene::IMeshBuffer *buf = mesh->getMeshBuffer(i);
524 result->buffer_colors.push_back(
525 std::pair<bool, video::SColor>(tile->has_color, tile->color));
526 colorizeMeshBuffer(buf, &tile->color);
527 video::SMaterial &material = buf->getMaterial();
528 material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
529 material.setFlag(video::EMF_BILINEAR_FILTER, false);
530 material.setFlag(video::EMF_TRILINEAR_FILTER, false);
531 material.setFlag(video::EMF_BACK_FACE_CULLING, true);
532 material.setFlag(video::EMF_LIGHTING, false);
533 if (tile->animation_frame_count > 1) {
534 FrameSpec animation_frame = tile->frames[0];
535 material.setTexture(0, animation_frame.texture);
537 material.setTexture(0, tile->texture);
541 rotateMeshXZby(mesh, -45);
542 rotateMeshYZby(mesh, -30);
547 scene::IMesh * getExtrudedMesh(ITextureSource *tsrc,
548 const std::string &imagename)
550 video::ITexture *texture = tsrc->getTextureForMesh(imagename);
555 core::dimension2d<u32> dim = texture->getSize();
556 scene::IMesh *mesh = cloneMesh(g_extrusion_mesh_cache->create(dim));
558 // Customize material
559 video::SMaterial &material = mesh->getMeshBuffer(0)->getMaterial();
560 material.setTexture(0, tsrc->getTexture(imagename));
561 material.TextureLayer[0].TextureWrapU = video::ETC_CLAMP_TO_EDGE;
562 material.TextureLayer[0].TextureWrapV = video::ETC_CLAMP_TO_EDGE;
563 material.setFlag(video::EMF_BILINEAR_FILTER, false);
564 material.setFlag(video::EMF_TRILINEAR_FILTER, false);
565 material.setFlag(video::EMF_BACK_FACE_CULLING, true);
566 material.setFlag(video::EMF_LIGHTING, false);
567 material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
568 scaleMesh(mesh, v3f(2.0, 2.0, 2.0));