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.
20 #include "wieldmesh.h"
21 #include "inventory.h"
28 #include "util/numeric.h"
30 #include <IMeshManipulator.h>
32 #define WIELD_SCALE_FACTOR 30.0
33 #define WIELD_SCALE_FACTOR_EXTRUDED 40.0
35 #define MIN_EXTRUSION_MESH_RESOLUTION 32 // not 16: causes too many "holes"
36 #define MAX_EXTRUSION_MESH_RESOLUTION 512
38 static scene::IMesh* createExtrusionMesh(int resolution_x, int resolution_y)
42 scene::IMeshBuffer *buf = new scene::SMeshBuffer();
43 video::SColor c(255,255,255,255);
44 v3f scale(1.0, 1.0, 0.1);
48 video::S3DVertex vertices[8] = {
50 video::S3DVertex(-r,+r,-r, 0,0,-1, c, 0,0),
51 video::S3DVertex(+r,+r,-r, 0,0,-1, c, 1,0),
52 video::S3DVertex(+r,-r,-r, 0,0,-1, c, 1,1),
53 video::S3DVertex(-r,-r,-r, 0,0,-1, c, 0,1),
55 video::S3DVertex(-r,+r,+r, 0,0,+1, c, 0,0),
56 video::S3DVertex(-r,-r,+r, 0,0,+1, c, 0,1),
57 video::S3DVertex(+r,-r,+r, 0,0,+1, c, 1,1),
58 video::S3DVertex(+r,+r,+r, 0,0,+1, c, 1,0),
60 u16 indices[12] = {0,1,2,2,3,0,4,5,6,6,7,4};
61 buf->append(vertices, 8, indices, 12);
64 f32 pixelsize_x = 1 / (f32) resolution_x;
65 f32 pixelsize_y = 1 / (f32) resolution_y;
67 for (int i = 0; i < resolution_x; ++i) {
68 f32 pixelpos_x = i * pixelsize_x - 0.5;
70 f32 x1 = pixelpos_x + pixelsize_x;
71 f32 tex0 = (i + 0.1) * pixelsize_x;
72 f32 tex1 = (i + 0.9) * pixelsize_x;
73 video::S3DVertex vertices[8] = {
75 video::S3DVertex(x0,-r,-r, -1,0,0, c, tex0,1),
76 video::S3DVertex(x0,-r,+r, -1,0,0, c, tex1,1),
77 video::S3DVertex(x0,+r,+r, -1,0,0, c, tex1,0),
78 video::S3DVertex(x0,+r,-r, -1,0,0, c, tex0,0),
80 video::S3DVertex(x1,-r,-r, +1,0,0, c, tex0,1),
81 video::S3DVertex(x1,+r,-r, +1,0,0, c, tex0,0),
82 video::S3DVertex(x1,+r,+r, +1,0,0, c, tex1,0),
83 video::S3DVertex(x1,-r,+r, +1,0,0, c, tex1,1),
85 u16 indices[12] = {0,1,2,2,3,0,4,5,6,6,7,4};
86 buf->append(vertices, 8, indices, 12);
88 for (int i = 0; i < resolution_y; ++i) {
89 f32 pixelpos_y = i * pixelsize_y - 0.5;
90 f32 y0 = -pixelpos_y - pixelsize_y;
92 f32 tex0 = (i + 0.1) * pixelsize_y;
93 f32 tex1 = (i + 0.9) * pixelsize_y;
94 video::S3DVertex vertices[8] = {
96 video::S3DVertex(-r,y0,-r, 0,-1,0, c, 0,tex0),
97 video::S3DVertex(+r,y0,-r, 0,-1,0, c, 1,tex0),
98 video::S3DVertex(+r,y0,+r, 0,-1,0, c, 1,tex1),
99 video::S3DVertex(-r,y0,+r, 0,-1,0, c, 0,tex1),
101 video::S3DVertex(-r,y1,-r, 0,+1,0, c, 0,tex0),
102 video::S3DVertex(-r,y1,+r, 0,+1,0, c, 0,tex1),
103 video::S3DVertex(+r,y1,+r, 0,+1,0, c, 1,tex1),
104 video::S3DVertex(+r,y1,-r, 0,+1,0, c, 1,tex0),
106 u16 indices[12] = {0,1,2,2,3,0,4,5,6,6,7,4};
107 buf->append(vertices, 8, indices, 12);
110 // Define default material
111 video::SMaterial *material = &buf->getMaterial();
112 material->MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF;
113 material->BackfaceCulling = true;
114 material->setFlag(video::EMF_LIGHTING, false);
115 material->setFlag(video::EMF_BILINEAR_FILTER, false);
116 material->setFlag(video::EMF_TRILINEAR_FILTER, false);
117 // anisotropic filtering removes "thin black line" artifacts
118 material->setFlag(video::EMF_ANISOTROPIC_FILTER, true);
119 material->setFlag(video::EMF_TEXTURE_WRAP, false);
121 // Create mesh object
122 scene::SMesh *mesh = new scene::SMesh();
123 mesh->addMeshBuffer(buf);
125 scaleMesh(mesh, scale); // also recalculates bounding box
130 Caches extrusion meshes so that only one of them per resolution
131 is needed. Also caches one cube (for convenience).
133 E.g. there is a single extrusion mesh that is used for all
134 16x16 px images, another for all 256x256 px images, and so on.
136 WARNING: Not thread safe. This should not be a problem since
137 rendering related classes (such as WieldMeshSceneNode) will be
138 used from the rendering thread only.
140 class ExtrusionMeshCache: public IReferenceCounted
146 for (int resolution = MIN_EXTRUSION_MESH_RESOLUTION;
147 resolution <= MAX_EXTRUSION_MESH_RESOLUTION;
149 m_extrusion_meshes[resolution] =
150 createExtrusionMesh(resolution, resolution);
152 m_cube = createCubeMesh(v3f(1.0, 1.0, 1.0));
155 virtual ~ExtrusionMeshCache()
157 for (std::map<int, scene::IMesh*>::iterator
158 it = m_extrusion_meshes.begin();
159 it != m_extrusion_meshes.end(); ++it) {
164 // Get closest extrusion mesh for given image dimensions
165 // Caller must drop the returned pointer
166 scene::IMesh* create(core::dimension2d<u32> dim)
168 // handle non-power of two textures inefficiently without cache
169 if (!is_power_of_two(dim.Width) || !is_power_of_two(dim.Height)) {
170 return createExtrusionMesh(dim.Width, dim.Height);
173 int maxdim = MYMAX(dim.Width, dim.Height);
175 std::map<int, scene::IMesh*>::iterator
176 it = m_extrusion_meshes.lower_bound(maxdim);
178 if (it == m_extrusion_meshes.end()) {
179 // no viable resolution found; use largest one
180 it = m_extrusion_meshes.find(MAX_EXTRUSION_MESH_RESOLUTION);
181 assert(it != m_extrusion_meshes.end());
184 scene::IMesh *mesh = it->second;
188 // Returns a 1x1x1 cube mesh with one meshbuffer (material) per face
189 // Caller must drop the returned pointer
190 scene::IMesh* createCube()
197 std::map<int, scene::IMesh*> m_extrusion_meshes;
198 scene::IMesh *m_cube;
201 ExtrusionMeshCache *g_extrusion_mesh_cache = NULL;
204 WieldMeshSceneNode::WieldMeshSceneNode(
205 scene::ISceneNode *parent,
206 scene::ISceneManager *mgr,
210 scene::ISceneNode(parent, mgr, id),
212 m_lighting(lighting),
213 m_bounding_box(0.0, 0.0, 0.0, 0.0, 0.0, 0.0)
215 // If this is the first wield mesh scene node, create a cache
216 // for extrusion meshes (and a cube mesh), otherwise reuse it
217 if (g_extrusion_mesh_cache == NULL)
218 g_extrusion_mesh_cache = new ExtrusionMeshCache();
220 g_extrusion_mesh_cache->grab();
222 // Disable bounding box culling for this scene node
223 // since we won't calculate the bounding box.
224 setAutomaticCulling(scene::EAC_OFF);
226 // Create the child scene node
227 scene::IMesh *dummymesh = g_extrusion_mesh_cache->createCube();
228 m_meshnode = SceneManager->addMeshSceneNode(dummymesh, this, -1);
229 m_meshnode->setReadOnlyMaterials(false);
230 m_meshnode->setVisible(false);
231 dummymesh->drop(); // m_meshnode grabbed it
234 WieldMeshSceneNode::~WieldMeshSceneNode()
236 assert(g_extrusion_mesh_cache);
237 if (g_extrusion_mesh_cache->drop())
238 g_extrusion_mesh_cache = NULL;
241 void WieldMeshSceneNode::setCube(const TileSpec tiles[6],
242 v3f wield_scale, ITextureSource *tsrc)
244 scene::IMesh *cubemesh = g_extrusion_mesh_cache->createCube();
245 changeToMesh(cubemesh);
248 m_meshnode->setScale(wield_scale * WIELD_SCALE_FACTOR);
250 // Customize materials
251 for (u32 i = 0; i < m_meshnode->getMaterialCount(); ++i) {
253 video::SMaterial &material = m_meshnode->getMaterial(i);
254 material.setTexture(0, tiles[i].texture);
255 tiles[i].applyMaterialOptions(material);
259 void WieldMeshSceneNode::setExtruded(const std::string &imagename,
260 v3f wield_scale, ITextureSource *tsrc)
262 video::ITexture *texture = tsrc->getTexture(imagename);
268 scene::IMesh *mesh = g_extrusion_mesh_cache->create(texture->getSize());
272 m_meshnode->setScale(wield_scale * WIELD_SCALE_FACTOR_EXTRUDED);
274 // Customize material
275 assert(m_meshnode->getMaterialCount() == 1);
276 video::SMaterial &material = m_meshnode->getMaterial(0);
277 material.setTexture(0, texture);
280 void WieldMeshSceneNode::setItem(const ItemStack &item, IGameDef *gamedef)
282 ITextureSource *tsrc = gamedef->getTextureSource();
283 IItemDefManager *idef = gamedef->getItemDefManager();
285 const ItemDefinition &def = item.getDefinition(idef);
287 // If wield_image is defined, it overrides everything else
288 if (def.wield_image != "") {
289 setExtruded(def.wield_image, def.wield_scale, tsrc);
294 // See also CItemDefManager::createClientCached()
295 if (def.type == ITEM_NODE) {
296 INodeDefManager *ndef = gamedef->getNodeDefManager();
297 const ContentFeatures &f = ndef->get(def.name);
299 // e.g. mesh nodes and nodeboxes
300 changeToMesh(f.mesh_ptr[0]);
301 // mesh_ptr[0] is pre-scaled by BS * f->visual_scale
302 m_meshnode->setScale(
303 def.wield_scale * WIELD_SCALE_FACTOR
304 / (BS * f.visual_scale));
305 // Customize materials
306 for (u32 i = 0; i < m_meshnode->getMaterialCount(); ++i) {
308 video::SMaterial &material = m_meshnode->getMaterial(i);
309 material.setTexture(0, f.tiles[i].texture);
310 f.tiles[i].applyMaterialOptions(material);
313 } else if (f.drawtype == NDT_NORMAL || f.drawtype == NDT_ALLFACES) {
314 setCube(f.tiles, def.wield_scale, tsrc);
316 } else if (f.drawtype == NDT_AIRLIKE) {
321 // If none of the above standard cases worked, use the wield mesh from ClientCached
322 scene::IMesh *mesh = idef->getWieldMesh(item.name, gamedef);
325 m_meshnode->setScale(def.wield_scale * WIELD_SCALE_FACTOR);
330 // default to inventory_image
331 if (def.inventory_image != "") {
332 setExtruded(def.inventory_image, def.wield_scale, tsrc);
336 // no wield mesh found
340 void WieldMeshSceneNode::setColor(video::SColor color)
343 setMeshColor(m_meshnode->getMesh(), color);
346 void WieldMeshSceneNode::render()
348 // note: if this method is changed to actually do something,
349 // you probably should implement OnRegisterSceneNode as well
352 void WieldMeshSceneNode::changeToMesh(scene::IMesh *mesh)
355 scene::IMesh *dummymesh = g_extrusion_mesh_cache->createCube();
356 m_meshnode->setVisible(false);
357 m_meshnode->setMesh(dummymesh);
358 dummymesh->drop(); // m_meshnode grabbed it
362 m_meshnode->setMesh(mesh);
365 Lighting is disabled, this means the caller can (and probably will)
366 call setColor later. We therefore need to clone the mesh so that
367 setColor will only modify this scene node's mesh, not others'.
369 scene::IMeshManipulator *meshmanip = SceneManager->getMeshManipulator();
370 scene::IMesh *new_mesh = meshmanip->createMeshCopy(mesh);
371 m_meshnode->setMesh(new_mesh);
372 new_mesh->drop(); // m_meshnode grabbed it
375 m_meshnode->setMaterialFlag(video::EMF_LIGHTING, m_lighting);
376 // need to normalize normals when lighting is enabled (because of setScale())
377 m_meshnode->setMaterialFlag(video::EMF_NORMALIZE_NORMALS, m_lighting);
378 m_meshnode->setVisible(true);