void Visibility::CalcUnitVisibility( int unitID ) { //unit = units; // debugging: 1st unit only GLRELASSERT( unitID >= 0 && unitID < MAX_UNITS ); const Unit* unit = &units[unitID]; Vector2I pos = unit->MapPos(); // Clear out the old settings. // Walk the area in range around the unit and cast rays. visibilityMap.ClearPlane( unitID ); visibilityProcessed.ClearAll(); Rectangle2I mapBounds = map->Bounds(); // Can always see yourself. visibilityMap.Set( pos.x, pos.y, unitID ); visibilityProcessed.Set( pos.x, pos.y, 0 ); const int MAX_SIGHT_SQUARED = MAX_EYESIGHT_RANGE*MAX_EYESIGHT_RANGE; for( int r=MAX_EYESIGHT_RANGE; r>0; --r ) { Vector2I p = { pos.x-r, pos.y-r }; static const Vector2I delta[4] = { { 1,0 }, {0,1}, {-1,0}, {0,-1} }; for( int k=0; k<4; ++k ) { for( int i=0; i<r*2; ++i ) { if ( mapBounds.Contains( p ) && !visibilityProcessed.IsSet( p.x, p.y ) && (p-pos).LengthSquared() <= MAX_SIGHT_SQUARED ) { CalcVisibilityRay( unitID, p, pos ); } p += delta[k]; } } } }
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; }