3 Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as published by
7 the Free Software Foundation; either version 2.1 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU Lesser General Public License for more details.
15 You should have received a copy of the GNU Lesser General Public License along
16 with this program; if not, write to the Free Software Foundation, Inc.,
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 #include "content_mapblock.h"
22 #include "util/numeric.h"
23 #include "util/directiontables.h"
24 #include "mapblock_mesh.h"
27 #include "client/tile.h"
29 #include <IMeshManipulator.h>
30 #include "client/meshgen/collector.h"
31 #include "client/renderingengine.h"
35 // Distance of light extrapolation (for oversized nodes)
36 // After this distance, it gives up and considers light level constant
37 #define SMOOTH_LIGHTING_OVERSIZE 1.0
39 // Node edge count (for glasslike-framed)
40 #define FRAMED_EDGE_COUNT 12
42 // Node neighbor count, including edge-connected, but not vertex-connected
43 // (for glasslike-framed)
44 // Corresponding offsets are listed in g_27dirs
45 #define FRAMED_NEIGHBOR_COUNT 18
47 static const v3s16 light_dirs[8] = {
58 // Standard index set to make a quad on 4 vertices
59 static constexpr u16 quad_indices[] = {0, 1, 2, 2, 3, 0};
61 const std::string MapblockMeshGenerator::raillike_groupname = "connect_to_raillike";
63 MapblockMeshGenerator::MapblockMeshGenerator(MeshMakeData *input, MeshCollector *output,
64 scene::IMeshManipulator *mm):
67 nodedef(data->m_client->ndef()),
69 blockpos_nodes(data->m_blockpos * MAP_BLOCKSIZE)
71 enable_mesh_cache = g_settings->getBool("enable_mesh_cache") &&
72 !data->m_smooth_lighting; // Mesh cache is not supported with smooth lighting
75 void MapblockMeshGenerator::useTile(int index, u8 set_flags, u8 reset_flags, bool special)
78 getSpecialTile(index, &tile, p == data->m_crack_pos_relative);
80 getTile(index, &tile);
81 if (!data->m_smooth_lighting)
82 color = encode_light(light, f->light_source);
84 for (auto &layer : tile.layers) {
85 layer.material_flags |= set_flags;
86 layer.material_flags &= ~reset_flags;
90 // Returns a tile, ready for use, non-rotated.
91 void MapblockMeshGenerator::getTile(int index, TileSpec *tile)
93 getNodeTileN(n, p, index, data, *tile);
96 // Returns a tile, ready for use, rotated according to the node facedir.
97 void MapblockMeshGenerator::getTile(v3s16 direction, TileSpec *tile)
99 getNodeTile(n, p, direction, data, *tile);
102 // Returns a special tile, ready for use, non-rotated.
103 void MapblockMeshGenerator::getSpecialTile(int index, TileSpec *tile, bool apply_crack)
105 *tile = f->special_tiles[index];
106 TileLayer *top_layer = nullptr;
108 for (auto &layernum : tile->layers) {
109 TileLayer *layer = &layernum;
110 if (layer->texture_id == 0)
113 if (!layer->has_color)
114 n.getColor(*f, &layer->color);
118 top_layer->material_flags |= MATERIAL_FLAG_CRACK;
121 void MapblockMeshGenerator::drawQuad(v3f *coords, const v3s16 &normal,
122 float vertical_tiling)
124 const v2f tcoords[4] = {v2f(0.0, 0.0), v2f(1.0, 0.0),
125 v2f(1.0, vertical_tiling), v2f(0.0, vertical_tiling)};
126 video::S3DVertex vertices[4];
127 bool shade_face = !f->light_source && (normal != v3s16(0, 0, 0));
128 v3f normal2(normal.X, normal.Y, normal.Z);
129 for (int j = 0; j < 4; j++) {
130 vertices[j].Pos = coords[j] + origin;
131 vertices[j].Normal = normal2;
132 if (data->m_smooth_lighting)
133 vertices[j].Color = blendLightColor(coords[j]);
135 vertices[j].Color = color;
137 applyFacesShading(vertices[j].Color, normal2);
138 vertices[j].TCoords = tcoords[j];
140 collector->append(tile, vertices, 4, quad_indices, 6);
144 // tiles - the tiles (materials) to use (for all 6 faces)
145 // tilecount - number of entries in tiles, 1<=tilecount<=6
146 // lights - vertex light levels. The order is the same as in light_dirs.
147 // NULL may be passed if smooth lighting is disabled.
148 // txc - texture coordinates - this is a list of texture coordinates
149 // for the opposite corners of each face - therefore, there
150 // should be (2+2)*6=24 values in the list. The order of
151 // the faces in the list is up-down-right-left-back-front
152 // (compatible with ContentFeatures).
153 void MapblockMeshGenerator::drawCuboid(const aabb3f &box,
154 TileSpec *tiles, int tilecount, const LightInfo *lights, const f32 *txc)
156 assert(tilecount >= 1 && tilecount <= 6); // pre-condition
158 v3f min = box.MinEdge;
159 v3f max = box.MaxEdge;
161 video::SColor colors[6];
162 if (!data->m_smooth_lighting) {
163 for (int face = 0; face != 6; ++face) {
164 colors[face] = encode_light(light, f->light_source);
166 if (!f->light_source) {
167 applyFacesShading(colors[0], v3f(0, 1, 0));
168 applyFacesShading(colors[1], v3f(0, -1, 0));
169 applyFacesShading(colors[2], v3f(1, 0, 0));
170 applyFacesShading(colors[3], v3f(-1, 0, 0));
171 applyFacesShading(colors[4], v3f(0, 0, 1));
172 applyFacesShading(colors[5], v3f(0, 0, -1));
176 video::S3DVertex vertices[24] = {
178 video::S3DVertex(min.X, max.Y, max.Z, 0, 1, 0, colors[0], txc[0], txc[1]),
179 video::S3DVertex(max.X, max.Y, max.Z, 0, 1, 0, colors[0], txc[2], txc[1]),
180 video::S3DVertex(max.X, max.Y, min.Z, 0, 1, 0, colors[0], txc[2], txc[3]),
181 video::S3DVertex(min.X, max.Y, min.Z, 0, 1, 0, colors[0], txc[0], txc[3]),
183 video::S3DVertex(min.X, min.Y, min.Z, 0, -1, 0, colors[1], txc[4], txc[5]),
184 video::S3DVertex(max.X, min.Y, min.Z, 0, -1, 0, colors[1], txc[6], txc[5]),
185 video::S3DVertex(max.X, min.Y, max.Z, 0, -1, 0, colors[1], txc[6], txc[7]),
186 video::S3DVertex(min.X, min.Y, max.Z, 0, -1, 0, colors[1], txc[4], txc[7]),
188 video::S3DVertex(max.X, max.Y, min.Z, 1, 0, 0, colors[2], txc[ 8], txc[9]),
189 video::S3DVertex(max.X, max.Y, max.Z, 1, 0, 0, colors[2], txc[10], txc[9]),
190 video::S3DVertex(max.X, min.Y, max.Z, 1, 0, 0, colors[2], txc[10], txc[11]),
191 video::S3DVertex(max.X, min.Y, min.Z, 1, 0, 0, colors[2], txc[ 8], txc[11]),
193 video::S3DVertex(min.X, max.Y, max.Z, -1, 0, 0, colors[3], txc[12], txc[13]),
194 video::S3DVertex(min.X, max.Y, min.Z, -1, 0, 0, colors[3], txc[14], txc[13]),
195 video::S3DVertex(min.X, min.Y, min.Z, -1, 0, 0, colors[3], txc[14], txc[15]),
196 video::S3DVertex(min.X, min.Y, max.Z, -1, 0, 0, colors[3], txc[12], txc[15]),
198 video::S3DVertex(max.X, max.Y, max.Z, 0, 0, 1, colors[4], txc[16], txc[17]),
199 video::S3DVertex(min.X, max.Y, max.Z, 0, 0, 1, colors[4], txc[18], txc[17]),
200 video::S3DVertex(min.X, min.Y, max.Z, 0, 0, 1, colors[4], txc[18], txc[19]),
201 video::S3DVertex(max.X, min.Y, max.Z, 0, 0, 1, colors[4], txc[16], txc[19]),
203 video::S3DVertex(min.X, max.Y, min.Z, 0, 0, -1, colors[5], txc[20], txc[21]),
204 video::S3DVertex(max.X, max.Y, min.Z, 0, 0, -1, colors[5], txc[22], txc[21]),
205 video::S3DVertex(max.X, min.Y, min.Z, 0, 0, -1, colors[5], txc[22], txc[23]),
206 video::S3DVertex(min.X, min.Y, min.Z, 0, 0, -1, colors[5], txc[20], txc[23]),
209 static const u8 light_indices[24] = {
218 for (int face = 0; face < 6; face++) {
219 int tileindex = MYMIN(face, tilecount - 1);
220 const TileSpec &tile = tiles[tileindex];
221 for (int j = 0; j < 4; j++) {
222 video::S3DVertex &vertex = vertices[face * 4 + j];
223 v2f &tcoords = vertex.TCoords;
224 switch (tile.rotation) {
228 tcoords.rotateBy(90, irr::core::vector2df(0, 0));
231 tcoords.rotateBy(180, irr::core::vector2df(0, 0));
234 tcoords.rotateBy(270, irr::core::vector2df(0, 0));
237 tcoords.X = 1.0 - tcoords.X;
238 tcoords.rotateBy(90, irr::core::vector2df(0, 0));
241 tcoords.X = 1.0 - tcoords.X;
242 tcoords.rotateBy(270, irr::core::vector2df(0, 0));
245 tcoords.Y = 1.0 - tcoords.Y;
246 tcoords.rotateBy(90, irr::core::vector2df(0, 0));
249 tcoords.Y = 1.0 - tcoords.Y;
250 tcoords.rotateBy(270, irr::core::vector2df(0, 0));
253 tcoords.X = 1.0 - tcoords.X;
256 tcoords.Y = 1.0 - tcoords.Y;
264 if (data->m_smooth_lighting) {
265 for (int j = 0; j < 24; ++j) {
266 video::S3DVertex &vertex = vertices[j];
267 vertex.Color = encode_light(
268 lights[light_indices[j]].getPair(MYMAX(0.0f, vertex.Normal.Y)),
270 if (!f->light_source)
271 applyFacesShading(vertex.Color, vertex.Normal);
275 // Add to mesh collector
276 for (int k = 0; k < 6; ++k) {
277 int tileindex = MYMIN(k, tilecount - 1);
278 collector->append(tiles[tileindex], vertices + 4 * k, 4, quad_indices, 6);
282 // Gets the base lighting values for a node
283 void MapblockMeshGenerator::getSmoothLightFrame()
285 for (int k = 0; k < 8; ++k)
286 frame.sunlight[k] = false;
287 for (int k = 0; k < 8; ++k) {
288 LightPair light(getSmoothLightTransparent(blockpos_nodes + p, light_dirs[k], data));
289 frame.lightsDay[k] = light.lightDay;
290 frame.lightsNight[k] = light.lightNight;
291 // If there is direct sunlight and no ambient occlusion at some corner,
292 // mark the vertical edge (top and bottom corners) containing it.
293 if (light.lightDay == 255) {
294 frame.sunlight[k] = true;
295 frame.sunlight[k ^ 2] = true;
300 // Calculates vertex light level
301 // vertex_pos - vertex position in the node (coordinates are clamped to [0.0, 1.0] or so)
302 LightInfo MapblockMeshGenerator::blendLight(const v3f &vertex_pos)
304 // Light levels at (logical) node corners are known. Here,
305 // trilinear interpolation is used to calculate light level
306 // at a given point in the node.
307 f32 x = core::clamp(vertex_pos.X / BS + 0.5, 0.0 - SMOOTH_LIGHTING_OVERSIZE, 1.0 + SMOOTH_LIGHTING_OVERSIZE);
308 f32 y = core::clamp(vertex_pos.Y / BS + 0.5, 0.0 - SMOOTH_LIGHTING_OVERSIZE, 1.0 + SMOOTH_LIGHTING_OVERSIZE);
309 f32 z = core::clamp(vertex_pos.Z / BS + 0.5, 0.0 - SMOOTH_LIGHTING_OVERSIZE, 1.0 + SMOOTH_LIGHTING_OVERSIZE);
310 f32 lightDay = 0.0; // daylight
311 f32 lightNight = 0.0;
312 f32 lightBoosted = 0.0; // daylight + direct sunlight, if any
313 for (int k = 0; k < 8; ++k) {
314 f32 dx = (k & 4) ? x : 1 - x;
315 f32 dy = (k & 2) ? y : 1 - y;
316 f32 dz = (k & 1) ? z : 1 - z;
317 // Use direct sunlight (255), if any; use daylight otherwise.
318 f32 light_boosted = frame.sunlight[k] ? 255 : frame.lightsDay[k];
319 lightDay += dx * dy * dz * frame.lightsDay[k];
320 lightNight += dx * dy * dz * frame.lightsNight[k];
321 lightBoosted += dx * dy * dz * light_boosted;
323 return LightInfo{lightDay, lightNight, lightBoosted};
326 // Calculates vertex color to be used in mapblock mesh
327 // vertex_pos - vertex position in the node (coordinates are clamped to [0.0, 1.0] or so)
328 // tile_color - node's tile color
329 video::SColor MapblockMeshGenerator::blendLightColor(const v3f &vertex_pos)
331 LightInfo light = blendLight(vertex_pos);
332 return encode_light(light.getPair(), f->light_source);
335 video::SColor MapblockMeshGenerator::blendLightColor(const v3f &vertex_pos,
336 const v3f &vertex_normal)
338 LightInfo light = blendLight(vertex_pos);
339 video::SColor color = encode_light(light.getPair(MYMAX(0.0f, vertex_normal.Y)), f->light_source);
340 if (!f->light_source)
341 applyFacesShading(color, vertex_normal);
345 void MapblockMeshGenerator::generateCuboidTextureCoords(const aabb3f &box, f32 *coords)
347 f32 tx1 = (box.MinEdge.X / BS) + 0.5;
348 f32 ty1 = (box.MinEdge.Y / BS) + 0.5;
349 f32 tz1 = (box.MinEdge.Z / BS) + 0.5;
350 f32 tx2 = (box.MaxEdge.X / BS) + 0.5;
351 f32 ty2 = (box.MaxEdge.Y / BS) + 0.5;
352 f32 tz2 = (box.MaxEdge.Z / BS) + 0.5;
354 tx1, 1 - tz2, tx2, 1 - tz1, // up
355 tx1, tz1, tx2, tz2, // down
356 tz1, 1 - ty2, tz2, 1 - ty1, // right
357 1 - tz2, 1 - ty2, 1 - tz1, 1 - ty1, // left
358 1 - tx2, 1 - ty2, 1 - tx1, 1 - ty1, // back
359 tx1, 1 - ty2, tx2, 1 - ty1, // front
361 for (int i = 0; i != 24; ++i)
365 void MapblockMeshGenerator::drawAutoLightedCuboid(aabb3f box, const f32 *txc,
366 TileSpec *tiles, int tile_count)
368 bool scale = std::fabs(f->visual_scale - 1.0f) > 1e-3f;
369 f32 texture_coord_buf[24];
370 f32 dx1 = box.MinEdge.X;
371 f32 dy1 = box.MinEdge.Y;
372 f32 dz1 = box.MinEdge.Z;
373 f32 dx2 = box.MaxEdge.X;
374 f32 dy2 = box.MaxEdge.Y;
375 f32 dz2 = box.MaxEdge.Z;
377 if (!txc) { // generate texture coords before scaling
378 generateCuboidTextureCoords(box, texture_coord_buf);
379 txc = texture_coord_buf;
381 box.MinEdge *= f->visual_scale;
382 box.MaxEdge *= f->visual_scale;
385 generateCuboidTextureCoords(box, texture_coord_buf);
386 txc = texture_coord_buf;
388 box.MinEdge += origin;
389 box.MaxEdge += origin;
394 if (data->m_smooth_lighting) {
396 for (int j = 0; j < 8; ++j) {
398 d.X = (j & 4) ? dx2 : dx1;
399 d.Y = (j & 2) ? dy2 : dy1;
400 d.Z = (j & 1) ? dz2 : dz1;
401 lights[j] = blendLight(d);
403 drawCuboid(box, tiles, tile_count, lights, txc);
405 drawCuboid(box, tiles, tile_count, nullptr, txc);
409 void MapblockMeshGenerator::prepareLiquidNodeDrawing()
411 getSpecialTile(0, &tile_liquid_top);
412 getSpecialTile(1, &tile_liquid);
414 MapNode ntop = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(p.X, p.Y + 1, p.Z));
415 MapNode nbottom = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(p.X, p.Y - 1, p.Z));
416 c_flowing = f->liquid_alternative_flowing_id;
417 c_source = f->liquid_alternative_source_id;
418 top_is_same_liquid = (ntop.getContent() == c_flowing) || (ntop.getContent() == c_source);
419 draw_liquid_bottom = (nbottom.getContent() != c_flowing) && (nbottom.getContent() != c_source);
420 if (draw_liquid_bottom) {
421 const ContentFeatures &f2 = nodedef->get(nbottom.getContent());
422 if (f2.solidness > 1)
423 draw_liquid_bottom = false;
426 if (data->m_smooth_lighting)
427 return; // don't need to pre-compute anything in this case
429 if (f->light_source != 0) {
430 // If this liquid emits light and doesn't contain light, draw
431 // it at what it emits, for an increased effect
432 u8 e = decode_light(f->light_source);
433 light = LightPair(std::max(e, light.lightDay), std::max(e, light.lightNight));
434 } else if (nodedef->get(ntop).param_type == CPT_LIGHT) {
435 // Otherwise, use the light of the node on top if possible
436 light = LightPair(getInteriorLight(ntop, 0, nodedef));
439 color_liquid_top = encode_light(light, f->light_source);
440 color = encode_light(light, f->light_source);
443 void MapblockMeshGenerator::getLiquidNeighborhood()
445 u8 range = rangelim(nodedef->get(c_flowing).liquid_range, 1, 8);
447 for (int w = -1; w <= 1; w++)
448 for (int u = -1; u <= 1; u++) {
449 NeighborData &neighbor = liquid_neighbors[w + 1][u + 1];
450 v3s16 p2 = p + v3s16(u, 0, w);
451 MapNode n2 = data->m_vmanip.getNodeNoEx(blockpos_nodes + p2);
452 neighbor.content = n2.getContent();
453 neighbor.level = -0.5 * BS;
454 neighbor.is_same_liquid = false;
455 neighbor.top_is_same_liquid = false;
457 if (neighbor.content == CONTENT_IGNORE)
460 if (neighbor.content == c_source) {
461 neighbor.is_same_liquid = true;
462 neighbor.level = 0.5 * BS;
463 } else if (neighbor.content == c_flowing) {
464 neighbor.is_same_liquid = true;
465 u8 liquid_level = (n2.param2 & LIQUID_LEVEL_MASK);
466 if (liquid_level <= LIQUID_LEVEL_MAX + 1 - range)
469 liquid_level -= (LIQUID_LEVEL_MAX + 1 - range);
470 neighbor.level = (-0.5 + (liquid_level + 0.5) / range) * BS;
473 // Check node above neighbor.
474 // NOTE: This doesn't get executed if neighbor
477 n2 = data->m_vmanip.getNodeNoEx(blockpos_nodes + p2);
478 if (n2.getContent() == c_source || n2.getContent() == c_flowing)
479 neighbor.top_is_same_liquid = true;
483 void MapblockMeshGenerator::calculateCornerLevels()
485 for (int k = 0; k < 2; k++)
486 for (int i = 0; i < 2; i++)
487 corner_levels[k][i] = getCornerLevel(i, k);
490 f32 MapblockMeshGenerator::getCornerLevel(int i, int k)
495 for (int dk = 0; dk < 2; dk++)
496 for (int di = 0; di < 2; di++) {
497 NeighborData &neighbor_data = liquid_neighbors[k + dk][i + di];
498 content_t content = neighbor_data.content;
500 // If top is liquid, draw starting from top of node
501 if (neighbor_data.top_is_same_liquid)
504 // Source always has the full height
505 if (content == c_source)
508 // Flowing liquid has level information
509 if (content == c_flowing) {
510 sum += neighbor_data.level;
512 } else if (content == CONTENT_AIR) {
517 return -0.5 * BS + 0.2;
524 struct LiquidFaceDesc {
526 v3s16 p[2]; // XZ only; 1 means +, 0 means -
531 static const LiquidFaceDesc liquid_base_faces[4] = {
532 {v3s16( 1, 0, 0), {v3s16(1, 0, 1), v3s16(1, 0, 0)}},
533 {v3s16(-1, 0, 0), {v3s16(0, 0, 0), v3s16(0, 0, 1)}},
534 {v3s16( 0, 0, 1), {v3s16(0, 0, 1), v3s16(1, 0, 1)}},
535 {v3s16( 0, 0, -1), {v3s16(1, 0, 0), v3s16(0, 0, 0)}},
537 static const UV liquid_base_vertices[4] = {
545 void MapblockMeshGenerator::drawLiquidSides()
547 for (const auto &face : liquid_base_faces) {
548 const NeighborData &neighbor = liquid_neighbors[face.dir.Z + 1][face.dir.X + 1];
550 // No face between nodes of the same liquid, unless there is node
551 // at the top to which it should be connected. Again, unless the face
552 // there would be inside the liquid
553 if (neighbor.is_same_liquid) {
554 if (!top_is_same_liquid)
556 if (neighbor.top_is_same_liquid)
560 const ContentFeatures &neighbor_features = nodedef->get(neighbor.content);
561 // Don't draw face if neighbor is blocking the view
562 if (neighbor_features.solidness == 2)
565 video::S3DVertex vertices[4];
566 for (int j = 0; j < 4; j++) {
567 const UV &vertex = liquid_base_vertices[j];
568 const v3s16 &base = face.p[vertex.u];
572 pos.X = (base.X - 0.5f) * BS;
573 pos.Z = (base.Z - 0.5f) * BS;
575 pos.Y = neighbor.is_same_liquid ? corner_levels[base.Z][base.X] : -0.5f * BS;
576 } else if (top_is_same_liquid) {
579 pos.Y = corner_levels[base.Z][base.X];
580 v += (0.5f * BS - corner_levels[base.Z][base.X]) / BS;
583 if (data->m_smooth_lighting)
584 color = blendLightColor(pos);
586 vertices[j] = video::S3DVertex(pos.X, pos.Y, pos.Z, 0, 0, 0, color, vertex.u, v);
588 collector->append(tile_liquid, vertices, 4, quad_indices, 6);
592 void MapblockMeshGenerator::drawLiquidTop()
594 // To get backface culling right, the vertices need to go
595 // clockwise around the front of the face. And we happened to
596 // calculate corner levels in exact reverse order.
597 static const int corner_resolve[4][2] = {{0, 1}, {1, 1}, {1, 0}, {0, 0}};
599 video::S3DVertex vertices[4] = {
600 video::S3DVertex(-BS / 2, 0, BS / 2, 0, 0, 0, color_liquid_top, 0, 1),
601 video::S3DVertex( BS / 2, 0, BS / 2, 0, 0, 0, color_liquid_top, 1, 1),
602 video::S3DVertex( BS / 2, 0, -BS / 2, 0, 0, 0, color_liquid_top, 1, 0),
603 video::S3DVertex(-BS / 2, 0, -BS / 2, 0, 0, 0, color_liquid_top, 0, 0),
606 for (int i = 0; i < 4; i++) {
607 int u = corner_resolve[i][0];
608 int w = corner_resolve[i][1];
609 vertices[i].Pos.Y += corner_levels[w][u];
610 if (data->m_smooth_lighting)
611 vertices[i].Color = blendLightColor(vertices[i].Pos);
612 vertices[i].Pos += origin;
615 // Default downwards-flowing texture animation goes from
616 // -Z towards +Z, thus the direction is +Z.
617 // Rotate texture to make animation go in flow direction
618 // Positive if liquid moves towards +Z
619 f32 dz = (corner_levels[0][0] + corner_levels[0][1]) -
620 (corner_levels[1][0] + corner_levels[1][1]);
621 // Positive if liquid moves towards +X
622 f32 dx = (corner_levels[0][0] + corner_levels[1][0]) -
623 (corner_levels[0][1] + corner_levels[1][1]);
624 f32 tcoord_angle = atan2(dz, dx) * core::RADTODEG;
625 v2f tcoord_center(0.5, 0.5);
626 v2f tcoord_translate(blockpos_nodes.Z + p.Z, blockpos_nodes.X + p.X);
627 tcoord_translate.rotateBy(tcoord_angle);
628 tcoord_translate.X -= floor(tcoord_translate.X);
629 tcoord_translate.Y -= floor(tcoord_translate.Y);
631 for (video::S3DVertex &vertex : vertices) {
632 vertex.TCoords.rotateBy(tcoord_angle, tcoord_center);
633 vertex.TCoords += tcoord_translate;
636 std::swap(vertices[0].TCoords, vertices[2].TCoords);
638 collector->append(tile_liquid_top, vertices, 4, quad_indices, 6);
641 void MapblockMeshGenerator::drawLiquidBottom()
643 video::S3DVertex vertices[4] = {
644 video::S3DVertex(-BS / 2, -BS / 2, -BS / 2, 0, 0, 0, color_liquid_top, 0, 0),
645 video::S3DVertex( BS / 2, -BS / 2, -BS / 2, 0, 0, 0, color_liquid_top, 1, 0),
646 video::S3DVertex( BS / 2, -BS / 2, BS / 2, 0, 0, 0, color_liquid_top, 1, 1),
647 video::S3DVertex(-BS / 2, -BS / 2, BS / 2, 0, 0, 0, color_liquid_top, 0, 1),
650 for (int i = 0; i < 4; i++) {
651 if (data->m_smooth_lighting)
652 vertices[i].Color = blendLightColor(vertices[i].Pos);
653 vertices[i].Pos += origin;
656 collector->append(tile_liquid_top, vertices, 4, quad_indices, 6);
659 void MapblockMeshGenerator::drawLiquidNode()
661 prepareLiquidNodeDrawing();
662 getLiquidNeighborhood();
663 calculateCornerLevels();
665 if (!top_is_same_liquid)
667 if (draw_liquid_bottom)
671 void MapblockMeshGenerator::drawGlasslikeNode()
675 for (int face = 0; face < 6; face++) {
676 // Check this neighbor
677 v3s16 dir = g_6dirs[face];
678 v3s16 neighbor_pos = blockpos_nodes + p + dir;
679 MapNode neighbor = data->m_vmanip.getNodeNoExNoEmerge(neighbor_pos);
680 // Don't make face if neighbor is of same type
681 if (neighbor.getContent() == n.getContent())
685 v3f(-BS / 2, BS / 2, -BS / 2),
686 v3f( BS / 2, BS / 2, -BS / 2),
687 v3f( BS / 2, -BS / 2, -BS / 2),
688 v3f(-BS / 2, -BS / 2, -BS / 2),
691 for (v3f &vertex : vertices) {
694 vertex.rotateXZBy(180); break;
696 vertex.rotateYZBy( 90); break;
698 vertex.rotateXZBy( 90); break;
700 vertex.rotateXZBy( 0); break;
702 vertex.rotateYZBy(-90); break;
704 vertex.rotateXZBy(-90); break;
707 drawQuad(vertices, dir);
711 void MapblockMeshGenerator::drawGlasslikeFramedNode()
714 for (int face = 0; face < 6; face++)
715 getTile(g_6dirs[face], &tiles[face]);
717 if (!data->m_smooth_lighting)
718 color = encode_light(light, f->light_source);
720 TileSpec glass_tiles[6];
721 for (auto &glass_tile : glass_tiles)
722 glass_tile = tiles[4];
724 // Only respect H/V merge bits when paramtype2 = "glasslikeliquidlevel" (liquid tank)
725 u8 param2 = (f->param_type_2 == CPT2_GLASSLIKE_LIQUID_LEVEL) ? n.getParam2() : 0;
726 bool H_merge = !(param2 & 128);
727 bool V_merge = !(param2 & 64);
730 static const float a = BS / 2.0f;
731 static const float g = a - 0.03f;
732 static const float b = 0.876f * (BS / 2.0f);
734 static const aabb3f frame_edges[FRAMED_EDGE_COUNT] = {
735 aabb3f( b, b, -a, a, a, a), // y+
736 aabb3f(-a, b, -a, -b, a, a), // y+
737 aabb3f( b, -a, -a, a, -b, a), // y-
738 aabb3f(-a, -a, -a, -b, -b, a), // y-
739 aabb3f( b, -a, b, a, a, a), // x+
740 aabb3f( b, -a, -a, a, a, -b), // x+
741 aabb3f(-a, -a, b, -b, a, a), // x-
742 aabb3f(-a, -a, -a, -b, a, -b), // x-
743 aabb3f(-a, b, b, a, a, a), // z+
744 aabb3f(-a, -a, b, a, -b, a), // z+
745 aabb3f(-a, -a, -a, a, -b, -b), // z-
746 aabb3f(-a, b, -a, a, a, -b), // z-
749 // tables of neighbour (connect if same type and merge allowed),
750 // checked with g_26dirs
752 // 1 = connect, 0 = face visible
753 bool nb[FRAMED_NEIGHBOR_COUNT] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
756 static const bool check_nb_vertical [FRAMED_NEIGHBOR_COUNT] =
757 {0,1,0,0,1,0, 0,0,0,0, 0,0,0,0, 0,0,0,0};
758 static const bool check_nb_horizontal [FRAMED_NEIGHBOR_COUNT] =
759 {1,0,1,1,0,1, 0,0,0,0, 1,1,1,1, 0,0,0,0};
760 static const bool check_nb_all [FRAMED_NEIGHBOR_COUNT] =
761 {1,1,1,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1};
762 const bool *check_nb = check_nb_all;
764 // neighbours checks for frames visibility
765 if (H_merge || V_merge) {
767 check_nb = check_nb_vertical; // vertical-only merge
769 check_nb = check_nb_horizontal; // horizontal-only merge
770 content_t current = n.getContent();
771 for (int i = 0; i < FRAMED_NEIGHBOR_COUNT; i++) {
774 v3s16 n2p = blockpos_nodes + p + g_26dirs[i];
775 MapNode n2 = data->m_vmanip.getNodeNoEx(n2p);
776 content_t n2c = n2.getContent();
784 static const u8 nb_triplet[FRAMED_EDGE_COUNT][3] = {
785 {1, 2, 7}, {1, 5, 6}, {4, 2, 15}, {4, 5, 14},
786 {2, 0, 11}, {2, 3, 13}, {5, 0, 10}, {5, 3, 12},
787 {0, 1, 8}, {0, 4, 16}, {3, 4, 17}, {3, 1, 9},
791 for (int edge = 0; edge < FRAMED_EDGE_COUNT; edge++) {
793 if (nb[nb_triplet[edge][2]])
794 edge_invisible = nb[nb_triplet[edge][0]] & nb[nb_triplet[edge][1]];
796 edge_invisible = nb[nb_triplet[edge][0]] ^ nb[nb_triplet[edge][1]];
799 drawAutoLightedCuboid(frame_edges[edge]);
802 for (int face = 0; face < 6; face++) {
806 tile = glass_tiles[face];
815 for (v3f &vertex : vertices) {
818 vertex.rotateXZBy(180); break;
820 vertex.rotateYZBy( 90); break;
822 vertex.rotateXZBy( 90); break;
824 vertex.rotateXZBy( 0); break;
826 vertex.rotateYZBy(-90); break;
828 vertex.rotateXZBy(-90); break;
831 v3s16 dir = g_6dirs[face];
832 drawQuad(vertices, dir);
835 // Optionally render internal liquid level defined by param2
836 // Liquid is textured with 1 tile defined in nodedef 'special_tiles'
837 if (param2 > 0 && f->param_type_2 == CPT2_GLASSLIKE_LIQUID_LEVEL &&
838 f->special_tiles[0].layers[0].texture) {
839 // Internal liquid level has param2 range 0 .. 63,
840 // convert it to -0.5 .. 0.5
841 float vlev = (param2 / 63.0f) * 2.0f - 1.0f;
842 getSpecialTile(0, &tile);
843 drawAutoLightedCuboid(aabb3f(-(nb[5] ? g : b),
847 (nb[1] ? g : b) * vlev,
852 void MapblockMeshGenerator::drawAllfacesNode()
854 static const aabb3f box(-BS / 2, -BS / 2, -BS / 2, BS / 2, BS / 2, BS / 2);
856 drawAutoLightedCuboid(box);
859 void MapblockMeshGenerator::drawTorchlikeNode()
861 u8 wall = n.getWallMounted(nodedef);
864 case DWM_YP: tileindex = 1; break; // ceiling
865 case DWM_YN: tileindex = 0; break; // floor
866 default: tileindex = 2; // side (or invalid—should we care?)
868 useTile(tileindex, MATERIAL_FLAG_CRACK_OVERLAY, MATERIAL_FLAG_BACKFACE_CULLING);
870 float size = BS / 2 * f->visual_scale;
874 v3f( size, -size, 0),
875 v3f(-size, -size, 0),
878 for (v3f &vertex : vertices) {
881 vertex.Y += -size + BS/2;
882 vertex.rotateXZBy(-45);
885 vertex.Y += size - BS/2;
886 vertex.rotateXZBy(45);
889 vertex.X += -size + BS/2;
892 vertex.X += -size + BS/2;
893 vertex.rotateXZBy(180);
896 vertex.X += -size + BS/2;
897 vertex.rotateXZBy(90);
900 vertex.X += -size + BS/2;
901 vertex.rotateXZBy(-90);
907 void MapblockMeshGenerator::drawSignlikeNode()
909 u8 wall = n.getWallMounted(nodedef);
910 useTile(0, MATERIAL_FLAG_CRACK_OVERLAY, MATERIAL_FLAG_BACKFACE_CULLING);
911 static const float offset = BS / 16;
912 float size = BS / 2 * f->visual_scale;
913 // Wall at X+ of node
915 v3f(BS / 2 - offset, size, size),
916 v3f(BS / 2 - offset, size, -size),
917 v3f(BS / 2 - offset, -size, -size),
918 v3f(BS / 2 - offset, -size, size),
921 for (v3f &vertex : vertices) {
924 vertex.rotateXYBy( 90); break;
926 vertex.rotateXYBy(-90); break;
928 vertex.rotateXZBy( 0); break;
930 vertex.rotateXZBy(180); break;
932 vertex.rotateXZBy( 90); break;
934 vertex.rotateXZBy(-90); break;
940 void MapblockMeshGenerator::drawPlantlikeQuad(float rotation, float quad_offset,
941 bool offset_top_only)
944 v3f(-scale, -BS / 2 + 2.0 * scale * plant_height, 0),
945 v3f( scale, -BS / 2 + 2.0 * scale * plant_height, 0),
946 v3f( scale, -BS / 2, 0),
947 v3f(-scale, -BS / 2, 0),
949 if (random_offset_Y) {
950 PseudoRandom yrng(face_num++ | p.X << 16 | p.Z << 8 | p.Y << 24);
951 offset.Y = -BS * ((yrng.next() % 16 / 16.0) * 0.125);
953 int offset_count = offset_top_only ? 2 : 4;
954 for (int i = 0; i < offset_count; i++)
955 vertices[i].Z += quad_offset;
957 for (v3f &vertex : vertices) {
958 vertex.rotateXZBy(rotation + rotate_degree);
962 u8 wall = n.getWallMounted(nodedef);
963 if (wall != DWM_YN) {
964 for (v3f &vertex : vertices) {
967 vertex.rotateYZBy(180);
968 vertex.rotateXZBy(180);
971 vertex.rotateXYBy(90);
974 vertex.rotateXYBy(-90);
975 vertex.rotateYZBy(180);
978 vertex.rotateYZBy(-90);
979 vertex.rotateXYBy(90);
982 vertex.rotateYZBy(90);
983 vertex.rotateXYBy(90);
989 drawQuad(vertices, v3s16(0, 0, 0), plant_height);
992 void MapblockMeshGenerator::drawPlantlike(bool is_rooted)
994 draw_style = PLANT_STYLE_CROSS;
995 scale = BS / 2 * f->visual_scale;
996 offset = v3f(0, 0, 0);
997 rotate_degree = 0.0f;
998 random_offset_Y = false;
1002 switch (f->param_type_2) {
1003 case CPT2_MESHOPTIONS:
1004 draw_style = PlantlikeStyle(n.param2 & MO_MASK_STYLE);
1005 if (n.param2 & MO_BIT_SCALE_SQRT2)
1007 if (n.param2 & MO_BIT_RANDOM_OFFSET) {
1008 PseudoRandom rng(p.X << 8 | p.Z | p.Y << 16);
1009 offset.X = BS * ((rng.next() % 16 / 16.0) * 0.29 - 0.145);
1010 offset.Z = BS * ((rng.next() % 16 / 16.0) * 0.29 - 0.145);
1012 if (n.param2 & MO_BIT_RANDOM_OFFSET_Y)
1013 random_offset_Y = true;
1016 case CPT2_DEGROTATE:
1017 case CPT2_COLORED_DEGROTATE:
1018 rotate_degree = 1.5f * n.getDegRotate(nodedef);
1022 plant_height = n.param2 / 16.0;
1030 u8 wall = n.getWallMounted(nodedef);
1045 switch (draw_style) {
1046 case PLANT_STYLE_CROSS:
1047 drawPlantlikeQuad(46);
1048 drawPlantlikeQuad(-44);
1051 case PLANT_STYLE_CROSS2:
1052 drawPlantlikeQuad(91);
1053 drawPlantlikeQuad(1);
1056 case PLANT_STYLE_STAR:
1057 drawPlantlikeQuad(121);
1058 drawPlantlikeQuad(241);
1059 drawPlantlikeQuad(1);
1062 case PLANT_STYLE_HASH:
1063 drawPlantlikeQuad( 1, BS / 4);
1064 drawPlantlikeQuad( 91, BS / 4);
1065 drawPlantlikeQuad(181, BS / 4);
1066 drawPlantlikeQuad(271, BS / 4);
1069 case PLANT_STYLE_HASH2:
1070 drawPlantlikeQuad( 1, -BS / 2, true);
1071 drawPlantlikeQuad( 91, -BS / 2, true);
1072 drawPlantlikeQuad(181, -BS / 2, true);
1073 drawPlantlikeQuad(271, -BS / 2, true);
1078 void MapblockMeshGenerator::drawPlantlikeNode()
1084 void MapblockMeshGenerator::drawPlantlikeRootedNode()
1086 useTile(0, MATERIAL_FLAG_CRACK_OVERLAY, 0, true);
1087 origin += v3f(0.0, BS, 0.0);
1089 if (data->m_smooth_lighting) {
1090 getSmoothLightFrame();
1092 MapNode ntop = data->m_vmanip.getNodeNoEx(blockpos_nodes + p);
1093 light = LightPair(getInteriorLight(ntop, 1, nodedef));
1095 drawPlantlike(true);
1099 void MapblockMeshGenerator::drawFirelikeQuad(float rotation, float opening_angle,
1100 float offset_h, float offset_v)
1103 v3f(-scale, -BS / 2 + scale * 2, 0),
1104 v3f( scale, -BS / 2 + scale * 2, 0),
1105 v3f( scale, -BS / 2, 0),
1106 v3f(-scale, -BS / 2, 0),
1109 for (v3f &vertex : vertices) {
1110 vertex.rotateYZBy(opening_angle);
1111 vertex.Z += offset_h;
1112 vertex.rotateXZBy(rotation);
1113 vertex.Y += offset_v;
1118 void MapblockMeshGenerator::drawFirelikeNode()
1121 scale = BS / 2 * f->visual_scale;
1123 // Check for adjacent nodes
1124 bool neighbors = false;
1125 bool neighbor[6] = {0, 0, 0, 0, 0, 0};
1126 content_t current = n.getContent();
1127 for (int i = 0; i < 6; i++) {
1128 v3s16 n2p = blockpos_nodes + p + g_6dirs[i];
1129 MapNode n2 = data->m_vmanip.getNodeNoEx(n2p);
1130 content_t n2c = n2.getContent();
1131 if (n2c != CONTENT_IGNORE && n2c != CONTENT_AIR && n2c != current) {
1136 bool drawBasicFire = neighbor[D6D_YN] || !neighbors;
1137 bool drawBottomFire = neighbor[D6D_YP];
1139 if (drawBasicFire || neighbor[D6D_ZP])
1140 drawFirelikeQuad(0, -10, 0.4 * BS);
1141 else if (drawBottomFire)
1142 drawFirelikeQuad(0, 70, 0.47 * BS, 0.484 * BS);
1144 if (drawBasicFire || neighbor[D6D_XN])
1145 drawFirelikeQuad(90, -10, 0.4 * BS);
1146 else if (drawBottomFire)
1147 drawFirelikeQuad(90, 70, 0.47 * BS, 0.484 * BS);
1149 if (drawBasicFire || neighbor[D6D_ZN])
1150 drawFirelikeQuad(180, -10, 0.4 * BS);
1151 else if (drawBottomFire)
1152 drawFirelikeQuad(180, 70, 0.47 * BS, 0.484 * BS);
1154 if (drawBasicFire || neighbor[D6D_XP])
1155 drawFirelikeQuad(270, -10, 0.4 * BS);
1156 else if (drawBottomFire)
1157 drawFirelikeQuad(270, 70, 0.47 * BS, 0.484 * BS);
1159 if (drawBasicFire) {
1160 drawFirelikeQuad(45, 0, 0.0);
1161 drawFirelikeQuad(-45, 0, 0.0);
1165 void MapblockMeshGenerator::drawFencelikeNode()
1168 TileSpec tile_nocrack = tile;
1170 for (auto &layer : tile_nocrack.layers)
1171 layer.material_flags &= ~MATERIAL_FLAG_CRACK;
1173 // Put wood the right way around in the posts
1174 TileSpec tile_rot = tile;
1175 tile_rot.rotation = 1;
1177 static const f32 post_rad = BS / 8;
1178 static const f32 bar_rad = BS / 16;
1179 static const f32 bar_len = BS / 2 - post_rad;
1181 // The post - always present
1182 static const aabb3f post(-post_rad, -BS / 2, -post_rad,
1183 post_rad, BS / 2, post_rad);
1184 static const f32 postuv[24] = {
1185 0.375, 0.375, 0.625, 0.625,
1186 0.375, 0.375, 0.625, 0.625,
1187 0.000, 0.000, 0.250, 1.000,
1188 0.250, 0.000, 0.500, 1.000,
1189 0.500, 0.000, 0.750, 1.000,
1190 0.750, 0.000, 1.000, 1.000,
1193 drawAutoLightedCuboid(post, postuv);
1195 tile = tile_nocrack;
1197 // Now a section of fence, +X, if there's a post there
1200 MapNode n2 = data->m_vmanip.getNodeNoEx(blockpos_nodes + p2);
1201 const ContentFeatures *f2 = &nodedef->get(n2);
1202 if (f2->drawtype == NDT_FENCELIKE) {
1203 static const aabb3f bar_x1(BS / 2 - bar_len, BS / 4 - bar_rad, -bar_rad,
1204 BS / 2 + bar_len, BS / 4 + bar_rad, bar_rad);
1205 static const aabb3f bar_x2(BS / 2 - bar_len, -BS / 4 - bar_rad, -bar_rad,
1206 BS / 2 + bar_len, -BS / 4 + bar_rad, bar_rad);
1207 static const f32 xrailuv[24] = {
1208 0.000, 0.125, 1.000, 0.250,
1209 0.000, 0.250, 1.000, 0.375,
1210 0.375, 0.375, 0.500, 0.500,
1211 0.625, 0.625, 0.750, 0.750,
1212 0.000, 0.500, 1.000, 0.625,
1213 0.000, 0.875, 1.000, 1.000,
1215 drawAutoLightedCuboid(bar_x1, xrailuv);
1216 drawAutoLightedCuboid(bar_x2, xrailuv);
1219 // Now a section of fence, +Z, if there's a post there
1222 n2 = data->m_vmanip.getNodeNoEx(blockpos_nodes + p2);
1223 f2 = &nodedef->get(n2);
1224 if (f2->drawtype == NDT_FENCELIKE) {
1225 static const aabb3f bar_z1(-bar_rad, BS / 4 - bar_rad, BS / 2 - bar_len,
1226 bar_rad, BS / 4 + bar_rad, BS / 2 + bar_len);
1227 static const aabb3f bar_z2(-bar_rad, -BS / 4 - bar_rad, BS / 2 - bar_len,
1228 bar_rad, -BS / 4 + bar_rad, BS / 2 + bar_len);
1229 static const f32 zrailuv[24] = {
1230 0.1875, 0.0625, 0.3125, 0.3125, // cannot rotate; stretch
1231 0.2500, 0.0625, 0.3750, 0.3125, // for wood texture instead
1232 0.0000, 0.5625, 1.0000, 0.6875,
1233 0.0000, 0.3750, 1.0000, 0.5000,
1234 0.3750, 0.3750, 0.5000, 0.5000,
1235 0.6250, 0.6250, 0.7500, 0.7500,
1237 drawAutoLightedCuboid(bar_z1, zrailuv);
1238 drawAutoLightedCuboid(bar_z2, zrailuv);
1242 bool MapblockMeshGenerator::isSameRail(v3s16 dir)
1244 MapNode node2 = data->m_vmanip.getNodeNoEx(blockpos_nodes + p + dir);
1245 if (node2.getContent() == n.getContent())
1247 const ContentFeatures &def2 = nodedef->get(node2);
1248 return ((def2.drawtype == NDT_RAILLIKE) &&
1249 (def2.getGroup(raillike_groupname) == raillike_group));
1253 static const v3s16 rail_direction[4] = {
1259 static const int rail_slope_angle[4] = {0, 180, 90, -90};
1271 static const RailDesc rail_kinds[16] = {
1274 {straight, 0}, // . . . .
1275 {straight, 0}, // . . . +Z
1276 {straight, 0}, // . . -Z .
1277 {straight, 0}, // . . -Z +Z
1278 {straight, 90}, // . -X . .
1279 { curved, 180}, // . -X . +Z
1280 { curved, 270}, // . -X -Z .
1281 {junction, 180}, // . -X -Z +Z
1282 {straight, 90}, // +X . . .
1283 { curved, 90}, // +X . . +Z
1284 { curved, 0}, // +X . -Z .
1285 {junction, 0}, // +X . -Z +Z
1286 {straight, 90}, // +X -X . .
1287 {junction, 90}, // +X -X . +Z
1288 {junction, 270}, // +X -X -Z .
1289 { cross, 0}, // +X -X -Z +Z
1293 void MapblockMeshGenerator::drawRaillikeNode()
1295 raillike_group = nodedef->get(n).getGroup(raillike_groupname);
1300 bool sloped = false;
1301 for (int dir = 0; dir < 4; dir++) {
1302 bool rail_above = isSameRail(rail_direction[dir] + v3s16(0, 1, 0));
1305 angle = rail_slope_angle[dir];
1308 isSameRail(rail_direction[dir]) ||
1309 isSameRail(rail_direction[dir] + v3s16(0, -1, 0)))
1314 tile_index = straight;
1316 tile_index = rail_kinds[code].tile_index;
1317 angle = rail_kinds[code].angle;
1320 useTile(tile_index, MATERIAL_FLAG_CRACK_OVERLAY, MATERIAL_FLAG_BACKFACE_CULLING);
1322 static const float offset = BS / 64;
1323 static const float size = BS / 2;
1324 float y2 = sloped ? size : -size;
1326 v3f(-size, y2 + offset, size),
1327 v3f( size, y2 + offset, size),
1328 v3f( size, -size + offset, -size),
1329 v3f(-size, -size + offset, -size),
1332 for (v3f &vertex : vertices)
1333 vertex.rotateXZBy(angle);
1338 static const v3s16 nodebox_tile_dirs[6] = {
1347 // we have this order for some reason...
1348 static const v3s16 nodebox_connection_dirs[6] = {
1349 v3s16( 0, 1, 0), // top
1350 v3s16( 0, -1, 0), // bottom
1351 v3s16( 0, 0, -1), // front
1352 v3s16(-1, 0, 0), // left
1353 v3s16( 0, 0, 1), // back
1354 v3s16( 1, 0, 0), // right
1358 void MapblockMeshGenerator::drawNodeboxNode()
1361 for (int face = 0; face < 6; face++) {
1362 // Handles facedir rotation for textures
1363 getTile(nodebox_tile_dirs[face], &tiles[face]);
1366 // locate possible neighboring nodes to connect to
1367 u8 neighbors_set = 0;
1368 if (f->node_box.type == NODEBOX_CONNECTED) {
1369 for (int dir = 0; dir != 6; dir++) {
1371 v3s16 p2 = blockpos_nodes + p + nodebox_connection_dirs[dir];
1372 MapNode n2 = data->m_vmanip.getNodeNoEx(p2);
1373 if (nodedef->nodeboxConnects(n, n2, flag))
1374 neighbors_set |= flag;
1378 std::vector<aabb3f> boxes;
1379 n.getNodeBoxes(nodedef, &boxes, neighbors_set);
1381 bool isTransparent = false;
1383 for (const TileSpec &tile : tiles) {
1384 if (tile.layers[0].isTransparent()) {
1385 isTransparent = true;
1390 if (isTransparent) {
1391 std::vector<float> sections;
1392 // Preallocate 8 default splits + Min&Max for each nodebox
1393 sections.reserve(8 + 2 * boxes.size());
1395 for (int axis = 0; axis < 3; axis++) {
1396 // identify sections
1399 // Default split at node bounds, up to 3 nodes in each direction
1400 for (float s = -3.5f * BS; s < 4.0f * BS; s += 1.0f * BS)
1401 sections.push_back(s);
1404 // Avoid readding the same 8 default splits for Y and Z
1408 // Add edges of existing node boxes, rounded to 1E-3
1409 for (size_t i = 0; i < boxes.size(); i++) {
1410 sections.push_back(std::floor(boxes[i].MinEdge[axis] * 1E3) * 1E-3);
1411 sections.push_back(std::floor(boxes[i].MaxEdge[axis] * 1E3) * 1E-3);
1414 // split the boxes at recorded sections
1415 // limit splits to avoid runaway crash if inner loop adds infinite splits
1416 // due to e.g. precision problems.
1417 // 100 is just an arbitrary, reasonably high number.
1418 for (size_t i = 0; i < boxes.size() && i < 100; i++) {
1419 aabb3f *box = &boxes[i];
1420 for (float section : sections) {
1421 if (box->MinEdge[axis] < section && box->MaxEdge[axis] > section) {
1423 copy.MinEdge[axis] = section;
1424 box->MaxEdge[axis] = section;
1425 boxes.push_back(copy);
1426 box = &boxes[i]; // find new address of the box in case of reallocation
1433 for (auto &box : boxes)
1434 drawAutoLightedCuboid(box, nullptr, tiles, 6);
1437 void MapblockMeshGenerator::drawMeshNode()
1441 bool private_mesh; // as a grab/drop pair is not thread-safe
1444 if (f->param_type_2 == CPT2_FACEDIR ||
1445 f->param_type_2 == CPT2_COLORED_FACEDIR) {
1446 facedir = n.getFaceDir(nodedef);
1447 } else if (f->param_type_2 == CPT2_WALLMOUNTED ||
1448 f->param_type_2 == CPT2_COLORED_WALLMOUNTED) {
1449 // Convert wallmounted to 6dfacedir.
1450 // When cache enabled, it is already converted.
1451 facedir = n.getWallMounted(nodedef);
1452 if (!enable_mesh_cache)
1453 facedir = wallmounted_to_facedir[facedir];
1454 } else if (f->param_type_2 == CPT2_DEGROTATE ||
1455 f->param_type_2 == CPT2_COLORED_DEGROTATE) {
1456 degrotate = n.getDegRotate(nodedef);
1459 if (!data->m_smooth_lighting && f->mesh_ptr[facedir] && !degrotate) {
1460 // use cached meshes
1461 private_mesh = false;
1462 mesh = f->mesh_ptr[facedir];
1463 } else if (f->mesh_ptr[0]) {
1464 // no cache, clone and rotate mesh
1465 private_mesh = true;
1466 mesh = cloneMesh(f->mesh_ptr[0]);
1468 rotateMeshBy6dFacedir(mesh, facedir);
1470 rotateMeshXZby(mesh, 1.5f * degrotate);
1471 recalculateBoundingBox(mesh);
1472 meshmanip->recalculateNormals(mesh, true, false);
1476 int mesh_buffer_count = mesh->getMeshBufferCount();
1477 for (int j = 0; j < mesh_buffer_count; j++) {
1479 scene::IMeshBuffer *buf = mesh->getMeshBuffer(j);
1480 video::S3DVertex *vertices = (video::S3DVertex *)buf->getVertices();
1481 int vertex_count = buf->getVertexCount();
1483 if (data->m_smooth_lighting) {
1484 // Mesh is always private here. So the lighting is applied to each
1485 // vertex right here.
1486 for (int k = 0; k < vertex_count; k++) {
1487 video::S3DVertex &vertex = vertices[k];
1488 vertex.Color = blendLightColor(vertex.Pos, vertex.Normal);
1489 vertex.Pos += origin;
1491 collector->append(tile, vertices, vertex_count,
1492 buf->getIndices(), buf->getIndexCount());
1494 // Don't modify the mesh, it may not be private here.
1495 // Instead, let the collector process colors, etc.
1496 collector->append(tile, vertices, vertex_count,
1497 buf->getIndices(), buf->getIndexCount(), origin,
1498 color, f->light_source);
1505 // also called when the drawtype is known but should have been pre-converted
1506 void MapblockMeshGenerator::errorUnknownDrawtype()
1508 infostream << "Got drawtype " << f->drawtype << std::endl;
1509 FATAL_ERROR("Unknown drawtype");
1512 void MapblockMeshGenerator::drawNode()
1514 // skip some drawtypes early
1515 switch (f->drawtype) {
1516 case NDT_NORMAL: // Drawn by MapBlockMesh
1517 case NDT_AIRLIKE: // Not drawn at all
1518 case NDT_LIQUID: // Drawn by MapBlockMesh
1523 origin = intToFloat(p, BS);
1524 if (data->m_smooth_lighting)
1525 getSmoothLightFrame();
1527 light = LightPair(getInteriorLight(n, 1, nodedef));
1528 switch (f->drawtype) {
1529 case NDT_FLOWINGLIQUID: drawLiquidNode(); break;
1530 case NDT_GLASSLIKE: drawGlasslikeNode(); break;
1531 case NDT_GLASSLIKE_FRAMED: drawGlasslikeFramedNode(); break;
1532 case NDT_ALLFACES: drawAllfacesNode(); break;
1533 case NDT_TORCHLIKE: drawTorchlikeNode(); break;
1534 case NDT_SIGNLIKE: drawSignlikeNode(); break;
1535 case NDT_PLANTLIKE: drawPlantlikeNode(); break;
1536 case NDT_PLANTLIKE_ROOTED: drawPlantlikeRootedNode(); break;
1537 case NDT_FIRELIKE: drawFirelikeNode(); break;
1538 case NDT_FENCELIKE: drawFencelikeNode(); break;
1539 case NDT_RAILLIKE: drawRaillikeNode(); break;
1540 case NDT_NODEBOX: drawNodeboxNode(); break;
1541 case NDT_MESH: drawMeshNode(); break;
1542 default: errorUnknownDrawtype(); break;
1547 TODO: Fix alpha blending for special nodes
1548 Currently only the last element rendered is blended correct
1550 void MapblockMeshGenerator::generate()
1552 for (p.Z = 0; p.Z < MAP_BLOCKSIZE; p.Z++)
1553 for (p.Y = 0; p.Y < MAP_BLOCKSIZE; p.Y++)
1554 for (p.X = 0; p.X < MAP_BLOCKSIZE; p.X++) {
1555 n = data->m_vmanip.getNodeNoEx(blockpos_nodes + p);
1556 f = &nodedef->get(n);
1561 void MapblockMeshGenerator::renderSingle(content_t node, u8 param2)
1564 n = MapNode(node, 0xff, param2);
1565 f = &nodedef->get(n);