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 if (!txc) { // generate texture coords before scaling
382 generateCuboidTextureCoords(box, texture_coord_buf);
383 txc = texture_coord_buf;
385 box.MinEdge *= f->visual_scale;
386 box.MaxEdge *= f->visual_scale;
388 box.MinEdge += origin;
389 box.MaxEdge += origin;
391 generateCuboidTextureCoords(box, texture_coord_buf);
392 txc = texture_coord_buf;
398 if (data->m_smooth_lighting) {
400 for (int j = 0; j < 8; ++j) {
402 d.X = (j & 4) ? dx2 : dx1;
403 d.Y = (j & 2) ? dy2 : dy1;
404 d.Z = (j & 1) ? dz2 : dz1;
405 lights[j] = blendLight(d);
407 drawCuboid(box, tiles, tile_count, lights, txc, mask);
409 drawCuboid(box, tiles, tile_count, nullptr, txc, mask);
413 u8 MapblockMeshGenerator::getNodeBoxMask(aabb3f box, u8 solid_neighbors, u8 sametype_neighbors) const
415 const f32 NODE_BOUNDARY = 0.5 * BS;
417 // For an oversized nodebox, return immediately
418 if (box.MaxEdge.X > NODE_BOUNDARY ||
419 box.MinEdge.X < -NODE_BOUNDARY ||
420 box.MaxEdge.Y > NODE_BOUNDARY ||
421 box.MinEdge.Y < -NODE_BOUNDARY ||
422 box.MaxEdge.Z > NODE_BOUNDARY ||
423 box.MinEdge.Z < -NODE_BOUNDARY)
426 // We can skip faces at node boundary if the matching neighbor is solid
428 (box.MaxEdge.Y == NODE_BOUNDARY ? 1 : 0) |
429 (box.MinEdge.Y == -NODE_BOUNDARY ? 2 : 0) |
430 (box.MaxEdge.X == NODE_BOUNDARY ? 4 : 0) |
431 (box.MinEdge.X == -NODE_BOUNDARY ? 8 : 0) |
432 (box.MaxEdge.Z == NODE_BOUNDARY ? 16 : 0) |
433 (box.MinEdge.Z == -NODE_BOUNDARY ? 32 : 0);
435 u8 sametype_mask = 0;
436 if (f->alpha == AlphaMode::ALPHAMODE_OPAQUE) {
437 // In opaque nodeboxes, faces on opposite sides can cancel
438 // each other out if there is a matching neighbor of the same type
440 ((solid_mask & 3) == 3 ? 3 : 0) |
441 ((solid_mask & 12) == 12 ? 12 : 0) |
442 ((solid_mask & 48) == 48 ? 48 : 0);
445 // Combine masks with actual neighbors to get the faces to be skipped
446 return (solid_mask & solid_neighbors) | (sametype_mask & sametype_neighbors);
450 void MapblockMeshGenerator::prepareLiquidNodeDrawing()
452 getSpecialTile(0, &tile_liquid_top);
453 getSpecialTile(1, &tile_liquid);
455 MapNode ntop = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(p.X, p.Y + 1, p.Z));
456 MapNode nbottom = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(p.X, p.Y - 1, p.Z));
457 c_flowing = f->liquid_alternative_flowing_id;
458 c_source = f->liquid_alternative_source_id;
459 top_is_same_liquid = (ntop.getContent() == c_flowing) || (ntop.getContent() == c_source);
460 draw_liquid_bottom = (nbottom.getContent() != c_flowing) && (nbottom.getContent() != c_source);
461 if (draw_liquid_bottom) {
462 const ContentFeatures &f2 = nodedef->get(nbottom.getContent());
463 if (f2.solidness > 1)
464 draw_liquid_bottom = false;
467 if (data->m_smooth_lighting)
468 return; // don't need to pre-compute anything in this case
470 if (f->light_source != 0) {
471 // If this liquid emits light and doesn't contain light, draw
472 // it at what it emits, for an increased effect
473 u8 e = decode_light(f->light_source);
474 light = LightPair(std::max(e, light.lightDay), std::max(e, light.lightNight));
475 } else if (nodedef->getLightingFlags(ntop).has_light) {
476 // Otherwise, use the light of the node on top if possible
477 light = LightPair(getInteriorLight(ntop, 0, nodedef));
480 color_liquid_top = encode_light(light, f->light_source);
481 color = encode_light(light, f->light_source);
484 void MapblockMeshGenerator::getLiquidNeighborhood()
486 u8 range = rangelim(nodedef->get(c_flowing).liquid_range, 1, 8);
488 for (int w = -1; w <= 1; w++)
489 for (int u = -1; u <= 1; u++) {
490 NeighborData &neighbor = liquid_neighbors[w + 1][u + 1];
491 v3s16 p2 = p + v3s16(u, 0, w);
492 MapNode n2 = data->m_vmanip.getNodeNoEx(blockpos_nodes + p2);
493 neighbor.content = n2.getContent();
494 neighbor.level = -0.5 * BS;
495 neighbor.is_same_liquid = false;
496 neighbor.top_is_same_liquid = false;
498 if (neighbor.content == CONTENT_IGNORE)
501 if (neighbor.content == c_source) {
502 neighbor.is_same_liquid = true;
503 neighbor.level = 0.5 * BS;
504 } else if (neighbor.content == c_flowing) {
505 neighbor.is_same_liquid = true;
506 u8 liquid_level = (n2.param2 & LIQUID_LEVEL_MASK);
507 if (liquid_level <= LIQUID_LEVEL_MAX + 1 - range)
510 liquid_level -= (LIQUID_LEVEL_MAX + 1 - range);
511 neighbor.level = (-0.5 + (liquid_level + 0.5) / range) * BS;
514 // Check node above neighbor.
515 // NOTE: This doesn't get executed if neighbor
518 n2 = data->m_vmanip.getNodeNoEx(blockpos_nodes + p2);
519 if (n2.getContent() == c_source || n2.getContent() == c_flowing)
520 neighbor.top_is_same_liquid = true;
524 void MapblockMeshGenerator::calculateCornerLevels()
526 for (int k = 0; k < 2; k++)
527 for (int i = 0; i < 2; i++)
528 corner_levels[k][i] = getCornerLevel(i, k);
531 f32 MapblockMeshGenerator::getCornerLevel(int i, int k)
536 for (int dk = 0; dk < 2; dk++)
537 for (int di = 0; di < 2; di++) {
538 NeighborData &neighbor_data = liquid_neighbors[k + dk][i + di];
539 content_t content = neighbor_data.content;
541 // If top is liquid, draw starting from top of node
542 if (neighbor_data.top_is_same_liquid)
545 // Source always has the full height
546 if (content == c_source)
549 // Flowing liquid has level information
550 if (content == c_flowing) {
551 sum += neighbor_data.level;
553 } else if (content == CONTENT_AIR) {
558 return -0.5 * BS + 0.2;
565 struct LiquidFaceDesc {
567 v3s16 p[2]; // XZ only; 1 means +, 0 means -
572 static const LiquidFaceDesc liquid_base_faces[4] = {
573 {v3s16( 1, 0, 0), {v3s16(1, 0, 1), v3s16(1, 0, 0)}},
574 {v3s16(-1, 0, 0), {v3s16(0, 0, 0), v3s16(0, 0, 1)}},
575 {v3s16( 0, 0, 1), {v3s16(0, 0, 1), v3s16(1, 0, 1)}},
576 {v3s16( 0, 0, -1), {v3s16(1, 0, 0), v3s16(0, 0, 0)}},
578 static const UV liquid_base_vertices[4] = {
586 void MapblockMeshGenerator::drawLiquidSides()
588 for (const auto &face : liquid_base_faces) {
589 const NeighborData &neighbor = liquid_neighbors[face.dir.Z + 1][face.dir.X + 1];
591 // No face between nodes of the same liquid, unless there is node
592 // at the top to which it should be connected. Again, unless the face
593 // there would be inside the liquid
594 if (neighbor.is_same_liquid) {
595 if (!top_is_same_liquid)
597 if (neighbor.top_is_same_liquid)
601 const ContentFeatures &neighbor_features = nodedef->get(neighbor.content);
602 // Don't draw face if neighbor is blocking the view
603 if (neighbor_features.solidness == 2)
606 video::S3DVertex vertices[4];
607 for (int j = 0; j < 4; j++) {
608 const UV &vertex = liquid_base_vertices[j];
609 const v3s16 &base = face.p[vertex.u];
613 pos.X = (base.X - 0.5f) * BS;
614 pos.Z = (base.Z - 0.5f) * BS;
616 pos.Y = neighbor.is_same_liquid ? corner_levels[base.Z][base.X] : -0.5f * BS;
617 } else if (top_is_same_liquid) {
620 pos.Y = corner_levels[base.Z][base.X];
621 v += (0.5f * BS - corner_levels[base.Z][base.X]) / BS;
624 if (data->m_smooth_lighting)
625 color = blendLightColor(pos);
627 vertices[j] = video::S3DVertex(pos.X, pos.Y, pos.Z, 0, 0, 0, color, vertex.u, v);
629 collector->append(tile_liquid, vertices, 4, quad_indices, 6);
633 void MapblockMeshGenerator::drawLiquidTop()
635 // To get backface culling right, the vertices need to go
636 // clockwise around the front of the face. And we happened to
637 // calculate corner levels in exact reverse order.
638 static const int corner_resolve[4][2] = {{0, 1}, {1, 1}, {1, 0}, {0, 0}};
640 video::S3DVertex vertices[4] = {
641 video::S3DVertex(-BS / 2, 0, BS / 2, 0, 0, 0, color_liquid_top, 0, 1),
642 video::S3DVertex( BS / 2, 0, BS / 2, 0, 0, 0, color_liquid_top, 1, 1),
643 video::S3DVertex( BS / 2, 0, -BS / 2, 0, 0, 0, color_liquid_top, 1, 0),
644 video::S3DVertex(-BS / 2, 0, -BS / 2, 0, 0, 0, color_liquid_top, 0, 0),
647 for (int i = 0; i < 4; i++) {
648 int u = corner_resolve[i][0];
649 int w = corner_resolve[i][1];
650 vertices[i].Pos.Y += corner_levels[w][u];
651 if (data->m_smooth_lighting)
652 vertices[i].Color = blendLightColor(vertices[i].Pos);
653 vertices[i].Pos += origin;
656 // Default downwards-flowing texture animation goes from
657 // -Z towards +Z, thus the direction is +Z.
658 // Rotate texture to make animation go in flow direction
659 // Positive if liquid moves towards +Z
660 f32 dz = (corner_levels[0][0] + corner_levels[0][1]) -
661 (corner_levels[1][0] + corner_levels[1][1]);
662 // Positive if liquid moves towards +X
663 f32 dx = (corner_levels[0][0] + corner_levels[1][0]) -
664 (corner_levels[0][1] + corner_levels[1][1]);
665 f32 tcoord_angle = atan2(dz, dx) * core::RADTODEG;
666 v2f tcoord_center(0.5, 0.5);
667 v2f tcoord_translate(blockpos_nodes.Z + p.Z, blockpos_nodes.X + p.X);
668 tcoord_translate.rotateBy(tcoord_angle);
669 tcoord_translate.X -= floor(tcoord_translate.X);
670 tcoord_translate.Y -= floor(tcoord_translate.Y);
672 for (video::S3DVertex &vertex : vertices) {
673 vertex.TCoords.rotateBy(tcoord_angle, tcoord_center);
674 vertex.TCoords += tcoord_translate;
677 std::swap(vertices[0].TCoords, vertices[2].TCoords);
679 collector->append(tile_liquid_top, vertices, 4, quad_indices, 6);
682 void MapblockMeshGenerator::drawLiquidBottom()
684 video::S3DVertex vertices[4] = {
685 video::S3DVertex(-BS / 2, -BS / 2, -BS / 2, 0, 0, 0, color_liquid_top, 0, 0),
686 video::S3DVertex( BS / 2, -BS / 2, -BS / 2, 0, 0, 0, color_liquid_top, 1, 0),
687 video::S3DVertex( BS / 2, -BS / 2, BS / 2, 0, 0, 0, color_liquid_top, 1, 1),
688 video::S3DVertex(-BS / 2, -BS / 2, BS / 2, 0, 0, 0, color_liquid_top, 0, 1),
691 for (int i = 0; i < 4; i++) {
692 if (data->m_smooth_lighting)
693 vertices[i].Color = blendLightColor(vertices[i].Pos);
694 vertices[i].Pos += origin;
697 collector->append(tile_liquid_top, vertices, 4, quad_indices, 6);
700 void MapblockMeshGenerator::drawLiquidNode()
702 prepareLiquidNodeDrawing();
703 getLiquidNeighborhood();
704 calculateCornerLevels();
706 if (!top_is_same_liquid)
708 if (draw_liquid_bottom)
712 void MapblockMeshGenerator::drawGlasslikeNode()
716 for (int face = 0; face < 6; face++) {
717 // Check this neighbor
718 v3s16 dir = g_6dirs[face];
719 v3s16 neighbor_pos = blockpos_nodes + p + dir;
720 MapNode neighbor = data->m_vmanip.getNodeNoExNoEmerge(neighbor_pos);
721 // Don't make face if neighbor is of same type
722 if (neighbor.getContent() == n.getContent())
726 v3f(-BS / 2, BS / 2, -BS / 2),
727 v3f( BS / 2, BS / 2, -BS / 2),
728 v3f( BS / 2, -BS / 2, -BS / 2),
729 v3f(-BS / 2, -BS / 2, -BS / 2),
732 for (v3f &vertex : vertices) {
735 vertex.rotateXZBy(180); break;
737 vertex.rotateYZBy( 90); break;
739 vertex.rotateXZBy( 90); break;
741 vertex.rotateXZBy( 0); break;
743 vertex.rotateYZBy(-90); break;
745 vertex.rotateXZBy(-90); break;
748 drawQuad(vertices, dir);
752 void MapblockMeshGenerator::drawGlasslikeFramedNode()
755 for (int face = 0; face < 6; face++)
756 getTile(g_6dirs[face], &tiles[face]);
758 if (!data->m_smooth_lighting)
759 color = encode_light(light, f->light_source);
761 TileSpec glass_tiles[6];
762 for (auto &glass_tile : glass_tiles)
763 glass_tile = tiles[4];
765 // Only respect H/V merge bits when paramtype2 = "glasslikeliquidlevel" (liquid tank)
766 u8 param2 = (f->param_type_2 == CPT2_GLASSLIKE_LIQUID_LEVEL) ? n.getParam2() : 0;
767 bool H_merge = !(param2 & 128);
768 bool V_merge = !(param2 & 64);
771 static const float a = BS / 2.0f;
772 static const float g = a - 0.03f;
773 static const float b = 0.876f * (BS / 2.0f);
775 static const aabb3f frame_edges[FRAMED_EDGE_COUNT] = {
776 aabb3f( b, b, -a, a, a, a), // y+
777 aabb3f(-a, b, -a, -b, a, a), // y+
778 aabb3f( b, -a, -a, a, -b, a), // y-
779 aabb3f(-a, -a, -a, -b, -b, a), // y-
780 aabb3f( b, -a, b, a, a, a), // x+
781 aabb3f( b, -a, -a, a, a, -b), // x+
782 aabb3f(-a, -a, b, -b, a, a), // x-
783 aabb3f(-a, -a, -a, -b, a, -b), // x-
784 aabb3f(-a, b, b, a, a, a), // z+
785 aabb3f(-a, -a, b, a, -b, a), // z+
786 aabb3f(-a, -a, -a, a, -b, -b), // z-
787 aabb3f(-a, b, -a, a, a, -b), // z-
790 // tables of neighbor (connect if same type and merge allowed),
791 // checked with g_26dirs
793 // 1 = connect, 0 = face visible
794 bool nb[FRAMED_NEIGHBOR_COUNT] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
797 static const bool check_nb_vertical [FRAMED_NEIGHBOR_COUNT] =
798 {0,1,0,0,1,0, 0,0,0,0, 0,0,0,0, 0,0,0,0};
799 static const bool check_nb_horizontal [FRAMED_NEIGHBOR_COUNT] =
800 {1,0,1,1,0,1, 0,0,0,0, 1,1,1,1, 0,0,0,0};
801 static const bool check_nb_all [FRAMED_NEIGHBOR_COUNT] =
802 {1,1,1,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1};
803 const bool *check_nb = check_nb_all;
805 // neighbors checks for frames visibility
806 if (H_merge || V_merge) {
808 check_nb = check_nb_vertical; // vertical-only merge
810 check_nb = check_nb_horizontal; // horizontal-only merge
811 content_t current = n.getContent();
812 for (int i = 0; i < FRAMED_NEIGHBOR_COUNT; i++) {
815 v3s16 n2p = blockpos_nodes + p + g_26dirs[i];
816 MapNode n2 = data->m_vmanip.getNodeNoEx(n2p);
817 content_t n2c = n2.getContent();
825 static const u8 nb_triplet[FRAMED_EDGE_COUNT][3] = {
826 {1, 2, 7}, {1, 5, 6}, {4, 2, 15}, {4, 5, 14},
827 {2, 0, 11}, {2, 3, 13}, {5, 0, 10}, {5, 3, 12},
828 {0, 1, 8}, {0, 4, 16}, {3, 4, 17}, {3, 1, 9},
832 for (int edge = 0; edge < FRAMED_EDGE_COUNT; edge++) {
834 if (nb[nb_triplet[edge][2]])
835 edge_invisible = nb[nb_triplet[edge][0]] & nb[nb_triplet[edge][1]];
837 edge_invisible = nb[nb_triplet[edge][0]] ^ nb[nb_triplet[edge][1]];
840 drawAutoLightedCuboid(frame_edges[edge]);
843 for (int face = 0; face < 6; face++) {
847 tile = glass_tiles[face];
856 for (v3f &vertex : vertices) {
859 vertex.rotateXZBy(180); break;
861 vertex.rotateYZBy( 90); break;
863 vertex.rotateXZBy( 90); break;
865 vertex.rotateXZBy( 0); break;
867 vertex.rotateYZBy(-90); break;
869 vertex.rotateXZBy(-90); break;
872 v3s16 dir = g_6dirs[face];
873 drawQuad(vertices, dir);
876 // Optionally render internal liquid level defined by param2
877 // Liquid is textured with 1 tile defined in nodedef 'special_tiles'
878 if (param2 > 0 && f->param_type_2 == CPT2_GLASSLIKE_LIQUID_LEVEL &&
879 f->special_tiles[0].layers[0].texture) {
880 // Internal liquid level has param2 range 0 .. 63,
881 // convert it to -0.5 .. 0.5
882 float vlev = (param2 / 63.0f) * 2.0f - 1.0f;
883 getSpecialTile(0, &tile);
884 drawAutoLightedCuboid(aabb3f(-(nb[5] ? g : b),
888 (nb[1] ? g : b) * vlev,
893 void MapblockMeshGenerator::drawAllfacesNode()
895 static const aabb3f box(-BS / 2, -BS / 2, -BS / 2, BS / 2, BS / 2, BS / 2);
897 drawAutoLightedCuboid(box);
900 void MapblockMeshGenerator::drawTorchlikeNode()
902 u8 wall = n.getWallMounted(nodedef);
905 case DWM_YP: tileindex = 1; break; // ceiling
906 case DWM_YN: tileindex = 0; break; // floor
907 default: tileindex = 2; // side (or invalid—should we care?)
909 useTile(tileindex, MATERIAL_FLAG_CRACK_OVERLAY, MATERIAL_FLAG_BACKFACE_CULLING);
911 float size = BS / 2 * f->visual_scale;
915 v3f( size, -size, 0),
916 v3f(-size, -size, 0),
919 for (v3f &vertex : vertices) {
922 vertex.Y += -size + BS/2;
923 vertex.rotateXZBy(-45);
926 vertex.Y += size - BS/2;
927 vertex.rotateXZBy(45);
930 vertex.X += -size + BS/2;
933 vertex.X += -size + BS/2;
934 vertex.rotateXZBy(180);
937 vertex.X += -size + BS/2;
938 vertex.rotateXZBy(90);
941 vertex.X += -size + BS/2;
942 vertex.rotateXZBy(-90);
948 void MapblockMeshGenerator::drawSignlikeNode()
950 u8 wall = n.getWallMounted(nodedef);
951 useTile(0, MATERIAL_FLAG_CRACK_OVERLAY, MATERIAL_FLAG_BACKFACE_CULLING);
952 static const float offset = BS / 16;
953 float size = BS / 2 * f->visual_scale;
954 // Wall at X+ of node
956 v3f(BS / 2 - offset, size, size),
957 v3f(BS / 2 - offset, size, -size),
958 v3f(BS / 2 - offset, -size, -size),
959 v3f(BS / 2 - offset, -size, size),
962 for (v3f &vertex : vertices) {
965 vertex.rotateXYBy( 90); break;
967 vertex.rotateXYBy(-90); break;
969 vertex.rotateXZBy( 0); break;
971 vertex.rotateXZBy(180); break;
973 vertex.rotateXZBy( 90); break;
975 vertex.rotateXZBy(-90); break;
981 void MapblockMeshGenerator::drawPlantlikeQuad(float rotation, float quad_offset,
982 bool offset_top_only)
985 v3f(-scale, -BS / 2 + 2.0 * scale * plant_height, 0),
986 v3f( scale, -BS / 2 + 2.0 * scale * plant_height, 0),
987 v3f( scale, -BS / 2, 0),
988 v3f(-scale, -BS / 2, 0),
990 if (random_offset_Y) {
991 PseudoRandom yrng(face_num++ | p.X << 16 | p.Z << 8 | p.Y << 24);
992 offset.Y = -BS * ((yrng.next() % 16 / 16.0) * 0.125);
994 int offset_count = offset_top_only ? 2 : 4;
995 for (int i = 0; i < offset_count; i++)
996 vertices[i].Z += quad_offset;
998 for (v3f &vertex : vertices) {
999 vertex.rotateXZBy(rotation + rotate_degree);
1003 u8 wall = n.getWallMounted(nodedef);
1004 if (wall != DWM_YN) {
1005 for (v3f &vertex : vertices) {
1008 vertex.rotateYZBy(180);
1009 vertex.rotateXZBy(180);
1012 vertex.rotateXYBy(90);
1015 vertex.rotateXYBy(-90);
1016 vertex.rotateYZBy(180);
1019 vertex.rotateYZBy(-90);
1020 vertex.rotateXYBy(90);
1023 vertex.rotateYZBy(90);
1024 vertex.rotateXYBy(90);
1030 drawQuad(vertices, v3s16(0, 0, 0), plant_height);
1033 void MapblockMeshGenerator::drawPlantlike(bool is_rooted)
1035 draw_style = PLANT_STYLE_CROSS;
1036 scale = BS / 2 * f->visual_scale;
1037 offset = v3f(0, 0, 0);
1038 rotate_degree = 0.0f;
1039 random_offset_Y = false;
1043 switch (f->param_type_2) {
1044 case CPT2_MESHOPTIONS:
1045 draw_style = PlantlikeStyle(n.param2 & MO_MASK_STYLE);
1046 if (n.param2 & MO_BIT_SCALE_SQRT2)
1048 if (n.param2 & MO_BIT_RANDOM_OFFSET) {
1049 PseudoRandom rng(p.X << 8 | p.Z | p.Y << 16);
1050 offset.X = BS * ((rng.next() % 16 / 16.0) * 0.29 - 0.145);
1051 offset.Z = BS * ((rng.next() % 16 / 16.0) * 0.29 - 0.145);
1053 if (n.param2 & MO_BIT_RANDOM_OFFSET_Y)
1054 random_offset_Y = true;
1057 case CPT2_DEGROTATE:
1058 case CPT2_COLORED_DEGROTATE:
1059 rotate_degree = 1.5f * n.getDegRotate(nodedef);
1063 plant_height = n.param2 / 16.0;
1071 u8 wall = n.getWallMounted(nodedef);
1086 switch (draw_style) {
1087 case PLANT_STYLE_CROSS:
1088 drawPlantlikeQuad(46);
1089 drawPlantlikeQuad(-44);
1092 case PLANT_STYLE_CROSS2:
1093 drawPlantlikeQuad(91);
1094 drawPlantlikeQuad(1);
1097 case PLANT_STYLE_STAR:
1098 drawPlantlikeQuad(121);
1099 drawPlantlikeQuad(241);
1100 drawPlantlikeQuad(1);
1103 case PLANT_STYLE_HASH:
1104 drawPlantlikeQuad( 1, BS / 4);
1105 drawPlantlikeQuad( 91, BS / 4);
1106 drawPlantlikeQuad(181, BS / 4);
1107 drawPlantlikeQuad(271, BS / 4);
1110 case PLANT_STYLE_HASH2:
1111 drawPlantlikeQuad( 1, -BS / 2, true);
1112 drawPlantlikeQuad( 91, -BS / 2, true);
1113 drawPlantlikeQuad(181, -BS / 2, true);
1114 drawPlantlikeQuad(271, -BS / 2, true);
1119 void MapblockMeshGenerator::drawPlantlikeNode()
1125 void MapblockMeshGenerator::drawPlantlikeRootedNode()
1127 useTile(0, MATERIAL_FLAG_CRACK_OVERLAY, 0, true);
1128 origin += v3f(0.0, BS, 0.0);
1130 if (data->m_smooth_lighting) {
1131 getSmoothLightFrame();
1133 MapNode ntop = data->m_vmanip.getNodeNoEx(blockpos_nodes + p);
1134 light = LightPair(getInteriorLight(ntop, 0, nodedef));
1136 drawPlantlike(true);
1140 void MapblockMeshGenerator::drawFirelikeQuad(float rotation, float opening_angle,
1141 float offset_h, float offset_v)
1144 v3f(-scale, -BS / 2 + scale * 2, 0),
1145 v3f( scale, -BS / 2 + scale * 2, 0),
1146 v3f( scale, -BS / 2, 0),
1147 v3f(-scale, -BS / 2, 0),
1150 for (v3f &vertex : vertices) {
1151 vertex.rotateYZBy(opening_angle);
1152 vertex.Z += offset_h;
1153 vertex.rotateXZBy(rotation);
1154 vertex.Y += offset_v;
1159 void MapblockMeshGenerator::drawFirelikeNode()
1162 scale = BS / 2 * f->visual_scale;
1164 // Check for adjacent nodes
1165 bool neighbors = false;
1166 bool neighbor[6] = {0, 0, 0, 0, 0, 0};
1167 content_t current = n.getContent();
1168 for (int i = 0; i < 6; i++) {
1169 v3s16 n2p = blockpos_nodes + p + g_6dirs[i];
1170 MapNode n2 = data->m_vmanip.getNodeNoEx(n2p);
1171 content_t n2c = n2.getContent();
1172 if (n2c != CONTENT_IGNORE && n2c != CONTENT_AIR && n2c != current) {
1177 bool drawBasicFire = neighbor[D6D_YN] || !neighbors;
1178 bool drawBottomFire = neighbor[D6D_YP];
1180 if (drawBasicFire || neighbor[D6D_ZP])
1181 drawFirelikeQuad(0, -10, 0.4 * BS);
1182 else if (drawBottomFire)
1183 drawFirelikeQuad(0, 70, 0.47 * BS, 0.484 * BS);
1185 if (drawBasicFire || neighbor[D6D_XN])
1186 drawFirelikeQuad(90, -10, 0.4 * BS);
1187 else if (drawBottomFire)
1188 drawFirelikeQuad(90, 70, 0.47 * BS, 0.484 * BS);
1190 if (drawBasicFire || neighbor[D6D_ZN])
1191 drawFirelikeQuad(180, -10, 0.4 * BS);
1192 else if (drawBottomFire)
1193 drawFirelikeQuad(180, 70, 0.47 * BS, 0.484 * BS);
1195 if (drawBasicFire || neighbor[D6D_XP])
1196 drawFirelikeQuad(270, -10, 0.4 * BS);
1197 else if (drawBottomFire)
1198 drawFirelikeQuad(270, 70, 0.47 * BS, 0.484 * BS);
1200 if (drawBasicFire) {
1201 drawFirelikeQuad(45, 0, 0.0);
1202 drawFirelikeQuad(-45, 0, 0.0);
1206 void MapblockMeshGenerator::drawFencelikeNode()
1209 TileSpec tile_nocrack = tile;
1211 for (auto &layer : tile_nocrack.layers)
1212 layer.material_flags &= ~MATERIAL_FLAG_CRACK;
1214 // Put wood the right way around in the posts
1215 TileSpec tile_rot = tile;
1216 tile_rot.rotation = 1;
1218 static const f32 post_rad = BS / 8;
1219 static const f32 bar_rad = BS / 16;
1220 static const f32 bar_len = BS / 2 - post_rad;
1222 // The post - always present
1223 static const aabb3f post(-post_rad, -BS / 2, -post_rad,
1224 post_rad, BS / 2, post_rad);
1225 static const f32 postuv[24] = {
1226 0.375, 0.375, 0.625, 0.625,
1227 0.375, 0.375, 0.625, 0.625,
1228 0.000, 0.000, 0.250, 1.000,
1229 0.250, 0.000, 0.500, 1.000,
1230 0.500, 0.000, 0.750, 1.000,
1231 0.750, 0.000, 1.000, 1.000,
1234 drawAutoLightedCuboid(post, postuv);
1236 tile = tile_nocrack;
1238 // Now a section of fence, +X, if there's a post there
1241 MapNode n2 = data->m_vmanip.getNodeNoEx(blockpos_nodes + p2);
1242 const ContentFeatures *f2 = &nodedef->get(n2);
1243 if (f2->drawtype == NDT_FENCELIKE) {
1244 static const aabb3f bar_x1(BS / 2 - bar_len, BS / 4 - bar_rad, -bar_rad,
1245 BS / 2 + bar_len, BS / 4 + bar_rad, bar_rad);
1246 static const aabb3f bar_x2(BS / 2 - bar_len, -BS / 4 - bar_rad, -bar_rad,
1247 BS / 2 + bar_len, -BS / 4 + bar_rad, bar_rad);
1248 static const f32 xrailuv[24] = {
1249 0.000, 0.125, 1.000, 0.250,
1250 0.000, 0.250, 1.000, 0.375,
1251 0.375, 0.375, 0.500, 0.500,
1252 0.625, 0.625, 0.750, 0.750,
1253 0.000, 0.500, 1.000, 0.625,
1254 0.000, 0.875, 1.000, 1.000,
1256 drawAutoLightedCuboid(bar_x1, xrailuv);
1257 drawAutoLightedCuboid(bar_x2, xrailuv);
1260 // Now a section of fence, +Z, if there's a post there
1263 n2 = data->m_vmanip.getNodeNoEx(blockpos_nodes + p2);
1264 f2 = &nodedef->get(n2);
1265 if (f2->drawtype == NDT_FENCELIKE) {
1266 static const aabb3f bar_z1(-bar_rad, BS / 4 - bar_rad, BS / 2 - bar_len,
1267 bar_rad, BS / 4 + bar_rad, BS / 2 + bar_len);
1268 static const aabb3f bar_z2(-bar_rad, -BS / 4 - bar_rad, BS / 2 - bar_len,
1269 bar_rad, -BS / 4 + bar_rad, BS / 2 + bar_len);
1270 static const f32 zrailuv[24] = {
1271 0.1875, 0.0625, 0.3125, 0.3125, // cannot rotate; stretch
1272 0.2500, 0.0625, 0.3750, 0.3125, // for wood texture instead
1273 0.0000, 0.5625, 1.0000, 0.6875,
1274 0.0000, 0.3750, 1.0000, 0.5000,
1275 0.3750, 0.3750, 0.5000, 0.5000,
1276 0.6250, 0.6250, 0.7500, 0.7500,
1278 drawAutoLightedCuboid(bar_z1, zrailuv);
1279 drawAutoLightedCuboid(bar_z2, zrailuv);
1283 bool MapblockMeshGenerator::isSameRail(v3s16 dir)
1285 MapNode node2 = data->m_vmanip.getNodeNoEx(blockpos_nodes + p + dir);
1286 if (node2.getContent() == n.getContent())
1288 const ContentFeatures &def2 = nodedef->get(node2);
1289 return ((def2.drawtype == NDT_RAILLIKE) &&
1290 (def2.getGroup(raillike_groupname) == raillike_group));
1294 static const v3s16 rail_direction[4] = {
1300 static const int rail_slope_angle[4] = {0, 180, 90, -90};
1312 static const RailDesc rail_kinds[16] = {
1315 {straight, 0}, // . . . .
1316 {straight, 0}, // . . . +Z
1317 {straight, 0}, // . . -Z .
1318 {straight, 0}, // . . -Z +Z
1319 {straight, 90}, // . -X . .
1320 { curved, 180}, // . -X . +Z
1321 { curved, 270}, // . -X -Z .
1322 {junction, 180}, // . -X -Z +Z
1323 {straight, 90}, // +X . . .
1324 { curved, 90}, // +X . . +Z
1325 { curved, 0}, // +X . -Z .
1326 {junction, 0}, // +X . -Z +Z
1327 {straight, 90}, // +X -X . .
1328 {junction, 90}, // +X -X . +Z
1329 {junction, 270}, // +X -X -Z .
1330 { cross, 0}, // +X -X -Z +Z
1334 void MapblockMeshGenerator::drawRaillikeNode()
1336 raillike_group = nodedef->get(n).getGroup(raillike_groupname);
1341 bool sloped = false;
1342 for (int dir = 0; dir < 4; dir++) {
1343 bool rail_above = isSameRail(rail_direction[dir] + v3s16(0, 1, 0));
1346 angle = rail_slope_angle[dir];
1349 isSameRail(rail_direction[dir]) ||
1350 isSameRail(rail_direction[dir] + v3s16(0, -1, 0)))
1355 tile_index = straight;
1357 tile_index = rail_kinds[code].tile_index;
1358 angle = rail_kinds[code].angle;
1361 useTile(tile_index, MATERIAL_FLAG_CRACK_OVERLAY, MATERIAL_FLAG_BACKFACE_CULLING);
1363 static const float offset = BS / 64;
1364 static const float size = BS / 2;
1365 float y2 = sloped ? size : -size;
1367 v3f(-size, y2 + offset, size),
1368 v3f( size, y2 + offset, size),
1369 v3f( size, -size + offset, -size),
1370 v3f(-size, -size + offset, -size),
1373 for (v3f &vertex : vertices)
1374 vertex.rotateXZBy(angle);
1379 static const v3s16 nodebox_tile_dirs[6] = {
1388 // we have this order for some reason...
1389 static const v3s16 nodebox_connection_dirs[6] = {
1390 v3s16( 0, 1, 0), // top
1391 v3s16( 0, -1, 0), // bottom
1392 v3s16( 0, 0, -1), // front
1393 v3s16(-1, 0, 0), // left
1394 v3s16( 0, 0, 1), // back
1395 v3s16( 1, 0, 0), // right
1399 void MapblockMeshGenerator::drawNodeboxNode()
1402 for (int face = 0; face < 6; face++) {
1403 // Handles facedir rotation for textures
1404 getTile(nodebox_tile_dirs[face], &tiles[face]);
1407 bool param2_is_rotation =
1408 f->param_type_2 == CPT2_COLORED_FACEDIR ||
1409 f->param_type_2 == CPT2_COLORED_WALLMOUNTED ||
1410 f->param_type_2 == CPT2_FACEDIR ||
1411 f->param_type_2 == CPT2_WALLMOUNTED;
1413 bool param2_is_level =
1414 f->param_type_2 == CPT2_LEVELED;
1416 // locate possible neighboring nodes to connect to
1417 u8 neighbors_set = 0;
1418 u8 solid_neighbors = 0;
1419 u8 sametype_neighbors = 0;
1420 for (int dir = 0; dir != 6; dir++) {
1422 v3s16 p2 = blockpos_nodes + p + nodebox_tile_dirs[dir];
1423 MapNode n2 = data->m_vmanip.getNodeNoEx(p2);
1425 // mark neighbors that are the same node type
1426 // and have the same rotation or higher level stored as param2
1427 if (n2.param0 == n.param0 &&
1428 (!param2_is_rotation || n.param2 == n2.param2) &&
1429 (!param2_is_level || n.param2 <= n2.param2))
1430 sametype_neighbors |= flag;
1432 // mark neighbors that are simple solid blocks
1433 if (nodedef->get(n2).drawtype == NDT_NORMAL)
1434 solid_neighbors |= flag;
1436 if (f->node_box.type == NODEBOX_CONNECTED) {
1437 p2 = blockpos_nodes + p + nodebox_connection_dirs[dir];
1438 n2 = data->m_vmanip.getNodeNoEx(p2);
1439 if (nodedef->nodeboxConnects(n, n2, flag))
1440 neighbors_set |= flag;
1444 std::vector<aabb3f> boxes;
1445 n.getNodeBoxes(nodedef, &boxes, neighbors_set);
1447 bool isTransparent = false;
1449 for (const TileSpec &tile : tiles) {
1450 if (tile.layers[0].isTransparent()) {
1451 isTransparent = true;
1456 if (isTransparent) {
1457 std::vector<float> sections;
1458 // Preallocate 8 default splits + Min&Max for each nodebox
1459 sections.reserve(8 + 2 * boxes.size());
1461 for (int axis = 0; axis < 3; axis++) {
1462 // identify sections
1465 // Default split at node bounds, up to 3 nodes in each direction
1466 for (float s = -3.5f * BS; s < 4.0f * BS; s += 1.0f * BS)
1467 sections.push_back(s);
1470 // Avoid readding the same 8 default splits for Y and Z
1474 // Add edges of existing node boxes, rounded to 1E-3
1475 for (size_t i = 0; i < boxes.size(); i++) {
1476 sections.push_back(std::floor(boxes[i].MinEdge[axis] * 1E3) * 1E-3);
1477 sections.push_back(std::floor(boxes[i].MaxEdge[axis] * 1E3) * 1E-3);
1480 // split the boxes at recorded sections
1481 // limit splits to avoid runaway crash if inner loop adds infinite splits
1482 // due to e.g. precision problems.
1483 // 100 is just an arbitrary, reasonably high number.
1484 for (size_t i = 0; i < boxes.size() && i < 100; i++) {
1485 aabb3f *box = &boxes[i];
1486 for (float section : sections) {
1487 if (box->MinEdge[axis] < section && box->MaxEdge[axis] > section) {
1489 copy.MinEdge[axis] = section;
1490 box->MaxEdge[axis] = section;
1491 boxes.push_back(copy);
1492 box = &boxes[i]; // find new address of the box in case of reallocation
1499 for (auto &box : boxes) {
1500 u8 mask = getNodeBoxMask(box, solid_neighbors, sametype_neighbors);
1501 drawAutoLightedCuboid(box, nullptr, tiles, 6, mask);
1505 void MapblockMeshGenerator::drawMeshNode()
1509 bool private_mesh; // as a grab/drop pair is not thread-safe
1512 if (f->param_type_2 == CPT2_FACEDIR ||
1513 f->param_type_2 == CPT2_COLORED_FACEDIR ||
1514 f->param_type_2 == CPT2_4DIR ||
1515 f->param_type_2 == CPT2_COLORED_4DIR) {
1516 facedir = n.getFaceDir(nodedef);
1517 } else if (f->param_type_2 == CPT2_WALLMOUNTED ||
1518 f->param_type_2 == CPT2_COLORED_WALLMOUNTED) {
1519 // Convert wallmounted to 6dfacedir.
1520 // When cache enabled, it is already converted.
1521 facedir = n.getWallMounted(nodedef);
1522 if (!enable_mesh_cache)
1523 facedir = wallmounted_to_facedir[facedir];
1524 } else if (f->param_type_2 == CPT2_DEGROTATE ||
1525 f->param_type_2 == CPT2_COLORED_DEGROTATE) {
1526 degrotate = n.getDegRotate(nodedef);
1529 if (!data->m_smooth_lighting && f->mesh_ptr[facedir] && !degrotate) {
1530 // use cached meshes
1531 private_mesh = false;
1532 mesh = f->mesh_ptr[facedir];
1533 } else if (f->mesh_ptr[0]) {
1534 // no cache, clone and rotate mesh
1535 private_mesh = true;
1536 mesh = cloneMesh(f->mesh_ptr[0]);
1538 rotateMeshBy6dFacedir(mesh, facedir);
1540 rotateMeshXZby(mesh, 1.5f * degrotate);
1541 recalculateBoundingBox(mesh);
1542 meshmanip->recalculateNormals(mesh, true, false);
1546 int mesh_buffer_count = mesh->getMeshBufferCount();
1547 for (int j = 0; j < mesh_buffer_count; j++) {
1549 scene::IMeshBuffer *buf = mesh->getMeshBuffer(j);
1550 video::S3DVertex *vertices = (video::S3DVertex *)buf->getVertices();
1551 int vertex_count = buf->getVertexCount();
1553 if (data->m_smooth_lighting) {
1554 // Mesh is always private here. So the lighting is applied to each
1555 // vertex right here.
1556 for (int k = 0; k < vertex_count; k++) {
1557 video::S3DVertex &vertex = vertices[k];
1558 vertex.Color = blendLightColor(vertex.Pos, vertex.Normal);
1559 vertex.Pos += origin;
1561 collector->append(tile, vertices, vertex_count,
1562 buf->getIndices(), buf->getIndexCount());
1564 // Don't modify the mesh, it may not be private here.
1565 // Instead, let the collector process colors, etc.
1566 collector->append(tile, vertices, vertex_count,
1567 buf->getIndices(), buf->getIndexCount(), origin,
1568 color, f->light_source);
1575 // also called when the drawtype is known but should have been pre-converted
1576 void MapblockMeshGenerator::errorUnknownDrawtype()
1578 infostream << "Got drawtype " << f->drawtype << std::endl;
1579 FATAL_ERROR("Unknown drawtype");
1582 void MapblockMeshGenerator::drawNode()
1584 // skip some drawtypes early
1585 switch (f->drawtype) {
1586 case NDT_NORMAL: // Drawn by MapBlockMesh
1587 case NDT_AIRLIKE: // Not drawn at all
1588 case NDT_LIQUID: // Drawn by MapBlockMesh
1593 origin = intToFloat(p, BS);
1594 if (data->m_smooth_lighting)
1595 getSmoothLightFrame();
1597 light = LightPair(getInteriorLight(n, 0, nodedef));
1598 switch (f->drawtype) {
1599 case NDT_FLOWINGLIQUID: drawLiquidNode(); break;
1600 case NDT_GLASSLIKE: drawGlasslikeNode(); break;
1601 case NDT_GLASSLIKE_FRAMED: drawGlasslikeFramedNode(); break;
1602 case NDT_ALLFACES: drawAllfacesNode(); break;
1603 case NDT_TORCHLIKE: drawTorchlikeNode(); break;
1604 case NDT_SIGNLIKE: drawSignlikeNode(); break;
1605 case NDT_PLANTLIKE: drawPlantlikeNode(); break;
1606 case NDT_PLANTLIKE_ROOTED: drawPlantlikeRootedNode(); break;
1607 case NDT_FIRELIKE: drawFirelikeNode(); break;
1608 case NDT_FENCELIKE: drawFencelikeNode(); break;
1609 case NDT_RAILLIKE: drawRaillikeNode(); break;
1610 case NDT_NODEBOX: drawNodeboxNode(); break;
1611 case NDT_MESH: drawMeshNode(); break;
1612 default: errorUnknownDrawtype(); break;
1617 TODO: Fix alpha blending for special nodes
1618 Currently only the last element rendered is blended correct
1620 void MapblockMeshGenerator::generate()
1622 for (p.Z = 0; p.Z < data->side_length; p.Z++)
1623 for (p.Y = 0; p.Y < data->side_length; p.Y++)
1624 for (p.X = 0; p.X < data->side_length; p.X++) {
1625 n = data->m_vmanip.getNodeNoEx(blockpos_nodes + p);
1626 f = &nodedef->get(n);
1631 void MapblockMeshGenerator::renderSingle(content_t node, u8 param2)
1634 n = MapNode(node, 0xff, param2);
1635 f = &nodedef->get(n);