void MapSpatialComponent::OnRemove() { Context()->chitBag->RemoveFromBuildingHash(this, bounds.min.x, bounds.min.y); WorldMap* worldMap = Context()->worldMap; LumosChitBag* chitBag = Context()->chitBag; super::OnRemove(); // Since we are removed from the HashTable, this // won't be found by UpdateGridLayer() Rectangle2I b = bounds; b.Outset(1); worldMap->UpdateBlock(bounds); UpdateGridLayer(worldMap, chitBag, b); }
Rectangle2I MapScene::MapBounds2() { Vector2I subOrigin = data->destSector; if (subOrigin.IsZero()) { subOrigin = lumosChitBag->GetHomeSector(); } if (subOrigin.IsZero()) { subOrigin.Set(NUM_SECTORS / 2, NUM_SECTORS / 2); } if ( subOrigin.x < MAP2_RAD ) subOrigin.x = MAP2_RAD; if ( subOrigin.y < MAP2_RAD ) subOrigin.y = MAP2_RAD; if ( subOrigin.x >= NUM_SECTORS - MAP2_RAD ) subOrigin.x = NUM_SECTORS - MAP2_RAD - 1; if ( subOrigin.y >= NUM_SECTORS - MAP2_RAD ) subOrigin.y = NUM_SECTORS - MAP2_RAD - 1; Rectangle2I subBounds; subBounds.min = subBounds.max = subOrigin; subBounds.Outset(MAP2_RAD); return subBounds; }
void PlantScript::DoTick(U32 delta) { // We need process at a steady rate so that // the time between ticks is constant. // This is performance regressive, so something // to keep an eye on. static const int MAP2 = MAX_MAP_SIZE*MAX_MAP_SIZE; static const int DELTA = 100*1000; // How frequenty to tick a given plant static const int N_PER_MSEC = MAP2 / DELTA; static const int GROWTH_CHANCE = 8; static const int PRIME = 1553; static const float SHADE_EFFECT = 0.7f; int n = N_PER_MSEC * delta; Weather* weather = Weather::Instance(); WorldMap* worldMap = context->worldMap; Rectangle2I bounds = worldMap->Bounds(); bounds.Outset(-1); // edge of map: don't want to tap over the edge. const Vector3F& light = context->engine->lighting.direction; const float norm = Max(fabs(light.x), fabs(light.z)); Vector2I lightTap = { int(LRintf(light.x / norm)), int(LRintf(light.z / norm)) }; Census* census = &context->chitBag->census; for (int i = 0; i < n; ++i) { index += PRIME; int x = IndexToMapX(index); int y = IndexToMapY(index); const WorldGrid& wg = worldMap->GetWorldGrid(x, y); if (!wg.Plant()) continue; Vector2I pos2i = { x, y }; Vector2F pos2f = ToWorld2F(pos2i); // --- Light Tap --- // const float height = PlantScript::PlantRes(wg.Plant() - 1, wg.PlantStage())->AABB().SizeY(); const float rainBase = weather->RainFraction(pos2f.x, pos2f.y); const float sunBase = (1.0f - rainBase); const float temperatureBase = weather->Temperature(pos2f.x, pos2f.y); float rain = rainBase; float sun = sunBase; float temperature = temperatureBase; float growth = 1.0f; Vector2I tap = pos2i + lightTap; // Check for something between us and the light. const WorldGrid& wgTap = worldMap->GetWorldGrid(tap); float tapHeight = float(wgTap.RockHeight()); if (wgTap.PlantStage()) { tapHeight = PlantScript::PlantRes(wgTap.Plant() - 1, wgTap.PlantStage())->AABB().SizeY(); } if (tapHeight > (height + 0.1f)) { // in shade sun *= SHADE_EFFECT; temperature *= SHADE_EFFECT; } // ---- Adjacent --- // static const int NADJ = 4; static const Vector2I check[NADJ] = { { -1, 0 }, { 1, 0 }, { 0, -1 }, { 0, 1 } }; int same = 0; for (int i = 0; i<NADJ; ++i) { tap = pos2i + check[i]; const WorldGrid& wgAdj = worldMap->GetWorldGrid(tap.x, tap.y); if (wgAdj.Plant() == wg.Plant()) { ++same; } if (wgAdj.RockHeight()) { // Water or rock runoff increase water. rain += 0.25f * rainBase; } if (wgAdj.IsFluid()) { rain += 0.25f; // just a lot of water. } if (wgAdj.IsWater()) { rain += 0.25f; // more water temperature = Mean(0.5f, temperature); // moderate temperature } } // Nutrient depletion? Too packed in? if (same == NADJ) { growth *= 0.25f; } // Are we under water? float fluidHeight = wg.FluidHeight(); if (fluidHeight > 0.01f) { // Any amount of water goes to rain 100% rain = 1.0f; // not sure what to do with temp...assume a little cooler? temperature *= 0.8f; // blocks light... float sizeY = PlantScript::PlantRes(wg.Plant() - 1, wg.PlantStage())->AABB().SizeY(); if (fluidHeight > sizeY) sun = 0; else if (fluidHeight > sizeY * 0.5f) sun = sun * (1.0f - fluidHeight / sizeY); } rain = Clamp(rain, 0.0f, 1.0f); temperature = Clamp(temperature, 0.0f, 1.0f); sun = Clamp(sun, 0.0f, 1.0f); // ------- calc ------- // Vector3F actual = { sun, rain, temperature }; Vector3F optimal = { 0.5f, 0.5f, 0.5f }; const GameItem* item = PlantScript::PlantDef(wg.Plant() - 1); item->keyValues.Get(ISC::sun, &optimal.x); item->keyValues.Get(ISC::rain, &optimal.y); item->keyValues.Get(ISC::temp, &optimal.z); float distance = (optimal - actual).Length(); distance = distance / growth; const float GROW = Lerp(0.2f, 0.1f, (float)wg.PlantStage() / (float)(MAX_PLANT_STAGES - 1)); const float DIE = 0.4f; float seconds = float(DELTA) / 1000.0f; if (distance < GROW) { // Heal. float hp = HP_PER_SECOND*seconds; DamageDesc heal( -hp, 0 ); worldMap->VoxelHit(pos2i, heal); // Grow int nStage = wg.IsFlower() ? PLANT_BLOCKING_STAGE : MAX_PLANT_STAGES; if (wg.HPFraction() > 0.8f) { if (wg.PlantStage() < (nStage - 1)) { int hp = wg.HP(); worldMap->SetPlant(pos2i.x, pos2i.y, wg.Plant(), wg.PlantStage() + 1); worldMap->SetWorldGridHP(pos2i.x, pos2i.y, hp); } if (random.Rand(GROWTH_CHANCE) < wg.PlantStage()) { // Number range reflects wind direction. int dx = -1 + random.Rand(4); // [-1,2] int dy = -1 + random.Rand(3); // [-1,1] // Remember that create plant will favor creating // existing plants, so we don't need to specify // what to create. Sim* sim = context->chitBag->GetSim(); GLASSERT(sim); sim->CreatePlant(pos2i.x + dx, pos2i.y + dy, -1); } } int stage = wg.PlantStage(); // 0-3 CoreScript* cs = CoreScript::GetCore(ToSector(pos2i)); // Totally "what feels right in world gen" constant in the random.Rand() if ((census->wildFruit < MAX_WILD_FRUIT) && cs && (!cs->InUse()) && int(random.Rand(200)) < (stage*stage)) { context->chitBag->NewWildFruit(pos2i); } } else if (distance > DIE) { DamageDesc dd(HP_PER_SECOND * seconds, 0); worldMap->VoxelHit(pos2i, dd); if (wg.HP() == 0) { worldMap->SetPlant(pos2i.x, pos2i.y, 0, 0); } } } }
void MapSpatialComponent::SyncWithSpatial() { if (!parentChit) return; Vector3F pos = parentChit->Position(); if (pos.IsZero()) { GLASSERT(bounds.min.IsZero()); return; } Rectangle2I oldBounds = bounds; // Position is in the center! if (size == 1) { bounds.min = ToWorld2I(pos); } else if (size == 2) { bounds.min = ToWorld2I(pos); bounds.min.x -= 1; bounds.min.y -= 1; } else { GLASSERT(0); } bounds.max.x = bounds.min.x + size - 1; bounds.max.y = bounds.min.y + size - 1; if (oldBounds != bounds) { // We have a new position, update in the hash tables: Context()->chitBag->RemoveFromBuildingHash(this, oldBounds.min.x, oldBounds.min.y); Context()->chitBag->AddToBuildingHash(this, bounds.min.x, bounds.min.y); } // And the pather. if (!oldBounds.min.IsZero()) { Context()->worldMap->UpdateBlock(oldBounds); } Context()->worldMap->UpdateBlock(bounds); // Compute a new porch type: EvalBuildingScript* ebs = 0; if (hasPorch) { ebs = (EvalBuildingScript*)parentChit->GetComponent("EvalBuildingScript"); const GameItem* item = parentChit->GetItem(); hasPorch = WorldGrid::BASE_PORCH; if (ebs && item) { double eval = ebs->EvalIndustrial(false); // sets "Reachable". Must be called first. if (ebs->Reachable()) { double consumes = item->GetBuildingIndustrial(); if (consumes) { double dot = eval * consumes; int q = int((1.0 + dot) * 2.0 + 0.5); // q=0, no porch. q=1 default. hasPorch = WorldGrid::BASE_PORCH + 1 + q; GLASSERT(hasPorch > 1 && hasPorch < WorldGrid::NUM_PORCH); } } else { hasPorch = WorldGrid::PORCH_UNREACHABLE; } } } // And the porches / circuits: (rotation doesn't change bounds); Rectangle2I oldOutset = oldBounds, outset = bounds; oldOutset.Outset(1); outset.Outset(1); if (!oldBounds.min.IsZero()) { UpdateGridLayer(Context()->worldMap, Context()->chitBag, oldOutset); } UpdateGridLayer(Context()->worldMap, Context()->chitBag, outset); }
void Engine::Draw() { GRINLIZ_PERFTRACK; // -------- Camera & Frustum -------- // screenport->SetView( camera.ViewMatrix() ); // Draw the camera #ifdef DEBUG { Vector3F at; CameraLookingAt( &at ); //GLOUTPUT(( "View set. Camera at (%.1f,%.1f,%.1f) looking at (%.1f,%.1f,%.1f)\n", // camera.PosWC().x, camera.PosWC().y, camera.PosWC().z, // at.x, at.y, at.z )); if ( map ) { Rectangle2I b = map->Bounds(); b.Outset( 2 ); if ( !b.Contains( (int)at.x, (int)at.z ) ) { GLASSERT( 0 ); // looking at nothing. } } } #endif // Compute the frustum planes and query the tree. Plane planes[6]; CalcFrustumPlanes( planes ); Model* modelRoot = spaceTree->Query( planes, 6, 0, Model::MODEL_INVISIBLE, false ); Color4F ambient, diffuse; Vector4F dir; CalcLights( ( !map || map->DayTime() ) ? DAY_TIME : NIGHT_TIME, &ambient, &dir, &diffuse ); LightShader lightShader( ambient, dir, diffuse, false ); LightShader blendLightShader( ambient, dir, diffuse, true ); // Some tiles use alpha - for instance the "splat" image LightShader mapItemShader( ambient, dir, diffuse, false ); LightShader mapBlendItemShader( ambient, dir, diffuse, true ); Rectangle2I mapBounds( 0, 0, EL_MAP_SIZE-1, EL_MAP_SIZE-1 ); if ( map ) { mapBounds = map->Bounds(); } // ------------ Process the models into the render queue ----------- { GLASSERT( renderQueue->Empty() ); const grinliz::BitArray<Map::SIZE, Map::SIZE, 1>* fogOfWar = (map) ? &map->GetFogOfWar() : 0; for( Model* model=modelRoot; model; model=model->next ) { if ( model->IsFlagSet( Model::MODEL_METADATA ) && !enableMeta ) continue; if ( model->IsFlagSet( Model::MODEL_OWNED_BY_MAP ) ) { model->Queue( renderQueue, &mapItemShader, &mapBlendItemShader, 0 ); } else { Vector3F pos = model->AABB().Center(); int x = LRintf( pos.x - 0.5f ); int y = LRintf( pos.z - 0.5f ); #ifdef EL_SHOW_ALL_UNITS { #else if ( mapBounds.Contains( x, y ) && (!fogOfWar || fogOfWar->IsSet( x, y ) ) ) { #endif model->Queue( renderQueue, &lightShader, &blendLightShader, 0 ); } } } } // ----------- Render Passess ---------- // Color4F color; if ( map ) { // If the map is enabled, we draw the basic map plane lighted. Then draw the model shadows. // The shadows are the tricky part: one matrix is used to transform the vertices to the ground // plane, and the other matrix is used to transform the vertices to texture coordinates. // Shaders make this much, much, much easier. // -------- Ground plane lighted -------- // #ifdef ENGINE_RENDER_MAP map->GenerateSeenUnseen(); map->DrawSeen(); #endif // -------- Shadow casters/ground plane ---------- // // Set up the planar projection matrix, with a little z offset // to help with z resolution fighting. const float SHADOW_START_HEIGHT = 80.0f; const float SHADOW_END_HEIGHT = SHADOW_START_HEIGHT + 5.0f; float shadowAmount = 1.0f; if ( camera.PosWC().y > SHADOW_START_HEIGHT ) { shadowAmount = 1.0f - ( camera.PosWC().y - SHADOW_START_HEIGHT ) / ( SHADOW_END_HEIGHT - SHADOW_START_HEIGHT ); } if ( shadowAmount > 0.0f ) { #ifdef ENGINE_RENDER_SHADOWS CompositingShader shadowShader; shadowShader.SetTexture0( map->BackgroundTexture() ); shadowShader.SetTexture1( map->LightMapTexture() ); // The shadow matrix pushes in a depth. Its the depth<0 that allows the GL_LESS // test for the shadow write, below. PushShadowSwizzleMatrix( &shadowShader ); // Just computes how dark the shadow is. LightGroundPlane( map->DayTime() ? DAY_TIME : NIGHT_TIME, IN_SHADOW, shadowAmount, &color ); shadowShader.SetColor( color ); renderQueue->Submit( &shadowShader, RenderQueue::MODE_PLANAR_SHADOW, 0, Model::MODEL_NO_SHADOW ); shadowShader.PopMatrix( GPUShader::MODELVIEW_MATRIX ); shadowShader.PopTextureMatrix( 3 ); } #endif { LightGroundPlane( map->DayTime() ? DAY_TIME : NIGHT_TIME, OPEN_LIGHT, 0, &color ); float ave = 0.5f*((color.r + color.g + color.b)*0.333f); Color4F c = { ave, ave, ave, 1.0f }; #ifdef ENGINE_RENDER_MAP map->DrawPastSeen( c ); #endif } #ifdef ENGINE_RENDER_MAP map->DrawOverlay( Map::LAYER_UNDER_LOW ); map->DrawUnseen(); map->DrawOverlay( Map::LAYER_UNDER_HIGH ); #endif } // -------- Models ---------- // #ifdef ENGINE_RENDER_MODELS { if ( iMap ) { mapItemShader.SetTexture1( iMap->LightFogMapTexture() ); mapBlendItemShader.SetTexture1( iMap->LightFogMapTexture() ); PushLightSwizzleMatrix( &mapItemShader ); renderQueue->Submit( 0, 0, Model::MODEL_OWNED_BY_MAP, 0 ); lightShader.PopTextureMatrix( 2 ); } // Render everything NOT in the map. renderQueue->Submit( 0, 0, 0, Model::MODEL_OWNED_BY_MAP ); } #endif if ( map ) map->DrawOverlay( Map::LAYER_OVER ); renderQueue->Clear(); } void Engine::CalcLights( DayNight dayNight, Color4F* ambient, Vector4F* dir, Color4F* diffuse ) { ambient->Set( AMBIENT, AMBIENT, AMBIENT, 1.0f ); diffuse->Set( DIFFUSE, DIFFUSE, DIFFUSE, 1.0f ); if ( dayNight == NIGHT_TIME ) { diffuse->r *= EL_NIGHT_RED; diffuse->g *= EL_NIGHT_GREEN; diffuse->b *= EL_NIGHT_BLUE; } dir->Set( lightDirection.x, lightDirection.y, lightDirection.z, 0 ); // '0' in last term is parallel }
double EvalBuildingScript::EvalIndustrial( bool debugLog ) { Chit* building = ParentChit(); int hitB = 0, hitIBuilding = 0, hitNBuilding = 0, hitWater = 0, hitPlant = 0, hitRock = 0, hitWaterfall = 0; int hitShrub = 0; // doesn't terminate a ray. if (lastEval == 0 || (Context()->chitBag->AbsTime() - lastEval) > 2000) { lastEval = Context()->chitBag->AbsTime(); GameItem* item = building->GetItem(); GLASSERT(item); reachable = true; IString consume = item->keyValues.GetIString(ISC::zone); if (consume.empty()) { eval = 0; return eval; } MapSpatialComponent* msc = GET_SUB_COMPONENT(building, SpatialComponent, MapSpatialComponent); GLASSERT(msc); if (!msc) { eval = 0; return eval; } Rectangle2I porch = msc->PorchPos(); static const int RAD = 4; Rectangle2I bounds = porch; if (porch.min.x == 0) { // shouldn't happen, but be sure. GLASSERT(0); eval = 0; return eval; } bounds.Outset(RAD); Vector2I sector = ToSector(building->Position()); WorldMap* worldMap = Context()->worldMap; Rectangle2I mapBounds = worldMap->Bounds(); if (!mapBounds.Contains(bounds)) { eval = 0; return eval; // not worth dealing with edge of world } // Check if we can go from the core to the porch. // And make sure the core is inUse! CoreScript* cs = CoreScript::GetCore(ToSector(porch.min)); if (!cs || !cs->InUse()) reachable = false; if (reachable) { const SectorData& sd = worldMap->GetSectorData(ToSector(porch.min)); reachable = worldMap->CalcPath(ToWorld2F(sd.core), ToWorld2F(porch.min), 0, 0, false); } CChitArray arr; BuildingFilter buildingFilter; const FluidSim* fluidSim = Context()->physicsSims->GetFluidSim(sector); bool hasWaterfalls = fluidSim->NumWaterfalls() > 0; LumosChitBag* chitBag = Context()->chitBag; Rectangle2IEdgeIterator it(bounds); while (!it.Done()) { Vector2I pos = { it.Pos().x >= porch.max.x ? porch.max.x : porch.min.x, it.Pos().y >= porch.max.y ? porch.max.y : porch.min.y }; LineWalk walk(pos.x, pos.y, it.Pos().x, it.Pos().y); walk.Step(); // ignore where we are standing. while ( !walk.Done() ) { // non-intuitive iterator. See linewalk docs. // - building // - plant // - ice // - rock // - waterfall // - water // Buildings. Can be 2x2. Extend out beyond current check. bool hitBuilding = false; Vector2I p = walk.P(); // Don't count self as a hit, but stops the ray cast. // Also, use a larger radius because buildings can be 2x2 chitBag->QuerySpatialHash(&arr, ToWorld2F(p), 0.8f, 0, &buildingFilter); for (int i = 0; i < arr.Size(); ++i) { if (arr[i] != building) { MapSpatialComponent* buildingMSC = GET_SUB_COMPONENT(arr[i], SpatialComponent, MapSpatialComponent); GLASSERT(buildingMSC); if (buildingMSC->Bounds().Contains(p)) { hitBuilding = true; double thisSys = arr[i]->GetItem()->GetBuildingIndustrial(); hitB++; if (thisSys <= -0.5) hitNBuilding++; if (thisSys >= 0.5) hitIBuilding++; break; } } } if (hitBuilding) break; const WorldGrid& wg = worldMap->GetWorldGrid(p.x, p.y); if (wg.Plant()) { // int type = wg.Plant() - 1; int stage = wg.PlantStage(); if (stage >= 2) { ++hitPlant; break; } else { hitShrub++; } } if (wg.RockHeight()) { ++hitRock; break; } if (wg.IsWater()) { ++hitWater; break; } Rectangle2I wb; wb.min = wb.max = p; if (hasWaterfalls && fluidSim->ContainsWaterfalls(wb)) { ++hitWaterfall; break; } walk.Step(); } it.Next(); } // Note rock/ice isn't counted either way. int natural = hitNBuilding + hitWater + hitPlant + 10 * hitWaterfall + hitShrub / 4; // small plants don't add to rRays, so divide is okay. int industrial = hitIBuilding; int nRays = hitNBuilding + hitWater + hitPlant + hitWaterfall + hitIBuilding; eval = 0; if (nRays) { // With this system, that one ray (say from a distillery to plant) can be // hugely impactful. This may need tweaking: if (nRays < 2) nRays = 2; eval = double(industrial - natural) / double(nRays); } eval = Clamp(eval, -1.0, 1.0); if (debugLog) { Vector2I pos = ToWorld2I(building->Position()); GLOUTPUT(("Building %s at %d,%d eval=%.2f nRays=%d \n hit: Build=%d (I=%d N=%d) water=%d plant=%d rock=%d\n", building->GetItem()->Name(), pos.x, pos.y, eval, nRays, hitB, hitIBuilding, hitNBuilding, hitWater, hitPlant, hitRock)); (void)pos; } } return eval; }