diff options
author | RealBadAngel <maciej.kasatkin@o2.pl> | 2014-10-15 04:13:53 +0200 |
---|---|---|
committer | RealBadAngel <maciej.kasatkin@o2.pl> | 2014-10-18 16:42:23 +0200 |
commit | 0066bd77d25793b76fdaa9a62755cca934f0121d (patch) | |
tree | 61760994616113cd307d6dc6c2980f5f547fa78f | |
parent | d1ccc64e1ecfd00df36d2b8a8b3420b70434fefd (diff) | |
download | minetest-0066bd77d25793b76fdaa9a62755cca934f0121d.tar.gz minetest-0066bd77d25793b76fdaa9a62755cca934f0121d.tar.bz2 minetest-0066bd77d25793b76fdaa9a62755cca934f0121d.zip |
Add meshnode drawtype.
-rw-r--r-- | doc/lua_api.txt | 14 | ||||
-rw-r--r-- | src/client.cpp | 2 | ||||
-rw-r--r-- | src/content_mapblock.cpp | 12 | ||||
-rw-r--r-- | src/mapblock_mesh.cpp | 51 | ||||
-rw-r--r-- | src/mapblock_mesh.h | 4 | ||||
-rw-r--r-- | src/mesh.cpp | 216 | ||||
-rw-r--r-- | src/mesh.h | 21 | ||||
-rw-r--r-- | src/nodedef.cpp | 48 | ||||
-rw-r--r-- | src/nodedef.h | 8 | ||||
-rw-r--r-- | src/script/common/c_content.cpp | 3 | ||||
-rw-r--r-- | src/script/cpp_api/s_node.cpp | 1 |
11 files changed, 374 insertions, 6 deletions
diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 131a63fa5..8f77366f7 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -103,6 +103,7 @@ mods | |-- screenshot.png | |-- description.txt | |-- init.lua +| |-- models | |-- textures | | |-- modname_stuff.png | | `-- modname_something_else.png @@ -137,6 +138,9 @@ init.lua: minetest.setting_get(name) and minetest.setting_getbool(name) can be used to read custom or existing settings at load time, if necessary. +models: + Models for entities or meshnodes. + textures, sounds, media: Media files (textures, sounds, whatever) that will be transferred to the client and will be available for use by the mod. @@ -430,6 +434,7 @@ Look for examples in games/minimal or games/minetest_game. - fencelike - raillike - nodebox -- See below. EXPERIMENTAL +- mesh -- use models for nodes *_optional drawtypes need less rendering time if deactivated (always client side) @@ -469,6 +474,12 @@ A box of a regular node would look like: type = "leveled" is same as "fixed", but y2 will be automatically set to level from param2 +Meshes +----------- +If drawtype "mesh" is used tiles should hold model materials textures. +Only static meshes are implemented. +For supported model formats see Irrlicht engine documentation. + Ore types --------------- These tell in what manner the ore is generated. @@ -2405,7 +2416,7 @@ Node definition (register_node) drawtype = "normal", -- See "Node drawtypes" visual_scale = 1.0, - ^ Supported for drawtypes "plantlike", "signlike", "torchlike". + ^ Supported for drawtypes "plantlike", "signlike", "torchlike", "mesh". ^ For plantlike, the image will start at the bottom of the node; for the ^ other drawtypes, the image will be centered on the node. ^ Note that positioning for "torchlike" may still change. @@ -2439,6 +2450,7 @@ Node definition (register_node) light_source = 0, -- Amount of light emitted by node damage_per_second = 0, -- If player is inside node, this damage is caused node_box = {type="regular"}, -- See "Node boxes" + mesh = "model", selection_box = {type="regular"}, -- See "Node boxes" ^ If drawtype "nodebox" is used and selection_box is nil, then node_box is used legacy_facedir_simple = false, -- Support maps made in and before January 2012 diff --git a/src/client.cpp b/src/client.cpp index 4a00283ee..0bc2e66a5 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -2678,7 +2678,7 @@ void Client::afterContentReceived(IrrlichtDevice *device, gui::IGUIFont* font) // Update node textures and assign shaders to each tile infostream<<"- Updating node textures"<<std::endl; - m_nodedef->updateTextures(m_tsrc, m_shsrc); + m_nodedef->updateTextures(this); // Preload item textures and meshes if configured to if(g_settings->getBool("preload_item_visuals")) diff --git a/src/content_mapblock.cpp b/src/content_mapblock.cpp index c84e75ac0..53b9874d4 100644 --- a/src/content_mapblock.cpp +++ b/src/content_mapblock.cpp @@ -1715,6 +1715,18 @@ void mapblock_mesh_generate_special(MeshMakeData *data, makeCuboid(&collector, box, tiles, 6, c, txc); } break;} + case NDT_MESH: + { + v3f pos = intToFloat(p, BS); + video::SColor c = MapBlock_LightColor(255, getInteriorLight(n, 1, nodedef), f.light_source); + u8 facedir = n.getFaceDir(nodedef); + for(u16 j = 0; j < f.mesh_ptr[facedir]->getMeshBufferCount(); j++) { + scene::IMeshBuffer *buf = f.mesh_ptr[facedir]->getMeshBuffer(j); + collector.append(getNodeTileN(n, p, j, data), + (video::S3DVertex *)buf->getVertices(), buf->getVertexCount(), + buf->getIndices(), buf->getIndexCount(), pos, c); + } + break;} } } } diff --git a/src/mapblock_mesh.cpp b/src/mapblock_mesh.cpp index d75d3e148..a7fafa683 100644 --- a/src/mapblock_mesh.cpp +++ b/src/mapblock_mesh.cpp @@ -1428,3 +1428,54 @@ void MeshCollector::append(const TileSpec &tile, p->vertices.push_back(vertices[i]); } } + +/* + MeshCollector - for meshnodes and converted drawtypes. +*/ + +void MeshCollector::append(const TileSpec &tile, + const video::S3DVertex *vertices, u32 numVertices, + const u16 *indices, u32 numIndices, + v3f pos, video::SColor c) +{ + if(numIndices > 65535) + { + dstream<<"FIXME: MeshCollector::append() called with numIndices="<<numIndices<<" (limit 65535)"<<std::endl; + return; + } + + PreMeshBuffer *p = NULL; + for(u32 i=0; i<prebuffers.size(); i++) + { + PreMeshBuffer &pp = prebuffers[i]; + if(pp.tile != tile) + continue; + if(pp.indices.size() + numIndices > 65535) + continue; + + p = &pp; + break; + } + + if(p == NULL) + { + PreMeshBuffer pp; + pp.tile = tile; + prebuffers.push_back(pp); + p = &prebuffers[prebuffers.size()-1]; + } + + u32 vertex_count = p->vertices.size(); + for(u32 i=0; i<numIndices; i++) + { + u32 j = indices[i] + vertex_count; + p->indices.push_back(j); + } + for(u32 i=0; i<numVertices; i++) + { + video::S3DVertex vert = vertices[i]; + vert.Pos += pos; + vert.Color = c; + p->vertices.push_back(vert); + } +} diff --git a/src/mapblock_mesh.h b/src/mapblock_mesh.h index c52954998..e1cccc64e 100644 --- a/src/mapblock_mesh.h +++ b/src/mapblock_mesh.h @@ -174,6 +174,10 @@ struct MeshCollector void append(const TileSpec &material, const video::S3DVertex *vertices, u32 numVertices, const u16 *indices, u32 numIndices); + void append(const TileSpec &material, + const video::S3DVertex *vertices, u32 numVertices, + const u16 *indices, u32 numIndices, + v3f pos, video::SColor c); }; // This encodes diff --git a/src/mesh.cpp b/src/mesh.cpp index 3200d5fa6..19d75f9f5 100644 --- a/src/mesh.cpp +++ b/src/mesh.cpp @@ -408,3 +408,219 @@ void setMeshColorByNormalXYZ(scene::IMesh *mesh, } } } + +void rotateMeshBy6dFacedir(scene::IMesh *mesh, int facedir) +{ + int axisdir = facedir>>2; + facedir &= 0x03; + + u16 mc = mesh->getMeshBufferCount(); + for(u16 j = 0; j < mc; j++) + { + scene::IMeshBuffer *buf = mesh->getMeshBuffer(j); + video::S3DVertex *vertices = (video::S3DVertex*)buf->getVertices(); + u16 vc = buf->getVertexCount(); + for(u16 i=0; i<vc; i++) + { + switch (axisdir) + { + case 0: + if(facedir == 1) + vertices[i].Pos.rotateXZBy(-90); + else if(facedir == 2) + vertices[i].Pos.rotateXZBy(180); + else if(facedir == 3) + vertices[i].Pos.rotateXZBy(90); + break; + case 1: // z+ + vertices[i].Pos.rotateYZBy(90); + if(facedir == 1) + vertices[i].Pos.rotateXYBy(90); + else if(facedir == 2) + vertices[i].Pos.rotateXYBy(180); + else if(facedir == 3) + vertices[i].Pos.rotateXYBy(-90); + break; + case 2: //z- + vertices[i].Pos.rotateYZBy(-90); + if(facedir == 1) + vertices[i].Pos.rotateXYBy(-90); + else if(facedir == 2) + vertices[i].Pos.rotateXYBy(180); + else if(facedir == 3) + vertices[i].Pos.rotateXYBy(90); + break; + case 3: //x+ + vertices[i].Pos.rotateXYBy(-90); + if(facedir == 1) + vertices[i].Pos.rotateYZBy(90); + else if(facedir == 2) + vertices[i].Pos.rotateYZBy(180); + else if(facedir == 3) + vertices[i].Pos.rotateYZBy(-90); + break; + case 4: //x- + vertices[i].Pos.rotateXYBy(90); + if(facedir == 1) + vertices[i].Pos.rotateYZBy(-90); + else if(facedir == 2) + vertices[i].Pos.rotateYZBy(180); + else if(facedir == 3) + vertices[i].Pos.rotateYZBy(90); + break; + case 5: + vertices[i].Pos.rotateXYBy(-180); + if(facedir == 1) + vertices[i].Pos.rotateXZBy(90); + else if(facedir == 2) + vertices[i].Pos.rotateXZBy(180); + else if(facedir == 3) + vertices[i].Pos.rotateXZBy(-90); + break; + default: + break; + } + } + } +} + +void recalculateBoundingBox(scene::IMesh *src_mesh) +{ + core::aabbox3d<f32> bbox; + bbox.reset(0,0,0); + for(u16 j = 0; j < src_mesh->getMeshBufferCount(); j++) + { + scene::IMeshBuffer *buf = src_mesh->getMeshBuffer(j); + buf->recalculateBoundingBox(); + if(j == 0) + bbox = buf->getBoundingBox(); + else + bbox.addInternalBox(buf->getBoundingBox()); + } + src_mesh->setBoundingBox(bbox); +} + +scene::IMesh* cloneMesh(scene::IMesh *src_mesh) +{ + scene::SMesh* dst_mesh = new scene::SMesh(); + for(u16 j = 0; j < src_mesh->getMeshBufferCount(); j++) + { + scene::IMeshBuffer *buf = src_mesh->getMeshBuffer(j); + video::S3DVertex *vertices = (video::S3DVertex*)buf->getVertices(); + u16 *indices = (u16*)buf->getIndices(); + scene::SMeshBuffer *temp_buf = new scene::SMeshBuffer(); + temp_buf->append(vertices, buf->getVertexCount(), + indices, buf->getIndexCount()); + dst_mesh->addMeshBuffer(temp_buf); + temp_buf->drop(); + } + return dst_mesh; +} + +scene::IMesh* convertNodeboxNodeToMesh(ContentFeatures *f) +{ + scene::SMesh* dst_mesh = new scene::SMesh(); + for (u16 j = 0; j < 6; j++) + { + scene::IMeshBuffer *buf = new scene::SMeshBuffer(); + dst_mesh->addMeshBuffer(buf); + buf->drop(); + } + + video::SColor c(255,255,255,255); + + std::vector<aabb3f> boxes = f->node_box.fixed; + + for(std::vector<aabb3f>::iterator + i = boxes.begin(); + i != boxes.end(); i++) + { + aabb3f box = *i; + + f32 temp; + if (box.MinEdge.X > box.MaxEdge.X) + { + temp=box.MinEdge.X; + box.MinEdge.X=box.MaxEdge.X; + box.MaxEdge.X=temp; + } + if (box.MinEdge.Y > box.MaxEdge.Y) + { + temp=box.MinEdge.Y; + box.MinEdge.Y=box.MaxEdge.Y; + box.MaxEdge.Y=temp; + } + if (box.MinEdge.Z > box.MaxEdge.Z) + { + temp=box.MinEdge.Z; + box.MinEdge.Z=box.MaxEdge.Z; + box.MaxEdge.Z=temp; + } + // Compute texture coords + f32 tx1 = (box.MinEdge.X/BS)+0.5; + f32 ty1 = (box.MinEdge.Y/BS)+0.5; + f32 tz1 = (box.MinEdge.Z/BS)+0.5; + f32 tx2 = (box.MaxEdge.X/BS)+0.5; + f32 ty2 = (box.MaxEdge.Y/BS)+0.5; + f32 tz2 = (box.MaxEdge.Z/BS)+0.5; + f32 txc[24] = { + // up + tx1, 1-tz2, tx2, 1-tz1, + // down + tx1, tz1, tx2, tz2, + // right + tz1, 1-ty2, tz2, 1-ty1, + // left + 1-tz2, 1-ty2, 1-tz1, 1-ty1, + // back + 1-tx2, 1-ty2, 1-tx1, 1-ty1, + // front + tx1, 1-ty2, tx2, 1-ty1, + }; + v3f min = box.MinEdge; + v3f max = box.MaxEdge; + + video::S3DVertex vertices[24] = + { + // up + video::S3DVertex(min.X,max.Y,max.Z, 0,1,0, c, txc[0],txc[1]), + video::S3DVertex(max.X,max.Y,max.Z, 0,1,0, c, txc[2],txc[1]), + video::S3DVertex(max.X,max.Y,min.Z, 0,1,0, c, txc[2],txc[3]), + video::S3DVertex(min.X,max.Y,min.Z, 0,1,0, c, txc[0],txc[3]), + // down + video::S3DVertex(min.X,min.Y,min.Z, 0,-1,0, c, txc[4],txc[5]), + video::S3DVertex(max.X,min.Y,min.Z, 0,-1,0, c, txc[6],txc[5]), + video::S3DVertex(max.X,min.Y,max.Z, 0,-1,0, c, txc[6],txc[7]), + video::S3DVertex(min.X,min.Y,max.Z, 0,-1,0, c, txc[4],txc[7]), + // right + video::S3DVertex(max.X,max.Y,min.Z, 1,0,0, c, txc[ 8],txc[9]), + video::S3DVertex(max.X,max.Y,max.Z, 1,0,0, c, txc[10],txc[9]), + video::S3DVertex(max.X,min.Y,max.Z, 1,0,0, c, txc[10],txc[11]), + video::S3DVertex(max.X,min.Y,min.Z, 1,0,0, c, txc[ 8],txc[11]), + // left + video::S3DVertex(min.X,max.Y,max.Z, -1,0,0, c, txc[12],txc[13]), + video::S3DVertex(min.X,max.Y,min.Z, -1,0,0, c, txc[14],txc[13]), + video::S3DVertex(min.X,min.Y,min.Z, -1,0,0, c, txc[14],txc[15]), + video::S3DVertex(min.X,min.Y,max.Z, -1,0,0, c, txc[12],txc[15]), + // back + video::S3DVertex(max.X,max.Y,max.Z, 0,0,1, c, txc[16],txc[17]), + video::S3DVertex(min.X,max.Y,max.Z, 0,0,1, c, txc[18],txc[17]), + video::S3DVertex(min.X,min.Y,max.Z, 0,0,1, c, txc[18],txc[19]), + video::S3DVertex(max.X,min.Y,max.Z, 0,0,1, c, txc[16],txc[19]), + // front + video::S3DVertex(min.X,max.Y,min.Z, 0,0,-1, c, txc[20],txc[21]), + video::S3DVertex(max.X,max.Y,min.Z, 0,0,-1, c, txc[22],txc[21]), + video::S3DVertex(max.X,min.Y,min.Z, 0,0,-1, c, txc[22],txc[23]), + video::S3DVertex(min.X,min.Y,min.Z, 0,0,-1, c, txc[20],txc[23]), + }; + + u16 indices[] = {0,1,2,2,3,0}; + + for(u16 j = 0; j < 24; j += 4) + { + scene::IMeshBuffer *buf = dst_mesh->getMeshBuffer(j / 4); + buf->append(vertices + j, 4, indices, 6); + } + } + return dst_mesh; +} diff --git a/src/mesh.h b/src/mesh.h index a89bea385..7539298cb 100644 --- a/src/mesh.h +++ b/src/mesh.h @@ -21,6 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #define MESH_HEADER #include "irrlichttypes_extrabloated.h" +#include "nodedef.h" #include <string> /* @@ -68,5 +69,25 @@ void setMeshColorByNormalXYZ(scene::IMesh *mesh, const video::SColor &colorX, const video::SColor &colorY, const video::SColor &colorZ); +/* + Rotate the mesh by 6d facedir value. + Method only for meshnodes, not suitable for entities. +*/ +void rotateMeshBy6dFacedir(scene::IMesh *mesh, int facedir); + +/* + Clone the mesh. +*/ +scene::IMesh* cloneMesh(scene::IMesh *src_mesh); + +/* + Convert nodebox drawtype node to mesh. +*/ +scene::IMesh* convertNodeboxNodeToMesh(ContentFeatures *f); + +/* + Update bounding box for a mesh. +*/ +void recalculateBoundingBox(scene::IMesh *src_mesh); #endif diff --git a/src/nodedef.cpp b/src/nodedef.cpp index f1a7ad694..ef61d0722 100644 --- a/src/nodedef.cpp +++ b/src/nodedef.cpp @@ -23,6 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "itemdef.h" #ifndef SERVER #include "tile.h" +#include "mesh.h" #endif #include "log.h" #include "settings.h" @@ -31,6 +32,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "util/serialize.h" #include "exceptions.h" #include "debug.h" +#include "gamedef.h" /* NodeBox @@ -195,6 +197,11 @@ void ContentFeatures::reset() // Unknown nodes can be dug groups["dig_immediate"] = 2; drawtype = NDT_NORMAL; + mesh = ""; +#ifndef SERVER + for(u32 i = 0; i < 24; i++) + mesh_ptr[i] = NULL; +#endif visual_scale = 1.0; for(u32 i = 0; i < 6; i++) tiledef[i] = TileDef(); @@ -295,6 +302,7 @@ void ContentFeatures::serialize(std::ostream &os, u16 protocol_version) writeU8(os, waving); // Stuff below should be moved to correct place in a version that otherwise changes // the protocol version + os<<serializeString(mesh); } void ContentFeatures::deSerialize(std::istream &is) @@ -363,6 +371,7 @@ void ContentFeatures::deSerialize(std::istream &is) try{ // Stuff below should be moved to correct place in a version that // otherwise changes the protocol version + mesh = deSerializeString(is); }catch(SerializationError &e) {}; } @@ -386,7 +395,7 @@ public: virtual content_t set(const std::string &name, const ContentFeatures &def); virtual content_t allocateDummy(const std::string &name); virtual void updateAliases(IItemDefManager *idef); - virtual void updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc); + virtual void updateTextures(IGameDef *gamedef); void serialize(std::ostream &os, u16 protocol_version); void deSerialize(std::istream &is); @@ -669,11 +678,14 @@ void CNodeDefManager::updateAliases(IItemDefManager *idef) } -void CNodeDefManager::updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc) +void CNodeDefManager::updateTextures(IGameDef *gamedef) { #ifndef SERVER infostream << "CNodeDefManager::updateTextures(): Updating " "textures in node definitions" << std::endl; + + ITextureSource *tsrc = gamedef->tsrc(); + IShaderSource *shdsrc = gamedef->getShaderSource(); bool new_style_water = g_settings->getBool("new_style_water"); bool new_style_leaves = g_settings->getBool("new_style_leaves"); @@ -771,6 +783,10 @@ void CNodeDefManager::updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc f->backface_culling = false; f->solidness = 0; break; + case NDT_MESH: + f->solidness = 0; + f->backface_culling = false; + break; case NDT_TORCHLIKE: case NDT_SIGNLIKE: case NDT_FENCELIKE: @@ -810,6 +826,34 @@ void CNodeDefManager::updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc tile_shader[j], use_normal_texture, f->tiledef_special[j].backface_culling, f->alpha, material_type); } + + // Meshnode drawtype + // Read the mesh and apply scale + if ((f->drawtype == NDT_MESH) && (f->mesh != "")) { + f->mesh_ptr[0] = gamedef->getMesh(f->mesh); + scaleMesh(f->mesh_ptr[0], v3f(f->visual_scale,f->visual_scale,f->visual_scale)); + recalculateBoundingBox(f->mesh_ptr[0]); + } + + //Convert regular nodebox nodes to meshnodes + //Change the drawtype and apply scale + if ((f->drawtype == NDT_NODEBOX) && + ((f->node_box.type == NODEBOX_REGULAR) || (f->node_box.type == NODEBOX_FIXED)) && + (!f->node_box.fixed.empty())) { + f->drawtype = NDT_MESH; + f->mesh_ptr[0] = convertNodeboxNodeToMesh(f); + scaleMesh(f->mesh_ptr[0], v3f(f->visual_scale,f->visual_scale,f->visual_scale)); + recalculateBoundingBox(f->mesh_ptr[0]); + } + + //Cache 6dfacedir rotated clones of meshes + if (f->mesh_ptr[0] && (f->param_type_2 == CPT2_FACEDIR)) { + for (u16 j = 1; j < 24; j++) { + f->mesh_ptr[j] = cloneMesh(f->mesh_ptr[0]); + rotateMeshBy6dFacedir(f->mesh_ptr[j], j); + recalculateBoundingBox(f->mesh_ptr[j]); + } + } } #endif } diff --git a/src/nodedef.h b/src/nodedef.h index b737e0237..2400f5f73 100644 --- a/src/nodedef.h +++ b/src/nodedef.h @@ -152,6 +152,7 @@ enum NodeDrawType NDT_FIRELIKE, // Draw faces slightly rotated and only on connecting nodes, NDT_GLASSLIKE_FRAMED_OPTIONAL, // enabled -> connected, disabled -> Glass-like // uses 2 textures, one for frames, second for faces + NDT_MESH, // Uses static meshes }; #define CF_SPECIAL_COUNT 6 @@ -187,6 +188,10 @@ struct ContentFeatures // Visual definition enum NodeDrawType drawtype; + std::string mesh; +#ifndef SERVER + scene::IMesh *mesh_ptr[24]; +#endif float visual_scale; // Misc. scale parameter TileDef tiledef[6]; TileDef tiledef_special[CF_SPECIAL_COUNT]; // eg. flowing liquid @@ -328,8 +333,7 @@ public: /* Update tile textures to latest return values of TextueSource. */ - virtual void updateTextures(ITextureSource *tsrc, - IShaderSource *shdsrc)=0; + virtual void updateTextures(IGameDef *gamedef)=0; virtual void serialize(std::ostream &os, u16 protocol_version)=0; virtual void deSerialize(std::istream &is)=0; diff --git a/src/script/common/c_content.cpp b/src/script/common/c_content.cpp index 2f749043e..4737f1993 100644 --- a/src/script/common/c_content.cpp +++ b/src/script/common/c_content.cpp @@ -281,6 +281,9 @@ ContentFeatures read_content_features(lua_State *L, int index) ScriptApiNode::es_DrawType,NDT_NORMAL); getfloatfield(L, index, "visual_scale", f.visual_scale); + /* Meshnode model filename */ + getstringfield(L, index, "mesh", f.mesh); + // tiles = {} lua_getfield(L, index, "tiles"); // If nil, try the deprecated name "tile_images" instead diff --git a/src/script/cpp_api/s_node.cpp b/src/script/cpp_api/s_node.cpp index 05f908004..e3d3fb58b 100644 --- a/src/script/cpp_api/s_node.cpp +++ b/src/script/cpp_api/s_node.cpp @@ -45,6 +45,7 @@ struct EnumString ScriptApiNode::es_DrawType[] = {NDT_FENCELIKE, "fencelike"}, {NDT_RAILLIKE, "raillike"}, {NDT_NODEBOX, "nodebox"}, + {NDT_MESH, "mesh"}, {0, NULL}, }; |