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];
			}
		}
	}
}
Example #2
0
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;
}