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.
20 #include "content_mapblock.h"
21 #include "util/numeric.h"
22 #include "util/directiontables.h"
23 #include "mapblock_mesh.h"
26 #include "client/tile.h"
28 #include <IMeshManipulator.h>
29 #include "client/meshgen/collector.h"
30 #include "client/renderingengine.h"
34 // Distance of light extrapolation (for oversized nodes)
35 // After this distance, it gives up and considers light level constant
36 #define SMOOTH_LIGHTING_OVERSIZE 1.0
38 // Node edge count (for glasslike-framed)
39 #define FRAMED_EDGE_COUNT 12
41 // Node neighbor count, including edge-connected, but not vertex-connected
42 // (for glasslike-framed)
43 // Corresponding offsets are listed in g_27dirs
44 #define FRAMED_NEIGHBOR_COUNT 18
46 static const v3s16 light_dirs[8] = {
57 // Standard index set to make a quad on 4 vertices
58 static constexpr u16 quad_indices[] = {0, 1, 2, 2, 3, 0};
60 const std::string MapblockMeshGenerator::raillike_groupname = "connect_to_raillike";
62 MapblockMeshGenerator::MapblockMeshGenerator(MeshMakeData *input, MeshCollector *output)
67 nodedef = data->m_client->ndef();
68 meshmanip = RenderingEngine::get_scene_manager()->getMeshManipulator();
70 enable_mesh_cache = g_settings->getBool("enable_mesh_cache") &&
71 !data->m_smooth_lighting; // Mesh cache is not supported with smooth lighting
73 blockpos_nodes = data->m_blockpos * MAP_BLOCKSIZE;
76 void MapblockMeshGenerator::useTile(int index, u8 set_flags, u8 reset_flags, bool special)
79 getSpecialTile(index, &tile, p == data->m_crack_pos_relative);
81 getTile(index, &tile);
82 if (!data->m_smooth_lighting)
83 color = encode_light(light, f->light_source);
85 for (auto &layer : tile.layers) {
86 layer.material_flags |= set_flags;
87 layer.material_flags &= ~reset_flags;
91 // Returns a tile, ready for use, non-rotated.
92 void MapblockMeshGenerator::getTile(int index, TileSpec *tile)
94 getNodeTileN(n, p, index, data, *tile);
97 // Returns a tile, ready for use, rotated according to the node facedir.
98 void MapblockMeshGenerator::getTile(v3s16 direction, TileSpec *tile)
100 getNodeTile(n, p, direction, data, *tile);
103 // Returns a special tile, ready for use, non-rotated.
104 void MapblockMeshGenerator::getSpecialTile(int index, TileSpec *tile, bool apply_crack)
106 *tile = f->special_tiles[index];
107 TileLayer *top_layer = nullptr;
109 for (auto &layernum : tile->layers) {
110 TileLayer *layer = &layernum;
111 if (layer->texture_id == 0)
114 if (!layer->has_color)
115 n.getColor(*f, &layer->color);
119 top_layer->material_flags |= MATERIAL_FLAG_CRACK;
122 void MapblockMeshGenerator::drawQuad(v3f *coords, const v3s16 &normal,
123 float vertical_tiling)
125 const v2f tcoords[4] = {v2f(0.0, 0.0), v2f(1.0, 0.0),
126 v2f(1.0, vertical_tiling), v2f(0.0, vertical_tiling)};
127 video::S3DVertex vertices[4];
128 bool shade_face = !f->light_source && (normal != v3s16(0, 0, 0));
129 v3f normal2(normal.X, normal.Y, normal.Z);
130 for (int j = 0; j < 4; j++) {
131 vertices[j].Pos = coords[j] + origin;
132 vertices[j].Normal = normal2;
133 if (data->m_smooth_lighting)
134 vertices[j].Color = blendLightColor(coords[j]);
136 vertices[j].Color = color;
138 applyFacesShading(vertices[j].Color, normal2);
139 vertices[j].TCoords = tcoords[j];
141 collector->append(tile, vertices, 4, quad_indices, 6);
145 // tiles - the tiles (materials) to use (for all 6 faces)
146 // tilecount - number of entries in tiles, 1<=tilecount<=6
147 // lights - vertex light levels. The order is the same as in light_dirs.
148 // NULL may be passed if smooth lighting is disabled.
149 // txc - texture coordinates - this is a list of texture coordinates
150 // for the opposite corners of each face - therefore, there
151 // should be (2+2)*6=24 values in the list. The order of
152 // the faces in the list is up-down-right-left-back-front
153 // (compatible with ContentFeatures).
154 void MapblockMeshGenerator::drawCuboid(const aabb3f &box,
155 TileSpec *tiles, int tilecount, const LightInfo *lights, const f32 *txc)
157 assert(tilecount >= 1 && tilecount <= 6); // pre-condition
159 v3f min = box.MinEdge;
160 v3f max = box.MaxEdge;
162 video::SColor colors[6];
163 if (!data->m_smooth_lighting) {
164 for (int face = 0; face != 6; ++face) {
165 colors[face] = encode_light(light, f->light_source);
167 if (!f->light_source) {
168 applyFacesShading(colors[0], v3f(0, 1, 0));
169 applyFacesShading(colors[1], v3f(0, -1, 0));
170 applyFacesShading(colors[2], v3f(1, 0, 0));
171 applyFacesShading(colors[3], v3f(-1, 0, 0));
172 applyFacesShading(colors[4], v3f(0, 0, 1));
173 applyFacesShading(colors[5], v3f(0, 0, -1));
177 video::S3DVertex vertices[24] = {
179 video::S3DVertex(min.X, max.Y, max.Z, 0, 1, 0, colors[0], txc[0], txc[1]),
180 video::S3DVertex(max.X, max.Y, max.Z, 0, 1, 0, colors[0], txc[2], txc[1]),
181 video::S3DVertex(max.X, max.Y, min.Z, 0, 1, 0, colors[0], txc[2], txc[3]),
182 video::S3DVertex(min.X, max.Y, min.Z, 0, 1, 0, colors[0], txc[0], txc[3]),
184 video::S3DVertex(min.X, min.Y, min.Z, 0, -1, 0, colors[1], txc[4], txc[5]),
185 video::S3DVertex(max.X, min.Y, min.Z, 0, -1, 0, colors[1], txc[6], txc[5]),
186 video::S3DVertex(max.X, min.Y, max.Z, 0, -1, 0, colors[1], txc[6], txc[7]),
187 video::S3DVertex(min.X, min.Y, max.Z, 0, -1, 0, colors[1], txc[4], txc[7]),
189 video::S3DVertex(max.X, max.Y, min.Z, 1, 0, 0, colors[2], txc[ 8], txc[9]),
190 video::S3DVertex(max.X, max.Y, max.Z, 1, 0, 0, colors[2], txc[10], txc[9]),
191 video::S3DVertex(max.X, min.Y, max.Z, 1, 0, 0, colors[2], txc[10], txc[11]),
192 video::S3DVertex(max.X, min.Y, min.Z, 1, 0, 0, colors[2], txc[ 8], txc[11]),
194 video::S3DVertex(min.X, max.Y, max.Z, -1, 0, 0, colors[3], txc[12], txc[13]),
195 video::S3DVertex(min.X, max.Y, min.Z, -1, 0, 0, colors[3], txc[14], txc[13]),
196 video::S3DVertex(min.X, min.Y, min.Z, -1, 0, 0, colors[3], txc[14], txc[15]),
197 video::S3DVertex(min.X, min.Y, max.Z, -1, 0, 0, colors[3], txc[12], txc[15]),
199 video::S3DVertex(max.X, max.Y, max.Z, 0, 0, 1, colors[4], txc[16], txc[17]),
200 video::S3DVertex(min.X, max.Y, max.Z, 0, 0, 1, colors[4], txc[18], txc[17]),
201 video::S3DVertex(min.X, min.Y, max.Z, 0, 0, 1, colors[4], txc[18], txc[19]),
202 video::S3DVertex(max.X, min.Y, max.Z, 0, 0, 1, colors[4], txc[16], txc[19]),
204 video::S3DVertex(min.X, max.Y, min.Z, 0, 0, -1, colors[5], txc[20], txc[21]),
205 video::S3DVertex(max.X, max.Y, min.Z, 0, 0, -1, colors[5], txc[22], txc[21]),
206 video::S3DVertex(max.X, min.Y, min.Z, 0, 0, -1, colors[5], txc[22], txc[23]),
207 video::S3DVertex(min.X, min.Y, min.Z, 0, 0, -1, colors[5], txc[20], txc[23]),
210 static const u8 light_indices[24] = {
219 for (int face = 0; face < 6; face++) {
220 int tileindex = MYMIN(face, tilecount - 1);
221 const TileSpec &tile = tiles[tileindex];
222 for (int j = 0; j < 4; j++) {
223 video::S3DVertex &vertex = vertices[face * 4 + j];
224 v2f &tcoords = vertex.TCoords;
225 switch (tile.rotation) {
229 tcoords.rotateBy(90, irr::core::vector2df(0, 0));
232 tcoords.rotateBy(180, irr::core::vector2df(0, 0));
235 tcoords.rotateBy(270, irr::core::vector2df(0, 0));
238 tcoords.X = 1.0 - tcoords.X;
239 tcoords.rotateBy(90, irr::core::vector2df(0, 0));
242 tcoords.X = 1.0 - tcoords.X;
243 tcoords.rotateBy(270, irr::core::vector2df(0, 0));
246 tcoords.Y = 1.0 - tcoords.Y;
247 tcoords.rotateBy(90, irr::core::vector2df(0, 0));
250 tcoords.Y = 1.0 - tcoords.Y;
251 tcoords.rotateBy(270, irr::core::vector2df(0, 0));
254 tcoords.X = 1.0 - tcoords.X;
257 tcoords.Y = 1.0 - tcoords.Y;
265 if (data->m_smooth_lighting) {
266 for (int j = 0; j < 24; ++j) {
267 video::S3DVertex &vertex = vertices[j];
268 vertex.Color = encode_light(
269 lights[light_indices[j]].getPair(MYMAX(0.0f, vertex.Normal.Y)),
271 if (!f->light_source)
272 applyFacesShading(vertex.Color, vertex.Normal);
276 // Add to mesh collector
277 for (int k = 0; k < 6; ++k) {
278 int tileindex = MYMIN(k, tilecount - 1);
279 collector->append(tiles[tileindex], vertices + 4 * k, 4, quad_indices, 6);
283 // Gets the base lighting values for a node
284 void MapblockMeshGenerator::getSmoothLightFrame()
286 for (int k = 0; k < 8; ++k)
287 frame.sunlight[k] = false;
288 for (int k = 0; k < 8; ++k) {
289 LightPair light(getSmoothLightTransparent(blockpos_nodes + p, light_dirs[k], data));
290 frame.lightsDay[k] = light.lightDay;
291 frame.lightsNight[k] = light.lightNight;
292 // If there is direct sunlight and no ambient occlusion at some corner,
293 // mark the vertical edge (top and bottom corners) containing it.
294 if (light.lightDay == 255) {
295 frame.sunlight[k] = true;
296 frame.sunlight[k ^ 2] = true;
301 // Calculates vertex light level
302 // vertex_pos - vertex position in the node (coordinates are clamped to [0.0, 1.0] or so)
303 LightInfo MapblockMeshGenerator::blendLight(const v3f &vertex_pos)
305 // Light levels at (logical) node corners are known. Here,
306 // trilinear interpolation is used to calculate light level
307 // at a given point in the node.
308 f32 x = core::clamp(vertex_pos.X / BS + 0.5, 0.0 - SMOOTH_LIGHTING_OVERSIZE, 1.0 + SMOOTH_LIGHTING_OVERSIZE);
309 f32 y = core::clamp(vertex_pos.Y / BS + 0.5, 0.0 - SMOOTH_LIGHTING_OVERSIZE, 1.0 + SMOOTH_LIGHTING_OVERSIZE);
310 f32 z = core::clamp(vertex_pos.Z / BS + 0.5, 0.0 - SMOOTH_LIGHTING_OVERSIZE, 1.0 + SMOOTH_LIGHTING_OVERSIZE);
311 f32 lightDay = 0.0; // daylight
312 f32 lightNight = 0.0;
313 f32 lightBoosted = 0.0; // daylight + direct sunlight, if any
314 for (int k = 0; k < 8; ++k) {
315 f32 dx = (k & 4) ? x : 1 - x;
316 f32 dy = (k & 2) ? y : 1 - y;
317 f32 dz = (k & 1) ? z : 1 - z;
318 // Use direct sunlight (255), if any; use daylight otherwise.
319 f32 light_boosted = frame.sunlight[k] ? 255 : frame.lightsDay[k];
320 lightDay += dx * dy * dz * frame.lightsDay[k];
321 lightNight += dx * dy * dz * frame.lightsNight[k];
322 lightBoosted += dx * dy * dz * light_boosted;
324 return LightInfo{lightDay, lightNight, lightBoosted};
327 // Calculates vertex color to be used in mapblock mesh
328 // vertex_pos - vertex position in the node (coordinates are clamped to [0.0, 1.0] or so)
329 // tile_color - node's tile color
330 video::SColor MapblockMeshGenerator::blendLightColor(const v3f &vertex_pos)
332 LightInfo light = blendLight(vertex_pos);
333 return encode_light(light.getPair(), f->light_source);
336 video::SColor MapblockMeshGenerator::blendLightColor(const v3f &vertex_pos,
337 const v3f &vertex_normal)
339 LightInfo light = blendLight(vertex_pos);
340 video::SColor color = encode_light(light.getPair(MYMAX(0.0f, vertex_normal.Y)), f->light_source);
341 if (!f->light_source)
342 applyFacesShading(color, vertex_normal);
346 void MapblockMeshGenerator::generateCuboidTextureCoords(const aabb3f &box, f32 *coords)
348 f32 tx1 = (box.MinEdge.X / BS) + 0.5;
349 f32 ty1 = (box.MinEdge.Y / BS) + 0.5;
350 f32 tz1 = (box.MinEdge.Z / BS) + 0.5;
351 f32 tx2 = (box.MaxEdge.X / BS) + 0.5;
352 f32 ty2 = (box.MaxEdge.Y / BS) + 0.5;
353 f32 tz2 = (box.MaxEdge.Z / BS) + 0.5;
355 tx1, 1 - tz2, tx2, 1 - tz1, // up
356 tx1, tz1, tx2, tz2, // down
357 tz1, 1 - ty2, tz2, 1 - ty1, // right
358 1 - tz2, 1 - ty2, 1 - tz1, 1 - ty1, // left
359 1 - tx2, 1 - ty2, 1 - tx1, 1 - ty1, // back
360 tx1, 1 - ty2, tx2, 1 - ty1, // front
362 for (int i = 0; i != 24; ++i)
366 void MapblockMeshGenerator::drawAutoLightedCuboid(aabb3f box, const f32 *txc,
367 TileSpec *tiles, int tile_count)
369 bool scale = std::fabs(f->visual_scale - 1.0f) > 1e-3f;
370 f32 texture_coord_buf[24];
371 f32 dx1 = box.MinEdge.X;
372 f32 dy1 = box.MinEdge.Y;
373 f32 dz1 = box.MinEdge.Z;
374 f32 dx2 = box.MaxEdge.X;
375 f32 dy2 = box.MaxEdge.Y;
376 f32 dz2 = box.MaxEdge.Z;
378 if (!txc) { // generate texture coords before scaling
379 generateCuboidTextureCoords(box, texture_coord_buf);
380 txc = texture_coord_buf;
382 box.MinEdge *= f->visual_scale;
383 box.MaxEdge *= f->visual_scale;
385 box.MinEdge += origin;
386 box.MaxEdge += origin;
388 generateCuboidTextureCoords(box, texture_coord_buf);
389 txc = texture_coord_buf;
395 if (data->m_smooth_lighting) {
397 for (int j = 0; j < 8; ++j) {
399 d.X = (j & 4) ? dx2 : dx1;
400 d.Y = (j & 2) ? dy2 : dy1;
401 d.Z = (j & 1) ? dz2 : dz1;
402 lights[j] = blendLight(d);
404 drawCuboid(box, tiles, tile_count, lights, txc);
406 drawCuboid(box, tiles, tile_count, nullptr, txc);
410 void MapblockMeshGenerator::prepareLiquidNodeDrawing()
412 getSpecialTile(0, &tile_liquid_top);
413 getSpecialTile(1, &tile_liquid);
415 MapNode ntop = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(p.X, p.Y + 1, p.Z));
416 MapNode nbottom = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(p.X, p.Y - 1, p.Z));
417 c_flowing = f->liquid_alternative_flowing_id;
418 c_source = f->liquid_alternative_source_id;
419 top_is_same_liquid = (ntop.getContent() == c_flowing) || (ntop.getContent() == c_source);
420 draw_liquid_bottom = (nbottom.getContent() != c_flowing) && (nbottom.getContent() != c_source);
421 if (draw_liquid_bottom) {
422 const ContentFeatures &f2 = nodedef->get(nbottom.getContent());
423 if (f2.solidness > 1)
424 draw_liquid_bottom = false;
427 if (data->m_smooth_lighting)
428 return; // don't need to pre-compute anything in this case
430 if (f->light_source != 0) {
431 // If this liquid emits light and doesn't contain light, draw
432 // it at what it emits, for an increased effect
433 u8 e = decode_light(f->light_source);
434 light = LightPair(std::max(e, light.lightDay), std::max(e, light.lightNight));
435 } else if (nodedef->get(ntop).param_type == CPT_LIGHT) {
436 // Otherwise, use the light of the node on top if possible
437 light = LightPair(getInteriorLight(ntop, 0, nodedef));
440 color_liquid_top = encode_light(light, f->light_source);
441 color = encode_light(light, f->light_source);
444 void MapblockMeshGenerator::getLiquidNeighborhood()
446 u8 range = rangelim(nodedef->get(c_flowing).liquid_range, 1, 8);
448 for (int w = -1; w <= 1; w++)
449 for (int u = -1; u <= 1; u++) {
450 NeighborData &neighbor = liquid_neighbors[w + 1][u + 1];
451 v3s16 p2 = p + v3s16(u, 0, w);
452 MapNode n2 = data->m_vmanip.getNodeNoEx(blockpos_nodes + p2);
453 neighbor.content = n2.getContent();
454 neighbor.level = -0.5 * BS;
455 neighbor.is_same_liquid = false;
456 neighbor.top_is_same_liquid = false;
458 if (neighbor.content == CONTENT_IGNORE)
461 if (neighbor.content == c_source) {
462 neighbor.is_same_liquid = true;
463 neighbor.level = 0.5 * BS;
464 } else if (neighbor.content == c_flowing) {
465 neighbor.is_same_liquid = true;
466 u8 liquid_level = (n2.param2 & LIQUID_LEVEL_MASK);
467 if (liquid_level <= LIQUID_LEVEL_MAX + 1 - range)
470 liquid_level -= (LIQUID_LEVEL_MAX + 1 - range);
471 neighbor.level = (-0.5 + (liquid_level + 0.5) / range) * BS;
474 // Check node above neighbor.
475 // NOTE: This doesn't get executed if neighbor
478 n2 = data->m_vmanip.getNodeNoEx(blockpos_nodes + p2);
479 if (n2.getContent() == c_source || n2.getContent() == c_flowing)
480 neighbor.top_is_same_liquid = true;
484 void MapblockMeshGenerator::calculateCornerLevels()
486 for (int k = 0; k < 2; k++)
487 for (int i = 0; i < 2; i++)
488 corner_levels[k][i] = getCornerLevel(i, k);
491 f32 MapblockMeshGenerator::getCornerLevel(int i, int k)
496 for (int dk = 0; dk < 2; dk++)
497 for (int di = 0; di < 2; di++) {
498 NeighborData &neighbor_data = liquid_neighbors[k + dk][i + di];
499 content_t content = neighbor_data.content;
501 // If top is liquid, draw starting from top of node
502 if (neighbor_data.top_is_same_liquid)
505 // Source always has the full height
506 if (content == c_source)
509 // Flowing liquid has level information
510 if (content == c_flowing) {
511 sum += neighbor_data.level;
513 } else if (content == CONTENT_AIR) {
516 return -0.5 * BS + 0.2;
525 struct LiquidFaceDesc {
527 v3s16 p[2]; // XZ only; 1 means +, 0 means -
532 static const LiquidFaceDesc liquid_base_faces[4] = {
533 {v3s16( 1, 0, 0), {v3s16(1, 0, 1), v3s16(1, 0, 0)}},
534 {v3s16(-1, 0, 0), {v3s16(0, 0, 0), v3s16(0, 0, 1)}},
535 {v3s16( 0, 0, 1), {v3s16(0, 0, 1), v3s16(1, 0, 1)}},
536 {v3s16( 0, 0, -1), {v3s16(1, 0, 0), v3s16(0, 0, 0)}},
538 static const UV liquid_base_vertices[4] = {
546 void MapblockMeshGenerator::drawLiquidSides()
548 for (const auto &face : liquid_base_faces) {
549 const NeighborData &neighbor = liquid_neighbors[face.dir.Z + 1][face.dir.X + 1];
551 // No face between nodes of the same liquid, unless there is node
552 // at the top to which it should be connected. Again, unless the face
553 // there would be inside the liquid
554 if (neighbor.is_same_liquid) {
555 if (!top_is_same_liquid)
557 if (neighbor.top_is_same_liquid)
561 const ContentFeatures &neighbor_features = nodedef->get(neighbor.content);
562 // Don't draw face if neighbor is blocking the view
563 if (neighbor_features.solidness == 2)
566 video::S3DVertex vertices[4];
567 for (int j = 0; j < 4; j++) {
568 const UV &vertex = liquid_base_vertices[j];
569 const v3s16 &base = face.p[vertex.u];
573 pos.X = (base.X - 0.5f) * BS;
574 pos.Z = (base.Z - 0.5f) * BS;
576 pos.Y = neighbor.is_same_liquid ? corner_levels[base.Z][base.X] : -0.5f * BS;
577 } else if (top_is_same_liquid) {
580 pos.Y = corner_levels[base.Z][base.X];
581 v += (0.5f * BS - corner_levels[base.Z][base.X]) / BS;
584 if (data->m_smooth_lighting)
585 color = blendLightColor(pos);
587 vertices[j] = video::S3DVertex(pos.X, pos.Y, pos.Z, 0, 0, 0, color, vertex.u, v);
589 collector->append(tile_liquid, vertices, 4, quad_indices, 6);
593 void MapblockMeshGenerator::drawLiquidTop()
595 // To get backface culling right, the vertices need to go
596 // clockwise around the front of the face. And we happened to
597 // calculate corner levels in exact reverse order.
598 static const int corner_resolve[4][2] = {{0, 1}, {1, 1}, {1, 0}, {0, 0}};
600 video::S3DVertex vertices[4] = {
601 video::S3DVertex(-BS / 2, 0, BS / 2, 0, 0, 0, color_liquid_top, 0, 1),
602 video::S3DVertex( BS / 2, 0, BS / 2, 0, 0, 0, color_liquid_top, 1, 1),
603 video::S3DVertex( BS / 2, 0, -BS / 2, 0, 0, 0, color_liquid_top, 1, 0),
604 video::S3DVertex(-BS / 2, 0, -BS / 2, 0, 0, 0, color_liquid_top, 0, 0),
607 for (int i = 0; i < 4; i++) {
608 int u = corner_resolve[i][0];
609 int w = corner_resolve[i][1];
610 vertices[i].Pos.Y += corner_levels[w][u];
611 if (data->m_smooth_lighting)
612 vertices[i].Color = blendLightColor(vertices[i].Pos);
613 vertices[i].Pos += origin;
616 // Default downwards-flowing texture animation goes from
617 // -Z towards +Z, thus the direction is +Z.
618 // Rotate texture to make animation go in flow direction
619 // Positive if liquid moves towards +Z
620 f32 dz = (corner_levels[0][0] + corner_levels[0][1]) -
621 (corner_levels[1][0] + corner_levels[1][1]);
622 // Positive if liquid moves towards +X
623 f32 dx = (corner_levels[0][0] + corner_levels[1][0]) -
624 (corner_levels[0][1] + corner_levels[1][1]);
625 f32 tcoord_angle = atan2(dz, dx) * core::RADTODEG;
626 v2f tcoord_center(0.5, 0.5);
627 v2f tcoord_translate(blockpos_nodes.Z + p.Z, blockpos_nodes.X + p.X);
628 tcoord_translate.rotateBy(tcoord_angle);
629 tcoord_translate.X -= floor(tcoord_translate.X);
630 tcoord_translate.Y -= floor(tcoord_translate.Y);
632 for (video::S3DVertex &vertex : vertices) {
633 vertex.TCoords.rotateBy(tcoord_angle, tcoord_center);
634 vertex.TCoords += tcoord_translate;
637 std::swap(vertices[0].TCoords, vertices[2].TCoords);
639 collector->append(tile_liquid_top, vertices, 4, quad_indices, 6);
642 void MapblockMeshGenerator::drawLiquidBottom()
644 video::S3DVertex vertices[4] = {
645 video::S3DVertex(-BS / 2, -BS / 2, -BS / 2, 0, 0, 0, color_liquid_top, 0, 0),
646 video::S3DVertex( BS / 2, -BS / 2, -BS / 2, 0, 0, 0, color_liquid_top, 1, 0),
647 video::S3DVertex( BS / 2, -BS / 2, BS / 2, 0, 0, 0, color_liquid_top, 1, 1),
648 video::S3DVertex(-BS / 2, -BS / 2, BS / 2, 0, 0, 0, color_liquid_top, 0, 1),
651 for (int i = 0; i < 4; i++) {
652 if (data->m_smooth_lighting)
653 vertices[i].Color = blendLightColor(vertices[i].Pos);
654 vertices[i].Pos += origin;
657 collector->append(tile_liquid_top, vertices, 4, quad_indices, 6);
660 void MapblockMeshGenerator::drawLiquidNode()
662 prepareLiquidNodeDrawing();
663 getLiquidNeighborhood();
664 calculateCornerLevels();
666 if (!top_is_same_liquid)
668 if (draw_liquid_bottom)
672 void MapblockMeshGenerator::drawGlasslikeNode()
676 for (int face = 0; face < 6; face++) {
677 // Check this neighbor
678 v3s16 dir = g_6dirs[face];
679 v3s16 neighbor_pos = blockpos_nodes + p + dir;
680 MapNode neighbor = data->m_vmanip.getNodeNoExNoEmerge(neighbor_pos);
681 // Don't make face if neighbor is of same type
682 if (neighbor.getContent() == n.getContent())
686 v3f(-BS / 2, BS / 2, -BS / 2),
687 v3f( BS / 2, BS / 2, -BS / 2),
688 v3f( BS / 2, -BS / 2, -BS / 2),
689 v3f(-BS / 2, -BS / 2, -BS / 2),
692 for (v3f &vertex : vertices) {
695 vertex.rotateXZBy(180); break;
697 vertex.rotateYZBy( 90); break;
699 vertex.rotateXZBy( 90); break;
701 vertex.rotateXZBy( 0); break;
703 vertex.rotateYZBy(-90); break;
705 vertex.rotateXZBy(-90); break;
708 drawQuad(vertices, dir);
712 void MapblockMeshGenerator::drawGlasslikeFramedNode()
715 for (int face = 0; face < 6; face++)
716 getTile(g_6dirs[face], &tiles[face]);
718 if (!data->m_smooth_lighting)
719 color = encode_light(light, f->light_source);
721 TileSpec glass_tiles[6];
722 for (auto &glass_tile : glass_tiles)
723 glass_tile = tiles[4];
725 u8 param2 = n.getParam2();
726 bool H_merge = !(param2 & 128);
727 bool V_merge = !(param2 & 64);
730 static const float a = BS / 2.0f;
731 static const float g = a - 0.03f;
732 static const float b = 0.876f * (BS / 2.0f);
734 static const aabb3f frame_edges[FRAMED_EDGE_COUNT] = {
735 aabb3f( b, b, -a, a, a, a), // y+
736 aabb3f(-a, b, -a, -b, a, a), // y+
737 aabb3f( b, -a, -a, a, -b, a), // y-
738 aabb3f(-a, -a, -a, -b, -b, a), // y-
739 aabb3f( b, -a, b, a, a, a), // x+
740 aabb3f( b, -a, -a, a, a, -b), // x+
741 aabb3f(-a, -a, b, -b, a, a), // x-
742 aabb3f(-a, -a, -a, -b, a, -b), // x-
743 aabb3f(-a, b, b, a, a, a), // z+
744 aabb3f(-a, -a, b, a, -b, a), // z+
745 aabb3f(-a, -a, -a, a, -b, -b), // z-
746 aabb3f(-a, b, -a, a, a, -b), // z-
749 // tables of neighbour (connect if same type and merge allowed),
750 // checked with g_26dirs
752 // 1 = connect, 0 = face visible
753 bool nb[FRAMED_NEIGHBOR_COUNT] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
756 static const bool check_nb_vertical [FRAMED_NEIGHBOR_COUNT] =
757 {0,1,0,0,1,0, 0,0,0,0, 0,0,0,0, 0,0,0,0};
758 static const bool check_nb_horizontal [FRAMED_NEIGHBOR_COUNT] =
759 {1,0,1,1,0,1, 0,0,0,0, 1,1,1,1, 0,0,0,0};
760 static const bool check_nb_all [FRAMED_NEIGHBOR_COUNT] =
761 {1,1,1,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1};
762 const bool *check_nb = check_nb_all;
764 // neighbours checks for frames visibility
765 if (H_merge || V_merge) {
767 check_nb = check_nb_vertical; // vertical-only merge
769 check_nb = check_nb_horizontal; // horizontal-only merge
770 content_t current = n.getContent();
771 for (int i = 0; i < FRAMED_NEIGHBOR_COUNT; i++) {
774 v3s16 n2p = blockpos_nodes + p + g_26dirs[i];
775 MapNode n2 = data->m_vmanip.getNodeNoEx(n2p);
776 content_t n2c = n2.getContent();
784 static const u8 nb_triplet[FRAMED_EDGE_COUNT][3] = {
785 {1, 2, 7}, {1, 5, 6}, {4, 2, 15}, {4, 5, 14},
786 {2, 0, 11}, {2, 3, 13}, {5, 0, 10}, {5, 3, 12},
787 {0, 1, 8}, {0, 4, 16}, {3, 4, 17}, {3, 1, 9},
791 for (int edge = 0; edge < FRAMED_EDGE_COUNT; edge++) {
793 if (nb[nb_triplet[edge][2]])
794 edge_invisible = nb[nb_triplet[edge][0]] & nb[nb_triplet[edge][1]];
796 edge_invisible = nb[nb_triplet[edge][0]] ^ nb[nb_triplet[edge][1]];
799 drawAutoLightedCuboid(frame_edges[edge]);
802 for (int face = 0; face < 6; face++) {
806 tile = glass_tiles[face];
815 for (v3f &vertex : vertices) {
818 vertex.rotateXZBy(180); break;
820 vertex.rotateYZBy( 90); break;
822 vertex.rotateXZBy( 90); break;
824 vertex.rotateXZBy( 0); break;
826 vertex.rotateYZBy(-90); break;
828 vertex.rotateXZBy(-90); break;
831 v3s16 dir = g_6dirs[face];
832 drawQuad(vertices, dir);
835 // Optionally render internal liquid level defined by param2
836 // Liquid is textured with 1 tile defined in nodedef 'special_tiles'
837 if (param2 > 0 && f->param_type_2 == CPT2_GLASSLIKE_LIQUID_LEVEL &&
838 f->special_tiles[0].layers[0].texture) {
839 // Internal liquid level has param2 range 0 .. 63,
840 // convert it to -0.5 .. 0.5
841 float vlev = (param2 / 63.0f) * 2.0f - 1.0f;
842 getSpecialTile(0, &tile);
843 drawAutoLightedCuboid(aabb3f(-(nb[5] ? g : b),
847 (nb[1] ? g : b) * vlev,
852 void MapblockMeshGenerator::drawAllfacesNode()
854 static const aabb3f box(-BS / 2, -BS / 2, -BS / 2, BS / 2, BS / 2, BS / 2);
856 drawAutoLightedCuboid(box);
859 void MapblockMeshGenerator::drawTorchlikeNode()
861 u8 wall = n.getWallMounted(nodedef);
864 case DWM_YP: tileindex = 1; break; // ceiling
865 case DWM_YN: tileindex = 0; break; // floor
866 default: tileindex = 2; // side (or invalid—should we care?)
868 useTile(tileindex, MATERIAL_FLAG_CRACK_OVERLAY, MATERIAL_FLAG_BACKFACE_CULLING);
870 float size = BS / 2 * f->visual_scale;
874 v3f( size, -size, 0),
875 v3f(-size, -size, 0),
878 for (v3f &vertex : vertices) {
881 vertex.Y += -size + BS/2;
882 vertex.rotateXZBy(-45);
885 vertex.Y += size - BS/2;
886 vertex.rotateXZBy(45);
889 vertex.X += -size + BS/2;
892 vertex.X += -size + BS/2;
893 vertex.rotateXZBy(180);
896 vertex.X += -size + BS/2;
897 vertex.rotateXZBy(90);
900 vertex.X += -size + BS/2;
901 vertex.rotateXZBy(-90);
907 void MapblockMeshGenerator::drawSignlikeNode()
909 u8 wall = n.getWallMounted(nodedef);
910 useTile(0, MATERIAL_FLAG_CRACK_OVERLAY, MATERIAL_FLAG_BACKFACE_CULLING);
911 static const float offset = BS / 16;
912 float size = BS / 2 * f->visual_scale;
913 // Wall at X+ of node
915 v3f(BS / 2 - offset, size, size),
916 v3f(BS / 2 - offset, size, -size),
917 v3f(BS / 2 - offset, -size, -size),
918 v3f(BS / 2 - offset, -size, size),
921 for (v3f &vertex : vertices) {
924 vertex.rotateXYBy( 90); break;
926 vertex.rotateXYBy(-90); break;
928 vertex.rotateXZBy( 0); break;
930 vertex.rotateXZBy(180); break;
932 vertex.rotateXZBy( 90); break;
934 vertex.rotateXZBy(-90); break;
940 void MapblockMeshGenerator::drawPlantlikeQuad(float rotation, float quad_offset,
941 bool offset_top_only)
944 v3f(-scale, -BS / 2 + 2.0 * scale * plant_height, 0),
945 v3f( scale, -BS / 2 + 2.0 * scale * plant_height, 0),
946 v3f( scale, -BS / 2, 0),
947 v3f(-scale, -BS / 2, 0),
949 if (random_offset_Y) {
950 PseudoRandom yrng(face_num++ | p.X << 16 | p.Z << 8 | p.Y << 24);
951 offset.Y = -BS * ((yrng.next() % 16 / 16.0) * 0.125);
953 int offset_count = offset_top_only ? 2 : 4;
954 for (int i = 0; i < offset_count; i++)
955 vertices[i].Z += quad_offset;
957 for (v3f &vertex : vertices) {
958 vertex.rotateXZBy(rotation + rotate_degree);
961 drawQuad(vertices, v3s16(0, 0, 0), plant_height);
964 void MapblockMeshGenerator::drawPlantlike()
966 draw_style = PLANT_STYLE_CROSS;
967 scale = BS / 2 * f->visual_scale;
968 offset = v3f(0, 0, 0);
970 random_offset_Y = false;
974 switch (f->param_type_2) {
975 case CPT2_MESHOPTIONS:
976 draw_style = PlantlikeStyle(n.param2 & MO_MASK_STYLE);
977 if (n.param2 & MO_BIT_SCALE_SQRT2)
979 if (n.param2 & MO_BIT_RANDOM_OFFSET) {
980 PseudoRandom rng(p.X << 8 | p.Z | p.Y << 16);
981 offset.X = BS * ((rng.next() % 16 / 16.0) * 0.29 - 0.145);
982 offset.Z = BS * ((rng.next() % 16 / 16.0) * 0.29 - 0.145);
984 if (n.param2 & MO_BIT_RANDOM_OFFSET_Y)
985 random_offset_Y = true;
989 rotate_degree = n.param2 * 2;
993 plant_height = n.param2 / 16.0;
1000 switch (draw_style) {
1001 case PLANT_STYLE_CROSS:
1002 drawPlantlikeQuad(46);
1003 drawPlantlikeQuad(-44);
1006 case PLANT_STYLE_CROSS2:
1007 drawPlantlikeQuad(91);
1008 drawPlantlikeQuad(1);
1011 case PLANT_STYLE_STAR:
1012 drawPlantlikeQuad(121);
1013 drawPlantlikeQuad(241);
1014 drawPlantlikeQuad(1);
1017 case PLANT_STYLE_HASH:
1018 drawPlantlikeQuad( 1, BS / 4);
1019 drawPlantlikeQuad( 91, BS / 4);
1020 drawPlantlikeQuad(181, BS / 4);
1021 drawPlantlikeQuad(271, BS / 4);
1024 case PLANT_STYLE_HASH2:
1025 drawPlantlikeQuad( 1, -BS / 2, true);
1026 drawPlantlikeQuad( 91, -BS / 2, true);
1027 drawPlantlikeQuad(181, -BS / 2, true);
1028 drawPlantlikeQuad(271, -BS / 2, true);
1033 void MapblockMeshGenerator::drawPlantlikeNode()
1039 void MapblockMeshGenerator::drawPlantlikeRootedNode()
1041 useTile(0, MATERIAL_FLAG_CRACK_OVERLAY, 0, true);
1042 origin += v3f(0.0, BS, 0.0);
1044 if (data->m_smooth_lighting) {
1045 getSmoothLightFrame();
1047 MapNode ntop = data->m_vmanip.getNodeNoEx(blockpos_nodes + p);
1048 light = LightPair(getInteriorLight(ntop, 1, nodedef));
1054 void MapblockMeshGenerator::drawFirelikeQuad(float rotation, float opening_angle,
1055 float offset_h, float offset_v)
1058 v3f(-scale, -BS / 2 + scale * 2, 0),
1059 v3f( scale, -BS / 2 + scale * 2, 0),
1060 v3f( scale, -BS / 2, 0),
1061 v3f(-scale, -BS / 2, 0),
1064 for (v3f &vertex : vertices) {
1065 vertex.rotateYZBy(opening_angle);
1066 vertex.Z += offset_h;
1067 vertex.rotateXZBy(rotation);
1068 vertex.Y += offset_v;
1073 void MapblockMeshGenerator::drawFirelikeNode()
1076 scale = BS / 2 * f->visual_scale;
1078 // Check for adjacent nodes
1079 bool neighbors = false;
1080 bool neighbor[6] = {0, 0, 0, 0, 0, 0};
1081 content_t current = n.getContent();
1082 for (int i = 0; i < 6; i++) {
1083 v3s16 n2p = blockpos_nodes + p + g_6dirs[i];
1084 MapNode n2 = data->m_vmanip.getNodeNoEx(n2p);
1085 content_t n2c = n2.getContent();
1086 if (n2c != CONTENT_IGNORE && n2c != CONTENT_AIR && n2c != current) {
1091 bool drawBasicFire = neighbor[D6D_YN] || !neighbors;
1092 bool drawBottomFire = neighbor[D6D_YP];
1094 if (drawBasicFire || neighbor[D6D_ZP])
1095 drawFirelikeQuad(0, -10, 0.4 * BS);
1096 else if (drawBottomFire)
1097 drawFirelikeQuad(0, 70, 0.47 * BS, 0.484 * BS);
1099 if (drawBasicFire || neighbor[D6D_XN])
1100 drawFirelikeQuad(90, -10, 0.4 * BS);
1101 else if (drawBottomFire)
1102 drawFirelikeQuad(90, 70, 0.47 * BS, 0.484 * BS);
1104 if (drawBasicFire || neighbor[D6D_ZN])
1105 drawFirelikeQuad(180, -10, 0.4 * BS);
1106 else if (drawBottomFire)
1107 drawFirelikeQuad(180, 70, 0.47 * BS, 0.484 * BS);
1109 if (drawBasicFire || neighbor[D6D_XP])
1110 drawFirelikeQuad(270, -10, 0.4 * BS);
1111 else if (drawBottomFire)
1112 drawFirelikeQuad(270, 70, 0.47 * BS, 0.484 * BS);
1114 if (drawBasicFire) {
1115 drawFirelikeQuad(45, 0, 0.0);
1116 drawFirelikeQuad(-45, 0, 0.0);
1120 void MapblockMeshGenerator::drawFencelikeNode()
1123 TileSpec tile_nocrack = tile;
1125 for (auto &layer : tile_nocrack.layers)
1126 layer.material_flags &= ~MATERIAL_FLAG_CRACK;
1128 // Put wood the right way around in the posts
1129 TileSpec tile_rot = tile;
1130 tile_rot.rotation = 1;
1132 static const f32 post_rad = BS / 8;
1133 static const f32 bar_rad = BS / 16;
1134 static const f32 bar_len = BS / 2 - post_rad;
1136 // The post - always present
1137 static const aabb3f post(-post_rad, -BS / 2, -post_rad,
1138 post_rad, BS / 2, post_rad);
1139 static const f32 postuv[24] = {
1140 0.375, 0.375, 0.625, 0.625,
1141 0.375, 0.375, 0.625, 0.625,
1142 0.000, 0.000, 0.250, 1.000,
1143 0.250, 0.000, 0.500, 1.000,
1144 0.500, 0.000, 0.750, 1.000,
1145 0.750, 0.000, 1.000, 1.000,
1148 drawAutoLightedCuboid(post, postuv);
1150 tile = tile_nocrack;
1152 // Now a section of fence, +X, if there's a post there
1155 MapNode n2 = data->m_vmanip.getNodeNoEx(blockpos_nodes + p2);
1156 const ContentFeatures *f2 = &nodedef->get(n2);
1157 if (f2->drawtype == NDT_FENCELIKE) {
1158 static const aabb3f bar_x1(BS / 2 - bar_len, BS / 4 - bar_rad, -bar_rad,
1159 BS / 2 + bar_len, BS / 4 + bar_rad, bar_rad);
1160 static const aabb3f bar_x2(BS / 2 - bar_len, -BS / 4 - bar_rad, -bar_rad,
1161 BS / 2 + bar_len, -BS / 4 + bar_rad, bar_rad);
1162 static const f32 xrailuv[24] = {
1163 0.000, 0.125, 1.000, 0.250,
1164 0.000, 0.250, 1.000, 0.375,
1165 0.375, 0.375, 0.500, 0.500,
1166 0.625, 0.625, 0.750, 0.750,
1167 0.000, 0.500, 1.000, 0.625,
1168 0.000, 0.875, 1.000, 1.000,
1170 drawAutoLightedCuboid(bar_x1, xrailuv);
1171 drawAutoLightedCuboid(bar_x2, xrailuv);
1174 // Now a section of fence, +Z, if there's a post there
1177 n2 = data->m_vmanip.getNodeNoEx(blockpos_nodes + p2);
1178 f2 = &nodedef->get(n2);
1179 if (f2->drawtype == NDT_FENCELIKE) {
1180 static const aabb3f bar_z1(-bar_rad, BS / 4 - bar_rad, BS / 2 - bar_len,
1181 bar_rad, BS / 4 + bar_rad, BS / 2 + bar_len);
1182 static const aabb3f bar_z2(-bar_rad, -BS / 4 - bar_rad, BS / 2 - bar_len,
1183 bar_rad, -BS / 4 + bar_rad, BS / 2 + bar_len);
1184 static const f32 zrailuv[24] = {
1185 0.1875, 0.0625, 0.3125, 0.3125, // cannot rotate; stretch
1186 0.2500, 0.0625, 0.3750, 0.3125, // for wood texture instead
1187 0.0000, 0.5625, 1.0000, 0.6875,
1188 0.0000, 0.3750, 1.0000, 0.5000,
1189 0.3750, 0.3750, 0.5000, 0.5000,
1190 0.6250, 0.6250, 0.7500, 0.7500,
1192 drawAutoLightedCuboid(bar_z1, zrailuv);
1193 drawAutoLightedCuboid(bar_z2, zrailuv);
1197 bool MapblockMeshGenerator::isSameRail(v3s16 dir)
1199 MapNode node2 = data->m_vmanip.getNodeNoEx(blockpos_nodes + p + dir);
1200 if (node2.getContent() == n.getContent())
1202 const ContentFeatures &def2 = nodedef->get(node2);
1203 return ((def2.drawtype == NDT_RAILLIKE) &&
1204 (def2.getGroup(raillike_groupname) == raillike_group));
1208 static const v3s16 rail_direction[4] = {
1214 static const int rail_slope_angle[4] = {0, 180, 90, -90};
1226 static const RailDesc rail_kinds[16] = {
1229 {straight, 0}, // . . . .
1230 {straight, 0}, // . . . +Z
1231 {straight, 0}, // . . -Z .
1232 {straight, 0}, // . . -Z +Z
1233 {straight, 90}, // . -X . .
1234 { curved, 180}, // . -X . +Z
1235 { curved, 270}, // . -X -Z .
1236 {junction, 180}, // . -X -Z +Z
1237 {straight, 90}, // +X . . .
1238 { curved, 90}, // +X . . +Z
1239 { curved, 0}, // +X . -Z .
1240 {junction, 0}, // +X . -Z +Z
1241 {straight, 90}, // +X -X . .
1242 {junction, 90}, // +X -X . +Z
1243 {junction, 270}, // +X -X -Z .
1244 { cross, 0}, // +X -X -Z +Z
1248 void MapblockMeshGenerator::drawRaillikeNode()
1250 raillike_group = nodedef->get(n).getGroup(raillike_groupname);
1255 bool sloped = false;
1256 for (int dir = 0; dir < 4; dir++) {
1257 bool rail_above = isSameRail(rail_direction[dir] + v3s16(0, 1, 0));
1260 angle = rail_slope_angle[dir];
1263 isSameRail(rail_direction[dir]) ||
1264 isSameRail(rail_direction[dir] + v3s16(0, -1, 0)))
1269 tile_index = straight;
1271 tile_index = rail_kinds[code].tile_index;
1272 angle = rail_kinds[code].angle;
1275 useTile(tile_index, MATERIAL_FLAG_CRACK_OVERLAY, MATERIAL_FLAG_BACKFACE_CULLING);
1277 static const float offset = BS / 64;
1278 static const float size = BS / 2;
1279 float y2 = sloped ? size : -size;
1281 v3f(-size, y2 + offset, size),
1282 v3f( size, y2 + offset, size),
1283 v3f( size, -size + offset, -size),
1284 v3f(-size, -size + offset, -size),
1287 for (v3f &vertex : vertices)
1288 vertex.rotateXZBy(angle);
1293 static const v3s16 nodebox_tile_dirs[6] = {
1302 // we have this order for some reason...
1303 static const v3s16 nodebox_connection_dirs[6] = {
1304 v3s16( 0, 1, 0), // top
1305 v3s16( 0, -1, 0), // bottom
1306 v3s16( 0, 0, -1), // front
1307 v3s16(-1, 0, 0), // left
1308 v3s16( 0, 0, 1), // back
1309 v3s16( 1, 0, 0), // right
1313 void MapblockMeshGenerator::drawNodeboxNode()
1316 for (int face = 0; face < 6; face++) {
1317 // Handles facedir rotation for textures
1318 getTile(nodebox_tile_dirs[face], &tiles[face]);
1321 // locate possible neighboring nodes to connect to
1322 u8 neighbors_set = 0;
1323 if (f->node_box.type == NODEBOX_CONNECTED) {
1324 for (int dir = 0; dir != 6; dir++) {
1326 v3s16 p2 = blockpos_nodes + p + nodebox_connection_dirs[dir];
1327 MapNode n2 = data->m_vmanip.getNodeNoEx(p2);
1328 if (nodedef->nodeboxConnects(n, n2, flag))
1329 neighbors_set |= flag;
1333 std::vector<aabb3f> boxes;
1334 n.getNodeBoxes(nodedef, &boxes, neighbors_set);
1335 for (auto &box : boxes)
1336 drawAutoLightedCuboid(box, nullptr, tiles, 6);
1339 void MapblockMeshGenerator::drawMeshNode()
1343 bool private_mesh; // as a grab/drop pair is not thread-safe
1345 if (f->param_type_2 == CPT2_FACEDIR ||
1346 f->param_type_2 == CPT2_COLORED_FACEDIR) {
1347 facedir = n.getFaceDir(nodedef);
1348 } else if (f->param_type_2 == CPT2_WALLMOUNTED ||
1349 f->param_type_2 == CPT2_COLORED_WALLMOUNTED) {
1350 // Convert wallmounted to 6dfacedir.
1351 // When cache enabled, it is already converted.
1352 facedir = n.getWallMounted(nodedef);
1353 if (!enable_mesh_cache)
1354 facedir = wallmounted_to_facedir[facedir];
1357 if (!data->m_smooth_lighting && f->mesh_ptr[facedir]) {
1358 // use cached meshes
1359 private_mesh = false;
1360 mesh = f->mesh_ptr[facedir];
1361 } else if (f->mesh_ptr[0]) {
1362 // no cache, clone and rotate mesh
1363 private_mesh = true;
1364 mesh = cloneMesh(f->mesh_ptr[0]);
1365 rotateMeshBy6dFacedir(mesh, facedir);
1366 recalculateBoundingBox(mesh);
1367 meshmanip->recalculateNormals(mesh, true, false);
1371 int mesh_buffer_count = mesh->getMeshBufferCount();
1372 for (int j = 0; j < mesh_buffer_count; j++) {
1374 scene::IMeshBuffer *buf = mesh->getMeshBuffer(j);
1375 video::S3DVertex *vertices = (video::S3DVertex *)buf->getVertices();
1376 int vertex_count = buf->getVertexCount();
1378 if (data->m_smooth_lighting) {
1379 // Mesh is always private here. So the lighting is applied to each
1380 // vertex right here.
1381 for (int k = 0; k < vertex_count; k++) {
1382 video::S3DVertex &vertex = vertices[k];
1383 vertex.Color = blendLightColor(vertex.Pos, vertex.Normal);
1384 vertex.Pos += origin;
1386 collector->append(tile, vertices, vertex_count,
1387 buf->getIndices(), buf->getIndexCount());
1389 // Don't modify the mesh, it may not be private here.
1390 // Instead, let the collector process colors, etc.
1391 collector->append(tile, vertices, vertex_count,
1392 buf->getIndices(), buf->getIndexCount(), origin,
1393 color, f->light_source);
1400 // also called when the drawtype is known but should have been pre-converted
1401 void MapblockMeshGenerator::errorUnknownDrawtype()
1403 infostream << "Got drawtype " << f->drawtype << std::endl;
1404 FATAL_ERROR("Unknown drawtype");
1407 void MapblockMeshGenerator::drawNode()
1409 // skip some drawtypes early
1410 switch (f->drawtype) {
1411 case NDT_NORMAL: // Drawn by MapBlockMesh
1412 case NDT_AIRLIKE: // Not drawn at all
1413 case NDT_LIQUID: // Drawn by MapBlockMesh
1418 origin = intToFloat(p, BS);
1419 if (data->m_smooth_lighting)
1420 getSmoothLightFrame();
1422 light = LightPair(getInteriorLight(n, 1, nodedef));
1423 switch (f->drawtype) {
1424 case NDT_FLOWINGLIQUID: drawLiquidNode(); break;
1425 case NDT_GLASSLIKE: drawGlasslikeNode(); break;
1426 case NDT_GLASSLIKE_FRAMED: drawGlasslikeFramedNode(); break;
1427 case NDT_ALLFACES: drawAllfacesNode(); break;
1428 case NDT_TORCHLIKE: drawTorchlikeNode(); break;
1429 case NDT_SIGNLIKE: drawSignlikeNode(); break;
1430 case NDT_PLANTLIKE: drawPlantlikeNode(); break;
1431 case NDT_PLANTLIKE_ROOTED: drawPlantlikeRootedNode(); break;
1432 case NDT_FIRELIKE: drawFirelikeNode(); break;
1433 case NDT_FENCELIKE: drawFencelikeNode(); break;
1434 case NDT_RAILLIKE: drawRaillikeNode(); break;
1435 case NDT_NODEBOX: drawNodeboxNode(); break;
1436 case NDT_MESH: drawMeshNode(); break;
1437 default: errorUnknownDrawtype(); break;
1442 TODO: Fix alpha blending for special nodes
1443 Currently only the last element rendered is blended correct
1445 void MapblockMeshGenerator::generate()
1447 for (p.Z = 0; p.Z < MAP_BLOCKSIZE; p.Z++)
1448 for (p.Y = 0; p.Y < MAP_BLOCKSIZE; p.Y++)
1449 for (p.X = 0; p.X < MAP_BLOCKSIZE; p.X++) {
1450 n = data->m_vmanip.getNodeNoEx(blockpos_nodes + p);
1451 f = &nodedef->get(n);
1456 void MapblockMeshGenerator::renderSingle(content_t node)
1459 n = MapNode(node, 0xff, 0x00);
1460 f = &nodedef->get(n);