/* Minetest Copyright (C) 2010-2013 celeron55, Perttu Ahola , 2012-2013 RealBadAngel, Maciej Kasatkin This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "irr_v3d.h" #include #include "util/pointer.h" #include "util/numeric.h" #include "util/mathconstants.h" #include "map.h" #include "environment.h" #include "nodedef.h" #include "treegen.h" namespace treegen { void make_tree(MMVManip &vmanip, v3s16 p0, bool is_apple_tree, INodeDefManager *ndef, s32 seed) { /* NOTE: Tree-placing code is currently duplicated in the engine and in games that have saplings; both are deprecated but not replaced yet */ MapNode treenode(ndef->getId("mapgen_tree")); MapNode leavesnode(ndef->getId("mapgen_leaves")); MapNode applenode(ndef->getId("mapgen_apple")); PseudoRandom pr(seed); s16 trunk_h = pr.range(4, 5); v3s16 p1 = p0; for (s16 ii = 0; ii < trunk_h; ii++) { if (vmanip.m_area.contains(p1)) { u32 vi = vmanip.m_area.index(p1); vmanip.m_data[vi] = treenode; } p1.Y++; } // p1 is now the last piece of the trunk p1.Y -= 1; VoxelArea leaves_a(v3s16(-2, -1, -2), v3s16(2, 2, 2)); //SharedPtr leaves_d(new u8[leaves_a.getVolume()]); Buffer leaves_d(leaves_a.getVolume()); for (s32 i = 0; i < leaves_a.getVolume(); i++) leaves_d[i] = 0; // Force leaves at near the end of the trunk s16 d = 1; for (s16 z = -d; z <= d; z++) for (s16 y = -d; y <= d; y++) for (s16 x = -d; x <= d; x++) { leaves_d[leaves_a.index(v3s16(x, y, z))] = 1; } // Add leaves randomly for (u32 iii = 0; iii < 7; iii++) { v3s16 p( pr.range(leaves_a.MinEdge.X, leaves_a.MaxEdge.X - d), pr.range(leaves_a.MinEdge.Y, leaves_a.MaxEdge.Y - d), pr.range(leaves_a.MinEdge.Z, leaves_a.MaxEdge.Z - d) ); for (s16 z = 0; z <= d; z++) for (s16 y = 0; y <= d; y++) for (s16 x = 0; x <= d; x++) { leaves_d[leaves_a.index(p + v3s16(x, y, z))] = 1; } } // Blit leaves to vmanip for (s16 z = leaves_a.MinEdge.Z; z <= leaves_a.MaxEdge.Z; z++) for (s16 y = leaves_a.MinEdge.Y; y <= leaves_a.MaxEdge.Y; y++) { v3s16 pmin(leaves_a.MinEdge.X, y, z); u32 i = leaves_a.index(pmin); u32 vi = vmanip.m_area.index(pmin + p1); for (s16 x = leaves_a.MinEdge.X; x <= leaves_a.MaxEdge.X; x++) { v3s16 p(x, y, z); if (vmanip.m_area.contains(p + p1) == true && (vmanip.m_data[vi].getContent() == CONTENT_AIR || vmanip.m_data[vi].getContent() == CONTENT_IGNORE)) { if (leaves_d[i] == 1) { bool is_apple = pr.range(0, 99) < 10; if (is_apple_tree && is_apple) vmanip.m_data[vi] = applenode; else vmanip.m_data[vi] = leavesnode; } } vi++; i++; } } } // L-System tree LUA spawner treegen::error spawn_ltree(ServerEnvironment *env, v3s16 p0, INodeDefManager *ndef, TreeDef tree_definition) { ServerMap *map = &env->getServerMap(); std::map modified_blocks; MMVManip vmanip(map); v3s16 tree_blockp = getNodeBlockPos(p0); treegen::error e; vmanip.initialEmerge(tree_blockp - v3s16(1, 1, 1), tree_blockp + v3s16(1, 3, 1)); e = make_ltree(vmanip, p0, ndef, tree_definition); if (e != SUCCESS) return e; vmanip.blitBackAll(&modified_blocks); // update lighting std::map lighting_modified_blocks; lighting_modified_blocks.insert(modified_blocks.begin(), modified_blocks.end()); map->updateLighting(lighting_modified_blocks, modified_blocks); // Send a MEET_OTHER event MapEditEvent event; event.type = MEET_OTHER; for (std::map::iterator i = modified_blocks.begin(); i != modified_blocks.end(); ++i) event.modified_blocks.insert(i->first); map->dispatchEvent(&event); return SUCCESS; } //L-System tree generator treegen::error make_ltree(MMVManip &vmanip, v3s16 p0, INodeDefManager *ndef, TreeDef tree_definition) { MapNode dirtnode(ndef->getId("mapgen_dirt")); s32 seed; if (tree_definition.explicit_seed) seed = tree_definition.seed + 14002; else seed = p0.X * 2 + p0.Y * 4 + p0.Z; // use the tree position to seed PRNG PseudoRandom ps(seed); // chance of inserting abcd rules double prop_a = 9; double prop_b = 8; double prop_c = 7; double prop_d = 6; //randomize tree growth level, minimum=2 s16 iterations = tree_definition.iterations; if (tree_definition.iterations_random_level > 0) iterations -= ps.range(0, tree_definition.iterations_random_level); if (iterations < 2) iterations = 2; s16 MAX_ANGLE_OFFSET = 5; double angle_in_radians = (double)tree_definition.angle * M_PI / 180; double angleOffset_in_radians = (s16)(ps.range(0, 1) % MAX_ANGLE_OFFSET) * M_PI / 180; //initialize rotation matrix, position and stacks for branches core::matrix4 rotation; rotation = setRotationAxisRadians(rotation, M_PI / 2, v3f(0, 0, 1)); v3f position; position.X = p0.X; position.Y = p0.Y; position.Z = p0.Z; std::stack stack_orientation; std::stack stack_position; //generate axiom std::string axiom = tree_definition.initial_axiom; for (s16 i = 0; i < iterations; i++) { std::string temp = ""; for (s16 j = 0; j < (s16)axiom.size(); j++) { char axiom_char = axiom.at(j); switch (axiom_char) { case 'A': temp += tree_definition.rules_a; break; case 'B': temp += tree_definition.rules_b; break; case 'C': temp += tree_definition.rules_c; break; case 'D': temp += tree_definition.rules_d; break; case 'a': if (prop_a >= ps.range(1, 10)) temp += tree_definition.rules_a; break; case 'b': if (prop_b >= ps.range(1, 10)) temp += tree_definition.rules_b; break; case 'c': if (prop_c >= ps.range(1, 10)) temp += tree_definition.rules_c; break; case 'd': if (prop_d >= ps.range(1, 10)) temp += tree_definition.rules_d; break; default: temp += axiom_char; break; } } axiom = temp; } //make sure tree is not floating in the air if (tree_definition.trunk_type == "double") { tree_node_placement( vmanip, v3f(position.X + 1, position.Y - 1, position.Z), dirtnode ); tree_node_placement( vmanip, v3f(position.X, position.Y - 1, position.Z + 1), dirtnode ); tree_node_placement( vmanip, v3f(position.X + 1, position.Y - 1, position.Z + 1), dirtnode ); } else if (tree_definition.trunk_type == "crossed") { tree_node_placement( vmanip, v3f(position.X + 1, position.Y - 1, position.Z), dirtnode ); tree_node_placement( vmanip, v3f(position.X - 1, position.Y - 1, position.Z), dirtnode ); tree_node_placement( vmanip, v3f(position.X, position.Y - 1, position.Z + 1), dirtnode ); tree_node_placement( vmanip, v3f(position.X, position.Y - 1, position.Z - 1), dirtnode ); } /* build tree out of generated axiom Key for Special L-System Symbols used in Axioms G - move forward one unit with the pen up F - move forward one unit with the pen down drawing trunks and branches f - move forward one unit with the pen down drawing leaves (100% chance) T - move forward one unit with the pen down drawing trunks only R - move forward one unit with the pen down placing fruit A - replace with rules set A B - replace with rules set B C - replace with rules set C D - replace with rules set D a - replace with rules set A, chance 90% b - replace with rules set B, chance 80% c - replace with rules set C, chance 70% d - replace with rules set D, chance 60% + - yaw the turtle right by angle degrees - - yaw the turtle left by angle degrees & - pitch the turtle down by angle degrees ^ - pitch the turtle up by angle degrees / - roll the turtle to the right by angle degrees * - roll the turtle to the left by angle degrees [ - save in stack current state info ] - recover from stack state info */ s16 x,y,z; for (s16 i = 0; i < (s16)axiom.size(); i++) { char axiom_char = axiom.at(i); core::matrix4 temp_rotation; temp_rotation.makeIdentity(); v3f dir; switch (axiom_char) { case 'G': dir = v3f(1, 0, 0); dir = transposeMatrix(rotation, dir); position += dir; break; case 'T': tree_trunk_placement( vmanip, v3f(position.X, position.Y, position.Z), tree_definition ); if (tree_definition.trunk_type == "double" && !tree_definition.thin_branches) { tree_trunk_placement( vmanip, v3f(position.X + 1, position.Y, position.Z), tree_definition ); tree_trunk_placement( vmanip, v3f(position.X, position.Y, position.Z + 1), tree_definition ); tree_trunk_placement( vmanip, v3f(position.X + 1, position.Y, position.Z + 1), tree_definition ); } else if (tree_definition.trunk_type == "crossed" && !tree_definition.thin_branches) { tree_trunk_placement( vmanip, v3f(position.X + 1, position.Y, position.Z), tree_definition ); tree_trunk_placement( vmanip, v3f(position.X - 1, position.Y, position.Z), tree_definition ); tree_trunk_placement( vmanip, v3f(position.X, position.Y, position.Z + 1), tree_definition ); tree_trunk_placement( vmanip, v3f(position.X, position.Y, position.Z - 1), tree_definition ); } dir = v3f(1, 0, 0); dir = transposeMatrix(rotation, dir); position += dir; break; case 'F': tree_trunk_placement( vmanip, v3f(position.X, position.Y, position.Z), tree_definition ); if ((stack_orientation.empty() && tree_definition.trunk_type == "double") || (!stack_orientation.empty() && tree_definition.trunk_type == "double" && !tree_definition.thin_branches)) { tree_trunk_placement( vmanip, v3f(position.X +1 , position.Y, position.Z), tree_definition ); tree_trunk_placement( vmanip, v3f(position.X, position.Y, position.Z + 1), tree_definition ); tree_trunk_placement( vmanip, v3f(position.X + 1, position.Y, position.Z + 1), tree_definition ); } else if ((stack_orientation.empty() && tree_defi const char *getName() { return "TestObjDef"; } void runTests(IGameDef *gamedef); void testHandles(); void testAddGetSetClear(); void testClone(); }; static TestObjDef g_test_instance; void TestObjDef::runTests(IGameDef *gamedef) { TEST(testHandles); TEST(testAddGetSetClear); TEST(testClone); } //////////////////////////////////////////////////////////////////////////////// /* Minimal implementation of ObjDef and ObjDefManager subclass */ class MyObjDef : public ObjDef { public: ObjDef *clone() const { auto def = new MyObjDef(); ObjDef::cloneTo(def); def->testvalue = testvalue; return def; }; u32 testvalue; }; class MyObjDefManager : public ObjDefManager { public: MyObjDefManager(ObjDefType type) : ObjDefManager(NULL, type){}; MyObjDefManager *clone() const { auto mgr = new MyObjDefManager(); ObjDefManager::cloneTo(mgr); return mgr; }; protected: MyObjDefManager(){}; }; void TestObjDef::testHandles() { u32 uid = 0; u32 index = 0; ObjDefType type = OBJDEF_GENERIC; ObjDefHandle handle = ObjDefManager::createHandle(9530, OBJDEF_ORE, 47); UASSERTEQ(ObjDefHandle, 0xAF507B55, handle); UASSERT(ObjDefManager::decodeHandle(handle, &index, &type, &uid)); UASSERTEQ(u32, 9530, index); UASSERTEQ(u32, 47, uid); UASSERTEQ(ObjDefHandle, OBJDEF_ORE, type); } void TestObjDef::testAddGetSetClear() { ObjDefManager testmgr(NULL, OBJDEF_GENERIC); ObjDefHandle hObj0, hObj1, hObj2, hObj3; ObjDef *obj0, *obj1, *obj2, *obj3; UASSERTEQ(ObjDefType, testmgr.getType(), OBJDEF_GENERIC); obj0 = new MyObjDef; obj0->name = "foobar"; hObj0 = testmgr.add(obj0); UASSERT(hObj0 != OBJDEF_INVALID_HANDLE); UASSERTEQ(u32, obj0->index, 0); obj1 = new MyObjDef; obj1->name = "FooBaz"; hObj1 = testmgr.add(obj1); UASSERT(hObj1 != OBJDEF_INVALID_HANDLE); UASSERTEQ(u32, obj1->index, 1); obj2 = new MyObjDef; obj2->name = "asdf"; hObj2 = testmgr.add(obj2); UASSERT(hObj2 != OBJDEF_INVALID_HANDLE); UASSERTEQ(u32, obj2->index, 2); obj3 = new MyObjDef; obj3->name = "foobaz"; hObj3 = testmgr.add(obj3); UASSERT(hObj3 == OBJDEF_INVALID_HANDLE); UASSERTEQ(size_t, testmgr.getNumObjects(), 3); UASSERT(testmgr.get(hObj0) == obj0); UASSERT(testmgr.getByName("FOOBAZ") == obj1); UASSERT(testmgr.set(hObj0, obj3) == obj0); UASSERT(testmgr.get(hObj0) == obj3); delete obj0; testmgr.clear(); UASSERTEQ(size_t, testmgr.getNumObjects(), 0); } void TestObjDef::testClone() { MyObjDefManager testmgr(OBJDEF_GENERIC); ObjDefManager *mgrcopy; MyObjDef *obj, *temp2; ObjDef *temp1; ObjDefHandle hObj; obj = new MyObjDef; obj->testvalue = 0xee00ff11; hObj = testmgr.add(obj); UASSERT(hObj != OBJDEF_INVALID_HANDLE); mgrcopy = testmgr.clone(); UASSERT(mgrcopy); UASSERTEQ(ObjDefType, mgrcopy->getType</