diff options
author | Perttu Ahola <celeron55@gmail.com> | 2011-02-01 16:17:55 +0200 |
---|---|---|
committer | Perttu Ahola <celeron55@gmail.com> | 2011-02-01 16:17:55 +0200 |
commit | e92238edc831a34081790e71249f1459e997974c (patch) | |
tree | 5406e23bb8fd7895f7ac741ad49085a217b7b758 /src | |
parent | 56320f7e8de3cf31cbaf11492528016c4446d8f4 (diff) | |
download | minetest-e92238edc831a34081790e71249f1459e997974c.tar.gz minetest-e92238edc831a34081790e71249f1459e997974c.tar.bz2 minetest-e92238edc831a34081790e71249f1459e997974c.zip |
This map generator is starting to look pretty good now... also, disabled loading player position from disk because map is regenerated always.
Diffstat (limited to 'src')
-rw-r--r-- | src/environment.cpp | 3 | ||||
-rw-r--r-- | src/main.cpp | 16 | ||||
-rw-r--r-- | src/map.cpp | 734 | ||||
-rw-r--r-- | src/map.h | 51 | ||||
-rw-r--r-- | src/mapblock.cpp | 3 | ||||
-rw-r--r-- | src/mapchunk.h | 27 | ||||
-rw-r--r-- | src/mapnode.cpp | 3 | ||||
-rw-r--r-- | src/player.cpp | 4 | ||||
-rw-r--r-- | src/player.h | 1 | ||||
-rw-r--r-- | src/server.cpp | 32 | ||||
-rw-r--r-- | src/utility.h | 5 |
11 files changed, 437 insertions, 442 deletions
diff --git a/src/environment.cpp b/src/environment.cpp index d4d189acf..dc44b3a36 100644 --- a/src/environment.cpp +++ b/src/environment.cpp @@ -112,7 +112,8 @@ void Environment::step(float dtime) { // Apply gravity to local player v3f speed = player->getSpeed(); - speed.Y -= 9.81 * BS * dtime_part * 2; + if(player->swimming_up == false) + speed.Y -= 9.81 * BS * dtime_part * 2; /* Apply water resistance diff --git a/src/main.cpp b/src/main.cpp index 87deefee4..176cde639 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -333,6 +333,19 @@ Doing now: * Make the generator to run in background and not blocking block
placement and transfer
* Fix the strange mineral occurences
+* When the map is generated and a place is found for the player, the
+ first chunk is actually still volatile and will have stuff still
+ changed after spawning, which creates a lot of glitches.
+ - This is partly fixed by now allowing only 2-sector deeep
+ modification of volatile chunks. But it should still be fixed?
+ - How about checking that the neighbors are fully generated too and
+ generate them when the middle piece is needed
+ - This is very slow
+ - How about just enabling changed_blocks properly
+ - This is probably a good idea
+ - The server has to make sure the spawn point is not at the
+ changing borders of a chunk
+* Add some kind of erosion and other stuff that now is possible
======================================================================
@@ -2690,8 +2703,7 @@ int main(int argc, char *argv[]) }
// We want a slight delay to very little
// time consuming nodes
- //float mindelay = 0.15;
- float mindelay = 0.20;
+ float mindelay = 0.15;
if(nodig_delay_counter < mindelay)
{
nodig_delay_counter = mindelay;
diff --git a/src/map.cpp b/src/map.cpp index 63fccc432..884482763 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -1735,8 +1735,8 @@ ServerMap::ServerMap(std::string savedir, HMParams hmp, MapParams mp): //m_chunksize = 64; //m_chunksize = 16; - //m_chunksize = 8; - m_chunksize = 4; + m_chunksize = 8; + //m_chunksize = 4; //m_chunksize = 2; /* @@ -1749,8 +1749,8 @@ ServerMap::ServerMap(std::string savedir, HMParams hmp, MapParams mp): PointAttributeList *list_baseheight = m_padb.getList("hm_baseheight"); PointAttributeList *list_randmax = m_padb.getList("hm_randmax"); PointAttributeList *list_randfactor = m_padb.getList("hm_randfactor"); - PointAttributeList *list_plants_amount = m_padb.getList("plants_amount"); - PointAttributeList *list_caves_amount = m_padb.getList("caves_amount"); + //PointAttributeList *list_plants_amount = m_padb.getList("plants_amount"); + //PointAttributeList *list_caves_amount = m_padb.getList("caves_amount"); #if 0 /* @@ -2026,119 +2026,6 @@ ServerMap::~ServerMap() } } -#if 0 -MapChunk* ServerMap::generateChunkRaw(v2s16 chunkpos) -{ - // Return if chunk already exists - MapChunk *chunk = getChunk(chunkpos); - if(chunk) - return chunk; - - /* - Add all sectors - */ - - dstream<<"generateChunkRaw(): " - <<"("<<chunkpos.X<<","<<chunkpos.Y<<")" - <<std::endl; - - TimeTaker timer("generateChunkRaw()"); - - v2s16 sectorpos_base = chunk_to_sector(chunkpos); - - core::map<v3s16, MapBlock*> changed_blocks; - core::map<v3s16, MapBlock*> lighting_invalidated_blocks; - - u32 generated_block_count = 0; - - for(s16 y=0; y<m_chunksize; y++) - { - /*dstream<<"Generating sectors " - <<"("<<sectorpos_base.X<<"..." - <<(sectorpos_base.X+m_chunksize-1) - <<", "<<y<<")" - <<std::endl;*/ - - // With caves_amount attribute fetch: ~90ms (379ms peaks) - // Without: ~38ms (396ms peaks) - //TimeTaker timer("Chunk sector row"); - - for(s16 x=0; x<m_chunksize; x++) - { - v2s16 sectorpos = sectorpos_base + v2s16(x,y); - - /*dstream<<"Generating sector " - <<"("<<sectorpos.X<<","<<sectorpos.Y<<")" - <<std::endl;*/ - - // Generate sector - ServerMapSector *sector = generateSector(sectorpos); - - /* - Generate main blocks of sector - */ - s16 d = 8; - for(s16 y2=-d/2; y2<d/2; y2++) - { - v3s16 p(x,y2,y); - - // Check that the block doesn't exist already - if(sector->getBlockNoCreateNoEx(y2)) - continue; - - generateBlock(p, NULL, sector, changed_blocks, - lighting_invalidated_blocks); - - generated_block_count++; - } - } - } - - dstream<<"generateChunkRaw generated "<<generated_block_count - <<" blocks"<<std::endl; - - /*{ - TimeTaker timer2("generateChunkRaw() lighting"); - // Update lighting - core::map<v3s16, MapBlock*> lighting_modified_blocks; - updateLighting(lighting_invalidated_blocks, lighting_modified_blocks); - }*/ - - // Add chunk meta information - chunk = new MapChunk(); - m_chunks.insert(chunkpos, chunk); - return chunk; -} - -MapChunk* ServerMap::generateChunk(v2s16 chunkpos) -{ - /* - Generate chunk and neighbors - */ - for(s16 x=-1; x<=1; x++) - for(s16 y=-1; y<=1; y++) - { - generateChunkRaw(chunkpos + v2s16(x,y)); - } - - /* - Get chunk - */ - MapChunk *chunk = getChunk(chunkpos); - assert(chunk); - // Set non-volatile - chunk->setIsVolatile(false); - // Return it - return chunk; -} -#endif - -MapChunk* ServerMap::generateChunkRaw(v2s16 chunkpos) -{ - dstream<<"WARNING: No-op "<<__FUNCTION_NAME<<" called"<<std::endl; - return NULL; -} - /* Some helper functions */ @@ -2234,16 +2121,37 @@ void make_tree(VoxelManipulator &vmanip, v3s16 p0) } } -MapChunk* ServerMap::generateChunk(v2s16 chunkpos) +MapChunk* ServerMap::generateChunkRaw(v2s16 chunkpos, + core::map<v3s16, MapBlock*> &changed_blocks) { - TimeTaker timer("generateChunk()"); + /* + Don't generate if already fully generated + */ + { + MapChunk *chunk = getChunk(chunkpos); + if(chunk != NULL && chunk->getGenLevel() == GENERATED_FULLY) + { + dstream<<"generateChunkRaw(): Chunk " + <<"("<<chunkpos.X<<","<<chunkpos.Y<<")" + <<" already generated"<<std::endl; + return chunk; + } + } + + dstream<<"generateChunkRaw(): Generating chunk " + <<"("<<chunkpos.X<<","<<chunkpos.Y<<")" + <<std::endl; + + TimeTaker timer("generateChunkRaw()"); // The distance how far into the neighbors the generator is allowed to go - s16 max_spread_amount = m_chunksize * MAP_BLOCKSIZE / 2 + 2; + s16 max_spread_amount_sectors = 2; + assert(max_spread_amount_sectors <= m_chunksize); + s16 max_spread_amount = max_spread_amount_sectors * MAP_BLOCKSIZE; // Minimum amount of space left on sides for mud to fall in s16 min_mud_fall_space = 2; // Maximum diameter of stone obstacles in X and Z - s16 stone_obstacle_max_size = m_chunksize*MAP_BLOCKSIZE/2; + s16 stone_obstacle_max_size = (max_spread_amount-min_mud_fall_space)*2; assert(stone_obstacle_max_size/2 <= max_spread_amount-min_mud_fall_space); s16 y_blocks_min = -4; @@ -2254,8 +2162,13 @@ MapChunk* ServerMap::generateChunk(v2s16 chunkpos) v2s16 sectorpos_base = chunk_to_sector(chunkpos); s16 sectorpos_base_size = m_chunksize; - v2s16 sectorpos_bigbase = chunk_to_sector(chunkpos - v2s16(1,1)); - s16 sectorpos_bigbase_size = m_chunksize * 3; + + /*v2s16 sectorpos_bigbase = chunk_to_sector(chunkpos - v2s16(1,1)); + s16 sectorpos_bigbase_size = m_chunksize * 3;*/ + v2s16 sectorpos_bigbase = + sectorpos_base - v2s16(1,1) * max_spread_amount_sectors; + s16 sectorpos_bigbase_size = + sectorpos_base_size + 2 * max_spread_amount_sectors; v3s16 bigarea_blocks_min( sectorpos_bigbase.X, @@ -2265,15 +2178,22 @@ MapChunk* ServerMap::generateChunk(v2s16 chunkpos) v3s16 bigarea_blocks_max( sectorpos_bigbase.X + sectorpos_bigbase_size - 1, - y_blocks_min + sectorpos_bigbase_size - 1, + y_blocks_max, sectorpos_bigbase.Y + sectorpos_bigbase_size - 1 ); - + + // Relative values to control amount of stuff in one chunk + u32 relative_area = (u32)sectorpos_base_size*MAP_BLOCKSIZE + *(u32)sectorpos_base_size*MAP_BLOCKSIZE; + u32 relative_volume = (u32)sectorpos_base_size*MAP_BLOCKSIZE + *(u32)sectorpos_base_size*MAP_BLOCKSIZE + *(u32)h_blocks*MAP_BLOCKSIZE; + /* Create the whole area of this and the neighboring chunks */ { - TimeTaker timer("generateChunk() create area"); + TimeTaker timer("generateChunkRaw() create area"); for(s16 x=0; x<sectorpos_bigbase_size; x++) for(s16 z=0; z<sectorpos_bigbase_size; z++) @@ -2293,11 +2213,13 @@ MapChunk* ServerMap::generateChunk(v2s16 chunkpos) block->setLightingExpired(false); /* - TODO: Do this better. Block gets sunlight if this is true. This should be set to true when the top side of a block is completely exposed to the sky. + + Actually this doesn't matter now because the + initial lighting is done here. */ block->setIsUnderground(y != y_blocks_max); } @@ -2314,15 +2236,19 @@ MapChunk* ServerMap::generateChunk(v2s16 chunkpos) ManualMapVoxelManipulator vmanip(this); // Add the area we just generated { - TimeTaker timer("generateChunk() initialEmerge"); + TimeTaker timer("generateChunkRaw() initialEmerge"); vmanip.initialEmerge(bigarea_blocks_min, bigarea_blocks_max); } - TimeTaker timer_generate("generateChunk() generate"); + TimeTaker timer_generate("generateChunkRaw() generate"); /* Generate general ground level to full area */ + + { + // 22ms @cs=8 + //TimeTaker timer1("ground level"); for(s16 x=0; x<sectorpos_bigbase_size*MAP_BLOCKSIZE; x++) for(s16 z=0; z<sectorpos_bigbase_size*MAP_BLOCKSIZE; z++) @@ -2375,6 +2301,17 @@ MapChunk* ServerMap::generateChunk(v2s16 chunkpos) } } } + + }//timer1 + + // This is set during the next operation. + // Maximum height of the stone surface and obstacles. + // This is used to disable dungeon generation from going too high. + s16 stone_surface_max_y = 0; + + { + // 8ms @cs=8 + //TimeTaker timer1("stone obstacles"); /* Add some random stone obstacles @@ -2385,7 +2322,7 @@ MapChunk* ServerMap::generateChunk(v2s16 chunkpos) // The size of these could actually be m_chunksize*MAP_BLOCKSIZE*2 v3s16 ob_size( myrand_range(5, stone_obstacle_max_size), - myrand_range(0, 15), + myrand_range(0, 23), myrand_range(5, stone_obstacle_max_size) ); v2s16 ob_place( @@ -2393,6 +2330,9 @@ MapChunk* ServerMap::generateChunk(v2s16 chunkpos) myrand_range(0, sectorpos_base_size*MAP_BLOCKSIZE-1) ); + // Minimum space left on top of the obstacle + s16 min_head_space = 10; + for(s16 x=-ob_size.X/2; x<ob_size.X/2; x++) for(s16 z=-ob_size.Z/2; z<ob_size.Z/2; z++) { @@ -2441,10 +2381,14 @@ MapChunk* ServerMap::generateChunk(v2s16 chunkpos) s16 y; // Add stone s16 count = 0; - for(y=y_start; y<=y_nodes_max; y++) + for(y=y_start; y<=y_nodes_max - min_head_space; y++) { MapNode &n = vmanip.m_data[i]; n.d = CONTENT_STONE; + + if(y > stone_surface_max_y) + stone_surface_max_y = y; + count++; if(count >= ob_size.Y) break; @@ -2468,12 +2412,29 @@ MapChunk* ServerMap::generateChunk(v2s16 chunkpos) } } + }//timer1 + { + // 24ms @cs=8 + //TimeTaker timer1("dungeons"); + /* Make dungeons */ - for(u32 jj=0; jj<2; jj++) + u32 dungeons_count = relative_volume/200000; + for(u32 jj=0; jj<dungeons_count; jj++) { - s16 max_tunnel_diameter = 8; + s16 min_tunnel_diameter = 1; + s16 max_tunnel_diameter = 5; + u16 tunnel_routepoints = 15; + + bool bruise_surface = (jj < dungeons_count / 3); + + if(bruise_surface) + { + min_tunnel_diameter = 5; + max_tunnel_diameter = 10; + tunnel_routepoints = 10; + } // Allowed route area size in nodes v3s16 ar( @@ -2491,14 +2452,29 @@ MapChunk* ServerMap::generateChunk(v2s16 chunkpos) // Allow a bit more //(this should be more than the maximum radius of the tunnel) - s16 more = max_spread_amount - max_tunnel_diameter/2 - 1; + s16 insure = 5; + s16 more = max_spread_amount - max_tunnel_diameter/2 - insure; ar += v3s16(1,0,1) * more * 2; of -= v3s16(1,0,1) * more; + s16 route_y_min = 0; + //s16 route_y_max = ar.Y-1; + s16 route_y_max = stone_surface_max_y - of.Y; + + if(bruise_surface) + { + // Minimum is at y=0 + route_y_min = -of.Y - 0; + route_y_min = rangelim(route_y_min, 0, route_y_max); + } + + /*dstream<<"route_y_min = "<<route_y_min + <<", route_y_max = "<<route_y_max<<std::endl;*/ + // Randomize starting position v3f orp( (float)(myrand()%ar.X)+0.5, - (float)(myrand()%ar.Y)+0.5, + (float)(myrand_range(route_y_min, route_y_max))+0.5, (float)(myrand()%ar.Z)+0.5 ); @@ -2507,17 +2483,16 @@ MapChunk* ServerMap::generateChunk(v2s16 chunkpos) /* Generate some tunnel starting from orp */ - - for(u16 j=0; j<10; j++) + + for(u16 j=0; j<tunnel_routepoints; j++) { - /*v3f rp( - (float)(myrand()%ar.X)+0.5, - (float)(myrand()%ar.Y)+0.5, - (float)(myrand()%ar.Z)+0.5 - ); - v3f vec = rp - orp;*/ - - v3s16 maxlen(60, 10, 60); + v3s16 maxlen(20, 10, 20); + + if(bruise_surface) + { + maxlen = v3s16(60,60,60); + } + v3f vec( (float)(myrand()%(maxlen.X*2))-(float)maxlen.X, (float)(myrand()%(maxlen.Y*2))-(float)maxlen.Y, @@ -2527,19 +2502,19 @@ MapChunk* ServerMap::generateChunk(v2s16 chunkpos) if(rp.X < 0) rp.X = 0; else if(rp.X >= ar.X) - rp.X = ar.X; - if(rp.Y < 0) - rp.Y = 0; - else if(rp.Y >= ar.Y) - rp.Y = ar.Y; + rp.X = ar.X-1; + if(rp.Y < route_y_min) + rp.Y = route_y_min; + else if(rp.Y >= route_y_max) + rp.Y = route_y_max-1; if(rp.Z < 0) rp.Z = 0; else if(rp.Z >= ar.Z) - rp.Z = ar.Z; + rp.Z = ar.Z-1; vec = rp - orp; // Randomize size - s16 min_d = 0; + s16 min_d = min_tunnel_diameter; s16 max_d = max_tunnel_diameter; s16 rs = myrand_range(min_d, max_d); @@ -2547,6 +2522,7 @@ MapChunk* ServerMap::generateChunk(v2s16 chunkpos) { v3f fp = orp + vec * f; v3s16 cp(fp.X, fp.Y, fp.Z); + s16 d0 = -rs/2; s16 d1 = d0 + rs - 1; for(s16 z0=d0; z0<=d1; z0++) @@ -2568,7 +2544,15 @@ MapChunk* ServerMap::generateChunk(v2s16 chunkpos) continue; p += of; - assert(vmanip.m_area.contains(p)); + //assert(vmanip.m_area.contains(p)); + if(vmanip.m_area.contains(p) == false) + { + dstream<<"WARNING: "<<__FUNCTION_NAME + <<":"<<__LINE__<<": " + <<"point not in area" + <<std::endl; + continue; + } // Just set it to air, it will be changed to // water afterwards @@ -2584,10 +2568,15 @@ MapChunk* ServerMap::generateChunk(v2s16 chunkpos) } + }//timer1 + { + // 46ms @cs=8 + //TimeTaker timer1("ore veins"); + /* Make ore veins */ - for(u32 jj=0; jj<1000; jj++) + for(u32 jj=0; jj<relative_volume/524; jj++) { s16 max_vein_diameter = 3; @@ -2607,7 +2596,8 @@ MapChunk* ServerMap::generateChunk(v2s16 chunkpos) // Allow a bit more //(this should be more than the maximum radius of the tunnel) - s16 more = max_spread_amount - max_vein_diameter/2 - 1; + s16 insure = 3; + s16 more = max_spread_amount - max_vein_diameter/2 - insure; ar += v3s16(1,0,1) * more * 2; of -= v3s16(1,0,1) * more; @@ -2707,6 +2697,11 @@ MapChunk* ServerMap::generateChunk(v2s16 chunkpos) } + }//timer1 + { + // 15ms @cs=8 + //TimeTaker timer1("add mud"); + /* Add mud to the central chunk */ @@ -2742,6 +2737,11 @@ MapChunk* ServerMap::generateChunk(v2s16 chunkpos) } + }//timer1 + { + // 179ms @cs=8 + //TimeTaker timer1("flow mud"); + /* Flow mud away from steep edges */ @@ -2832,6 +2832,11 @@ MapChunk* ServerMap::generateChunk(v2s16 chunkpos) } + }//timer1 + { + // 50ms @cs=8 + //TimeTaker timer1("add water"); + /* Add water to the central chunk (and a bit more) */ @@ -2847,11 +2852,16 @@ MapChunk* ServerMap::generateChunk(v2s16 chunkpos) v2s16 p2d = sectorpos_base*MAP_BLOCKSIZE + v2s16(x,z); // Find ground level - s16 surface_y = find_ground_level(vmanip, p2d); + //s16 surface_y = find_ground_level(vmanip, p2d); - // If ground level is over water level, skip. - if(surface_y > WATER_LEVEL) - continue; + /* + If ground level is over water level, skip. + NOTE: This leaves caves near water without water, + which looks especially crappy when the nearby water + won't start flowing either for some reason + */ + /*if(surface_y > WATER_LEVEL) + continue;*/ /* Add water on ground @@ -2861,24 +2871,44 @@ MapChunk* ServerMap::generateChunk(v2s16 chunkpos) s16 y_start = WATER_LEVEL; u8 light = LIGHT_MAX; u32 i = vmanip.m_area.index(v3s16(p2d.X, y_start, p2d.Y)); + MapNode *n = &vmanip.m_data[i]; + /* + Add first one to transforming liquid queue + */ + if(n->d == CONTENT_WATER || n->d == CONTENT_WATERSOURCE) + { + v3s16 p = v3s16(p2d.X, y_start, p2d.Y); + m_transforming_liquid.push_back(p); + } for(s16 y=y_start; y>=y_nodes_min; y--) { - MapNode *n = &vmanip.m_data[i]; + n = &vmanip.m_data[i]; - // Fill gaps inside water, too + // Stop when there is no water and no air if(n->d != CONTENT_AIR && n->d != CONTENT_WATERSOURCE && n->d != CONTENT_WATER) + { + /* + Add bottom one to transforming liquid queue + */ + vmanip.m_area.add_y(em, i, 1); + n = &vmanip.m_data[i]; + if(n->d == CONTENT_WATER || n->d == CONTENT_WATERSOURCE) + { + v3s16 p = v3s16(p2d.X, y, p2d.Y); + m_transforming_liquid.push_back(p); + } + break; + } n->d = CONTENT_WATERSOURCE; n->setLight(LIGHTBANK_DAY, light); - /* - Add to transforming liquid queue (in case it'd - start flowing) - */ + /*// Add to transforming liquid queue (in case it'd + // start flowing) v3s16 p = v3s16(p2d.X, y, p2d.Y); - m_transforming_liquid.push_back(p); + m_transforming_liquid.push_back(p);*/ // Next one vmanip.m_area.add_y(em, i, -1); @@ -2889,11 +2919,16 @@ MapChunk* ServerMap::generateChunk(v2s16 chunkpos) } + }//timer1 + { + // 1ms @cs=8 + //TimeTaker timer1("plant trees"); + /* Plant some trees */ { - u32 tree_max = 100; + u32 tree_max = relative_area / 100; u32 count = myrand_range(0, tree_max); for(u32 i=0; i<count; i++) @@ -2912,12 +2947,23 @@ MapChunk* ServerMap::generateChunk(v2s16 chunkpos) } } + }//timer1 + { + // 19ms @cs=8 + //TimeTaker timer1("grow grass"); + /* Grow grass */ - for(s16 x=0-4; x<sectorpos_base_size*MAP_BLOCKSIZE+4; x++) - for(s16 z=0-4; z<sectorpos_base_size*MAP_BLOCKSIZE+4; z++) + /*for(s16 x=0-4; x<sectorpos_base_size*MAP_BLOCKSIZE+4; x++) + for(s16 z=0-4; z<sectorpos_base_size*MAP_BLOCKSIZE+4; z++)*/ + for(s16 x=0-max_spread_amount; + x<sectorpos_base_size*MAP_BLOCKSIZE+max_spread_amount; + x++) + for(s16 z=0-max_spread_amount; + z<sectorpos_base_size*MAP_BLOCKSIZE+max_spread_amount; + z++) { // Node position in 2d v2s16 p2d = sectorpos_base*MAP_BLOCKSIZE + v2s16(x,z); @@ -2954,19 +3000,25 @@ MapChunk* ServerMap::generateChunk(v2s16 chunkpos) n->d = CONTENT_GRASS; } + }//timer1 + /* Handle lighting */ core::map<v3s16, bool> light_sources; + { + // 750ms @cs=8, can't optimize more + //TimeTaker timer1("initial lighting"); + /*for(s16 x=0; x<sectorpos_base_size*MAP_BLOCKSIZE; x++) for(s16 z=0; z<sectorpos_base_size*MAP_BLOCKSIZE; z++)*/ - for(s16 x=0-max_spread_amount; - x<sectorpos_base_size*MAP_BLOCKSIZE+max_spread_amount; + for(s16 x=0-max_spread_amount+1; + x<sectorpos_base_size*MAP_BLOCKSIZE+max_spread_amount-1; x++) - for(s16 z=0-max_spread_amount; - z<sectorpos_base_size*MAP_BLOCKSIZE+max_spread_amount; + for(s16 z=0-max_spread_amount+1; + z<sectorpos_base_size*MAP_BLOCKSIZE+max_spread_amount-1; z++) { // Node position in 2d @@ -2976,9 +3028,10 @@ MapChunk* ServerMap::generateChunk(v2s16 chunkpos) Apply initial sunlight */ { + u8 light = LIGHT_SUN; + bool add_to_sources = false; v3s16 em = vmanip.m_area.getExtent(); s16 y_start = y_nodes_max; - u8 light = LIGHT_SUN; u32 i = vmanip.m_area.index(v3s16(p2d.X, y_start, p2d.Y)); for(s16 y=y_start; y>=y_nodes_min; y--) { @@ -2994,11 +3047,42 @@ MapChunk* ServerMap::generateChunk(v2s16 chunkpos) if(light > 0) light--; } + + // This doesn't take much time + if(add_to_sources == false) + { + /* + Check sides. If side is not air or water, start + adding to light_sources. + */ + v3s16 dirs4[4] = { + v3s16(0,0,1), // back + v3s16(1,0,0), // right + v3s16(0,0,-1), // front + v3s16(-1,0,0), // left + }; + for(u32 di=0; di<4; di++) + { + v3s16 dirp = dirs4[di]; + u32 i2 = i; + vmanip.m_area.add_p(em, i2, dirp); + MapNode *n2 = &vmanip.m_data[i2]; + if( + n2->d != CONTENT_AIR + && n2->d != CONTENT_WATERSOURCE + && n2->d != CONTENT_WATER + ){ + add_to_sources = true; + break; + } + } + } n->setLight(LIGHTBANK_DAY, light); n->setLight(LIGHTBANK_NIGHT, 0); - if(light != 0) + // This doesn't take much time + if(light != 0 && add_to_sources) { // Insert light source light_sources.insert(v3s16(p2d.X, y, p2d.Y), true); @@ -3010,9 +3094,11 @@ MapChunk* ServerMap::generateChunk(v2s16 chunkpos) } } + }//timer1 + // Spread light around { - TimeTaker timer("generateChunk() spreadLight"); + TimeTaker timer("generateChunkRaw() spreadLight"); vmanip.spreadLight(LIGHTBANK_DAY, light_sources); } @@ -3025,16 +3111,16 @@ MapChunk* ServerMap::generateChunk(v2s16 chunkpos) /* Blit generated stuff to map */ - core::map<v3s16, MapBlock*> modified_blocks; { - TimeTaker timer("generateChunk() blitBackAll"); - vmanip.blitBackAll(&modified_blocks); + // 70ms @cs=8 + //TimeTaker timer("generateChunkRaw() blitBackAll"); + vmanip.blitBackAll(&changed_blocks); } /* Update day/night difference cache of the MapBlocks */ { - for(core::map<v3s16, MapBlock*>::Iterator i = modified_blocks.getIterator(); + for(core::map<v3s16, MapBlock*>::Iterator i = changed_blocks.getIterator(); i.atEnd() == false; i++) { MapBlock *block = i.getNode()->getValue(); @@ -3044,7 +3130,7 @@ MapChunk* ServerMap::generateChunk(v2s16 chunkpos) /* - Create chunks and set them volatile + Create chunk metadata */ for(s16 x=-1; x<=1; x++) @@ -3052,13 +3138,15 @@ MapChunk* ServerMap::generateChunk(v2s16 chunkpos) { v2s16 chunkpos0 = chunkpos + v2s16(x,y); // Add chunk meta information - MapChunk *chunk = getChunk(chunkpos); + MapChunk *chunk = getChunk(chunkpos0); if(chunk == NULL) { chunk = new MapChunk(); m_chunks.insert(chunkpos0, chunk); } - chunk->setIsVolatile(true); + //chunk->setIsVolatile(true); + if(chunk->getGenLevel() > GENERATED_PARTLY) + chunk->setGenLevel(GENERATED_PARTLY); } /* @@ -3067,243 +3155,37 @@ MapChunk* ServerMap::generateChunk(v2s16 chunkpos) MapChunk *chunk = getChunk(chunkpos); assert(chunk); // Set non-volatile - chunk->setIsVolatile(false); + //chunk->setIsVolatile(false); + chunk->setGenLevel(GENERATED_FULLY); // Return it return chunk; } -#if 0 -ServerMapSector * ServerMap::generateSector(v2s16 p2d) +MapChunk* ServerMap::generateChunk(v2s16 chunkpos1, + core::map<v3s16, MapBlock*> &changed_blocks) { - DSTACK("%s: p2d=(%d,%d)", - __FUNCTION_NAME, - p2d.X, p2d.Y); - - // Check that it doesn't exist already - ServerMapSector *sector = (ServerMapSector*)getSectorNoGenerateNoEx(p2d); - if(sector != NULL) - return sector; - - /* - If there is no master heightmap, throw. - */ - if(m_heightmap == NULL) - { - throw InvalidPositionException("generateSector(): no heightmap"); - } - - /* - Do not generate over-limit - */ - if(p2d.X < -MAP_GENERATION_LIMIT / MAP_BLOCKSIZE - || p2d.X > MAP_GENERATION_LIMIT / MAP_BLOCKSIZE - || p2d.Y < -MAP_GENERATION_LIMIT / MAP_BLOCKSIZE - || p2d.Y > MAP_GENERATION_LIMIT / MAP_BLOCKSIZE) - throw InvalidPositionException("generateSector(): pos. over limit"); - - /* - Generate sector and heightmaps - */ - - // Number of heightmaps in sector in each direction - u16 hm_split = SECTOR_HEIGHTMAP_SPLIT; - - // Heightmap side width - s16 hm_d = MAP_BLOCKSIZE / hm_split; - - sector = new ServerMapSector(this, p2d, hm_split); - - // Sector position on map in nodes - v2s16 nodepos2d = p2d * MAP_BLOCKSIZE; - - /*dstream<<"Generating sector ("<<p2d.X<<","<<p2d.Y<<")" - " heightmaps and objects"<<std::endl;*/ - - /* - Calculate some information about local properties - */ - - v2s16 mhm_p = p2d * hm_split; - f32 corners[4] = { - m_heightmap->getGroundHeight(mhm_p+v2s16(0,0)*hm_split), - m_heightmap->getGroundHeight(mhm_p+v2s16(1,0)*hm_split), - m_heightmap->getGroundHeight(mhm_p+v2s16(1,1)*hm_split), - m_heightmap->getGroundHeight(mhm_p+v2s16(0,1)*hm_split), - }; - - float avgheight = (corners[0]+corners[1]+corners[2]+corners[3])/4.0; - float avgslope = 0.0; - avgslope += fabs(avgheight - corners[0]); - avgslope += fabs(avgheight - corners[1]); - avgslope += fabs(avgheight - corners[2]); - avgslope += fabs(avgheight - corners[3]); - avgslope /= 4.0; - avgslope /= MAP_BLOCKSIZE; - //dstream<<"avgslope="<<avgslope<<std::endl; - - float pitness = 0.0; - v2f32 a; - a = m_heightmap->getSlope(p2d+v2s16(0,0)); - pitness += -a.X; - pitness += -a.Y; - a = m_heightmap->getSlope(p2d+v2s16(0,1)); - pitness += -a.X; - pitness += a.Y; - a = m_heightmap->getSlope(p2d+v2s16(1,1)); - pitness += a.X; - pitness += a.Y; - a = m_heightmap->getSlope(p2d+v2s16(1,0)); - pitness += a.X; - pitness += -a.Y; - pitness /= 4.0; - pitness /= MAP_BLOCKSIZE; - //dstream<<"pitness="<<pitness<<std::endl; - - /* - Get local attributes - */ - - float local_plants_amount = 0.5; + dstream<<"generateChunk(): Generating chunk " + <<"("<<chunkpos1.X<<","<<chunkpos1.Y<<")" + <<std::endl; -#if 0 + /*for(s16 x=-1; x<=1; x++) + for(s16 y=-1; y<=1; y++)*/ + for(s16 x=-0; x<=0; x++) + for(s16 y=-0; y<=0; y++) { - //dstream<<"generateSector(): Reading point attribute lists"<<std::endl; - //TimeTaker attrtimer("generateSector() attribute fetch"); - - // Get plant amount from attributes - PointAttributeList *palist = m_padb.getList("plants_amount"); - assert(palist); - /*local_plants_amount = - palist->getNearAttr(nodepos2d).getFloat();*/ - local_plants_amount = - palist->getInterpolatedFloat(nodepos2d); - } -#endif - - /* - Generate sector heightmap - */ - - // Loop through sub-heightmaps - for(s16 y=0; y<hm_split; y++) - for(s16 x=0; x<hm_split; x++) - { - v2s16 p_in_sector = v2s16(x,y); - v2s16 mhm_p = p2d * hm_split + p_in_sector; - f32 corners[4] = { - m_heightmap->getGroundHeight(mhm_p+v2s16(0,0)), - m_heightmap->getGroundHeight(mhm_p+v2s16(1,0)), - m_heightmap->getGroundHeight(mhm_p+v2s16(1,1)), - m_heightmap->getGroundHeight(mhm_p+v2s16(0,1)), - }; - - /*dstream<<"p_in_sector=("<<p_in_sector.X<<","<<p_in_sector.Y<<")" - <<" mhm_p=("<<mhm_p.X<<","<<mhm_p.Y<<")" - <<std::endl;*/ - - FixedHeightmap *hm = new FixedHeightmap(&m_hwrapper, - mhm_p, hm_d); - sector->setHeightmap(p_in_sector, hm); - - //TODO: Make these values configurable - //hm->generateContinued(0.0, 0.0, corners); - //hm->generateContinued(0.25, 0.2, corners); - //hm->generateContinued(0.5, 0.2, corners); - //hm->generateContinued(1.0, 0.2, corners); - //hm->generateContinued(2.0, 0.2, corners); - //hm->generateContinued(2.0 * avgslope, 0.5, corners); - hm->generateContinued(avgslope * MAP_BLOCKSIZE/8, 0.5, corners); - - //hm->print(); + v2s16 chunkpos0 = chunkpos1 + v2s16(x,y); + MapChunk *chunk = getChunk(chunkpos0); + // Skip if already generated + if(chunk != NULL && chunk->getGenLevel() == GENERATED_FULLY) + continue; + generateChunkRaw(chunkpos0, changed_blocks); } - - /* - Generate objects - */ - core::map<v3s16, u8> *objects = new core::map<v3s16, u8>; - sector->setObjects(objects); + assert(chunkNonVolatile(chunkpos1)); - float area = MAP_BLOCKSIZE * MAP_BLOCKSIZE; - - /* - Plant some trees if there is not much slope - */ - { - // Avgslope is the derivative of a hill - //float t = avgslope * avgslope; - float t = avgslope; - float a = area/16 * m_params.plants_amount * local_plants_amount; - u32 tree_max; - //float something = 0.17*0.17; - float something = 0.3; - if(t > something) - tree_max = a / (t/something); - else - tree_max = a; - - u32 count = (myrand()%(tree_max+1)); - //u32 count = tree_max; - for(u32 i=0; i<count; i++) - { - s16 x = (myrand()%(MAP_BLOCKSIZE-2))+1; - s16 z = (myrand()%(MAP_BLOCKSIZE-2))+1; - s16 y = sector->getGroundHeight(v2s16(x,z))+1; - if(y < WATER_LEVEL) - continue; - objects->insert(v3s16(x, y, z), - SECTOR_OBJECT_TREE_1); - } - } - /* - Plant some bushes if sector is pit-like - */ - { - // Pitness usually goes at around -0.5...0.5 - u32 bush_max = 0; - u32 a = area/16 * 3.0 * m_params.plants_amount * local_plants_amount; - if(pitness > 0) - bush_max = (pitness*a*4); - if(bush_max > a) - bush_max = a; - u32 count = (myrand()%(bush_max+1)); - for(u32 i=0; i<count; i++) - { - s16 x = myrand()%(MAP_BLOCKSIZE-0)+0; - s16 z = myrand()%(MAP_BLOCKSIZE-0)+0; - s16 y = sector->getGroundHeight(v2s16(x,z))+1; - if(y < WATER_LEVEL) - continue; - objects->insert(v3s16(x, y, z), - SECTOR_OBJECT_BUSH_1); - } - } - /* - Add ravine (randomly) - */ - if(m_params.ravines_amount > 0.001) - { - if(myrand()%(s32)(200.0 / m_params.ravines_amount) == 0) - { - s16 s = 6; - s16 x = myrand()%(MAP_BLOCKSIZE-s*2-1)+s; - s16 z = myrand()%(MAP_BLOCKSIZE-s*2-1)+s; - /*s16 x = 8; - s16 z = 8;*/ - s16 y = sector->getGroundHeight(v2s16(x,z))+1; - objects->insert(v3s16(x, y, z), - SECTOR_OBJECT_RAVINE); - } - } - - /* - Insert to container - */ - m_sectors.insert(p2d, sector); - - return sector; + MapChunk *chunk = getChunk(chunkpos1); + return chunk; } -#endif ServerMapSector * ServerMap::createSector(v2s16 p2d) { @@ -3418,7 +3300,8 @@ ServerMapSector * ServerMap::createSector(v2s16 p2d) return sector; } -MapSector * ServerMap::emergeSector(v2s16 p2d) +MapSector * ServerMap::emergeSector(v2s16 p2d, + core::map<v3s16, MapBlock*> &changed_blocks) { DSTACK("%s: p2d=(%d,%d)", __FUNCTION_NAME, @@ -3428,18 +3311,19 @@ MapSector * ServerMap::emergeSector(v2s16 p2d) Check chunk status */ v2s16 chunkpos = sector_to_chunk(p2d); - bool chunk_exists = false; + /*bool chunk_nonvolatile = false; MapChunk *chunk = getChunk(chunkpos); if(chunk && chunk->getIsVolatile() == false) - chunk_exists = true; + chunk_nonvolatile = true;*/ + bool chunk_nonvolatile = chunkNonVolatile(chunkpos); /* If chunk is not fully generated, generate chunk */ - if(chunk_exists == false) + if(chunk_nonvolatile == false) { // Generate chunk and neighbors - generateChunk(chunkpos); + generateChunk(chunkpos, changed_blocks); } /* @@ -3474,6 +3358,10 @@ MapSector * ServerMap::emergeSector(v2s16 p2d) //return generateSector(); } +/* + NOTE: This is not used for main map generation, only for blocks + that are very high or low +*/ MapBlock * ServerMap::generateBlock( v3s16 p, MapBlock *original_dummy, @@ -4418,14 +4306,9 @@ MapBlock * ServerMap::emergeBlock( */ ServerMapSector *sector; try{ - sector = (ServerMapSector*)emergeSector(p2d); + sector = (ServerMapSector*)emergeSector(p2d, changed_blocks); assert(sector->getId() == MAPSECTOR_SERVER); } - /*catch(InvalidPositionException &e) - { - dstream<<"emergeBlock: emergeSector() failed"<<std::endl; - throw e; - }*/ catch(std::exception &e) { dstream<<"emergeBlock: emergeSector() failed: " @@ -5690,7 +5573,7 @@ void ManualMapVoxelManipulator::emerge(VoxelArea a, s32 caller_id) void ManualMapVoxelManipulator::initialEmerge( v3s16 blockpos_min, v3s16 blockpos_max) { - TimeTaker timer1("emerge", &emerge_time); + TimeTaker timer1("initialEmerge", &emerge_time); // Units of these are MapBlocks v3s16 p_min = blockpos_min; @@ -5698,6 +5581,15 @@ void ManualMapVoxelManipulator::initialEmerge( VoxelArea block_area_nodes (p_min*MAP_BLOCKSIZE, (p_max+1)*MAP_BLOCKSIZE-v3s16(1,1,1)); + + u32 size_MB = block_area_nodes.getVolume()*4/1000000; + if(size_MB >= 1) + { + dstream<<"initialEmerge: area: "; + block_area_nodes.print(dstream); + dstream<<" ("<<size_MB<<"MB)"; + dstream<<std::endl; + } addArea(block_area_nodes); @@ -100,7 +100,9 @@ public: This is overloaded by ClientMap and ServerMap to allow their differing fetch methods. */ - virtual MapSector * emergeSector(v2s16 p) = 0; + virtual MapSector * emergeSector(v2s16 p){ return NULL; } + virtual MapSector * emergeSector(v2s16 p, + core::map<v3s16, MapBlock*> &changed_blocks){ return NULL; } // Returns InvalidPositionException if not found MapBlock * getBlockNoCreate(v3s16 p); @@ -365,21 +367,41 @@ public: } /* + True if the chunk and its neighbors are fully generated. + It means the chunk will not be touched in the future by the + generator. If false, generateChunk will make it true. + */ + bool chunkNonVolatile(v2s16 chunkpos) + { + /*for(s16 x=-1; x<=1; x++) + for(s16 y=-1; y<=1; y++)*/ + s16 x=0; + s16 y=0; + { + v2s16 chunkpos0 = chunkpos + v2s16(x,y); + MapChunk *chunk = getChunk(chunkpos); + if(chunk == NULL) + return false; + if(chunk->getGenLevel() != GENERATED_FULLY) + return false; + } + return true; + } + + /* Generate a chunk. All chunks touching this one can be altered also. - - Doesn't update lighting. */ - MapChunk* generateChunkRaw(v2s16 chunkpos); + MapChunk* generateChunkRaw(v2s16 chunkpos, + core::map<v3s16, MapBlock*> &changed_blocks); /* Generate a chunk and its neighbors so that it won't be touched anymore. - - Doesn't update lighting. */ - MapChunk* generateChunk(v2s16 chunkpos); + MapChunk* generateChunk(v2s16 chunkpos, + core::map<v3s16, MapBlock*> &changed_blocks); /* Generate a sector. @@ -402,7 +424,14 @@ public: - Check disk (loads blocks also) - Generate chunk */ - MapSector * emergeSector(v2s16 p); + MapSector * emergeSector(v2s16 p, + core::map<v3s16, MapBlock*> &changed_blocks); + + MapSector * emergeSector(v2s16 p) + { + core::map<v3s16, MapBlock*> changed_blocks; + return emergeSector(p, changed_blocks); + } MapBlock * generateBlock( v3s16 p, @@ -418,7 +447,11 @@ public: - Create blank */ MapBlock * createBlock(v3s16 p); - + + /* + only_from_disk, changed_blocks and lighting_invalidated_blocks + are not properly used by the new map generator. + */ MapBlock * emergeBlock( v3s16 p, bool only_from_disk, diff --git a/src/mapblock.cpp b/src/mapblock.cpp index 3e20cb4cf..b388a82c4 100644 --- a/src/mapblock.cpp +++ b/src/mapblock.cpp @@ -700,6 +700,9 @@ void MapBlock::updateMesh(u32 daynight_ratio) const u16 indices[] = {0,1,2,2,3,0}; video::ITexture *texture = g_irrlicht->getTexture(f.tile.spec); + if(texture == NULL) + continue; + material.setTexture(0, texture); if(f.tile.alpha != 255) material.MaterialType = video::EMT_TRANSPARENT_VERTEX_ALPHA; diff --git a/src/mapchunk.h b/src/mapchunk.h index 284eebe60..1f89b38a7 100644 --- a/src/mapchunk.h +++ b/src/mapchunk.h @@ -22,14 +22,21 @@ with this program; if not, write to the Free Software Foundation, Inc., /* MapChunk contains map-generation-time metadata for an area of - some MapSectors. (something like 64x64) + some MapSectors. (something like 16x16) */ +enum{ + GENERATED_FULLY = 0, + GENERATED_PARTLY = 10, + GENERATED_NOT = 20 +}; + class MapChunk { public: MapChunk(): - m_is_volatile(true) + //m_is_volatile(true) + m_generation_level(GENERATED_NOT) { } @@ -40,11 +47,21 @@ public: It is set to false when all the 8 neighboring chunks have been generated. */ - bool getIsVolatile(){ return m_is_volatile; } - void setIsVolatile(bool is){ m_is_volatile = is; } + /*bool getIsVolatile(){ return m_is_volatile; } + void setIsVolatile(bool is){ m_is_volatile = is; }*/ + + /* + Generation level. Possible values: + GENERATED_FULLY = 0 = fully generated + GENERATED_PARTLY = partly generated + GENERATED_NOT = not generated + */ + u16 getGenLevel(){ return m_generation_level; } + void setGenLevel(u16 lev){ m_generation_level = lev; } private: - bool m_is_volatile; + //bool m_is_volatile; + u16 m_generation_level; }; #endif diff --git a/src/mapnode.cpp b/src/mapnode.cpp index b2d579d5d..f5ca0ca9e 100644 --- a/src/mapnode.cpp +++ b/src/mapnode.cpp @@ -82,6 +82,9 @@ void init_mapnode(IIrrlichtWrapper *irrlicht) i = CONTENT_TREE; f = &g_content_features[i]; f->setAllTextures(irrlicht->getTextureId("tree.png")); + f->setTexture(0, irrlicht->getTextureId("tree_top.png")); + f->setTexture(1, irrlicht->getTextureId("tree_top.png")); + f->setInventoryTexture(irrlicht->getTextureId("tree_top.png")); f->param_type = CPT_MINERAL; f->is_ground_content = true; diff --git a/src/player.cpp b/src/player.cpp index b260e5056..1a553157a 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -428,6 +428,9 @@ void LocalPlayer::move(f32 dtime, Map &map) void LocalPlayer::applyControl(float dtime) { + // Clear stuff + swimming_up = false; + // Random constants #define WALK_ACCELERATION (4.0 * BS) #define WALKSPEED_MAX (4.0 * BS) @@ -510,6 +513,7 @@ void LocalPlayer::applyControl(float dtime) v3f speed = getSpeed(); speed.Y = 2.0*BS; setSpeed(speed); + swimming_up = true; } } diff --git a/src/player.h b/src/player.h index 5ab027e0a..30df1db8b 100644 --- a/src/player.h +++ b/src/player.h @@ -115,6 +115,7 @@ public: bool touching_ground; bool in_water; + bool swimming_up; Inventory inventory; diff --git a/src/server.cpp b/src/server.cpp index 28faaf440..fea4ce734 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -515,9 +515,13 @@ void RemoteClient::GetNextBlocks(Server *server, float dtime, generate = false; } +#if 0 /* If block is far away, don't generate it unless it is near ground level + + NOTE: We can't know the ground level this way with the + new generator. */ if(d > 4) { @@ -543,6 +547,7 @@ void RemoteClient::GetNextBlocks(Server *server, float dtime, generate = false; } } +#endif /* Don't draw if not in sight @@ -610,11 +615,13 @@ void RemoteClient::GetNextBlocks(Server *server, float dtime, v2s16 p2d(p.X, p.Z); ServerMap *map = (ServerMap*)(&server->m_env.getMap()); v2s16 chunkpos = map->sector_to_chunk(p2d); - MapChunk *chunk = map->getChunk(chunkpos); + if(map->chunkNonVolatile(chunkpos) == false) + block_is_invalid = true; + /*MapChunk *chunk = map->getChunk(chunkpos); if(chunk == NULL) block_is_invalid = true; else if(chunk->getIsVolatile() == true) - block_is_invalid = true; + block_is_invalid = true;*/ } /* @@ -3175,6 +3182,16 @@ Player *Server::emergePlayer(const char *name, const char *password, { setCreativeInventory(player); } + + /* + With new map generator the map is regenerated anyway, + so start at somewhere where you probably don't get underground + */ + player->setPosition(intToFloat(v3s16( + 0, + 50, + 0 + ))); return player; } @@ -3206,9 +3223,17 @@ Player *Server::emergePlayer(const char *name, const char *password, dstream<<"Server: Finding spawn place for player \"" <<player->getName()<<"\""<<std::endl; -#if 1 v2s16 nodepos; f32 groundheight = 0; +#if 1 + player->setPosition(intToFloat(v3s16( + 0, + 50, + 0 + ))); +#endif +#if 0 +#if 0 // Try to find a good place a few times for(s32 i=0; i<500; i++) { @@ -3271,6 +3296,7 @@ Player *Server::emergePlayer(const char *name, const char *password, groundheight + 15, nodepos.Y ))); +#endif /* Add player to environment diff --git a/src/utility.h b/src/utility.h index 8839887f7..cc69099b4 100644 --- a/src/utility.h +++ b/src/utility.h @@ -1474,8 +1474,11 @@ void mysrand(unsigned seed); inline int myrand_range(int min, int max) { - if(min >= max) + if(min > max) + { + assert(0); return max; + } return (myrand()%(max-min+1))+min; } |