3 Copyright (C) 2010-2013 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 "content_mapblock.h"
22 #include "util/numeric.h"
23 #include "util/directiontables.h"
24 #include "mapblock_mesh.h"
27 #include "client/tile.h"
29 #include <IMeshManipulator.h>
30 #include "client/meshgen/collector.h"
31 #include "client/renderingengine.h"
35 // Distance of light extrapolation (for oversized nodes)
36 // After this distance, it gives up and considers light level constant
37 #define SMOOTH_LIGHTING_OVERSIZE 1.0
39 // Node edge count (for glasslike-framed)
40 #define FRAMED_EDGE_COUNT 12
42 // Node neighbor count, including edge-connected, but not vertex-connected
43 // (for glasslike-framed)
44 // Corresponding offsets are listed in g_27dirs
45 #define FRAMED_NEIGHBOR_COUNT 18
47 static const v3s16 light_dirs[8] = {
58 // Standard index set to make a quad on 4 vertices
59 static constexpr u16 quad_indices[] = {0, 1, 2, 2, 3, 0};
61 const std::string MapblockMeshGenerator::raillike_groupname = "connect_to_raillike";
63 MapblockMeshGenerator::MapblockMeshGenerator(MeshMakeData *input, MeshCollector *output,
64 scene::IMeshManipulator *mm):
67 nodedef(data->m_client->ndef()),
69 blockpos_nodes(data->m_blockpos * MAP_BLOCKSIZE)
71 enable_mesh_cache = g_settings->getBool("enable_mesh_cache") &&
72 !data->m_smooth_lighting; // Mesh cache is not supported with smooth lighting
75 void MapblockMeshGenerator::useTile(int index, u8 set_flags, u8 reset_flags, bool special)
78 getSpecialTile(index, &tile, p == data->m_crack_pos_relative);
80 getTile(index, &tile);
81 if (!data->m_smooth_lighting)
82 color = encode_light(light, f->light_source);
84 for (auto &layer : tile.layers) {
85 layer.material_flags |= set_flags;
86 layer.material_flags &= ~reset_flags;
90 // Returns a tile, ready for use, non-rotated.
91 void MapblockMeshGenerator::getTile(int index, TileSpec *tile)
93 getNodeTileN(n, p, index, data, *tile);
96 // Returns a tile, ready for use, rotated according to the node facedir.
97 void MapblockMeshGenerator::getTile(v3s16 direction, TileSpec *tile)
99 getNodeTile(n, p, direction, data, *tile);
102 // Returns a special tile, ready for use, non-rotated.
103 void MapblockMeshGenerator::getSpecialTile(int index, TileSpec *tile, bool apply_crack)
105 *tile = f->special_tiles[index];
106 TileLayer *top_layer = nullptr;
108 for (auto &layernum : tile->layers) {
109 TileLayer *layer = &layernum;
110 if (layer->texture_id == 0)
113 if (!layer->has_color)
114 n.getColor(*f, &layer->color);
118 top_layer->material_flags |= MATERIAL_FLAG_CRACK;
121 void MapblockMeshGenerator::drawQuad(v3f *coords, const v3s16 &normal,
122 float vertical_tiling)
124 const v2f tcoords[4] = {v2f(0.0, 0.0), v2f(1.0, 0.0),
125 v2f(1.0, vertical_tiling), v2f(0.0, vertical_tiling)};
126 video::S3DVertex vertices[4];
127 bool shade_face = !f->light_source && (normal != v3s16(0, 0, 0));
128 v3f normal2(normal.X, normal.Y, normal.Z);
129 for (int j = 0; j < 4; j++) {
130 vertices[j].Pos = coords[j] + origin;
131 vertices[j].Normal = normal2;
132 if (data->m_smooth_lighting)
133 vertices[j].Color = blendLightColor(coords[j]);
135 vertices[j].Color = color;
137 applyFacesShading(vertices[j].Color, normal2);
138 vertices[j].TCoords = tcoords[j];
140 collector->append(tile, vertices, 4, quad_indices, 6);
144 // tiles - the tiles (materials) to use (for all 6 faces)
145 // tilecount - number of entries in tiles, 1<=tilecount<=6
146 // lights - vertex light levels. The order is the same as in light_dirs.
147 // NULL may be passed if smooth lighting is disabled.
148 // txc - texture coordinates - this is a list of texture coordinates
149 // for the opposite corners of each face - therefore, there
150 // should be (2+2)*6=24 values in the list. The order of
151 // the faces in the list is up-down-right-left-back-front
152 // (compatible with ContentFeatures).
153 void MapblockMeshGenerator::drawCuboid(const aabb3f &box,
154 TileSpec *tiles, int tilecount, const LightInfo *lights, const f32 *txc)
156 assert(tilecount >= 1 && tilecount <= 6); // pre-condition
158 v3f min = box.MinEdge;
159 v3f max = box.MaxEdge;
161 video::SColor colors[6];
162 if (!data->m_smooth_lighting) {
163 for (int face = 0; face != 6; ++face) {
164 colors[face] = encode_light(light, f->light_source);
166 if (!f->light_source) {
167 applyFacesShading(colors[0], v3f(0, 1, 0));
168 applyFacesShading(colors[1], v3f(0, -1, 0));
169 applyFacesShading(colors[2], v3f(1, 0, 0));
170 applyFacesShading(colors[3], v3f(-1, 0, 0));
171 applyFacesShading(colors[4], v3f(0, 0, 1));
172 applyFacesShading(colors[5], v3f(0, 0, -1));
176 video::S3DVertex vertices[24] = {
178 video::S3DVertex(min.X, max.Y, max.Z, 0, 1, 0, colors[0], txc[0], txc[1]),
179 video::S3DVertex(max.X, max.Y, max.Z, 0, 1, 0, colors[0], txc[2], txc[1]),
180 video::S3DVertex(max.X, max.Y, min.Z, 0, 1, 0, colors[0], txc[2], txc[3]),
181 video::S3DVertex(min.X, max.Y, min.Z, 0, 1, 0, colors[0], txc[0], txc[3]),
183 video::S3DVertex(min.X, min.Y, min.Z, 0, -1, 0, colors[1], txc[4], txc[5]),
184 video::S3DVertex(max.X, min.Y, min.Z, 0, -1, 0, colors[1], txc[6], txc[5]),
185 video::S3DVertex(max.X, min.Y, max.Z, 0, -1, 0, colors[1], txc[6], txc[7]),
186 video::S3DVertex(min.X, min.Y, max.Z, 0, -1, 0, colors[1], txc[4], txc[7]),
188 video::S3DVertex(max.X, max.Y, min.Z, 1, 0, 0, colors[2], txc[ 8], txc[9]),
189 video::S3DVertex(max.X, max.Y, max.Z, 1, 0, 0, colors[2], txc[10], txc[9]),
190 video::S3DVertex(max.X, min.Y, max.Z, 1, 0, 0, colors[2], txc[10], txc[11]),
191 video::S3DVertex(max.X, min.Y, min.Z, 1, 0, 0, colors[2], txc[ 8], txc[11]),
193 video::S3DVertex(min.X, max.Y, max.Z, -1, 0, 0, colors[3], txc[12], txc[13]),
194 video::S3DVertex(min.X, max.Y, min.Z, -1, 0, 0, colors[3], txc[14], txc[13]),
195 video::S3DVertex(min.X, min.Y, min.Z, -1, 0, 0, colors[3], txc[14], txc[15]),
196 video::S3DVertex(min.X, min.Y, max.Z, -1, 0, 0, colors[3], txc[12], txc[15]),
198 video::S3DVertex(max.X, max.Y, max.Z, 0, 0, 1, colors[4], txc[16], txc[17]),
199 video::S3DVertex(min.X, max.Y, max.Z, 0, 0, 1, colors[4], txc[18], txc[17]),
200 video::S3DVertex(min.X, min.Y, max.Z, 0, 0, 1, colors[4], txc[18], txc[19]),
201 video::S3DVertex(max.X, min.Y, max.Z, 0, 0, 1, colors[4], txc[16], txc[19]),
203 video::S3DVertex(min.X, max.Y, min.Z, 0, 0, -1, colors[5], txc[20], txc[21]),
204 video::S3DVertex(max.X, max.Y, min.Z, 0, 0, -1, colors[5], txc[22], txc[21]),
205 video::S3DVertex(max.X, min.Y, min.Z, 0, 0, -1, colors[5], txc[22], txc[23]),
206 video::S3DVertex(min.X, min.Y, min.Z, 0, 0, -1, colors[5], txc[20], txc[23]),
209 static const u8 light_indices[24] = {
218 for (int face = 0; face < 6; face++) {
219 int tileindex = MYMIN(face, tilecount - 1);
220 const TileSpec &tile = tiles[tileindex];
221 for (int j = 0; j < 4; j++) {
222 video::S3DVertex &vertex = vertices[face * 4 + j];
223 v2f &tcoords = vertex.TCoords;
224 switch (tile.rotation) {
228 tcoords.rotateBy(90, irr::core::vector2df(0, 0));
231 tcoords.rotateBy(180, irr::core::vector2df(0, 0));
234 tcoords.rotateBy(270, irr::core::vector2df(0, 0));
237 tcoords.X = 1.0 - tcoords.X;
238 tcoords.rotateBy(90, irr::core::vector2df(0, 0));
241 tcoords.X = 1.0 - tcoords.X;
242 tcoords.rotateBy(270, irr::core::vector2df(0, 0));
245 tcoords.Y = 1.0 - tcoords.Y;
246 tcoords.rotateBy(90, irr::core::vector2df(0, 0));
249 tcoords.Y = 1.0 - tcoords.Y;
250 tcoords.rotateBy(270, irr::core::vector2df(0, 0));
253 tcoords.X = 1.0 - tcoords.X;
256 tcoords.Y = 1.0 - tcoords.Y;
264 if (data->m_smooth_lighting) {
265 for (int j = 0; j < 24; ++j) {
266 video::S3DVertex &vertex = vertices[j];
267 vertex.Color = encode_light(
268 lights[light_indices[j]].getPair(MYMAX(0.0f, vertex.Normal.Y)),
270 if (!f->light_source)
271 applyFacesShading(vertex.Color, vertex.Normal);
275 // Add to mesh collector
276 for (int k = 0; k < 6; ++k) {
277 int tileindex = MYMIN(k, tilecount - 1);
278 collector->append(tiles[tileindex], vertices + 4 * k, 4, quad_indices, 6);
282 // Gets the base lighting values for a node
283 void MapblockMeshGenerator::getSmoothLightFrame()
285 for (int k = 0; k < 8; ++k)
286 frame.sunlight[k] = false;
287 for (int k = 0; k < 8; ++k) {
288 LightPair light(getSmoothLightTransparent(blockpos_nodes + p, light_dirs[k], data));
289 frame.lightsDay[k] = light.lightDay;
290 frame.lightsNight[k] = light.lightNight;
291 // If there is direct sunlight and no ambient occlusion at some corner,
292 // mark the vertical edge (top and bottom corners) containing it.
293 if (light.lightDay == 255) {
294 frame.sunlight[k] = true;
295 frame.sunlight[k ^ 2] = true;
300 // Calculates vertex light level
301 // vertex_pos - vertex position in the node (coordinates are clamped to [0.0, 1.0] or so)
302 LightInfo MapblockMeshGenerator::blendLight(const v3f &vertex_pos)
304 // Light levels at (logical) node corners are known. Here,
305 // trilinear interpolation is used to calculate light level
306 // at a given point in the node.
307 f32 x = core::clamp(vertex_pos.X / BS + 0.5, 0.0 - SMOOTH_LIGHTING_OVERSIZE, 1.0 + SMOOTH_LIGHTING_OVERSIZE);
308 f32 y = core::clamp(vertex_pos.Y / BS + 0.5, 0.0 - SMOOTH_LIGHTING_OVERSIZE, 1.0 + SMOOTH_LIGHTING_OVERSIZE);
309 f32 z = core::clamp(vertex_pos.Z / BS + 0.5, 0.0 - SMOOTH_LIGHTING_OVERSIZE, 1.0 + SMOOTH_LIGHTING_OVERSIZE);
310 f32 lightDay = 0.0; // daylight
311 f32 lightNight = 0.0;
312 f32 lightBoosted = 0.0; // daylight + direct sunlight, if any
313 for (int k = 0; k < 8; ++k) {
314 f32 dx = (k & 4) ? x : 1 - x;
315 f32 dy = (k & 2) ? y : 1 - y;
316 f32 dz = (k & 1) ? z : 1 - z;
317 // Use direct sunlight (255), if any; use daylight otherwise.
318 f32 light_boosted = frame.sunlight[k] ? 255 : frame.lightsDay[k];
319 lightDay += dx * dy * dz * frame.lightsDay[k];
320 lightNight += dx * dy * dz * frame.lightsNight[k];
321 lightBoosted += dx * dy * dz * light_boosted;
323 return LightInfo{lightDay, lightNight, lightBoosted};
326 // Calculates vertex color to be used in mapblock mesh
327 // vertex_pos - vertex position in the node (coordinates are clamped to [0.0, 1.0] or so)
328 // tile_color - node's tile color
329 video::SColor MapblockMeshGenerator::blendLightColor(const v3f &vertex_pos)
331 LightInfo light = blendLight(vertex_pos);
332 return encode_light(light.getPair(), f->light_source);
335 video::SColor MapblockMeshGenerator::blendLightColor(const v3f &vertex_pos,
336 const v3f &vertex_normal)
338 LightInfo light = blendLight(vertex_pos);
339 video::SColor color = encode_light(light.getPair(MYMAX(0.0f, vertex_normal.Y)), f->light_source);
340 if (!f->light_source)
341 applyFacesShading(color, vertex_normal);
345 void MapblockMeshGenerator::generateCuboidTextureCoords(const aabb3f &box, f32 *coords)
347 f32 tx1 = (box.MinEdge.X / BS) + 0.5;
348 f32 ty1 = (box.MinEdge.Y / BS) + 0.5;
349 f32 tz1 = (box.MinEdge.Z / BS) + 0.5;
350 f32 tx2 = (box.MaxEdge.X / BS) + 0.5;
351 f32 ty2 = (box.MaxEdge.Y / BS) + 0.5;
352 f32 tz2 = (box.MaxEdge.Z / BS) + 0.5;
354 tx1, 1 - tz2, tx2, 1 - tz1, // up
355 tx1, tz1, tx2, tz2, // down
356 tz1, 1 - ty2, tz2, 1 - ty1, // right
357 1 - tz2, 1 - ty2, 1 - tz1, 1 - ty1, // left
358 1 - tx2, 1 - ty2, 1 - tx1, 1 - ty1, // back
359 tx1, 1 - ty2, tx2, 1 - ty1, // front
361 for (int i = 0; i != 24; ++i)
365 void MapblockMeshGenerator::drawAutoLightedCuboid(aabb3f box, const f32 *txc,
366 TileSpec *tiles, int tile_count)
368 bool scale = std::fabs(f->visual_scale - 1.0f) > 1e-3f;
369 f32 texture_coord_buf[24];
370 f32 dx1 = box.MinEdge.X;
371 f32 dy1 = box.MinEdge.Y;
372 f32 dz1 = box.MinEdge.Z;
373 f32 dx2 = box.MaxEdge.X;
374 f32 dy2 = box.MaxEdge.Y;
375 f32 dz2 = box.MaxEdge.Z;
377 if (!txc) { // generate texture coords before scaling
378 generateCuboidTextureCoords(box, texture_coord_buf);
379 txc = texture_coord_buf;
381 box.MinEdge *= f->visual_scale;
382 box.MaxEdge *= f->visual_scale;
384 box.MinEdge += origin;
385 box.MaxEdge += origin;
387 generateCuboidTextureCoords(box, texture_coord_buf);
388 txc = texture_coord_buf;
394 if (data->m_smooth_lighting) {
396 for (int j = 0; j < 8; ++j) {
398 d.X = (j & 4) ? dx2 : dx1;
399 d.Y = (j & 2) ? dy2 : dy1;
400 d.Z = (j & 1) ? dz2 : dz1;
401 lights[j] = blendLight(d);
403 drawCuboid(box, tiles, tile_count, lights, txc);
405 drawCuboid(box, tiles, tile_count, nullptr, txc);
409 void MapblockMeshGenerator::prepareLiquidNodeDrawing()
411 getSpecialTile(0, &tile_liquid_top);
412 getSpecialTile(1, &tile_liquid);
414 MapNode ntop = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(p.X, p.Y + 1, p.Z));
415 MapNode nbottom = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(p.X, p.Y - 1, p.Z));
416 c_flowing = f->liquid_alternative_flowing_id;
417 c_source = f->liquid_alternative_source_id;
418 top_is_same_liquid = (ntop.getContent() == c_flowing) || (ntop.getContent() == c_source);
419 draw_liquid_bottom = (nbottom.getContent() != c_flowing) && (nbottom.getContent() != c_source);
420 if (draw_liquid_bottom) {
421 const ContentFeatures &f2 = nodedef->get(nbottom.getContent());
422 if (f2.solidness > 1)
423 draw_liquid_bottom = false;
426 if (data->m_smooth_lighting)
427 return; // don't need to pre-compute anything in this case
429 if (f->light_source != 0) {
430 // If this liquid emits light and doesn't contain light, draw
431 // it at what it emits, for an increased effect
432 u8 e = decode_light(f->light_source);
433 light = LightPair(std::max(e, light.lightDay), std::max(e, light.lightNight));
434 } else if (nodedef->get(ntop).param_type == CPT_LIGHT) {
435 // Otherwise, use the light of the node on top if possible
436 light = LightPair(getInteriorLight(ntop, 0, nodedef));
439 color_liquid_top = encode_light(light, f->light_source);
440 color = encode_light(light, f->light_source);
443 void MapblockMeshGenerator::getLiquidNeighborhood()
445 u8 range = rangelim(nodedef->get(c_flowing).liquid_range, 1, 8);
447 for (int w = -1; w <= 1; w++)
448 for (int u = -1; u <= 1; u++) {
449 NeighborData &neighbor = liquid_neighbors[w + 1][u + 1];
450 v3s16 p2 = p + v3s16(u, 0, w);
451 MapNode n2 = data->m_vmanip.getNodeNoEx(blockpos_nodes + p2);
452 neighbor.content = n2.getContent();
453 neighbor.level = -0.5 * BS;
454 neighbor.is_same_liquid = false;
455 neighbor.top_is_same_liquid = false;
457 if (neighbor.content == CONTENT_IGNORE)
460 if (neighbor.content == c_source) {
461 neighbor.is_same_liquid = true;
462 neighbor.level = 0.5 * BS;
463 } else if (neighbor.content == c_flowing) {
464 neighbor.is_same_liquid = true;
465 u8 liquid_level = (n2.param2 & LIQUID_LEVEL_MASK);
466 if (liquid_level <= LIQUID_LEVEL_MAX + 1 - range)
469 liquid_level -= (LIQUID_LEVEL_MAX + 1 - range);
470 neighbor.level = (-0.5 + (liquid_level + 0.5) / range) * BS;
473 // Check node above neighbor.
474 // NOTE: This doesn't get executed if neighbor
477 n2 = data->m_vmanip.getNodeNoEx(blockpos_nodes + p2);
478 if (n2.getContent() == c_source || n2.getContent() == c_flowing)
479 neighbor.top_is_same_liquid = true;
483 void MapblockMeshGenerator::calculateCornerLevels()
485 for (int k = 0; k < 2; k++)
486 for (int i = 0; i < 2; i++)
487 corner_levels[k][i] = getCornerLevel(i, k);
490 f32 MapblockMeshGenerator::getCornerLevel(int i, int k)
495 for (int dk = 0; dk < 2; dk++)
496 for (int di = 0; di < 2; di++) {
497 NeighborData &neighbor_data = liquid_neighbors[k + dk][i + di];
498 content_t content = neighbor_data.content;
500 // If top is liquid, draw starting from top of node
501 if (neighbor_data.top_is_same_liquid)
504 // Source always has the full height
505 if (content == c_source)
508 // Flowing liquid has level information
509 if (content == c_flowing) {
510 sum += neighbor_data.level;
512 } else if (content == CONTENT_AIR) {
517 return -0.5 * BS + 0.2;
524 struct LiquidFaceDesc {
526 v3s16 p[2]; // XZ only; 1 means +, 0 means -
531 static const LiquidFaceDesc liquid_base_faces[4] = {
532 {v3s16( 1, 0, 0), {v3s16(1, 0, 1), v3s16(1, 0, 0)}},
533 {v3s16(-1, 0, 0), {v3s16(0, 0, 0), v3s16(0, 0, 1)}},
534 {v3s16( 0, 0, 1), {v3s16(0, 0, 1), v3s16(1, 0, 1)}},
535 {v3s16( 0, 0, -1), {v3s16(1, 0, 0), v3s16(0, 0, 0)}},
537 static const UV liquid_base_vertices[4] = {
545 void MapblockMeshGenerator::drawLiquidSides()
547 for (const auto &face : liquid_base_faces) {
548 const NeighborData &neighbor = liquid_neighbors[face.dir.Z + 1][face.dir.X + 1];
550 // No face between nodes of the same liquid, unless there is node
551 // at the top to which it should be connected. Again, unless the face
552 // there would be inside the liquid
553 if (neighbor.is_same_liquid) {
554 if (!top_is_same_liquid)
556 if (neighbor.top_is_same_liquid)
560 const ContentFeatures &neighbor_features = nodedef->get(neighbor.content);
561 // Don't draw face if neighbor is blocking the view
562 if (neighbor_features.solidness == 2)
565 video::S3DVertex vertices[4];
566 for (int j = 0; j < 4; j++) {
567 const UV &vertex = liquid_base_vertices[j];
568 const v3s16 &base = face.p[vertex.u];
572 pos.X = (base.X - 0.5f) * BS;
573 pos.Z = (base.Z - 0.5f) * BS;
575 pos.Y = neighbor.is_same_liquid ? corner_levels[base.Z][base.X] : -0.5f * BS;
576 } else if (top_is_same_liquid) {
579 pos.Y = corner_levels[base.Z][base.X];
580 v += (0.5f * BS - corner_levels[base.Z][base.X]) / BS;
583 if (data->m_smooth_lighting)
584 color = blendLightColor(pos);
586 vertices[j] = video::S3DVertex(pos.X, pos.Y, pos.Z, 0, 0, 0, color, vertex.u, v);
588 collector->append(tile_liquid, vertices, 4, quad_indices, 6);
592 void MapblockMeshGenerator::drawLiquidTop()
594 // To get backface culling right, the vertices need to go
595 // clockwise around the front of the face. And we happened to
596 // calculate corner levels in exact reverse order.
597 static const int corner_resolve[4][2] = {{0, 1}, {1, 1}, {1, 0}, {0, 0}};
599 video::S3DVertex vertices[4] = {
600 video::S3DVertex(-BS / 2, 0, BS / 2, 0, 0, 0, color_liquid_top, 0, 1),
601 video::S3DVertex( BS / 2, 0, BS / 2, 0, 0, 0, color_liquid_top, 1, 1),
602 video::S3DVertex( BS / 2, 0, -BS / 2, 0, 0, 0, color_liquid_top, 1, 0),
603 video::S3DVertex(-BS / 2, 0, -BS / 2, 0, 0, 0, color_liquid_top, 0, 0),
606 for (int i = 0; i < 4; i++) {
607 int u = corner_resolve[i][0];
608 int w = corner_resolve[i][1];
609 vertices[i].Pos.Y += corner_levels[w][u];
610 if (data->m_smooth_lighting)
611 vertices[i].Color = blendLightColor(vertices[i].Pos);
612 vertices[i].Pos += origin;
615 // Default downwards-flowing texture animation goes from
616 // -Z towards +Z, thus the direction is +Z.
617 // Rotate texture to make animation go in flow direction
618 // Positive if liquid moves towards +Z
619 f32 dz = (corner_levels[0][0] + corner_levels[0][1]) -
620 (corner_levels[1][0] + corner_levels[1][1]);
621 // Positive if liquid moves towards +X
622 f32 dx = (corner_levels[0][0] + corner_levels[1][0]) -
623 (corner_levels[0][1] + corner_levels[1][1]);
624 f32 tcoord_angle = atan2(dz, dx) * core::RADTODEG;
625 v2f tcoord_center(0.5, 0.5);
626 v2f tcoord_translate(blockpos_nodes.Z + p.Z, blockpos_nodes.X + p.X);
627 tcoord_translate.rotateBy(tcoord_angle);
628 tcoord_translate.X -= floor(tcoord_translate.X);
629 tcoord_translate.Y -= floor(tcoord_translate.Y);
631 for (video::S3DVertex &vertex : vertices) {
632 vertex.TCoords.rotateBy(tcoord_angle, tcoord_center);
633 vertex.TCoords += tcoord_translate;
636 std::swap(vertices[0].TCoords, vertices[2].TCoords);
638 collector->append(tile_liquid_top, vertices, 4, quad_indices, 6);
641 void MapblockMeshGenerator::drawLiquidBottom()
643 video::S3DVertex vertices[4] = {
644 video::S3DVertex(-BS / 2, -BS / 2, -BS / 2, 0, 0, 0, color_liquid_top, 0, 0),
645 video::S3DVertex( BS / 2, -BS / 2, -BS / 2, 0, 0, 0, color_liquid_top, 1, 0),
646 video::S3DVertex( BS / 2, -BS / 2, BS / 2, 0, 0, 0, color_liquid_top, 1, 1),
647 video::S3DVertex(-BS / 2, -BS / 2, BS / 2, 0, 0, 0, color_liquid_top, 0, 1),
650 for (int i = 0; i < 4; i++) {
651 if (data->m_smooth_lighting)
652 vertices[i].Color = blendLightColor(vertices[i].Pos);
653 vertices[i].Pos += origin;
656 collector->append(tile_liquid_top, vertices, 4, quad_indices, 6);
659 void MapblockMeshGenerator::drawLiquidNode()
661 prepareLiquidNodeDrawing();
662 getLiquidNeighborhood();
663 calculateCornerLevels();
665 if (!top_is_same_liquid)
667 if (draw_liquid_bottom)
671 void MapblockMeshGenerator::drawGlasslikeNode()
675 for (int face = 0; face < 6; face++) {
676 // Check this neighbor
677 v3s16 dir = g_6dirs[face];
678 v3s16 neighbor_pos = blockpos_nodes + p + dir;
679 MapNode neighbor = data->m_vmanip.getNodeNoExNoEmerge(neighbor_pos);
680 // Don't make face if neighbor is of same type
681 if (neighbor.getContent() == n.getContent())
685 v3f(-BS / 2, BS / 2, -BS / 2),
686 v3f( BS / 2, BS / 2, -BS / 2),
687 v3f( BS / 2, -BS / 2, -BS / 2),
688 v3f(-BS / 2, -BS / 2, -BS / 2),
691 for (v3f &vertex : vertices) {
694 vertex.rotateXZBy(180); break;
696 vertex.rotateYZBy( 90); break;
698 vertex.rotateXZBy( 90); break;
700 vertex.rotateXZBy( 0); break;
702 vertex.rotateYZBy(-90); break;
704 vertex.rotateXZBy(-90); break;
707 drawQuad(vertices, dir);
711 void MapblockMeshGenerator::drawGlasslikeFramedNode()
714 for (int face = 0; face < 6; face++)
715 getTile(g_6dirs[face], &tiles[face]);
717 if (!data->m_smooth_lighting)
718 color = encode_light(light, f->light_source);
720 TileSpec glass_tiles[6];
721 for (auto &glass_tile : glass_tiles)
722 glass_tile = tiles[4];
724 // Only respect H/V merge bits when paramtype2 = "glasslikeliquidlevel" (liquid tank)
725 u8 param2 = (f->param_type_2 == CPT2_GLASSLIKE_LIQUID_LEVEL) ? n.getParam2() : 0;
726 bool H_merge = !(param2 & 128);
727 bool V_merge = !(param2 & 64);
730 static const float a = BS / 2.0f;
731 static const float g = a - 0.03f;
732 static const float b = 0.876f * (BS / 2.0f);
734 static const aabb3f frame_edges[FRAMED_EDGE_COUNT] = {
735 aabb3f( b, b, -a, a, a, a), // y+
736 aabb3f(-a, b, -a, -b, a, a), // y+
737 aabb3f( b, -a, -a, a, -b, a), // y-
738 aabb3f(-a, -a, -a, -b, -b, a), // y-
739 aabb3f( b, -a, b, a, a, a), // x+
740 aabb3f( b, -a, -a, a, a, -b), // x+
741 aabb3f(-a, -a, b, -b, a, a), // x-
742 aabb3f(-a, -a, -a, -b, a, -b), // x-
743 aabb3f(-a, b, b, a, a, a), // z+
744 aabb3f(-a, -a, b, a, -b, a), // z+
745 aabb3f(-a, -a, -a, a, -b, -b), // z-
746 aabb3f(-a, b, -a, a, a, -b), // z-
749 // tables of neighbour (connect if same type and merge allowed),
750 // checked with g_26dirs
752 // 1 = connect, 0 = face visible
753 bool nb[FRAMED_NEIGHBOR_COUNT] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
756 static const bool check_nb_vertical [FRAMED_NEIGHBOR_COUNT] =
757 {0,1,0,0,1,0, 0,0,0,0, 0,0,0,0, 0,0,0,0};
758 static const bool check_nb_horizontal [FRAMED_NEIGHBOR_COUNT] =
759 {1,0,1,1,0,1, 0,0,0,0, 1,1,1,1, 0,0,0,0};
760 static const bool check_nb_all [FRAMED_NEIGHBOR_COUNT] =
761 {1,1,1,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1};
762 const bool *check_nb = check_nb_all;
764 // neighbours checks for frames visibility
765 if (H_merge || V_merge) {
767 check_nb = check_nb_vertical; // vertical-only merge
769 check_nb = check_nb_horizontal; // horizontal-only merge
770 content_t current = n.getContent();
771 for (int i = 0; i < FRAMED_NEIGHBOR_COUNT; i++) {
774 v3s16 n2p = blockpos_nodes + p + g_26dirs[i];
775 MapNode n2 = data->m_vmanip.getNodeNoEx(n2p);
776 content_t n2c = n2.getContent();
784 static const u8 nb_triplet[FRAMED_EDGE_COUNT][3] = {
785 {1, 2, 7}, {1, 5, 6}, {4, 2, 15}, {4, 5, 14},
786 {2, 0, 11}, {2, 3, 13}, {5, 0, 10}, {5, 3, 12},
787 {0, 1, 8}, {0, 4, 16}, {3, 4, 17}, {3, 1, 9},
791 for (int edge = 0; edge < FRAMED_EDGE_COUNT; edge++) {
793 if (nb[nb_triplet[edge][2]])
794 edge_invisible = nb[nb_triplet[edge][0]] & nb[nb_triplet[edge][1]];
796 edge_invisible = nb[nb_triplet[edge][0]] ^ nb[nb_triplet[edge][1]];
799 drawAutoLightedCuboid(frame_edges[edge]);
802 for (int face = 0; face < 6; face++) {
806 tile = glass_tiles[face];
815 for (v3f &vertex : vertices) {
818 vertex.rotateXZBy(180); break;
820 vertex.rotateYZBy( 90); break;
822 vertex.rotateXZBy( 90); break;
824 vertex.rotateXZBy( 0); break;
826 vertex.rotateYZBy(-90); break;
828 vertex.rotateXZBy(-90); break;
831 v3s16 dir = g_6dirs[face];
832 drawQuad(vertices, dir);
835 // Optionally render internal liquid level defined by param2
836 // Liquid is textured with 1 tile defined in nodedef 'special_tiles'
837 if (param2 > 0 && f->param_type_2 == CPT2_GLASSLIKE_LIQUID_LEVEL &&
838 f->special_tiles[0].layers[0].texture) {
839 // Internal liquid level has param2 range 0 .. 63,
840 // convert it to -0.5 .. 0.5
841 float vlev = (param2 / 63.0f) * 2.0f - 1.0f;
842 getSpecialTile(0, &tile);
843 drawAutoLightedCuboid(aabb3f(-(nb[5] ? g : b),
847 (nb[1] ? g : b) * vlev,
852 void MapblockMeshGenerator::drawAllfacesNode()
854 static const aabb3f box(-BS / 2, -BS / 2, -BS / 2, BS / 2, BS / 2, BS / 2);
856 drawAutoLightedCuboid(box);
859 void MapblockMeshGenerator::drawTorchlikeNode()
861 u8 wall = n.getWallMounted(nodedef);
864 case DWM_YP: tileindex = 1; break; // ceiling
865 case DWM_YN: tileindex = 0; break; // floor
866 default: tileindex = 2; // side (or invalid—should we care?)
868 useTile(tileindex, MATERIAL_FLAG_CRACK_OVERLAY, MATERIAL_FLAG_BACKFACE_CULLING);
870 float size = BS / 2 * f->visual_scale;
874 v3f( size, -size, 0),
875 v3f(-size, -size, 0),
878 for (v3f &vertex : vertices) {
881 vertex.Y += -size + BS/2;
882 vertex.rotateXZBy(-45);
885 vertex.Y += size - BS/2;
886 vertex.rotateXZBy(45);
889 vertex.X += -size + BS/2;
892 vertex.X += -size + BS/2;
893 vertex.rotateXZBy(180);
896 vertex.X += -size + BS/2;
897 vertex.rotateXZBy(90);
900 vertex.X += -size + BS/2;
901 vertex.rotateXZBy(-90);
907 void MapblockMeshGenerator::drawSignlikeNode()
909 u8 wall = n.getWallMounted(nodedef);
910 useTile(0, MATERIAL_FLAG_CRACK_OVERLAY, MATERIAL_FLAG_BACKFACE_CULLING);
911 static const float offset = BS / 16;
912 float size = BS / 2 * f->visual_scale;
913 // Wall at X+ of node
915 v3f(BS / 2 - offset, size, size),
916 v3f(BS / 2 - offset, size, -size),
917 v3f(BS / 2 - offset, -size, -size),
918 v3f(BS / 2 - offset, -size, size),
921 for (v3f &vertex : vertices) {
924 vertex.rotateXYBy( 90); break;
926 vertex.rotateXYBy(-90); break;
928 vertex.rotateXZBy( 0); break;
930 vertex.rotateXZBy(180); break;
932 vertex.rotateXZBy( 90); break;
934 vertex.rotateXZBy(-90); break;
940 void MapblockMeshGenerator::drawPlantlikeQuad(float rotation, float quad_offset,
941 bool offset_top_only)
944 v3f(-scale, -BS / 2 + 2.0 * scale * plant_height, 0),
945 v3f( scale, -BS / 2 + 2.0 * scale * plant_height, 0),
946 v3f( scale, -BS / 2, 0),
947 v3f(-scale, -BS / 2, 0),
949 if (random_offset_Y) {
950 PseudoRandom yrng(face_num++ | p.X << 16 | p.Z << 8 | p.Y << 24);
951 offset.Y = -BS * ((yrng.next() % 16 / 16.0) * 0.125);
953 int offset_count = offset_top_only ? 2 : 4;
954 for (int i = 0; i < offset_count; i++)
955 vertices[i].Z += quad_offset;
957 for (v3f &vertex : vertices) {
958 vertex.rotateXZBy(rotation + rotate_degree);
961 drawQuad(vertices, v3s16(0, 0, 0), plant_height);
964 void MapblockMeshGenerator::drawPlantlike()
966 draw_style = PLANT_STYLE_CROSS;
967 scale = BS / 2 * f->visual_scale;
968 offset = v3f(0, 0, 0);
969 rotate_degree = 0.0f;
970 random_offset_Y = false;
974 switch (f->param_type_2) {
975 case CPT2_MESHOPTIONS:
976 draw_style = PlantlikeStyle(n.param2 & MO_MASK_STYLE);
977 if (n.param2 & MO_BIT_SCALE_SQRT2)
979 if (n.param2 & MO_BIT_RANDOM_OFFSET) {
980 PseudoRandom rng(p.X << 8 | p.Z | p.Y << 16);
981 offset.X = BS * ((rng.next() % 16 / 16.0) * 0.29 - 0.145);
982 offset.Z = BS * ((rng.next() % 16 / 16.0) * 0.29 - 0.145);
984 if (n.param2 & MO_BIT_RANDOM_OFFSET_Y)
985 random_offset_Y = true;
989 case CPT2_COLORED_DEGROTATE:
990 rotate_degree = 1.5f * n.getDegRotate(nodedef);
994 plant_height = n.param2 / 16.0;
1001 switch (draw_style) {
1002 case PLANT_STYLE_CROSS:
1003 drawPlantlikeQuad(46);
1004 drawPlantlikeQuad(-44);
1007 case PLANT_STYLE_CROSS2:
1008 drawPlantlikeQuad(91);
1009 drawPlantlikeQuad(1);
1012 case PLANT_STYLE_STAR:
1013 drawPlantlikeQuad(121);
1014 drawPlantlikeQuad(241);
1015 drawPlantlikeQuad(1);
1018 case PLANT_STYLE_HASH:
1019 drawPlantlikeQuad( 1, BS / 4);
1020 drawPlantlikeQuad( 91, BS / 4);
1021 drawPlantlikeQuad(181, BS / 4);
1022 drawPlantlikeQuad(271, BS / 4);
1025 case PLANT_STYLE_HASH2:
1026 drawPlantlikeQuad( 1, -BS / 2, true);
1027 drawPlantlikeQuad( 91, -BS / 2, true);
1028 drawPlantlikeQuad(181, -BS / 2, true);
1029 drawPlantlikeQuad(271, -BS / 2, true);
1034 void MapblockMeshGenerator::drawPlantlikeNode()
1040 void MapblockMeshGenerator::drawPlantlikeRootedNode()
1042 useTile(0, MATERIAL_FLAG_CRACK_OVERLAY, 0, true);
1043 origin += v3f(0.0, BS, 0.0);
1045 if (data->m_smooth_lighting) {
1046 getSmoothLightFrame();
1048 MapNode ntop = data->m_vmanip.getNodeNoEx(blockpos_nodes + p);
1049 light = LightPair(getInteriorLight(ntop, 1, nodedef));
1055 void MapblockMeshGenerator::drawFirelikeQuad(float rotation, float opening_angle,
1056 float offset_h, float offset_v)
1059 v3f(-scale, -BS / 2 + scale * 2, 0),
1060 v3f( scale, -BS / 2 + scale * 2, 0),
1061 v3f( scale, -BS / 2, 0),
1062 v3f(-scale, -BS / 2, 0),
1065 for (v3f &vertex : vertices) {
1066 vertex.rotateYZBy(opening_angle);
1067 vertex.Z += offset_h;
1068 vertex.rotateXZBy(rotation);
1069 vertex.Y += offset_v;
1074 void MapblockMeshGenerator::drawFirelikeNode()
1077 scale = BS / 2 * f->visual_scale;
1079 // Check for adjacent nodes
1080 bool neighbors = false;
1081 bool neighbor[6] = {0, 0, 0, 0, 0, 0};
1082 content_t current = n.getContent();
1083 for (int i = 0; i < 6; i++) {
1084 v3s16 n2p = blockpos_nodes + p + g_6dirs[i];
1085 MapNode n2 = data->m_vmanip.getNodeNoEx(n2p);
1086 content_t n2c = n2.getContent();
1087 if (n2c != CONTENT_IGNORE && n2c != CONTENT_AIR && n2c != current) {
1092 bool drawBasicFire = neighbor[D6D_YN] || !neighbors;
1093 bool drawBottomFire = neighbor[D6D_YP];
1095 if (drawBasicFire || neighbor[D6D_ZP])
1096 drawFirelikeQuad(0, -10, 0.4 * BS);
1097 else if (drawBottomFire)
1098 drawFirelikeQuad(0, 70, 0.47 * BS, 0.484 * BS);
1100 if (drawBasicFire || neighbor[D6D_XN])
1101 drawFirelikeQuad(90, -10, 0.4 * BS);
1102 else if (drawBottomFire)
1103 drawFirelikeQuad(90, 70, 0.47 * BS, 0.484 * BS);
1105 if (drawBasicFire || neighbor[D6D_ZN])
1106 drawFirelikeQuad(180, -10, 0.4 * BS);
1107 else if (drawBottomFire)
1108 drawFirelikeQuad(180, 70, 0.47 * BS, 0.484 * BS);
1110 if (drawBasicFire || neighbor[D6D_XP])
1111 drawFirelikeQuad(270, -10, 0.4 * BS);
1112 else if (drawBottomFire)
1113 drawFirelikeQuad(270, 70, 0.47 * BS, 0.484 * BS);
1115 if (drawBasicFire) {
1116 drawFirelikeQuad(45, 0, 0.0);
1117 drawFirelikeQuad(-45, 0, 0.0);
1121 void MapblockMeshGenerator::drawFencelikeNode()
1124 TileSpec tile_nocrack = tile;
1126 for (auto &layer : tile_nocrack.layers)
1127 layer.material_flags &= ~MATERIAL_FLAG_CRACK;
1129 // Put wood the right way around in the posts
1130 TileSpec tile_rot = tile;
1131 tile_rot.rotation = 1;
1133 static const f32 post_rad = BS / 8;
1134 static const f32 bar_rad = BS / 16;
1135 static const f32 bar_len = BS / 2 - post_rad;
1137 // The post - always present
1138 static const aabb3f post(-post_rad, -BS / 2, -post_rad,
1139 post_rad, BS / 2, post_rad);
1140 static const f32 postuv[24] = {
1141 0.375, 0.375, 0.625, 0.625,
1142 0.375, 0.375, 0.625, 0.625,
1143 0.000, 0.000, 0.250, 1.000,
1144 0.250, 0.000, 0.500, 1.000,
1145 0.500, 0.000, 0.750, 1.000,
1146 0.750, 0.000, 1.000, 1.000,
1149 drawAutoLightedCuboid(post, postuv);
1151 tile = tile_nocrack;
1153 // Now a section of fence, +X, if there's a post there
1156 MapNode n2 = data->m_vmanip.getNodeNoEx(blockpos_nodes + p2);
1157 const ContentFeatures *f2 = &nodedef->get(n2);
1158 if (f2->drawtype == NDT_FENCELIKE) {
1159 static const aabb3f bar_x1(BS / 2 - bar_len, BS / 4 - bar_rad, -bar_rad,
1160 BS / 2 + bar_len, BS / 4 + bar_rad, bar_rad);
1161 static const aabb3f bar_x2(BS / 2 - bar_len, -BS / 4 - bar_rad, -bar_rad,
1162 BS / 2 + bar_len, -BS / 4 + bar_rad, bar_rad);
1163 static const f32 xrailuv[24] = {
1164 0.000, 0.125, 1.000, 0.250,
1165 0.000, 0.250, 1.000, 0.375,
1166 0.375, 0.375, 0.500, 0.500,
1167 0.625, 0.625, 0.750, 0.750,
1168 0.000, 0.500, 1.000, 0.625,
1169 0.000, 0.875, 1.000, 1.000,
1171 drawAutoLightedCuboid(bar_x1, xrailuv);
1172 drawAutoLightedCuboid(bar_x2, xrailuv);
1175 // Now a section of fence, +Z, if there's a post there
1178 n2 = data->m_vmanip.getNodeNoEx(blockpos_nodes + p2);
1179 f2 = &nodedef->get(n2);
1180 if (f2->drawtype == NDT_FENCELIKE) {
1181 static const aabb3f bar_z1(-bar_rad, BS / 4 - bar_rad, BS / 2 - bar_len,
1182 bar_rad, BS / 4 + bar_rad, BS / 2 + bar_len);
1183 static const aabb3f bar_z2(-bar_rad, -BS / 4 - bar_rad, BS / 2 - bar_len,
1184 bar_rad, -BS / 4 + bar_rad, BS / 2 + bar_len);
1185 static const f32 zrailuv[24] = {
1186 0.1875, 0.0625, 0.3125, 0.3125, // cannot rotate; stretch
1187 0.2500, 0.0625, 0.3750, 0.3125, // for wood texture instead
1188 0.0000, 0.5625, 1.0000, 0.6875,
1189 0.0000, 0.3750, 1.0000, 0.5000,
1190 0.3750, 0.3750, 0.5000, 0.5000,
1191 0.6250, 0.6250, 0.7500, 0.7500,
1193 drawAutoLightedCuboid(bar_z1, zrailuv);
1194 drawAutoLightedCuboid(bar_z2, zrailuv);
1198 bool MapblockMeshGenerator::isSameRail(v3s16 dir)
1200 MapNode node2 = data->m_vmanip.getNodeNoEx(blockpos_nodes + p + dir);
1201 if (node2.getContent() == n.getContent())
1203 const ContentFeatures &def2 = nodedef->get(node2);
1204 return ((def2.drawtype == NDT_RAILLIKE) &&
1205 (def2.getGroup(raillike_groupname) == raillike_group));
1209 static const v3s16 rail_direction[4] = {
1215 static const int rail_slope_angle[4] = {0, 180, 90, -90};
1227 static const RailDesc rail_kinds[16] = {
1230 {straight, 0}, // . . . .
1231 {straight, 0}, // . . . +Z
1232 {straight, 0}, // . . -Z .
1233 {straight, 0}, // . . -Z +Z
1234 {straight, 90}, // . -X . .
1235 { curved, 180}, // . -X . +Z
1236 { curved, 270}, // . -X -Z .
1237 {junction, 180}, // . -X -Z +Z
1238 {straight, 90}, // +X . . .
1239 { curved, 90}, // +X . . +Z
1240 { curved, 0}, // +X . -Z .
1241 {junction, 0}, // +X . -Z +Z
1242 {straight, 90}, // +X -X . .
1243 {junction, 90}, // +X -X . +Z
1244 {junction, 270}, // +X -X -Z .
1245 { cross, 0}, // +X -X -Z +Z
1249 void MapblockMeshGenerator::drawRaillikeNode()
1251 raillike_group = nodedef->get(n).getGroup(raillike_groupname);
1256 bool sloped = false;
1257 for (int dir = 0; dir < 4; dir++) {
1258 bool rail_above = isSameRail(rail_direction[dir] + v3s16(0, 1, 0));
1261 angle = rail_slope_angle[dir];
1264 isSameRail(rail_direction[dir]) ||
1265 isSameRail(rail_direction[dir] + v3s16(0, -1, 0)))
1270 tile_index = straight;
1272 tile_index = rail_kinds[code].tile_index;
1273 angle = rail_kinds[code].angle;
1276 useTile(tile_index, MATERIAL_FLAG_CRACK_OVERLAY, MATERIAL_FLAG_BACKFACE_CULLING);
1278 static const float offset = BS / 64;
1279 static const float size = BS / 2;
1280 float y2 = sloped ? size : -size;
1282 v3f(-size, y2 + offset, size),
1283 v3f( size, y2 + offset, size),
1284 v3f( size, -size + offset, -size),
1285 v3f(-size, -size + offset, -size),
1288 for (v3f &vertex : vertices)
1289 vertex.rotateXZBy(angle);
1294 static const v3s16 nodebox_tile_dirs[6] = {
1303 // we have this order for some reason...
1304 static const v3s16 nodebox_connection_dirs[6] = {
1305 v3s16( 0, 1, 0), // top
1306 v3s16( 0, -1, 0), // bottom
1307 v3s16( 0, 0, -1), // front
1308 v3s16(-1, 0, 0), // left
1309 v3s16( 0, 0, 1), // back
1310 v3s16( 1, 0, 0), // right
1314 void MapblockMeshGenerator::drawNodeboxNode()
1317 for (int face = 0; face < 6; face++) {
1318 // Handles facedir rotation for textures
1319 getTile(nodebox_tile_dirs[face], &tiles[face]);
1322 // locate possible neighboring nodes to connect to
1323 u8 neighbors_set = 0;
1324 if (f->node_box.type == NODEBOX_CONNECTED) {
1325 for (int dir = 0; dir != 6; dir++) {
1327 v3s16 p2 = blockpos_nodes + p + nodebox_connection_dirs[dir];
1328 MapNode n2 = data->m_vmanip.getNodeNoEx(p2);
1329 if (nodedef->nodeboxConnects(n, n2, flag))
1330 neighbors_set |= flag;
1334 std::vector<aabb3f> boxes;
1335 n.getNodeBoxes(nodedef, &boxes, neighbors_set);
1336 for (auto &box : boxes)
1337 drawAutoLightedCuboid(box, nullptr, tiles, 6);
1340 void MapblockMeshGenerator::drawMeshNode()
1344 bool private_mesh; // as a grab/drop pair is not thread-safe
1347 if (f->param_type_2 == CPT2_FACEDIR ||
1348 f->param_type_2 == CPT2_COLORED_FACEDIR) {
1349 facedir = n.getFaceDir(nodedef);
1350 } else if (f->param_type_2 == CPT2_WALLMOUNTED ||
1351 f->param_type_2 == CPT2_COLORED_WALLMOUNTED) {
1352 // Convert wallmounted to 6dfacedir.
1353 // When cache enabled, it is already converted.
1354 facedir = n.getWallMounted(nodedef);
1355 if (!enable_mesh_cache)
1356 facedir = wallmounted_to_facedir[facedir];
1357 } else if (f->param_type_2 == CPT2_DEGROTATE ||
1358 f->param_type_2 == CPT2_COLORED_DEGROTATE) {
1359 degrotate = n.getDegRotate(nodedef);
1362 if (!data->m_smooth_lighting && f->mesh_ptr[facedir] && !degrotate) {
1363 // use cached meshes
1364 private_mesh = false;
1365 mesh = f->mesh_ptr[facedir];
1366 } else if (f->mesh_ptr[0]) {
1367 // no cache, clone and rotate mesh
1368 private_mesh = true;
1369 mesh = cloneMesh(f->mesh_ptr[0]);
1371 rotateMeshBy6dFacedir(mesh, facedir);
1373 rotateMeshXZby(mesh, 1.5f * degrotate);
1374 recalculateBoundingBox(mesh);
1375 meshmanip->recalculateNormals(mesh, true, false);
1379 int mesh_buffer_count = mesh->getMeshBufferCount();
1380 for (int j = 0; j < mesh_buffer_count; j++) {
1382 scene::IMeshBuffer *buf = mesh->getMeshBuffer(j);
1383 video::S3DVertex *vertices = (video::S3DVertex *)buf->getVertices();
1384 int vertex_count = buf->getVertexCount();
1386 if (data->m_smooth_lighting) {
1387 // Mesh is always private here. So the lighting is applied to each
1388 // vertex right here.
1389 for (int k = 0; k < vertex_count; k++) {
1390 video::S3DVertex &vertex = vertices[k];
1391 vertex.Color = blendLightColor(vertex.Pos, vertex.Normal);
1392 vertex.Pos += origin;
1394 collector->append(tile, vertices, vertex_count,
1395 buf->getIndices(), buf->getIndexCount());
1397 // Don't modify the mesh, it may not be private here.
1398 // Instead, let the collector process colors, etc.
1399 collector->append(tile, vertices, vertex_count,
1400 buf->getIndices(), buf->getIndexCount(), origin,
1401 color, f->light_source);
1408 // also called when the drawtype is known but should have been pre-converted
1409 void MapblockMeshGenerator::errorUnknownDrawtype()
1411 infostream << "Got drawtype " << f->drawtype << std::endl;
1412 FATAL_ERROR("Unknown drawtype");
1415 void MapblockMeshGenerator::drawNode()
1417 // skip some drawtypes early
1418 switch (f->drawtype) {
1419 case NDT_NORMAL: // Drawn by MapBlockMesh
1420 case NDT_AIRLIKE: // Not drawn at all
1421 case NDT_LIQUID: // Drawn by MapBlockMesh
1426 origin = intToFloat(p, BS);
1427 if (data->m_smooth_lighting)
1428 getSmoothLightFrame();
1430 light = LightPair(getInteriorLight(n, 1, nodedef));
1431 switch (f->drawtype) {
1432 case NDT_FLOWINGLIQUID: drawLiquidNode(); break;
1433 case NDT_GLASSLIKE: drawGlasslikeNode(); break;
1434 case NDT_GLASSLIKE_FRAMED: drawGlasslikeFramedNode(); break;
1435 case NDT_ALLFACES: drawAllfacesNode(); break;
1436 case NDT_TORCHLIKE: drawTorchlikeNode(); break;
1437 case NDT_SIGNLIKE: drawSignlikeNode(); break;
1438 case NDT_PLANTLIKE: drawPlantlikeNode(); break;
1439 case NDT_PLANTLIKE_ROOTED: drawPlantlikeRootedNode(); break;
1440 case NDT_FIRELIKE: drawFirelikeNode(); break;
1441 case NDT_FENCELIKE: drawFencelikeNode(); break;
1442 case NDT_RAILLIKE: drawRaillikeNode(); break;
1443 case NDT_NODEBOX: drawNodeboxNode(); break;
1444 case NDT_MESH: drawMeshNode(); break;
1445 default: errorUnknownDrawtype(); break;
1450 TODO: Fix alpha blending for special nodes
1451 Currently only the last element rendered is blended correct
1453 void MapblockMeshGenerator::generate()
1455 for (p.Z = 0; p.Z < MAP_BLOCKSIZE; p.Z++)
1456 for (p.Y = 0; p.Y < MAP_BLOCKSIZE; p.Y++)
1457 for (p.X = 0; p.X < MAP_BLOCKSIZE; p.X++) {
1458 n = data->m_vmanip.getNodeNoEx(blockpos_nodes + p);
1459 f = &nodedef->get(n);
1464 void MapblockMeshGenerator::renderSingle(content_t node, u8 param2)
1467 n = MapNode(node, 0xff, param2);
1468 f = &nodedef->get(n);