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)
68 nodedef = data->m_client->ndef();
69 meshmanip = RenderingEngine::get_scene_manager()->getMeshManipulator();
71 enable_mesh_cache = g_settings->getBool("enable_mesh_cache") &&
72 !data->m_smooth_lighting; // Mesh cache is not supported with smooth lighting
74 blockpos_nodes = data->m_blockpos * MAP_BLOCKSIZE;
77 void MapblockMeshGenerator::useTile(int index, u8 set_flags, u8 reset_flags, bool special)
80 getSpecialTile(index, &tile, p == data->m_crack_pos_relative);
82 getTile(index, &tile);
83 if (!data->m_smooth_lighting)
84 color = encode_light(light, f->light_source);
86 for (auto &layer : tile.layers) {
87 layer.material_flags |= set_flags;
88 layer.material_flags &= ~reset_flags;
92 // Returns a tile, ready for use, non-rotated.
93 void MapblockMeshGenerator::getTile(int index, TileSpec *tile)
95 getNodeTileN(n, p, index, data, *tile);
98 // Returns a tile, ready for use, rotated according to the node facedir.
99 void MapblockMeshGenerator::getTile(v3s16 direction, TileSpec *tile)
101 getNodeTile(n, p, direction, data, *tile);
104 // Returns a special tile, ready for use, non-rotated.
105 void MapblockMeshGenerator::getSpecialTile(int index, TileSpec *tile, bool apply_crack)
107 *tile = f->special_tiles[index];
108 TileLayer *top_layer = nullptr;
110 for (auto &layernum : tile->layers) {
111 TileLayer *layer = &layernum;
112 if (layer->texture_id == 0)
115 if (!layer->has_color)
116 n.getColor(*f, &layer->color);
120 top_layer->material_flags |= MATERIAL_FLAG_CRACK;
123 void MapblockMeshGenerator::drawQuad(v3f *coords, const v3s16 &normal,
124 float vertical_tiling)
126 const v2f tcoords[4] = {v2f(0.0, 0.0), v2f(1.0, 0.0),
127 v2f(1.0, vertical_tiling), v2f(0.0, vertical_tiling)};
128 video::S3DVertex vertices[4];
129 bool shade_face = !f->light_source && (normal != v3s16(0, 0, 0));
130 v3f normal2(normal.X, normal.Y, normal.Z);
131 for (int j = 0; j < 4; j++) {
132 vertices[j].Pos = coords[j] + origin;
133 vertices[j].Normal = normal2;
134 if (data->m_smooth_lighting)
135 vertices[j].Color = blendLightColor(coords[j]);
137 vertices[j].Color = color;
139 applyFacesShading(vertices[j].Color, normal2);
140 vertices[j].TCoords = tcoords[j];
142 collector->append(tile, vertices, 4, quad_indices, 6);
146 // tiles - the tiles (materials) to use (for all 6 faces)
147 // tilecount - number of entries in tiles, 1<=tilecount<=6
148 // lights - vertex light levels. The order is the same as in light_dirs.
149 // NULL may be passed if smooth lighting is disabled.
150 // txc - texture coordinates - this is a list of texture coordinates
151 // for the opposite corners of each face - therefore, there
152 // should be (2+2)*6=24 values in the list. The order of
153 // the faces in the list is up-down-right-left-back-front
154 // (compatible with ContentFeatures).
155 void MapblockMeshGenerator::drawCuboid(const aabb3f &box,
156 TileSpec *tiles, int tilecount, const LightInfo *lights, const f32 *txc)
158 assert(tilecount >= 1 && tilecount <= 6); // pre-condition
160 v3f min = box.MinEdge;
161 v3f max = box.MaxEdge;
163 video::SColor colors[6];
164 if (!data->m_smooth_lighting) {
165 for (int face = 0; face != 6; ++face) {
166 colors[face] = encode_light(light, f->light_source);
168 if (!f->light_source) {
169 applyFacesShading(colors[0], v3f(0, 1, 0));
170 applyFacesShading(colors[1], v3f(0, -1, 0));
171 applyFacesShading(colors[2], v3f(1, 0, 0));
172 applyFacesShading(colors[3], v3f(-1, 0, 0));
173 applyFacesShading(colors[4], v3f(0, 0, 1));
174 applyFacesShading(colors[5], v3f(0, 0, -1));
178 video::S3DVertex vertices[24] = {
180 video::S3DVertex(min.X, max.Y, max.Z, 0, 1, 0, colors[0], txc[0], txc[1]),
181 video::S3DVertex(max.X, max.Y, max.Z, 0, 1, 0, colors[0], txc[2], txc[1]),
182 video::S3DVertex(max.X, max.Y, min.Z, 0, 1, 0, colors[0], txc[2], txc[3]),
183 video::S3DVertex(min.X, max.Y, min.Z, 0, 1, 0, colors[0], txc[0], txc[3]),
185 video::S3DVertex(min.X, min.Y, min.Z, 0, -1, 0, colors[1], txc[4], txc[5]),
186 video::S3DVertex(max.X, min.Y, min.Z, 0, -1, 0, colors[1], txc[6], txc[5]),
187 video::S3DVertex(max.X, min.Y, max.Z, 0, -1, 0, colors[1], txc[6], txc[7]),
188 video::S3DVertex(min.X, min.Y, max.Z, 0, -1, 0, colors[1], txc[4], txc[7]),
190 video::S3DVertex(max.X, max.Y, min.Z, 1, 0, 0, colors[2], txc[ 8], txc[9]),
191 video::S3DVertex(max.X, max.Y, max.Z, 1, 0, 0, colors[2], txc[10], txc[9]),
192 video::S3DVertex(max.X, min.Y, max.Z, 1, 0, 0, colors[2], txc[10], txc[11]),
193 video::S3DVertex(max.X, min.Y, min.Z, 1, 0, 0, colors[2], txc[ 8], txc[11]),
195 video::S3DVertex(min.X, max.Y, max.Z, -1, 0, 0, colors[3], txc[12], txc[13]),
196 video::S3DVertex(min.X, max.Y, min.Z, -1, 0, 0, colors[3], txc[14], txc[13]),
197 video::S3DVertex(min.X, min.Y, min.Z, -1, 0, 0, colors[3], txc[14], txc[15]),
198 video::S3DVertex(min.X, min.Y, max.Z, -1, 0, 0, colors[3], txc[12], txc[15]),
200 video::S3DVertex(max.X, max.Y, max.Z, 0, 0, 1, colors[4], txc[16], txc[17]),
201 video::S3DVertex(min.X, max.Y, max.Z, 0, 0, 1, colors[4], txc[18], txc[17]),
202 video::S3DVertex(min.X, min.Y, max.Z, 0, 0, 1, colors[4], txc[18], txc[19]),
203 video::S3DVertex(max.X, min.Y, max.Z, 0, 0, 1, colors[4], txc[16], txc[19]),
205 video::S3DVertex(min.X, max.Y, min.Z, 0, 0, -1, colors[5], txc[20], txc[21]),
206 video::S3DVertex(max.X, max.Y, min.Z, 0, 0, -1, colors[5], txc[22], txc[21]),
207 video::S3DVertex(max.X, min.Y, min.Z, 0, 0, -1, colors[5], txc[22], txc[23]),
208 video::S3DVertex(min.X, min.Y, min.Z, 0, 0, -1, colors[5], txc[20], txc[23]),
211 static const u8 light_indices[24] = {
220 for (int face = 0; face < 6; face++) {
221 int tileindex = MYMIN(face, tilecount - 1);
222 const TileSpec &tile = tiles[tileindex];
223 for (int j = 0; j < 4; j++) {
224 video::S3DVertex &vertex = vertices[face * 4 + j];
225 v2f &tcoords = vertex.TCoords;
226 switch (tile.rotation) {
230 tcoords.rotateBy(90, irr::core::vector2df(0, 0));
233 tcoords.rotateBy(180, irr::core::vector2df(0, 0));
236 tcoords.rotateBy(270, irr::core::vector2df(0, 0));
239 tcoords.X = 1.0 - tcoords.X;
240 tcoords.rotateBy(90, irr::core::vector2df(0, 0));
243 tcoords.X = 1.0 - tcoords.X;
244 tcoords.rotateBy(270, irr::core::vector2df(0, 0));
247 tcoords.Y = 1.0 - tcoords.Y;
248 tcoords.rotateBy(90, irr::core::vector2df(0, 0));
251 tcoords.Y = 1.0 - tcoords.Y;
252 tcoords.rotateBy(270, irr::core::vector2df(0, 0));
255 tcoords.X = 1.0 - tcoords.X;
258 tcoords.Y = 1.0 - tcoords.Y;
266 if (data->m_smooth_lighting) {
267 for (int j = 0; j < 24; ++j) {
268 video::S3DVertex &vertex = vertices[j];
269 vertex.Color = encode_light(
270 lights[light_indices[j]].getPair(MYMAX(0.0f, vertex.Normal.Y)),
272 if (!f->light_source)
273 applyFacesShading(vertex.Color, vertex.Normal);
277 // Add to mesh collector
278 for (int k = 0; k < 6; ++k) {
279 int tileindex = MYMIN(k, tilecount - 1);
280 collector->append(tiles[tileindex], vertices + 4 * k, 4, quad_indices, 6);
284 // Gets the base lighting values for a node
285 void MapblockMeshGenerator::getSmoothLightFrame()
287 for (int k = 0; k < 8; ++k)
288 frame.sunlight[k] = false;
289 for (int k = 0; k < 8; ++k) {
290 LightPair light(getSmoothLightTransparent(blockpos_nodes + p, light_dirs[k], data));
291 frame.lightsDay[k] = light.lightDay;
292 frame.lightsNight[k] = light.lightNight;
293 // If there is direct sunlight and no ambient occlusion at some corner,
294 // mark the vertical edge (top and bottom corners) containing it.
295 if (light.lightDay == 255) {
296 frame.sunlight[k] = true;
297 frame.sunlight[k ^ 2] = true;
302 // Calculates vertex light level
303 // vertex_pos - vertex position in the node (coordinates are clamped to [0.0, 1.0] or so)
304 LightInfo MapblockMeshGenerator::blendLight(const v3f &vertex_pos)
306 // Light levels at (logical) node corners are known. Here,
307 // trilinear interpolation is used to calculate light level
308 // at a given point in the node.
309 f32 x = core::clamp(vertex_pos.X / BS + 0.5, 0.0 - SMOOTH_LIGHTING_OVERSIZE, 1.0 + SMOOTH_LIGHTING_OVERSIZE);
310 f32 y = core::clamp(vertex_pos.Y / BS + 0.5, 0.0 - SMOOTH_LIGHTING_OVERSIZE, 1.0 + SMOOTH_LIGHTING_OVERSIZE);
311 f32 z = core::clamp(vertex_pos.Z / BS + 0.5, 0.0 - SMOOTH_LIGHTING_OVERSIZE, 1.0 + SMOOTH_LIGHTING_OVERSIZE);
312 f32 lightDay = 0.0; // daylight
313 f32 lightNight = 0.0;
314 f32 lightBoosted = 0.0; // daylight + direct sunlight, if any
315 for (int k = 0; k < 8; ++k) {
316 f32 dx = (k & 4) ? x : 1 - x;
317 f32 dy = (k & 2) ? y : 1 - y;
318 f32 dz = (k & 1) ? z : 1 - z;
319 // Use direct sunlight (255), if any; use daylight otherwise.
320 f32 light_boosted = frame.sunlight[k] ? 255 : frame.lightsDay[k];
321 lightDay += dx * dy * dz * frame.lightsDay[k];
322 lightNight += dx * dy * dz * frame.lightsNight[k];
323 lightBoosted += dx * dy * dz * light_boosted;
325 return LightInfo{lightDay, lightNight, lightBoosted};
328 // Calculates vertex color to be used in mapblock mesh
329 // vertex_pos - vertex position in the node (coordinates are clamped to [0.0, 1.0] or so)
330 // tile_color - node's tile color
331 video::SColor MapblockMeshGenerator::blendLightColor(const v3f &vertex_pos)
333 LightInfo light = blendLight(vertex_pos);
334 return encode_light(light.getPair(), f->light_source);
337 video::SColor MapblockMeshGenerator::blendLightColor(const v3f &vertex_pos,
338 const v3f &vertex_normal)
340 LightInfo light = blendLight(vertex_pos);
341 video::SColor color = encode_light(light.getPair(MYMAX(0.0f, vertex_normal.Y)), f->light_source);
342 if (!f->light_source)
343 applyFacesShading(color, vertex_normal);
347 void MapblockMeshGenerator::generateCuboidTextureCoords(const aabb3f &box, f32 *coords)
349 f32 tx1 = (box.MinEdge.X / BS) + 0.5;
350 f32 ty1 = (box.MinEdge.Y / BS) + 0.5;
351 f32 tz1 = (box.MinEdge.Z / BS) + 0.5;
352 f32 tx2 = (box.MaxEdge.X / BS) + 0.5;
353 f32 ty2 = (box.MaxEdge.Y / BS) + 0.5;
354 f32 tz2 = (box.MaxEdge.Z / BS) + 0.5;
356 tx1, 1 - tz2, tx2, 1 - tz1, // up
357 tx1, tz1, tx2, tz2, // down
358 tz1, 1 - ty2, tz2, 1 - ty1, // right
359 1 - tz2, 1 - ty2, 1 - tz1, 1 - ty1, // left
360 1 - tx2, 1 - ty2, 1 - tx1, 1 - ty1, // back
361 tx1, 1 - ty2, tx2, 1 - ty1, // front
363 for (int i = 0; i != 24; ++i)
367 void MapblockMeshGenerator::drawAutoLightedCuboid(aabb3f box, const f32 *txc,
368 TileSpec *tiles, int tile_count)
370 bool scale = std::fabs(f->visual_scale - 1.0f) > 1e-3f;
371 f32 texture_coord_buf[24];
372 f32 dx1 = box.MinEdge.X;
373 f32 dy1 = box.MinEdge.Y;
374 f32 dz1 = box.MinEdge.Z;
375 f32 dx2 = box.MaxEdge.X;
376 f32 dy2 = box.MaxEdge.Y;
377 f32 dz2 = box.MaxEdge.Z;
379 if (!txc) { // generate texture coords before scaling
380 generateCuboidTextureCoords(box, texture_coord_buf);
381 txc = texture_coord_buf;
383 box.MinEdge *= f->visual_scale;
384 box.MaxEdge *= f->visual_scale;
386 box.MinEdge += origin;
387 box.MaxEdge += origin;
389 generateCuboidTextureCoords(box, texture_coord_buf);
390 txc = texture_coord_buf;
396 if (data->m_smooth_lighting) {
398 for (int j = 0; j < 8; ++j) {
400 d.X = (j & 4) ? dx2 : dx1;
401 d.Y = (j & 2) ? dy2 : dy1;
402 d.Z = (j & 1) ? dz2 : dz1;
403 lights[j] = blendLight(d);
405 drawCuboid(box, tiles, tile_count, lights, txc);
407 drawCuboid(box, tiles, tile_count, nullptr, txc);
411 void MapblockMeshGenerator::prepareLiquidNodeDrawing()
413 getSpecialTile(0, &tile_liquid_top);
414 getSpecialTile(1, &tile_liquid);
416 MapNode ntop = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(p.X, p.Y + 1, p.Z));
417 MapNode nbottom = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(p.X, p.Y - 1, p.Z));
418 c_flowing = f->liquid_alternative_flowing_id;
419 c_source = f->liquid_alternative_source_id;
420 top_is_same_liquid = (ntop.getContent() == c_flowing) || (ntop.getContent() == c_source);
421 draw_liquid_bottom = (nbottom.getContent() != c_flowing) && (nbottom.getContent() != c_source);
422 if (draw_liquid_bottom) {
423 const ContentFeatures &f2 = nodedef->get(nbottom.getContent());
424 if (f2.solidness > 1)
425 draw_liquid_bottom = false;
428 if (data->m_smooth_lighting)
429 return; // don't need to pre-compute anything in this case
431 if (f->light_source != 0) {
432 // If this liquid emits light and doesn't contain light, draw
433 // it at what it emits, for an increased effect
434 u8 e = decode_light(f->light_source);
435 light = LightPair(std::max(e, light.lightDay), std::max(e, light.lightNight));
436 } else if (nodedef->get(ntop).param_type == CPT_LIGHT) {
437 // Otherwise, use the light of the node on top if possible
438 light = LightPair(getInteriorLight(ntop, 0, nodedef));
441 color_liquid_top = encode_light(light, f->light_source);
442 color = encode_light(light, f->light_source);
445 void MapblockMeshGenerator::getLiquidNeighborhood()
447 u8 range = rangelim(nodedef->get(c_flowing).liquid_range, 1, 8);
449 for (int w = -1; w <= 1; w++)
450 for (int u = -1; u <= 1; u++) {
451 NeighborData &neighbor = liquid_neighbors[w + 1][u + 1];
452 v3s16 p2 = p + v3s16(u, 0, w);
453 MapNode n2 = data->m_vmanip.getNodeNoEx(blockpos_nodes + p2);
454 neighbor.content = n2.getContent();
455 neighbor.level = -0.5 * BS;
456 neighbor.is_same_liquid = false;
457 neighbor.top_is_same_liquid = false;
459 if (neighbor.content == CONTENT_IGNORE)
462 if (neighbor.content == c_source) {
463 neighbor.is_same_liquid = true;
464 neighbor.level = 0.5 * BS;
465 } else if (neighbor.content == c_flowing) {
466 neighbor.is_same_liquid = true;
467 u8 liquid_level = (n2.param2 & LIQUID_LEVEL_MASK);
468 if (liquid_level <= LIQUID_LEVEL_MAX + 1 - range)
471 liquid_level -= (LIQUID_LEVEL_MAX + 1 - range);
472 neighbor.level = (-0.5 + (liquid_level + 0.5) / range) * BS;
475 // Check node above neighbor.
476 // NOTE: This doesn't get executed if neighbor
479 n2 = data->m_vmanip.getNodeNoEx(blockpos_nodes + p2);
480 if (n2.getContent() == c_source || n2.getContent() == c_flowing)
481 neighbor.top_is_same_liquid = true;
485 void MapblockMeshGenerator::calculateCornerLevels()
487 for (int k = 0; k < 2; k++)
488 for (int i = 0; i < 2; i++)
489 corner_levels[k][i] = getCornerLevel(i, k);
492 f32 MapblockMeshGenerator::getCornerLevel(int i, int k)
497 for (int dk = 0; dk < 2; dk++)
498 for (int di = 0; di < 2; di++) {
499 NeighborData &neighbor_data = liquid_neighbors[k + dk][i + di];
500 content_t content = neighbor_data.content;
502 // If top is liquid, draw starting from top of node
503 if (neighbor_data.top_is_same_liquid)
506 // Source always has the full height
507 if (content == c_source)
510 // Flowing liquid has level information
511 if (content == c_flowing) {
512 sum += neighbor_data.level;
514 } else if (content == CONTENT_AIR) {
519 return -0.5 * BS + 0.2;
526 struct LiquidFaceDesc {
528 v3s16 p[2]; // XZ only; 1 means +, 0 means -
533 static const LiquidFaceDesc liquid_base_faces[4] = {
534 {v3s16( 1, 0, 0), {v3s16(1, 0, 1), v3s16(1, 0, 0)}},
535 {v3s16(-1, 0, 0), {v3s16(0, 0, 0), v3s16(0, 0, 1)}},
536 {v3s16( 0, 0, 1), {v3s16(0, 0, 1), v3s16(1, 0, 1)}},
537 {v3s16( 0, 0, -1), {v3s16(1, 0, 0), v3s16(0, 0, 0)}},
539 static const UV liquid_base_vertices[4] = {
547 void MapblockMeshGenerator::drawLiquidSides()
549 for (const auto &face : liquid_base_faces) {
550 const NeighborData &neighbor = liquid_neighbors[face.dir.Z + 1][face.dir.X + 1];
552 // No face between nodes of the same liquid, unless there is node
553 // at the top to which it should be connected. Again, unless the face
554 // there would be inside the liquid
555 if (neighbor.is_same_liquid) {
556 if (!top_is_same_liquid)
558 if (neighbor.top_is_same_liquid)
562 const ContentFeatures &neighbor_features = nodedef->get(neighbor.content);
563 // Don't draw face if neighbor is blocking the view
564 if (neighbor_features.solidness == 2)
567 video::S3DVertex vertices[4];
568 for (int j = 0; j < 4; j++) {
569 const UV &vertex = liquid_base_vertices[j];
570 const v3s16 &base = face.p[vertex.u];
574 pos.X = (base.X - 0.5f) * BS;
575 pos.Z = (base.Z - 0.5f) * BS;
577 pos.Y = neighbor.is_same_liquid ? corner_levels[base.Z][base.X] : -0.5f * BS;
578 } else if (top_is_same_liquid) {
581 pos.Y = corner_levels[base.Z][base.X];
582 v += (0.5f * BS - corner_levels[base.Z][base.X]) / BS;
585 if (data->m_smooth_lighting)
586 color = blendLightColor(pos);
588 vertices[j] = video::S3DVertex(pos.X, pos.Y, pos.Z, 0, 0, 0, color, vertex.u, v);
590 collector->append(tile_liquid, vertices, 4, quad_indices, 6);
594 void MapblockMeshGenerator::drawLiquidTop()
596 // To get backface culling right, the vertices need to go
597 // clockwise around the front of the face. And we happened to
598 // calculate corner levels in exact reverse order.
599 static const int corner_resolve[4][2] = {{0, 1}, {1, 1}, {1, 0}, {0, 0}};
601 video::S3DVertex vertices[4] = {
602 video::S3DVertex(-BS / 2, 0, BS / 2, 0, 0, 0, color_liquid_top, 0, 1),
603 video::S3DVertex( BS / 2, 0, BS / 2, 0, 0, 0, color_liquid_top, 1, 1),
604 video::S3DVertex( BS / 2, 0, -BS / 2, 0, 0, 0, color_liquid_top, 1, 0),
605 video::S3DVertex(-BS / 2, 0, -BS / 2, 0, 0, 0, color_liquid_top, 0, 0),
608 for (int i = 0; i < 4; i++) {
609 int u = corner_resolve[i][0];
610 int w = corner_resolve[i][1];
611 vertices[i].Pos.Y += corner_levels[w][u];
612 if (data->m_smooth_lighting)
613 vertices[i].Color = blendLightColor(vertices[i].Pos);
614 vertices[i].Pos += origin;
617 // Default downwards-flowing texture animation goes from
618 // -Z towards +Z, thus the direction is +Z.
619 // Rotate texture to make animation go in flow direction
620 // Positive if liquid moves towards +Z
621 f32 dz = (corner_levels[0][0] + corner_levels[0][1]) -
622 (corner_levels[1][0] + corner_levels[1][1]);
623 // Positive if liquid moves towards +X
624 f32 dx = (corner_levels[0][0] + corner_levels[1][0]) -
625 (corner_levels[0][1] + corner_levels[1][1]);
626 f32 tcoord_angle = atan2(dz, dx) * core::RADTODEG;
627 v2f tcoord_center(0.5, 0.5);
628 v2f tcoord_translate(blockpos_nodes.Z + p.Z, blockpos_nodes.X + p.X);
629 tcoord_translate.rotateBy(tcoord_angle);
630 tcoord_translate.X -= floor(tcoord_translate.X);
631 tcoord_translate.Y -= floor(tcoord_translate.Y);
633 for (video::S3DVertex &vertex : vertices) {
634 vertex.TCoords.rotateBy(tcoord_angle, tcoord_center);
635 vertex.TCoords += tcoord_translate;
638 std::swap(vertices[0].TCoords, vertices[2].TCoords);
640 collector->append(tile_liquid_top, vertices, 4, quad_indices, 6);
643 void MapblockMeshGenerator::drawLiquidBottom()
645 video::S3DVertex vertices[4] = {
646 video::S3DVertex(-BS / 2, -BS / 2, -BS / 2, 0, 0, 0, color_liquid_top, 0, 0),
647 video::S3DVertex( BS / 2, -BS / 2, -BS / 2, 0, 0, 0, color_liquid_top, 1, 0),
648 video::S3DVertex( BS / 2, -BS / 2, BS / 2, 0, 0, 0, color_liquid_top, 1, 1),
649 video::S3DVertex(-BS / 2, -BS / 2, BS / 2, 0, 0, 0, color_liquid_top, 0, 1),
652 for (int i = 0; i < 4; i++) {
653 if (data->m_smooth_lighting)
654 vertices[i].Color = blendLightColor(vertices[i].Pos);
655 vertices[i].Pos += origin;
658 collector->append(tile_liquid_top, vertices, 4, quad_indices, 6);
661 void MapblockMeshGenerator::drawLiquidNode()
663 prepareLiquidNodeDrawing();
664 getLiquidNeighborhood();
665 calculateCornerLevels();
667 if (!top_is_same_liquid)
669 if (draw_liquid_bottom)
673 void MapblockMeshGenerator::drawGlasslikeNode()
677 for (int face = 0; face < 6; face++) {
678 // Check this neighbor
679 v3s16 dir = g_6dirs[face];
680 v3s16 neighbor_pos = blockpos_nodes + p + dir;
681 MapNode neighbor = data->m_vmanip.getNodeNoExNoEmerge(neighbor_pos);
682 // Don't make face if neighbor is of same type
683 if (neighbor.getContent() == n.getContent())
687 v3f(-BS / 2, BS / 2, -BS / 2),
688 v3f( BS / 2, BS / 2, -BS / 2),
689 v3f( BS / 2, -BS / 2, -BS / 2),
690 v3f(-BS / 2, -BS / 2, -BS / 2),
693 for (v3f &vertex : vertices) {
696 vertex.rotateXZBy(180); break;
698 vertex.rotateYZBy( 90); break;
700 vertex.rotateXZBy( 90); break;
702 vertex.rotateXZBy( 0); break;
704 vertex.rotateYZBy(-90); break;
706 vertex.rotateXZBy(-90); break;
709 drawQuad(vertices, dir);
713 void MapblockMeshGenerator::drawGlasslikeFramedNode()
716 for (int face = 0; face < 6; face++)
717 getTile(g_6dirs[face], &tiles[face]);
719 if (!data->m_smooth_lighting)
720 color = encode_light(light, f->light_source);
722 TileSpec glass_tiles[6];
723 for (auto &glass_tile : glass_tiles)
724 glass_tile = tiles[4];
726 // Only respect H/V merge bits when paramtype2 = "glasslikeliquidlevel" (liquid tank)
727 u8 param2 = (f->param_type_2 == CPT2_GLASSLIKE_LIQUID_LEVEL) ? n.getParam2() : 0;
728 bool H_merge = !(param2 & 128);
729 bool V_merge = !(param2 & 64);
732 static const float a = BS / 2.0f;
733 static const float g = a - 0.03f;
734 static const float b = 0.876f * (BS / 2.0f);
736 static const aabb3f frame_edges[FRAMED_EDGE_COUNT] = {
737 aabb3f( b, b, -a, a, a, a), // y+
738 aabb3f(-a, b, -a, -b, a, a), // y+
739 aabb3f( b, -a, -a, a, -b, a), // y-
740 aabb3f(-a, -a, -a, -b, -b, a), // y-
741 aabb3f( b, -a, b, a, a, a), // x+
742 aabb3f( b, -a, -a, a, a, -b), // x+
743 aabb3f(-a, -a, b, -b, a, a), // x-
744 aabb3f(-a, -a, -a, -b, a, -b), // x-
745 aabb3f(-a, b, b, a, a, a), // z+
746 aabb3f(-a, -a, b, a, -b, a), // z+
747 aabb3f(-a, -a, -a, a, -b, -b), // z-
748 aabb3f(-a, b, -a, a, a, -b), // z-
751 // tables of neighbour (connect if same type and merge allowed),
752 // checked with g_26dirs
754 // 1 = connect, 0 = face visible
755 bool nb[FRAMED_NEIGHBOR_COUNT] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
758 static const bool check_nb_vertical [FRAMED_NEIGHBOR_COUNT] =
759 {0,1,0,0,1,0, 0,0,0,0, 0,0,0,0, 0,0,0,0};
760 static const bool check_nb_horizontal [FRAMED_NEIGHBOR_COUNT] =
761 {1,0,1,1,0,1, 0,0,0,0, 1,1,1,1, 0,0,0,0};
762 static const bool check_nb_all [FRAMED_NEIGHBOR_COUNT] =
763 {1,1,1,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1};
764 const bool *check_nb = check_nb_all;
766 // neighbours checks for frames visibility
767 if (H_merge || V_merge) {
769 check_nb = check_nb_vertical; // vertical-only merge
771 check_nb = check_nb_horizontal; // horizontal-only merge
772 content_t current = n.getContent();
773 for (int i = 0; i < FRAMED_NEIGHBOR_COUNT; i++) {
776 v3s16 n2p = blockpos_nodes + p + g_26dirs[i];
777 MapNode n2 = data->m_vmanip.getNodeNoEx(n2p);
778 content_t n2c = n2.getContent();
786 static const u8 nb_triplet[FRAMED_EDGE_COUNT][3] = {
787 {1, 2, 7}, {1, 5, 6}, {4, 2, 15}, {4, 5, 14},
788 {2, 0, 11}, {2, 3, 13}, {5, 0, 10}, {5, 3, 12},
789 {0, 1, 8}, {0, 4, 16}, {3, 4, 17}, {3, 1, 9},
793 for (int edge = 0; edge < FRAMED_EDGE_COUNT; edge++) {
795 if (nb[nb_triplet[edge][2]])
796 edge_invisible = nb[nb_triplet[edge][0]] & nb[nb_triplet[edge][1]];
798 edge_invisible = nb[nb_triplet[edge][0]] ^ nb[nb_triplet[edge][1]];
801 drawAutoLightedCuboid(frame_edges[edge]);
804 for (int face = 0; face < 6; face++) {
808 tile = glass_tiles[face];
817 for (v3f &vertex : vertices) {
820 vertex.rotateXZBy(180); break;
822 vertex.rotateYZBy( 90); break;
824 vertex.rotateXZBy( 90); break;
826 vertex.rotateXZBy( 0); break;
828 vertex.rotateYZBy(-90); break;
830 vertex.rotateXZBy(-90); break;
833 v3s16 dir = g_6dirs[face];
834 drawQuad(vertices, dir);
837 // Optionally render internal liquid level defined by param2
838 // Liquid is textured with 1 tile defined in nodedef 'special_tiles'
839 if (param2 > 0 && f->param_type_2 == CPT2_GLASSLIKE_LIQUID_LEVEL &&
840 f->special_tiles[0].layers[0].texture) {
841 // Internal liquid level has param2 range 0 .. 63,
842 // convert it to -0.5 .. 0.5
843 float vlev = (param2 / 63.0f) * 2.0f - 1.0f;
844 getSpecialTile(0, &tile);
845 drawAutoLightedCuboid(aabb3f(-(nb[5] ? g : b),
849 (nb[1] ? g : b) * vlev,
854 void MapblockMeshGenerator::drawAllfacesNode()
856 static const aabb3f box(-BS / 2, -BS / 2, -BS / 2, BS / 2, BS / 2, BS / 2);
858 drawAutoLightedCuboid(box);
861 void MapblockMeshGenerator::drawTorchlikeNode()
863 u8 wall = n.getWallMounted(nodedef);
866 case DWM_YP: tileindex = 1; break; // ceiling
867 case DWM_YN: tileindex = 0; break; // floor
868 default: tileindex = 2; // side (or invalid—should we care?)
870 useTile(tileindex, MATERIAL_FLAG_CRACK_OVERLAY, MATERIAL_FLAG_BACKFACE_CULLING);
872 float size = BS / 2 * f->visual_scale;
876 v3f( size, -size, 0),
877 v3f(-size, -size, 0),
880 for (v3f &vertex : vertices) {
883 vertex.Y += -size + BS/2;
884 vertex.rotateXZBy(-45);
887 vertex.Y += size - BS/2;
888 vertex.rotateXZBy(45);
891 vertex.X += -size + BS/2;
894 vertex.X += -size + BS/2;
895 vertex.rotateXZBy(180);
898 vertex.X += -size + BS/2;
899 vertex.rotateXZBy(90);
902 vertex.X += -size + BS/2;
903 vertex.rotateXZBy(-90);
909 void MapblockMeshGenerator::drawSignlikeNode()
911 u8 wall = n.getWallMounted(nodedef);
912 useTile(0, MATERIAL_FLAG_CRACK_OVERLAY, MATERIAL_FLAG_BACKFACE_CULLING);
913 static const float offset = BS / 16;
914 float size = BS / 2 * f->visual_scale;
915 // Wall at X+ of node
917 v3f(BS / 2 - offset, size, size),
918 v3f(BS / 2 - offset, size, -size),
919 v3f(BS / 2 - offset, -size, -size),
920 v3f(BS / 2 - offset, -size, size),
923 for (v3f &vertex : vertices) {
926 vertex.rotateXYBy( 90); break;
928 vertex.rotateXYBy(-90); break;
930 vertex.rotateXZBy( 0); break;
932 vertex.rotateXZBy(180); break;
934 vertex.rotateXZBy( 90); break;
936 vertex.rotateXZBy(-90); break;
942 void MapblockMeshGenerator::drawPlantlikeQuad(float rotation, float quad_offset,
943 bool offset_top_only)
946 v3f(-scale, -BS / 2 + 2.0 * scale * plant_height, 0),
947 v3f( scale, -BS / 2 + 2.0 * scale * plant_height, 0),
948 v3f( scale, -BS / 2, 0),
949 v3f(-scale, -BS / 2, 0),
951 if (random_offset_Y) {
952 PseudoRandom yrng(face_num++ | p.X << 16 | p.Z << 8 | p.Y << 24);
953 offset.Y = -BS * ((yrng.next() % 16 / 16.0) * 0.125);
955 int offset_count = offset_top_only ? 2 : 4;
956 for (int i = 0; i < offset_count; i++)
957 vertices[i].Z += quad_offset;
959 for (v3f &vertex : vertices) {
960 vertex.rotateXZBy(rotation + rotate_degree);
963 drawQuad(vertices, v3s16(0, 0, 0), plant_height);
966 void MapblockMeshGenerator::drawPlantlike()
968 draw_style = PLANT_STYLE_CROSS;
969 scale = BS / 2 * f->visual_scale;
970 offset = v3f(0, 0, 0);
971 rotate_degree = 0.0f;
972 random_offset_Y = false;
976 switch (f->param_type_2) {
977 case CPT2_MESHOPTIONS:
978 draw_style = PlantlikeStyle(n.param2 & MO_MASK_STYLE);
979 if (n.param2 & MO_BIT_SCALE_SQRT2)
981 if (n.param2 & MO_BIT_RANDOM_OFFSET) {
982 PseudoRandom rng(p.X << 8 | p.Z | p.Y << 16);
983 offset.X = BS * ((rng.next() % 16 / 16.0) * 0.29 - 0.145);
984 offset.Z = BS * ((rng.next() % 16 / 16.0) * 0.29 - 0.145);
986 if (n.param2 & MO_BIT_RANDOM_OFFSET_Y)
987 random_offset_Y = true;
991 case CPT2_COLORED_DEGROTATE:
992 rotate_degree = 1.5f * n.getDegRotate(nodedef);
996 plant_height = n.param2 / 16.0;
1003 switch (draw_style) {
1004 case PLANT_STYLE_CROSS:
1005 drawPlantlikeQuad(46);
1006 drawPlantlikeQuad(-44);
1009 case PLANT_STYLE_CROSS2:
1010 drawPlantlikeQuad(91);
1011 drawPlantlikeQuad(1);
1014 case PLANT_STYLE_STAR:
1015 drawPlantlikeQuad(121);
1016 drawPlantlikeQuad(241);
1017 drawPlantlikeQuad(1);
1020 case PLANT_STYLE_HASH:
1021 drawPlantlikeQuad( 1, BS / 4);
1022 drawPlantlikeQuad( 91, BS / 4);
1023 drawPlantlikeQuad(181, BS / 4);
1024 drawPlantlikeQuad(271, BS / 4);
1027 case PLANT_STYLE_HASH2:
1028 drawPlantlikeQuad( 1, -BS / 2, true);
1029 drawPlantlikeQuad( 91, -BS / 2, true);
1030 drawPlantlikeQuad(181, -BS / 2, true);
1031 drawPlantlikeQuad(271, -BS / 2, true);
1036 void MapblockMeshGenerator::drawPlantlikeNode()
1042 void MapblockMeshGenerator::drawPlantlikeRootedNode()
1044 useTile(0, MATERIAL_FLAG_CRACK_OVERLAY, 0, true);
1045 origin += v3f(0.0, BS, 0.0);
1047 if (data->m_smooth_lighting) {
1048 getSmoothLightFrame();
1050 MapNode ntop = data->m_vmanip.getNodeNoEx(blockpos_nodes + p);
1051 light = LightPair(getInteriorLight(ntop, 1, nodedef));
1057 void MapblockMeshGenerator::drawFirelikeQuad(float rotation, float opening_angle,
1058 float offset_h, float offset_v)
1061 v3f(-scale, -BS / 2 + scale * 2, 0),
1062 v3f( scale, -BS / 2 + scale * 2, 0),
1063 v3f( scale, -BS / 2, 0),
1064 v3f(-scale, -BS / 2, 0),
1067 for (v3f &vertex : vertices) {
1068 vertex.rotateYZBy(opening_angle);
1069 vertex.Z += offset_h;
1070 vertex.rotateXZBy(rotation);
1071 vertex.Y += offset_v;
1076 void MapblockMeshGenerator::drawFirelikeNode()
1079 scale = BS / 2 * f->visual_scale;
1081 // Check for adjacent nodes
1082 bool neighbors = false;
1083 bool neighbor[6] = {0, 0, 0, 0, 0, 0};
1084 content_t current = n.getContent();
1085 for (int i = 0; i < 6; i++) {
1086 v3s16 n2p = blockpos_nodes + p + g_6dirs[i];
1087 MapNode n2 = data->m_vmanip.getNodeNoEx(n2p);
1088 content_t n2c = n2.getContent();
1089 if (n2c != CONTENT_IGNORE && n2c != CONTENT_AIR && n2c != current) {
1094 bool drawBasicFire = neighbor[D6D_YN] || !neighbors;
1095 bool drawBottomFire = neighbor[D6D_YP];
1097 if (drawBasicFire || neighbor[D6D_ZP])
1098 drawFirelikeQuad(0, -10, 0.4 * BS);
1099 else if (drawBottomFire)
1100 drawFirelikeQuad(0, 70, 0.47 * BS, 0.484 * BS);
1102 if (drawBasicFire || neighbor[D6D_XN])
1103 drawFirelikeQuad(90, -10, 0.4 * BS);
1104 else if (drawBottomFire)
1105 drawFirelikeQuad(90, 70, 0.47 * BS, 0.484 * BS);
1107 if (drawBasicFire || neighbor[D6D_ZN])
1108 drawFirelikeQuad(180, -10, 0.4 * BS);
1109 else if (drawBottomFire)
1110 drawFirelikeQuad(180, 70, 0.47 * BS, 0.484 * BS);
1112 if (drawBasicFire || neighbor[D6D_XP])
1113 drawFirelikeQuad(270, -10, 0.4 * BS);
1114 else if (drawBottomFire)
1115 drawFirelikeQuad(270, 70, 0.47 * BS, 0.484 * BS);
1117 if (drawBasicFire) {
1118 drawFirelikeQuad(45, 0, 0.0);
1119 drawFirelikeQuad(-45, 0, 0.0);
1123 void MapblockMeshGenerator::drawFencelikeNode()
1126 TileSpec tile_nocrack = tile;
1128 for (auto &layer : tile_nocrack.layers)
1129 layer.material_flags &= ~MATERIAL_FLAG_CRACK;
1131 // Put wood the right way around in the posts
1132 TileSpec tile_rot = tile;
1133 tile_rot.rotation = 1;
1135 static const f32 post_rad = BS / 8;
1136 static const f32 bar_rad = BS / 16;
1137 static const f32 bar_len = BS / 2 - post_rad;
1139 // The post - always present
1140 static const aabb3f post(-post_rad, -BS / 2, -post_rad,
1141 post_rad, BS / 2, post_rad);
1142 static const f32 postuv[24] = {
1143 0.375, 0.375, 0.625, 0.625,
1144 0.375, 0.375, 0.625, 0.625,
1145 0.000, 0.000, 0.250, 1.000,
1146 0.250, 0.000, 0.500, 1.000,
1147 0.500, 0.000, 0.750, 1.000,
1148 0.750, 0.000, 1.000, 1.000,
1151 drawAutoLightedCuboid(post, postuv);
1153 tile = tile_nocrack;
1155 // Now a section of fence, +X, if there's a post there
1158 MapNode n2 = data->m_vmanip.getNodeNoEx(blockpos_nodes + p2);
1159 const ContentFeatures *f2 = &nodedef->get(n2);
1160 if (f2->drawtype == NDT_FENCELIKE) {
1161 static const aabb3f bar_x1(BS / 2 - bar_len, BS / 4 - bar_rad, -bar_rad,
1162 BS / 2 + bar_len, BS / 4 + bar_rad, bar_rad);
1163 static const aabb3f bar_x2(BS / 2 - bar_len, -BS / 4 - bar_rad, -bar_rad,
1164 BS / 2 + bar_len, -BS / 4 + bar_rad, bar_rad);
1165 static const f32 xrailuv[24] = {
1166 0.000, 0.125, 1.000, 0.250,
1167 0.000, 0.250, 1.000, 0.375,
1168 0.375, 0.375, 0.500, 0.500,
1169 0.625, 0.625, 0.750, 0.750,
1170 0.000, 0.500, 1.000, 0.625,
1171 0.000, 0.875, 1.000, 1.000,
1173 drawAutoLightedCuboid(bar_x1, xrailuv);
1174 drawAutoLightedCuboid(bar_x2, xrailuv);
1177 // Now a section of fence, +Z, if there's a post there
1180 n2 = data->m_vmanip.getNodeNoEx(blockpos_nodes + p2);
1181 f2 = &nodedef->get(n2);
1182 if (f2->drawtype == NDT_FENCELIKE) {
1183 static const aabb3f bar_z1(-bar_rad, BS / 4 - bar_rad, BS / 2 - bar_len,
1184 bar_rad, BS / 4 + bar_rad, BS / 2 + bar_len);
1185 static const aabb3f bar_z2(-bar_rad, -BS / 4 - bar_rad, BS / 2 - bar_len,
1186 bar_rad, -BS / 4 + bar_rad, BS / 2 + bar_len);
1187 static const f32 zrailuv[24] = {
1188 0.1875, 0.0625, 0.3125, 0.3125, // cannot rotate; stretch
1189 0.2500, 0.0625, 0.3750, 0.3125, // for wood texture instead
1190 0.0000, 0.5625, 1.0000, 0.6875,
1191 0.0000, 0.3750, 1.0000, 0.5000,
1192 0.3750, 0.3750, 0.5000, 0.5000,
1193 0.6250, 0.6250, 0.7500, 0.7500,
1195 drawAutoLightedCuboid(bar_z1, zrailuv);
1196 drawAutoLightedCuboid(bar_z2, zrailuv);
1200 bool MapblockMeshGenerator::isSameRail(v3s16 dir)
1202 MapNode node2 = data->m_vmanip.getNodeNoEx(blockpos_nodes + p + dir);
1203 if (node2.getContent() == n.getContent())
1205 const ContentFeatures &def2 = nodedef->get(node2);
1206 return ((def2.drawtype == NDT_RAILLIKE) &&
1207 (def2.getGroup(raillike_groupname) == raillike_group));
1211 static const v3s16 rail_direction[4] = {
1217 static const int rail_slope_angle[4] = {0, 180, 90, -90};
1229 static const RailDesc rail_kinds[16] = {
1232 {straight, 0}, // . . . .
1233 {straight, 0}, // . . . +Z
1234 {straight, 0}, // . . -Z .
1235 {straight, 0}, // . . -Z +Z
1236 {straight, 90}, // . -X . .
1237 { curved, 180}, // . -X . +Z
1238 { curved, 270}, // . -X -Z .
1239 {junction, 180}, // . -X -Z +Z
1240 {straight, 90}, // +X . . .
1241 { curved, 90}, // +X . . +Z
1242 { curved, 0}, // +X . -Z .
1243 {junction, 0}, // +X . -Z +Z
1244 {straight, 90}, // +X -X . .
1245 {junction, 90}, // +X -X . +Z
1246 {junction, 270}, // +X -X -Z .
1247 { cross, 0}, // +X -X -Z +Z
1251 void MapblockMeshGenerator::drawRaillikeNode()
1253 raillike_group = nodedef->get(n).getGroup(raillike_groupname);
1258 bool sloped = false;
1259 for (int dir = 0; dir < 4; dir++) {
1260 bool rail_above = isSameRail(rail_direction[dir] + v3s16(0, 1, 0));
1263 angle = rail_slope_angle[dir];
1266 isSameRail(rail_direction[dir]) ||
1267 isSameRail(rail_direction[dir] + v3s16(0, -1, 0)))
1272 tile_index = straight;
1274 tile_index = rail_kinds[code].tile_index;
1275 angle = rail_kinds[code].angle;
1278 useTile(tile_index, MATERIAL_FLAG_CRACK_OVERLAY, MATERIAL_FLAG_BACKFACE_CULLING);
1280 static const float offset = BS / 64;
1281 static const float size = BS / 2;
1282 float y2 = sloped ? size : -size;
1284 v3f(-size, y2 + offset, size),
1285 v3f( size, y2 + offset, size),
1286 v3f( size, -size + offset, -size),
1287 v3f(-size, -size + offset, -size),
1290 for (v3f &vertex : vertices)
1291 vertex.rotateXZBy(angle);
1296 static const v3s16 nodebox_tile_dirs[6] = {
1305 // we have this order for some reason...
1306 static const v3s16 nodebox_connection_dirs[6] = {
1307 v3s16( 0, 1, 0), // top
1308 v3s16( 0, -1, 0), // bottom
1309 v3s16( 0, 0, -1), // front
1310 v3s16(-1, 0, 0), // left
1311 v3s16( 0, 0, 1), // back
1312 v3s16( 1, 0, 0), // right
1316 void MapblockMeshGenerator::drawNodeboxNode()
1319 for (int face = 0; face < 6; face++) {
1320 // Handles facedir rotation for textures
1321 getTile(nodebox_tile_dirs[face], &tiles[face]);
1324 // locate possible neighboring nodes to connect to
1325 u8 neighbors_set = 0;
1326 if (f->node_box.type == NODEBOX_CONNECTED) {
1327 for (int dir = 0; dir != 6; dir++) {
1329 v3s16 p2 = blockpos_nodes + p + nodebox_connection_dirs[dir];
1330 MapNode n2 = data->m_vmanip.getNodeNoEx(p2);
1331 if (nodedef->nodeboxConnects(n, n2, flag))
1332 neighbors_set |= flag;
1336 std::vector<aabb3f> boxes;
1337 n.getNodeBoxes(nodedef, &boxes, neighbors_set);
1338 for (auto &box : boxes)
1339 drawAutoLightedCuboid(box, nullptr, tiles, 6);
1342 void MapblockMeshGenerator::drawMeshNode()
1346 bool private_mesh; // as a grab/drop pair is not thread-safe
1349 if (f->param_type_2 == CPT2_FACEDIR ||
1350 f->param_type_2 == CPT2_COLORED_FACEDIR) {
1351 facedir = n.getFaceDir(nodedef);
1352 } else if (f->param_type_2 == CPT2_WALLMOUNTED ||
1353 f->param_type_2 == CPT2_COLORED_WALLMOUNTED) {
1354 // Convert wallmounted to 6dfacedir.
1355 // When cache enabled, it is already converted.
1356 facedir = n.getWallMounted(nodedef);
1357 if (!enable_mesh_cache)
1358 facedir = wallmounted_to_facedir[facedir];
1359 } else if (f->param_type_2 == CPT2_DEGROTATE ||
1360 f->param_type_2 == CPT2_COLORED_DEGROTATE) {
1361 degrotate = n.getDegRotate(nodedef);
1364 if (!data->m_smooth_lighting && f->mesh_ptr[facedir] && !degrotate) {
1365 // use cached meshes
1366 private_mesh = false;
1367 mesh = f->mesh_ptr[facedir];
1368 } else if (f->mesh_ptr[0]) {
1369 // no cache, clone and rotate mesh
1370 private_mesh = true;
1371 mesh = cloneMesh(f->mesh_ptr[0]);
1373 rotateMeshBy6dFacedir(mesh, facedir);
1375 rotateMeshXZby(mesh, 1.5f * degrotate);
1376 recalculateBoundingBox(mesh);
1377 meshmanip->recalculateNormals(mesh, true, false);
1381 int mesh_buffer_count = mesh->getMeshBufferCount();
1382 for (int j = 0; j < mesh_buffer_count; j++) {
1384 scene::IMeshBuffer *buf = mesh->getMeshBuffer(j);
1385 video::S3DVertex *vertices = (video::S3DVertex *)buf->getVertices();
1386 int vertex_count = buf->getVertexCount();
1388 if (data->m_smooth_lighting) {
1389 // Mesh is always private here. So the lighting is applied to each
1390 // vertex right here.
1391 for (int k = 0; k < vertex_count; k++) {
1392 video::S3DVertex &vertex = vertices[k];
1393 vertex.Color = blendLightColor(vertex.Pos, vertex.Normal);
1394 vertex.Pos += origin;
1396 collector->append(tile, vertices, vertex_count,
1397 buf->getIndices(), buf->getIndexCount());
1399 // Don't modify the mesh, it may not be private here.
1400 // Instead, let the collector process colors, etc.
1401 collector->append(tile, vertices, vertex_count,
1402 buf->getIndices(), buf->getIndexCount(), origin,
1403 color, f->light_source);
1410 // also called when the drawtype is known but should have been pre-converted
1411 void MapblockMeshGenerator::errorUnknownDrawtype()
1413 infostream << "Got drawtype " << f->drawtype << std::endl;
1414 FATAL_ERROR("Unknown drawtype");
1417 void MapblockMeshGenerator::drawNode()
1419 // skip some drawtypes early
1420 switch (f->drawtype) {
1421 case NDT_NORMAL: // Drawn by MapBlockMesh
1422 case NDT_AIRLIKE: // Not drawn at all
1423 case NDT_LIQUID: // Drawn by MapBlockMesh
1428 origin = intToFloat(p, BS);
1429 if (data->m_smooth_lighting)
1430 getSmoothLightFrame();
1432 light = LightPair(getInteriorLight(n, 1, nodedef));
1433 switch (f->drawtype) {
1434 case NDT_FLOWINGLIQUID: drawLiquidNode(); break;
1435 case NDT_GLASSLIKE: drawGlasslikeNode(); break;
1436 case NDT_GLASSLIKE_FRAMED: drawGlasslikeFramedNode(); break;
1437 case NDT_ALLFACES: drawAllfacesNode(); break;
1438 case NDT_TORCHLIKE: drawTorchlikeNode(); break;
1439 case NDT_SIGNLIKE: drawSignlikeNode(); break;
1440 case NDT_PLANTLIKE: drawPlantlikeNode(); break;
1441 case NDT_PLANTLIKE_ROOTED: drawPlantlikeRootedNode(); break;
1442 case NDT_FIRELIKE: drawFirelikeNode(); break;
1443 case NDT_FENCELIKE: drawFencelikeNode(); break;
1444 case NDT_RAILLIKE: drawRaillikeNode(); break;
1445 case NDT_NODEBOX: drawNodeboxNode(); break;
1446 case NDT_MESH: drawMeshNode(); break;
1447 default: errorUnknownDrawtype(); break;
1452 TODO: Fix alpha blending for special nodes
1453 Currently only the last element rendered is blended correct
1455 void MapblockMeshGenerator::generate()
1457 for (p.Z = 0; p.Z < MAP_BLOCKSIZE; p.Z++)
1458 for (p.Y = 0; p.Y < MAP_BLOCKSIZE; p.Y++)
1459 for (p.X = 0; p.X < MAP_BLOCKSIZE; p.X++) {
1460 n = data->m_vmanip.getNodeNoEx(blockpos_nodes + p);
1461 f = &nodedef->get(n);
1466 void MapblockMeshGenerator::renderSingle(content_t node, u8 param2)
1469 n = MapNode(node, 0xff, param2);
1470 f = &nodedef->get(n);