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 // mask - a bit mask that suppresses drawing of tiles.
154 // tile i will not be drawn if mask & (1 << i) is 1
155 void MapblockMeshGenerator::drawCuboid(const aabb3f &box,
156 TileSpec *tiles, int tilecount, const LightInfo *lights, const f32 *txc, u8 mask)
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) {
281 int tileindex = MYMIN(k, tilecount - 1);
282 collector->append(tiles[tileindex], vertices + 4 * k, 4, quad_indices, 6);
286 // Gets the base lighting values for a node
287 void MapblockMeshGenerator::getSmoothLightFrame()
289 for (int k = 0; k < 8; ++k)
290 frame.sunlight[k] = false;
291 for (int k = 0; k < 8; ++k) {
292 LightPair light(getSmoothLightTransparent(blockpos_nodes + p, light_dirs[k], data));
293 frame.lightsDay[k] = light.lightDay;
294 frame.lightsNight[k] = light.lightNight;
295 // If there is direct sunlight and no ambient occlusion at some corner,
296 // mark the vertical edge (top and bottom corners) containing it.
297 if (light.lightDay == 255) {
298 frame.sunlight[k] = true;
299 frame.sunlight[k ^ 2] = true;
304 // Calculates vertex light level
305 // vertex_pos - vertex position in the node (coordinates are clamped to [0.0, 1.0] or so)
306 LightInfo MapblockMeshGenerator::blendLight(const v3f &vertex_pos)
308 // Light levels at (logical) node corners are known. Here,
309 // trilinear interpolation is used to calculate light level
310 // at a given point in the node.
311 f32 x = core::clamp(vertex_pos.X / BS + 0.5, 0.0 - SMOOTH_LIGHTING_OVERSIZE, 1.0 + SMOOTH_LIGHTING_OVERSIZE);
312 f32 y = core::clamp(vertex_pos.Y / BS + 0.5, 0.0 - SMOOTH_LIGHTING_OVERSIZE, 1.0 + SMOOTH_LIGHTING_OVERSIZE);
313 f32 z = core::clamp(vertex_pos.Z / BS + 0.5, 0.0 - SMOOTH_LIGHTING_OVERSIZE, 1.0 + SMOOTH_LIGHTING_OVERSIZE);
314 f32 lightDay = 0.0; // daylight
315 f32 lightNight = 0.0;
316 f32 lightBoosted = 0.0; // daylight + direct sunlight, if any
317 for (int k = 0; k < 8; ++k) {
318 f32 dx = (k & 4) ? x : 1 - x;
319 f32 dy = (k & 2) ? y : 1 - y;
320 f32 dz = (k & 1) ? z : 1 - z;
321 // Use direct sunlight (255), if any; use daylight otherwise.
322 f32 light_boosted = frame.sunlight[k] ? 255 : frame.lightsDay[k];
323 lightDay += dx * dy * dz * frame.lightsDay[k];
324 lightNight += dx * dy * dz * frame.lightsNight[k];
325 lightBoosted += dx * dy * dz * light_boosted;
327 return LightInfo{lightDay, lightNight, lightBoosted};
330 // Calculates vertex color to be used in mapblock mesh
331 // vertex_pos - vertex position in the node (coordinates are clamped to [0.0, 1.0] or so)
332 // tile_color - node's tile color
333 video::SColor MapblockMeshGenerator::blendLightColor(const v3f &vertex_pos)
335 LightInfo light = blendLight(vertex_pos);
336 return encode_light(light.getPair(), f->light_source);
339 video::SColor MapblockMeshGenerator::blendLightColor(const v3f &vertex_pos,
340 const v3f &vertex_normal)
342 LightInfo light = blendLight(vertex_pos);
343 video::SColor color = encode_light(light.getPair(MYMAX(0.0f, vertex_normal.Y)), f->light_source);
344 if (!f->light_source)
345 applyFacesShading(color, vertex_normal);
349 void MapblockMeshGenerator::generateCuboidTextureCoords(const aabb3f &box, f32 *coords)
351 f32 tx1 = (box.MinEdge.X / BS) + 0.5;
352 f32 ty1 = (box.MinEdge.Y / BS) + 0.5;
353 f32 tz1 = (box.MinEdge.Z / BS) + 0.5;
354 f32 tx2 = (box.MaxEdge.X / BS) + 0.5;
355 f32 ty2 = (box.MaxEdge.Y / BS) + 0.5;
356 f32 tz2 = (box.MaxEdge.Z / BS) + 0.5;
358 tx1, 1 - tz2, tx2, 1 - tz1, // up
359 tx1, tz1, tx2, tz2, // down
360 tz1, 1 - ty2, tz2, 1 - ty1, // right
361 1 - tz2, 1 - ty2, 1 - tz1, 1 - ty1, // left
362 1 - tx2, 1 - ty2, 1 - tx1, 1 - ty1, // back
363 tx1, 1 - ty2, tx2, 1 - ty1, // front
365 for (int i = 0; i != 24; ++i)
369 void MapblockMeshGenerator::drawAutoLightedCuboid(aabb3f box, const f32 *txc,
370 TileSpec *tiles, int tile_count, u8 mask)
372 bool scale = std::fabs(f->visual_scale - 1.0f) > 1e-3f;
373 f32 texture_coord_buf[24];
374 f32 dx1 = box.MinEdge.X;
375 f32 dy1 = box.MinEdge.Y;
376 f32 dz1 = box.MinEdge.Z;
377 f32 dx2 = box.MaxEdge.X;
378 f32 dy2 = box.MaxEdge.Y;
379 f32 dz2 = box.MaxEdge.Z;
381 box.MinEdge += origin;
382 box.MaxEdge += origin;
385 if (!txc) { // generate texture coords before scaling
386 generateCuboidTextureCoords(box, texture_coord_buf);
387 txc = texture_coord_buf;
389 box.MinEdge *= f->visual_scale;
390 box.MaxEdge *= f->visual_scale;
393 generateCuboidTextureCoords(box, texture_coord_buf);
394 txc = texture_coord_buf;
401 if (data->m_smooth_lighting) {
403 for (int j = 0; j < 8; ++j) {
405 d.X = (j & 4) ? dx2 : dx1;
406 d.Y = (j & 2) ? dy2 : dy1;
407 d.Z = (j & 1) ? dz2 : dz1;
408 lights[j] = blendLight(d);
410 drawCuboid(box, tiles, tile_count, lights, txc, mask);
412 drawCuboid(box, tiles, tile_count, nullptr, txc, mask);
416 u8 MapblockMeshGenerator::getNodeBoxMask(aabb3f box, u8 solid_neighbors, u8 sametype_neighbors) const
418 const f32 NODE_BOUNDARY = 0.5 * BS;
420 // For an oversized nodebox, return immediately
421 if (box.MaxEdge.X > NODE_BOUNDARY ||
422 box.MinEdge.X < -NODE_BOUNDARY ||
423 box.MaxEdge.Y > NODE_BOUNDARY ||
424 box.MinEdge.Y < -NODE_BOUNDARY ||
425 box.MaxEdge.Z > NODE_BOUNDARY ||
426 box.MinEdge.Z < -NODE_BOUNDARY)
429 // We can skip faces at node boundary if the matching neighbor is solid
431 (box.MaxEdge.Y == NODE_BOUNDARY ? 1 : 0) |
432 (box.MinEdge.Y == -NODE_BOUNDARY ? 2 : 0) |
433 (box.MaxEdge.X == NODE_BOUNDARY ? 4 : 0) |
434 (box.MinEdge.X == -NODE_BOUNDARY ? 8 : 0) |
435 (box.MaxEdge.Z == NODE_BOUNDARY ? 16 : 0) |
436 (box.MinEdge.Z == -NODE_BOUNDARY ? 32 : 0);
438 u8 sametype_mask = 0;
439 if (f->alpha == AlphaMode::ALPHAMODE_OPAQUE) {
440 // In opaque nodeboxes, faces on opposite sides can cancel
441 // each other out if there is a matching neighbor of the same type
443 ((solid_mask & 3) == 3 ? 3 : 0) |
444 ((solid_mask & 12) == 12 ? 12 : 0) |
445 ((solid_mask & 48) == 48 ? 48 : 0);
448 // Combine masks with actual neighbors to get the faces to be skipped
449 return (solid_mask & solid_neighbors) | (sametype_mask & sametype_neighbors);
453 void MapblockMeshGenerator::prepareLiquidNodeDrawing()
455 getSpecialTile(0, &tile_liquid_top);
456 getSpecialTile(1, &tile_liquid);
458 MapNode ntop = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(p.X, p.Y + 1, p.Z));
459 MapNode nbottom = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(p.X, p.Y - 1, p.Z));
460 c_flowing = f->liquid_alternative_flowing_id;
461 c_source = f->liquid_alternative_source_id;
462 top_is_same_liquid = (ntop.getContent() == c_flowing) || (ntop.getContent() == c_source);
463 draw_liquid_bottom = (nbottom.getContent() != c_flowing) && (nbottom.getContent() != c_source);
464 if (draw_liquid_bottom) {
465 const ContentFeatures &f2 = nodedef->get(nbottom.getContent());
466 if (f2.solidness > 1)
467 draw_liquid_bottom = false;
470 if (data->m_smooth_lighting)
471 return; // don't need to pre-compute anything in this case
473 if (f->light_source != 0) {
474 // If this liquid emits light and doesn't contain light, draw
475 // it at what it emits, for an increased effect
476 u8 e = decode_light(f->light_source);
477 light = LightPair(std::max(e, light.lightDay), std::max(e, light.lightNight));
478 } else if (nodedef->get(ntop).param_type == CPT_LIGHT) {
479 // Otherwise, use the light of the node on top if possible
480 light = LightPair(getInteriorLight(ntop, 0, nodedef));
483 color_liquid_top = encode_light(light, f->light_source);
484 color = encode_light(light, f->light_source);
487 void MapblockMeshGenerator::getLiquidNeighborhood()
489 u8 range = rangelim(nodedef->get(c_flowing).liquid_range, 1, 8);
491 for (int w = -1; w <= 1; w++)
492 for (int u = -1; u <= 1; u++) {
493 NeighborData &neighbor = liquid_neighbors[w + 1][u + 1];
494 v3s16 p2 = p + v3s16(u, 0, w);
495 MapNode n2 = data->m_vmanip.getNodeNoEx(blockpos_nodes + p2);
496 neighbor.content = n2.getContent();
497 neighbor.level = -0.5 * BS;
498 neighbor.is_same_liquid = false;
499 neighbor.top_is_same_liquid = false;
501 if (neighbor.content == CONTENT_IGNORE)
504 if (neighbor.content == c_source) {
505 neighbor.is_same_liquid = true;
506 neighbor.level = 0.5 * BS;
507 } else if (neighbor.content == c_flowing) {
508 neighbor.is_same_liquid = true;
509 u8 liquid_level = (n2.param2 & LIQUID_LEVEL_MASK);
510 if (liquid_level <= LIQUID_LEVEL_MAX + 1 - range)
513 liquid_level -= (LIQUID_LEVEL_MAX + 1 - range);
514 neighbor.level = (-0.5 + (liquid_level + 0.5) / range) * BS;
517 // Check node above neighbor.
518 // NOTE: This doesn't get executed if neighbor
521 n2 = data->m_vmanip.getNodeNoEx(blockpos_nodes + p2);
522 if (n2.getContent() == c_source || n2.getContent() == c_flowing)
523 neighbor.top_is_same_liquid = true;
527 void MapblockMeshGenerator::calculateCornerLevels()
529 for (int k = 0; k < 2; k++)
530 for (int i = 0; i < 2; i++)
531 corner_levels[k][i] = getCornerLevel(i, k);
534 f32 MapblockMeshGenerator::getCornerLevel(int i, int k)
539 for (int dk = 0; dk < 2; dk++)
540 for (int di = 0; di < 2; di++) {
541 NeighborData &neighbor_data = liquid_neighbors[k + dk][i + di];
542 content_t content = neighbor_data.content;
544 // If top is liquid, draw starting from top of node
545 if (neighbor_data.top_is_same_liquid)
548 // Source always has the full height
549 if (content == c_source)
552 // Flowing liquid has level information
553 if (content == c_flowing) {
554 sum += neighbor_data.level;
556 } else if (content == CONTENT_AIR) {
561 return -0.5 * BS + 0.2;
568 struct LiquidFaceDesc {
570 v3s16 p[2]; // XZ only; 1 means +, 0 means -
575 static const LiquidFaceDesc liquid_base_faces[4] = {
576 {v3s16( 1, 0, 0), {v3s16(1, 0, 1), v3s16(1, 0, 0)}},
577 {v3s16(-1, 0, 0), {v3s16(0, 0, 0), v3s16(0, 0, 1)}},
578 {v3s16( 0, 0, 1), {v3s16(0, 0, 1), v3s16(1, 0, 1)}},
579 {v3s16( 0, 0, -1), {v3s16(1, 0, 0), v3s16(0, 0, 0)}},
581 static const UV liquid_base_vertices[4] = {
589 void MapblockMeshGenerator::drawLiquidSides()
591 for (const auto &face : liquid_base_faces) {
592 const NeighborData &neighbor = liquid_neighbors[face.dir.Z + 1][face.dir.X + 1];
594 // No face between nodes of the same liquid, unless there is node
595 // at the top to which it should be connected. Again, unless the face
596 // there would be inside the liquid
597 if (neighbor.is_same_liquid) {
598 if (!top_is_same_liquid)
600 if (neighbor.top_is_same_liquid)
604 const ContentFeatures &neighbor_features = nodedef->get(neighbor.content);
605 // Don't draw face if neighbor is blocking the view
606 if (neighbor_features.solidness == 2)
609 video::S3DVertex vertices[4];
610 for (int j = 0; j < 4; j++) {
611 const UV &vertex = liquid_base_vertices[j];
612 const v3s16 &base = face.p[vertex.u];
616 pos.X = (base.X - 0.5f) * BS;
617 pos.Z = (base.Z - 0.5f) * BS;
619 pos.Y = neighbor.is_same_liquid ? corner_levels[base.Z][base.X] : -0.5f * BS;
620 } else if (top_is_same_liquid) {
623 pos.Y = corner_levels[base.Z][base.X];
624 v += (0.5f * BS - corner_levels[base.Z][base.X]) / BS;
627 if (data->m_smooth_lighting)
628 color = blendLightColor(pos);
630 vertices[j] = video::S3DVertex(pos.X, pos.Y, pos.Z, 0, 0, 0, color, vertex.u, v);
632 collector->append(tile_liquid, vertices, 4, quad_indices, 6);
636 void MapblockMeshGenerator::drawLiquidTop()
638 // To get backface culling right, the vertices need to go
639 // clockwise around the front of the face. And we happened to
640 // calculate corner levels in exact reverse order.
641 static const int corner_resolve[4][2] = {{0, 1}, {1, 1}, {1, 0}, {0, 0}};
643 video::S3DVertex vertices[4] = {
644 video::S3DVertex(-BS / 2, 0, BS / 2, 0, 0, 0, color_liquid_top, 0, 1),
645 video::S3DVertex( BS / 2, 0, BS / 2, 0, 0, 0, color_liquid_top, 1, 1),
646 video::S3DVertex( BS / 2, 0, -BS / 2, 0, 0, 0, color_liquid_top, 1, 0),
647 video::S3DVertex(-BS / 2, 0, -BS / 2, 0, 0, 0, color_liquid_top, 0, 0),
650 for (int i = 0; i < 4; i++) {
651 int u = corner_resolve[i][0];
652 int w = corner_resolve[i][1];
653 vertices[i].Pos.Y += corner_levels[w][u];
654 if (data->m_smooth_lighting)
655 vertices[i].Color = blendLightColor(vertices[i].Pos);
656 vertices[i].Pos += origin;
659 // Default downwards-flowing texture animation goes from
660 // -Z towards +Z, thus the direction is +Z.
661 // Rotate texture to make animation go in flow direction
662 // Positive if liquid moves towards +Z
663 f32 dz = (corner_levels[0][0] + corner_levels[0][1]) -
664 (corner_levels[1][0] + corner_levels[1][1]);
665 // Positive if liquid moves towards +X
666 f32 dx = (corner_levels[0][0] + corner_levels[1][0]) -
667 (corner_levels[0][1] + corner_levels[1][1]);
668 f32 tcoord_angle = atan2(dz, dx) * core::RADTODEG;
669 v2f tcoord_center(0.5, 0.5);
670 v2f tcoord_translate(blockpos_nodes.Z + p.Z, blockpos_nodes.X + p.X);
671 tcoord_translate.rotateBy(tcoord_angle);
672 tcoord_translate.X -= floor(tcoord_translate.X);
673 tcoord_translate.Y -= floor(tcoord_translate.Y);
675 for (video::S3DVertex &vertex : vertices) {
676 vertex.TCoords.rotateBy(tcoord_angle, tcoord_center);
677 vertex.TCoords += tcoord_translate;
680 std::swap(vertices[0].TCoords, vertices[2].TCoords);
682 collector->append(tile_liquid_top, vertices, 4, quad_indices, 6);
685 void MapblockMeshGenerator::drawLiquidBottom()
687 video::S3DVertex vertices[4] = {
688 video::S3DVertex(-BS / 2, -BS / 2, -BS / 2, 0, 0, 0, color_liquid_top, 0, 0),
689 video::S3DVertex( BS / 2, -BS / 2, -BS / 2, 0, 0, 0, color_liquid_top, 1, 0),
690 video::S3DVertex( BS / 2, -BS / 2, BS / 2, 0, 0, 0, color_liquid_top, 1, 1),
691 video::S3DVertex(-BS / 2, -BS / 2, BS / 2, 0, 0, 0, color_liquid_top, 0, 1),
694 for (int i = 0; i < 4; i++) {
695 if (data->m_smooth_lighting)
696 vertices[i].Color = blendLightColor(vertices[i].Pos);
697 vertices[i].Pos += origin;
700 collector->append(tile_liquid_top, vertices, 4, quad_indices, 6);
703 void MapblockMeshGenerator::drawLiquidNode()
705 prepareLiquidNodeDrawing();
706 getLiquidNeighborhood();
707 calculateCornerLevels();
709 if (!top_is_same_liquid)
711 if (draw_liquid_bottom)
715 void MapblockMeshGenerator::drawGlasslikeNode()
719 for (int face = 0; face < 6; face++) {
720 // Check this neighbor
721 v3s16 dir = g_6dirs[face];
722 v3s16 neighbor_pos = blockpos_nodes + p + dir;
723 MapNode neighbor = data->m_vmanip.getNodeNoExNoEmerge(neighbor_pos);
724 // Don't make face if neighbor is of same type
725 if (neighbor.getContent() == n.getContent())
729 v3f(-BS / 2, BS / 2, -BS / 2),
730 v3f( BS / 2, BS / 2, -BS / 2),
731 v3f( BS / 2, -BS / 2, -BS / 2),
732 v3f(-BS / 2, -BS / 2, -BS / 2),
735 for (v3f &vertex : vertices) {
738 vertex.rotateXZBy(180); break;
740 vertex.rotateYZBy( 90); break;
742 vertex.rotateXZBy( 90); break;
744 vertex.rotateXZBy( 0); break;
746 vertex.rotateYZBy(-90); break;
748 vertex.rotateXZBy(-90); break;
751 drawQuad(vertices, dir);
755 void MapblockMeshGenerator::drawGlasslikeFramedNode()
758 for (int face = 0; face < 6; face++)
759 getTile(g_6dirs[face], &tiles[face]);
761 if (!data->m_smooth_lighting)
762 color = encode_light(light, f->light_source);
764 TileSpec glass_tiles[6];
765 for (auto &glass_tile : glass_tiles)
766 glass_tile = tiles[4];
768 // Only respect H/V merge bits when paramtype2 = "glasslikeliquidlevel" (liquid tank)
769 u8 param2 = (f->param_type_2 == CPT2_GLASSLIKE_LIQUID_LEVEL) ? n.getParam2() : 0;
770 bool H_merge = !(param2 & 128);
771 bool V_merge = !(param2 & 64);
774 static const float a = BS / 2.0f;
775 static const float g = a - 0.03f;
776 static const float b = 0.876f * (BS / 2.0f);
778 static const aabb3f frame_edges[FRAMED_EDGE_COUNT] = {
779 aabb3f( b, b, -a, a, a, a), // y+
780 aabb3f(-a, b, -a, -b, a, a), // y+
781 aabb3f( b, -a, -a, a, -b, a), // y-
782 aabb3f(-a, -a, -a, -b, -b, a), // y-
783 aabb3f( b, -a, b, a, a, a), // x+
784 aabb3f( b, -a, -a, a, a, -b), // x+
785 aabb3f(-a, -a, b, -b, a, a), // x-
786 aabb3f(-a, -a, -a, -b, a, -b), // x-
787 aabb3f(-a, b, b, a, a, a), // z+
788 aabb3f(-a, -a, b, a, -b, a), // z+
789 aabb3f(-a, -a, -a, a, -b, -b), // z-
790 aabb3f(-a, b, -a, a, a, -b), // z-
793 // tables of neighbour (connect if same type and merge allowed),
794 // checked with g_26dirs
796 // 1 = connect, 0 = face visible
797 bool nb[FRAMED_NEIGHBOR_COUNT] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
800 static const bool check_nb_vertical [FRAMED_NEIGHBOR_COUNT] =
801 {0,1,0,0,1,0, 0,0,0,0, 0,0,0,0, 0,0,0,0};
802 static const bool check_nb_horizontal [FRAMED_NEIGHBOR_COUNT] =
803 {1,0,1,1,0,1, 0,0,0,0, 1,1,1,1, 0,0,0,0};
804 static const bool check_nb_all [FRAMED_NEIGHBOR_COUNT] =
805 {1,1,1,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1};
806 const bool *check_nb = check_nb_all;
808 // neighbours checks for frames visibility
809 if (H_merge || V_merge) {
811 check_nb = check_nb_vertical; // vertical-only merge
813 check_nb = check_nb_horizontal; // horizontal-only merge
814 content_t current = n.getContent();
815 for (int i = 0; i < FRAMED_NEIGHBOR_COUNT; i++) {
818 v3s16 n2p = blockpos_nodes + p + g_26dirs[i];
819 MapNode n2 = data->m_vmanip.getNodeNoEx(n2p);
820 content_t n2c = n2.getContent();
828 static const u8 nb_triplet[FRAMED_EDGE_COUNT][3] = {
829 {1, 2, 7}, {1, 5, 6}, {4, 2, 15}, {4, 5, 14},
830 {2, 0, 11}, {2, 3, 13}, {5, 0, 10}, {5, 3, 12},
831 {0, 1, 8}, {0, 4, 16}, {3, 4, 17}, {3, 1, 9},
835 for (int edge = 0; edge < FRAMED_EDGE_COUNT; edge++) {
837 if (nb[nb_triplet[edge][2]])
838 edge_invisible = nb[nb_triplet[edge][0]] & nb[nb_triplet[edge][1]];
840 edge_invisible = nb[nb_triplet[edge][0]] ^ nb[nb_triplet[edge][1]];
843 drawAutoLightedCuboid(frame_edges[edge]);
846 for (int face = 0; face < 6; face++) {
850 tile = glass_tiles[face];
859 for (v3f &vertex : vertices) {
862 vertex.rotateXZBy(180); break;
864 vertex.rotateYZBy( 90); break;
866 vertex.rotateXZBy( 90); break;
868 vertex.rotateXZBy( 0); break;
870 vertex.rotateYZBy(-90); break;
872 vertex.rotateXZBy(-90); break;
875 v3s16 dir = g_6dirs[face];
876 drawQuad(vertices, dir);
879 // Optionally render internal liquid level defined by param2
880 // Liquid is textured with 1 tile defined in nodedef 'special_tiles'
881 if (param2 > 0 && f->param_type_2 == CPT2_GLASSLIKE_LIQUID_LEVEL &&
882 f->special_tiles[0].layers[0].texture) {
883 // Internal liquid level has param2 range 0 .. 63,
884 // convert it to -0.5 .. 0.5
885 float vlev = (param2 / 63.0f) * 2.0f - 1.0f;
886 getSpecialTile(0, &tile);
887 drawAutoLightedCuboid(aabb3f(-(nb[5] ? g : b),
891 (nb[1] ? g : b) * vlev,
896 void MapblockMeshGenerator::drawAllfacesNode()
898 static const aabb3f box(-BS / 2, -BS / 2, -BS / 2, BS / 2, BS / 2, BS / 2);
900 drawAutoLightedCuboid(box);
903 void MapblockMeshGenerator::drawTorchlikeNode()
905 u8 wall = n.getWallMounted(nodedef);
908 case DWM_YP: tileindex = 1; break; // ceiling
909 case DWM_YN: tileindex = 0; break; // floor
910 default: tileindex = 2; // side (or invalid—should we care?)
912 useTile(tileindex, MATERIAL_FLAG_CRACK_OVERLAY, MATERIAL_FLAG_BACKFACE_CULLING);
914 float size = BS / 2 * f->visual_scale;
918 v3f( size, -size, 0),
919 v3f(-size, -size, 0),
922 for (v3f &vertex : vertices) {
925 vertex.Y += -size + BS/2;
926 vertex.rotateXZBy(-45);
929 vertex.Y += size - BS/2;
930 vertex.rotateXZBy(45);
933 vertex.X += -size + BS/2;
936 vertex.X += -size + BS/2;
937 vertex.rotateXZBy(180);
940 vertex.X += -size + BS/2;
941 vertex.rotateXZBy(90);
944 vertex.X += -size + BS/2;
945 vertex.rotateXZBy(-90);
951 void MapblockMeshGenerator::drawSignlikeNode()
953 u8 wall = n.getWallMounted(nodedef);
954 useTile(0, MATERIAL_FLAG_CRACK_OVERLAY, MATERIAL_FLAG_BACKFACE_CULLING);
955 static const float offset = BS / 16;
956 float size = BS / 2 * f->visual_scale;
957 // Wall at X+ of node
959 v3f(BS / 2 - offset, size, size),
960 v3f(BS / 2 - offset, size, -size),
961 v3f(BS / 2 - offset, -size, -size),
962 v3f(BS / 2 - offset, -size, size),
965 for (v3f &vertex : vertices) {
968 vertex.rotateXYBy( 90); break;
970 vertex.rotateXYBy(-90); break;
972 vertex.rotateXZBy( 0); break;
974 vertex.rotateXZBy(180); break;
976 vertex.rotateXZBy( 90); break;
978 vertex.rotateXZBy(-90); break;
984 void MapblockMeshGenerator::drawPlantlikeQuad(float rotation, float quad_offset,
985 bool offset_top_only)
988 v3f(-scale, -BS / 2 + 2.0 * scale * plant_height, 0),
989 v3f( scale, -BS / 2 + 2.0 * scale * plant_height, 0),
990 v3f( scale, -BS / 2, 0),
991 v3f(-scale, -BS / 2, 0),
993 if (random_offset_Y) {
994 PseudoRandom yrng(face_num++ | p.X << 16 | p.Z << 8 | p.Y << 24);
995 offset.Y = -BS * ((yrng.next() % 16 / 16.0) * 0.125);
997 int offset_count = offset_top_only ? 2 : 4;
998 for (int i = 0; i < offset_count; i++)
999 vertices[i].Z += quad_offset;
1001 for (v3f &vertex : vertices) {
1002 vertex.rotateXZBy(rotation + rotate_degree);
1006 u8 wall = n.getWallMounted(nodedef);
1007 if (wall != DWM_YN) {
1008 for (v3f &vertex : vertices) {
1011 vertex.rotateYZBy(180);
1012 vertex.rotateXZBy(180);
1015 vertex.rotateXYBy(90);
1018 vertex.rotateXYBy(-90);
1019 vertex.rotateYZBy(180);
1022 vertex.rotateYZBy(-90);
1023 vertex.rotateXYBy(90);
1026 vertex.rotateYZBy(90);
1027 vertex.rotateXYBy(90);
1033 drawQuad(vertices, v3s16(0, 0, 0), plant_height);
1036 void MapblockMeshGenerator::drawPlantlike(bool is_rooted)
1038 draw_style = PLANT_STYLE_CROSS;
1039 scale = BS / 2 * f->visual_scale;
1040 offset = v3f(0, 0, 0);
1041 rotate_degree = 0.0f;
1042 random_offset_Y = false;
1046 switch (f->param_type_2) {
1047 case CPT2_MESHOPTIONS:
1048 draw_style = PlantlikeStyle(n.param2 & MO_MASK_STYLE);
1049 if (n.param2 & MO_BIT_SCALE_SQRT2)
1051 if (n.param2 & MO_BIT_RANDOM_OFFSET) {
1052 PseudoRandom rng(p.X << 8 | p.Z | p.Y << 16);
1053 offset.X = BS * ((rng.next() % 16 / 16.0) * 0.29 - 0.145);
1054 offset.Z = BS * ((rng.next() % 16 / 16.0) * 0.29 - 0.145);
1056 if (n.param2 & MO_BIT_RANDOM_OFFSET_Y)
1057 random_offset_Y = true;
1060 case CPT2_DEGROTATE:
1061 case CPT2_COLORED_DEGROTATE:
1062 rotate_degree = 1.5f * n.getDegRotate(nodedef);
1066 plant_height = n.param2 / 16.0;
1074 u8 wall = n.getWallMounted(nodedef);
1089 switch (draw_style) {
1090 case PLANT_STYLE_CROSS:
1091 drawPlantlikeQuad(46);
1092 drawPlantlikeQuad(-44);
1095 case PLANT_STYLE_CROSS2:
1096 drawPlantlikeQuad(91);
1097 drawPlantlikeQuad(1);
1100 case PLANT_STYLE_STAR:
1101 drawPlantlikeQuad(121);
1102 drawPlantlikeQuad(241);
1103 drawPlantlikeQuad(1);
1106 case PLANT_STYLE_HASH:
1107 drawPlantlikeQuad( 1, BS / 4);
1108 drawPlantlikeQuad( 91, BS / 4);
1109 drawPlantlikeQuad(181, BS / 4);
1110 drawPlantlikeQuad(271, BS / 4);
1113 case PLANT_STYLE_HASH2:
1114 drawPlantlikeQuad( 1, -BS / 2, true);
1115 drawPlantlikeQuad( 91, -BS / 2, true);
1116 drawPlantlikeQuad(181, -BS / 2, true);
1117 drawPlantlikeQuad(271, -BS / 2, true);
1122 void MapblockMeshGenerator::drawPlantlikeNode()
1128 void MapblockMeshGenerator::drawPlantlikeRootedNode()
1130 useTile(0, MATERIAL_FLAG_CRACK_OVERLAY, 0, true);
1131 origin += v3f(0.0, BS, 0.0);
1133 if (data->m_smooth_lighting) {
1134 getSmoothLightFrame();
1136 MapNode ntop = data->m_vmanip.getNodeNoEx(blockpos_nodes + p);
1137 light = LightPair(getInteriorLight(ntop, 1, nodedef));
1139 drawPlantlike(true);
1143 void MapblockMeshGenerator::drawFirelikeQuad(float rotation, float opening_angle,
1144 float offset_h, float offset_v)
1147 v3f(-scale, -BS / 2 + scale * 2, 0),
1148 v3f( scale, -BS / 2 + scale * 2, 0),
1149 v3f( scale, -BS / 2, 0),
1150 v3f(-scale, -BS / 2, 0),
1153 for (v3f &vertex : vertices) {
1154 vertex.rotateYZBy(opening_angle);
1155 vertex.Z += offset_h;
1156 vertex.rotateXZBy(rotation);
1157 vertex.Y += offset_v;
1162 void MapblockMeshGenerator::drawFirelikeNode()
1165 scale = BS / 2 * f->visual_scale;
1167 // Check for adjacent nodes
1168 bool neighbors = false;
1169 bool neighbor[6] = {0, 0, 0, 0, 0, 0};
1170 content_t current = n.getContent();
1171 for (int i = 0; i < 6; i++) {
1172 v3s16 n2p = blockpos_nodes + p + g_6dirs[i];
1173 MapNode n2 = data->m_vmanip.getNodeNoEx(n2p);
1174 content_t n2c = n2.getContent();
1175 if (n2c != CONTENT_IGNORE && n2c != CONTENT_AIR && n2c != current) {
1180 bool drawBasicFire = neighbor[D6D_YN] || !neighbors;
1181 bool drawBottomFire = neighbor[D6D_YP];
1183 if (drawBasicFire || neighbor[D6D_ZP])
1184 drawFirelikeQuad(0, -10, 0.4 * BS);
1185 else if (drawBottomFire)
1186 drawFirelikeQuad(0, 70, 0.47 * BS, 0.484 * BS);
1188 if (drawBasicFire || neighbor[D6D_XN])
1189 drawFirelikeQuad(90, -10, 0.4 * BS);
1190 else if (drawBottomFire)
1191 drawFirelikeQuad(90, 70, 0.47 * BS, 0.484 * BS);
1193 if (drawBasicFire || neighbor[D6D_ZN])
1194 drawFirelikeQuad(180, -10, 0.4 * BS);
1195 else if (drawBottomFire)
1196 drawFirelikeQuad(180, 70, 0.47 * BS, 0.484 * BS);
1198 if (drawBasicFire || neighbor[D6D_XP])
1199 drawFirelikeQuad(270, -10, 0.4 * BS);
1200 else if (drawBottomFire)
1201 drawFirelikeQuad(270, 70, 0.47 * BS, 0.484 * BS);
1203 if (drawBasicFire) {
1204 drawFirelikeQuad(45, 0, 0.0);
1205 drawFirelikeQuad(-45, 0, 0.0);
1209 void MapblockMeshGenerator::drawFencelikeNode()
1212 TileSpec tile_nocrack = tile;
1214 for (auto &layer : tile_nocrack.layers)
1215 layer.material_flags &= ~MATERIAL_FLAG_CRACK;
1217 // Put wood the right way around in the posts
1218 TileSpec tile_rot = tile;
1219 tile_rot.rotation = 1;
1221 static const f32 post_rad = BS / 8;
1222 static const f32 bar_rad = BS / 16;
1223 static const f32 bar_len = BS / 2 - post_rad;
1225 // The post - always present
1226 static const aabb3f post(-post_rad, -BS / 2, -post_rad,
1227 post_rad, BS / 2, post_rad);
1228 static const f32 postuv[24] = {
1229 0.375, 0.375, 0.625, 0.625,
1230 0.375, 0.375, 0.625, 0.625,
1231 0.000, 0.000, 0.250, 1.000,
1232 0.250, 0.000, 0.500, 1.000,
1233 0.500, 0.000, 0.750, 1.000,
1234 0.750, 0.000, 1.000, 1.000,
1237 drawAutoLightedCuboid(post, postuv);
1239 tile = tile_nocrack;
1241 // Now a section of fence, +X, if there's a post there
1244 MapNode n2 = data->m_vmanip.getNodeNoEx(blockpos_nodes + p2);
1245 const ContentFeatures *f2 = &nodedef->get(n2);
1246 if (f2->drawtype == NDT_FENCELIKE) {
1247 static const aabb3f bar_x1(BS / 2 - bar_len, BS / 4 - bar_rad, -bar_rad,
1248 BS / 2 + bar_len, BS / 4 + bar_rad, bar_rad);
1249 static const aabb3f bar_x2(BS / 2 - bar_len, -BS / 4 - bar_rad, -bar_rad,
1250 BS / 2 + bar_len, -BS / 4 + bar_rad, bar_rad);
1251 static const f32 xrailuv[24] = {
1252 0.000, 0.125, 1.000, 0.250,
1253 0.000, 0.250, 1.000, 0.375,
1254 0.375, 0.375, 0.500, 0.500,
1255 0.625, 0.625, 0.750, 0.750,
1256 0.000, 0.500, 1.000, 0.625,
1257 0.000, 0.875, 1.000, 1.000,
1259 drawAutoLightedCuboid(bar_x1, xrailuv);
1260 drawAutoLightedCuboid(bar_x2, xrailuv);
1263 // Now a section of fence, +Z, if there's a post there
1266 n2 = data->m_vmanip.getNodeNoEx(blockpos_nodes + p2);
1267 f2 = &nodedef->get(n2);
1268 if (f2->drawtype == NDT_FENCELIKE) {
1269 static const aabb3f bar_z1(-bar_rad, BS / 4 - bar_rad, BS / 2 - bar_len,
1270 bar_rad, BS / 4 + bar_rad, BS / 2 + bar_len);
1271 static const aabb3f bar_z2(-bar_rad, -BS / 4 - bar_rad, BS / 2 - bar_len,
1272 bar_rad, -BS / 4 + bar_rad, BS / 2 + bar_len);
1273 static const f32 zrailuv[24] = {
1274 0.1875, 0.0625, 0.3125, 0.3125, // cannot rotate; stretch
1275 0.2500, 0.0625, 0.3750, 0.3125, // for wood texture instead
1276 0.0000, 0.5625, 1.0000, 0.6875,
1277 0.0000, 0.3750, 1.0000, 0.5000,
1278 0.3750, 0.3750, 0.5000, 0.5000,
1279 0.6250, 0.6250, 0.7500, 0.7500,
1281 drawAutoLightedCuboid(bar_z1, zrailuv);
1282 drawAutoLightedCuboid(bar_z2, zrailuv);
1286 bool MapblockMeshGenerator::isSameRail(v3s16 dir)
1288 MapNode node2 = data->m_vmanip.getNodeNoEx(blockpos_nodes + p + dir);
1289 if (node2.getContent() == n.getContent())
1291 const ContentFeatures &def2 = nodedef->get(node2);
1292 return ((def2.drawtype == NDT_RAILLIKE) &&
1293 (def2.getGroup(raillike_groupname) == raillike_group));
1297 static const v3s16 rail_direction[4] = {
1303 static const int rail_slope_angle[4] = {0, 180, 90, -90};
1315 static const RailDesc rail_kinds[16] = {
1318 {straight, 0}, // . . . .
1319 {straight, 0}, // . . . +Z
1320 {straight, 0}, // . . -Z .
1321 {straight, 0}, // . . -Z +Z
1322 {straight, 90}, // . -X . .
1323 { curved, 180}, // . -X . +Z
1324 { curved, 270}, // . -X -Z .
1325 {junction, 180}, // . -X -Z +Z
1326 {straight, 90}, // +X . . .
1327 { curved, 90}, // +X . . +Z
1328 { curved, 0}, // +X . -Z .
1329 {junction, 0}, // +X . -Z +Z
1330 {straight, 90}, // +X -X . .
1331 {junction, 90}, // +X -X . +Z
1332 {junction, 270}, // +X -X -Z .
1333 { cross, 0}, // +X -X -Z +Z
1337 void MapblockMeshGenerator::drawRaillikeNode()
1339 raillike_group = nodedef->get(n).getGroup(raillike_groupname);
1344 bool sloped = false;
1345 for (int dir = 0; dir < 4; dir++) {
1346 bool rail_above = isSameRail(rail_direction[dir] + v3s16(0, 1, 0));
1349 angle = rail_slope_angle[dir];
1352 isSameRail(rail_direction[dir]) ||
1353 isSameRail(rail_direction[dir] + v3s16(0, -1, 0)))
1358 tile_index = straight;
1360 tile_index = rail_kinds[code].tile_index;
1361 angle = rail_kinds[code].angle;
1364 useTile(tile_index, MATERIAL_FLAG_CRACK_OVERLAY, MATERIAL_FLAG_BACKFACE_CULLING);
1366 static const float offset = BS / 64;
1367 static const float size = BS / 2;
1368 float y2 = sloped ? size : -size;
1370 v3f(-size, y2 + offset, size),
1371 v3f( size, y2 + offset, size),
1372 v3f( size, -size + offset, -size),
1373 v3f(-size, -size + offset, -size),
1376 for (v3f &vertex : vertices)
1377 vertex.rotateXZBy(angle);
1382 static const v3s16 nodebox_tile_dirs[6] = {
1391 // we have this order for some reason...
1392 static const v3s16 nodebox_connection_dirs[6] = {
1393 v3s16( 0, 1, 0), // top
1394 v3s16( 0, -1, 0), // bottom
1395 v3s16( 0, 0, -1), // front
1396 v3s16(-1, 0, 0), // left
1397 v3s16( 0, 0, 1), // back
1398 v3s16( 1, 0, 0), // right
1402 void MapblockMeshGenerator::drawNodeboxNode()
1405 for (int face = 0; face < 6; face++) {
1406 // Handles facedir rotation for textures
1407 getTile(nodebox_tile_dirs[face], &tiles[face]);
1410 bool param2_is_rotation =
1411 f->param_type_2 == CPT2_COLORED_FACEDIR ||
1412 f->param_type_2 == CPT2_COLORED_WALLMOUNTED ||
1413 f->param_type_2 == CPT2_FACEDIR ||
1414 f->param_type_2 == CPT2_WALLMOUNTED;
1416 bool param2_is_level =
1417 f->param_type_2 == CPT2_LEVELED;
1419 // locate possible neighboring nodes to connect to
1420 u8 neighbors_set = 0;
1421 u8 solid_neighbors = 0;
1422 u8 sametype_neighbors = 0;
1423 for (int dir = 0; dir != 6; dir++) {
1425 v3s16 p2 = blockpos_nodes + p + nodebox_tile_dirs[dir];
1426 MapNode n2 = data->m_vmanip.getNodeNoEx(p2);
1428 // mark neighbors that are the same node type
1429 // and have the same rotation or higher level stored as param2
1430 if (n2.param0 == n.param0 &&
1431 (!param2_is_rotation || n.param2 == n2.param2) &&
1432 (!param2_is_level || n.param2 <= n2.param2))
1433 sametype_neighbors |= flag;
1435 // mark neighbors that are simple solid blocks
1436 if (nodedef->get(n2).drawtype == NDT_NORMAL)
1437 solid_neighbors |= flag;
1439 if (f->node_box.type == NODEBOX_CONNECTED) {
1440 p2 = blockpos_nodes + p + nodebox_connection_dirs[dir];
1441 n2 = data->m_vmanip.getNodeNoEx(p2);
1442 if (nodedef->nodeboxConnects(n, n2, flag))
1443 neighbors_set |= flag;
1447 std::vector<aabb3f> boxes;
1448 n.getNodeBoxes(nodedef, &boxes, neighbors_set);
1450 bool isTransparent = false;
1452 for (const TileSpec &tile : tiles) {
1453 if (tile.layers[0].isTransparent()) {
1454 isTransparent = true;
1459 if (isTransparent) {
1460 std::vector<float> sections;
1461 // Preallocate 8 default splits + Min&Max for each nodebox
1462 sections.reserve(8 + 2 * boxes.size());
1464 for (int axis = 0; axis < 3; axis++) {
1465 // identify sections
1468 // Default split at node bounds, up to 3 nodes in each direction
1469 for (float s = -3.5f * BS; s < 4.0f * BS; s += 1.0f * BS)
1470 sections.push_back(s);
1473 // Avoid readding the same 8 default splits for Y and Z
1477 // Add edges of existing node boxes, rounded to 1E-3
1478 for (size_t i = 0; i < boxes.size(); i++) {
1479 sections.push_back(std::floor(boxes[i].MinEdge[axis] * 1E3) * 1E-3);
1480 sections.push_back(std::floor(boxes[i].MaxEdge[axis] * 1E3) * 1E-3);
1483 // split the boxes at recorded sections
1484 // limit splits to avoid runaway crash if inner loop adds infinite splits
1485 // due to e.g. precision problems.
1486 // 100 is just an arbitrary, reasonably high number.
1487 for (size_t i = 0; i < boxes.size() && i < 100; i++) {
1488 aabb3f *box = &boxes[i];
1489 for (float section : sections) {
1490 if (box->MinEdge[axis] < section && box->MaxEdge[axis] > section) {
1492 copy.MinEdge[axis] = section;
1493 box->MaxEdge[axis] = section;
1494 boxes.push_back(copy);
1495 box = &boxes[i]; // find new address of the box in case of reallocation
1502 for (auto &box : boxes) {
1503 u8 mask = getNodeBoxMask(box, solid_neighbors, sametype_neighbors);
1504 drawAutoLightedCuboid(box, nullptr, tiles, 6, mask);
1508 void MapblockMeshGenerator::drawMeshNode()
1512 bool private_mesh; // as a grab/drop pair is not thread-safe
1515 if (f->param_type_2 == CPT2_FACEDIR ||
1516 f->param_type_2 == CPT2_COLORED_FACEDIR) {
1517 facedir = n.getFaceDir(nodedef);
1518 } else if (f->param_type_2 == CPT2_WALLMOUNTED ||
1519 f->param_type_2 == CPT2_COLORED_WALLMOUNTED) {
1520 // Convert wallmounted to 6dfacedir.
1521 // When cache enabled, it is already converted.
1522 facedir = n.getWallMounted(nodedef);
1523 if (!enable_mesh_cache)
1524 facedir = wallmounted_to_facedir[facedir];
1525 } else if (f->param_type_2 == CPT2_DEGROTATE ||
1526 f->param_type_2 == CPT2_COLORED_DEGROTATE) {
1527 degrotate = n.getDegRotate(nodedef);
1530 if (!data->m_smooth_lighting && f->mesh_ptr[facedir] && !degrotate) {
1531 // use cached meshes
1532 private_mesh = false;
1533 mesh = f->mesh_ptr[facedir];
1534 } else if (f->mesh_ptr[0]) {
1535 // no cache, clone and rotate mesh
1536 private_mesh = true;
1537 mesh = cloneMesh(f->mesh_ptr[0]);
1539 rotateMeshBy6dFacedir(mesh, facedir);
1541 rotateMeshXZby(mesh, 1.5f * degrotate);
1542 recalculateBoundingBox(mesh);
1543 meshmanip->recalculateNormals(mesh, true, false);
1547 int mesh_buffer_count = mesh->getMeshBufferCount();
1548 for (int j = 0; j < mesh_buffer_count; j++) {
1550 scene::IMeshBuffer *buf = mesh->getMeshBuffer(j);
1551 video::S3DVertex *vertices = (video::S3DVertex *)buf->getVertices();
1552 int vertex_count = buf->getVertexCount();
1554 if (data->m_smooth_lighting) {
1555 // Mesh is always private here. So the lighting is applied to each
1556 // vertex right here.
1557 for (int k = 0; k < vertex_count; k++) {
1558 video::S3DVertex &vertex = vertices[k];
1559 vertex.Color = blendLightColor(vertex.Pos, vertex.Normal);
1560 vertex.Pos += origin;
1562 collector->append(tile, vertices, vertex_count,
1563 buf->getIndices(), buf->getIndexCount());
1565 // Don't modify the mesh, it may not be private here.
1566 // Instead, let the collector process colors, etc.
1567 collector->append(tile, vertices, vertex_count,
1568 buf->getIndices(), buf->getIndexCount(), origin,
1569 color, f->light_source);
1576 // also called when the drawtype is known but should have been pre-converted
1577 void MapblockMeshGenerator::errorUnknownDrawtype()
1579 infostream << "Got drawtype " << f->drawtype << std::endl;
1580 FATAL_ERROR("Unknown drawtype");
1583 void MapblockMeshGenerator::drawNode()
1585 // skip some drawtypes early
1586 switch (f->drawtype) {
1587 case NDT_NORMAL: // Drawn by MapBlockMesh
1588 case NDT_AIRLIKE: // Not drawn at all
1589 case NDT_LIQUID: // Drawn by MapBlockMesh
1594 origin = intToFloat(p, BS);
1595 if (data->m_smooth_lighting)
1596 getSmoothLightFrame();
1598 light = LightPair(getInteriorLight(n, 1, nodedef));
1599 switch (f->drawtype) {
1600 case NDT_FLOWINGLIQUID: drawLiquidNode(); break;
1601 case NDT_GLASSLIKE: drawGlasslikeNode(); break;
1602 case NDT_GLASSLIKE_FRAMED: drawGlasslikeFramedNode(); break;
1603 case NDT_ALLFACES: drawAllfacesNode(); break;
1604 case NDT_TORCHLIKE: drawTorchlikeNode(); break;
1605 case NDT_SIGNLIKE: drawSignlikeNode(); break;
1606 case NDT_PLANTLIKE: drawPlantlikeNode(); break;
1607 case NDT_PLANTLIKE_ROOTED: drawPlantlikeRootedNode(); break;
1608 case NDT_FIRELIKE: drawFirelikeNode(); break;
1609 case NDT_FENCELIKE: drawFencelikeNode(); break;
1610 case NDT_RAILLIKE: drawRaillikeNode(); break;
1611 case NDT_NODEBOX: drawNodeboxNode(); break;
1612 case NDT_MESH: drawMeshNode(); break;
1613 default: errorUnknownDrawtype(); break;
1618 TODO: Fix alpha blending for special nodes
1619 Currently only the last element rendered is blended correct
1621 void MapblockMeshGenerator::generate()
1623 for (p.Z = 0; p.Z < MAP_BLOCKSIZE; p.Z++)
1624 for (p.Y = 0; p.Y < MAP_BLOCKSIZE; p.Y++)
1625 for (p.X = 0; p.X < MAP_BLOCKSIZE; p.X++) {
1626 n = data->m_vmanip.getNodeNoEx(blockpos_nodes + p);
1627 f = &nodedef->get(n);
1632 void MapblockMeshGenerator::renderSingle(content_t node, u8 param2)
1635 n = MapNode(node, 0xff, param2);
1636 f = &nodedef->get(n);