void Screenport::SetUI( const Rectangle2I* clip ) { if ( clip && clip->Area() > 1 ) { clipInUI2D = Rectangle2F( (float)clip->min.x, (float)clip->min.y, (float)clip->max.x, (float)clip->max.y ); } else { clipInUI2D = Rectangle2F( 0, 0, UIWidth(), UIHeight() ); } GLASSERT( clipInUI2D.IsValid() ); GLASSERT( clipInUI2D.min.x >= 0 && clipInUI2D.max.x <= UIWidth() ); GLASSERT( clipInUI2D.min.y >= 0 && clipInUI2D.max.y <= UIHeight() ); Rectangle2F scissor; UIToWindow( clipInUI2D, &scissor ); Rectangle2I clean; CleanScissor( scissor, &clean ); GPUShader::SetScissor( clean.min.x, clean.min.y, clean.Width(), clean.Height() ); //view2D.SetIdentity(); projection2D.SetIdentity(); projection2D.SetOrtho( 0, screenWidth, screenHeight, 0, -1, 1 ); GPUShader::SetOrthoTransform( (int)screenWidth, (int)screenHeight, Rotation()*90 ); uiMode = true; }
KrEngine::KrEngine( SDL_Surface* _screen ) : paintInfo( _screen ) { Rectangle2I bounds; bounds.Set( 0, 0, _screen->w - 1, _screen->h - 1 ); Init( _screen, 1, &bounds, 0 ); }
Rectangle2I MapSpatialComponent::PorchPos() const { Rectangle2I v; v.Set( 0, 0, 0, 0 ); if ( !hasPorch ) return v; float yRotation = YRotation(parentChit->Rotation()); v = CalcPorchPos(bounds.min, bounds.Width(), yRotation); return v; }
void MapScene::ItemTapped(const gamui::UIItem* item) { Vector2I sector = { 0, 0 }; Vector2I v = data->destSector; CoreScript* cs = CoreScript::GetCore(v); CoreScript* homeCore = lumosChitBag->GetHomeCore(); if (item == &okay) { data->destSector.Zero(); lumosGame->PopScene(); } else if (item == &gridTravel) { lumosGame->PopScene(); } else if (item == &viewButton) { data->view = true; lumosGame->PopScene(); } else if (item == &mapImage) { float x = 0, y = 0; gamui2D.GetRelativeTap(&x, &y); sector.x = int(x * float(NUM_SECTORS)); sector.y = int(y * float(NUM_SECTORS)); data->destSector = sector; DrawMap(); EnableButtons(); } else if (item == &mapImage2) { float x = 0, y = 0; gamui2D.GetRelativeTap(&x, &y); Rectangle2I b = MapBounds2(); sector.x = b.min.x + int(x * float(b.Width())); sector.y = b.min.y + int(y * float(b.Height())); data->destSector = sector; DrawMap(); EnableButtons(); } else if (item == &warButton) { Team::Instance()->War(cs, homeCore, true, &lumosChitBag->GetSim()->GetCachedWeb()); DrawMap(); EnableButtons(); } else if (item == &peaceButton) { int cost = Team::Instance()->Peace(cs, homeCore, false, &lumosChitBag->GetSim()->GetCachedWeb()); Wallet* wallet = homeCore->ParentChit()->GetWallet(); if (wallet->HasGold(cost)) { ReserveBank::Instance()->GetWallet()->Deposit(wallet, cost); Team::Instance()->Peace(cs, homeCore, true, &lumosChitBag->GetSim()->GetCachedWeb()); DrawMap(); EnableButtons(); } } }
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); }
void Visibility::InvalidateAll( const Rectangle2I& bounds ) { if ( !bounds.IsValid() ) return; Rectangle2I vis; for( int i=0; i<MAX_UNITS; ++i ) { if ( units[i].IsAlive() ) { units[i].CalcVisBounds( &vis ); if ( bounds.Intersect( vis ) ) { current[i] = false; if ( units[i].Team() == TERRAN_TEAM ) { fogInvalid = true; } } } } }
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 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 KrOglTexture::SetTexture( const KrRGBA* pixels, const Rectangle2I& pixelCoords ) { glBindTexture( GL_TEXTURE_2D, textureId ); /* sigh. Still have to flip bytes. */ int byteSize = pixelCoords.Width() * pixelCoords.Height() * 4; int colorSize = pixelCoords.Width() * pixelCoords.Height(); U8* data = new U8[ byteSize ]; GLASSERT( data ); if ( !data ) return; memcpy( data, pixels, byteSize ); #ifdef KYRA_FLIP_COLORS U8* p = data; for ( int i=0; i<colorSize; ++i ) { Swap( &p[0], &p[2] ); p += 4; } #endif glTexSubImage2D(GL_TEXTURE_2D, 0, // no mip mapping pixelCoords.min.x, // coords on the texture pixelCoords.min.y, pixelCoords.Width(), // coords on the source -- must be equal pixelCoords.Height(), GL_RGBA, // format GL_UNSIGNED_BYTE, data ); GLASSERT( glGetError() == GL_NO_ERROR ); delete [] data; }
void Screenport::SetPerspective( const grinliz::Rectangle2I* clip ) { uiMode = false; if ( clip && clip->Area() > 1 ) { clipInUI3D = Rectangle2F( (float)clip->min.x, (float)clip->min.y, (float)clip->max.x, (float)clip->max.y ); } else { clipInUI3D = Rectangle2F( 0, 0, UIWidth(), UIHeight() ); } GLASSERT( clipInUI3D.IsValid() ); GLASSERT( clipInUI3D.min.x >= 0 && clipInUI3D.max.x <= UIWidth() ); GLASSERT( clipInUI3D.min.y >= 0 && clipInUI3D.max.y <= UIHeight() ); Rectangle2F scissor; UIToWindow( clipInUI3D, &scissor ); Rectangle2I clean; CleanScissor( scissor, &clean ); GPUShader::SetScissor( clean.min.x, clean.min.y, clean.Width(), clean.Height() ); GLASSERT( uiMode == false ); GLASSERT( EL_NEAR > 0.0f ); GLASSERT( EL_FAR > EL_NEAR ); frustum.zNear = EL_NEAR; frustum.zFar = EL_FAR; // Convert from the FOV to the half angle. float theta = ToRadian( EL_FOV * 0.5f ); float tanTheta = tanf( theta ); float halfLongSide = tanTheta * frustum.zNear; // left, right, top, & bottom are on the near clipping // plane. (Not an obvious point to my mind.) // Also, the 3D camera applies the rotation. if ( Rotation() & 1 ) { float ratio = (float)clipInUI3D.Height() / (float)clipInUI3D.Width(); // frustum is in original screen coordinates. frustum.top = halfLongSide; frustum.bottom = -halfLongSide; frustum.left = -ratio * halfLongSide; frustum.right = ratio * halfLongSide; } else { // Since FOV is specified as the 1/2 width, the ratio // is the height/width (different than gluPerspective) float ratio = (float)clipInUI3D.Height() / (float)clipInUI3D.Width(); frustum.top = ratio * halfLongSide; frustum.bottom = -frustum.top; frustum.left = -halfLongSide; frustum.right = halfLongSide; } Matrix4 rot; rot.SetZRotation( (float)(-90 * Rotation()) ); // In normalized coordinates. projection3D.SetFrustum( frustum.left, frustum.right, frustum.bottom, frustum.top, frustum.zNear, frustum.zFar ); projection3D = projection3D * rot; GPUShader::SetPerspectiveTransform( frustum.left, frustum.right, frustum.bottom, frustum.top, frustum.zNear, frustum.zFar, (90*Rotation()) ); }
bool PitMorph::DoSequence( TimeClock* timeClock ) { const float SPEED = 4.0f * ( TERRAIN_HEIGHT / 10.0f ); // m term in y = mx+b const float SLOPE = 1.0f; bool last = false; Lilith3D* lilith = Lilith3D::Instance(); TerrainMesh* tmesh = lilith->GetTerrainMesh(); float height = tmesh->Height( terrainX, terrainY ) - timeClock->CalcVelocity( SPEED ); height = Clamp( height, TERRAIN_MIN, TERRAIN_MAX ); // essentially the x-intecept: // y = mx + b, b=height // x = -height / m float radius = ( TERRAIN_MAX - height ) / SLOPE; int irad = int( radius ) + 1; Rectangle2I bounds; bounds.Set( terrainX - irad, terrainY - irad, terrainX + irad, terrainY + irad ); Rectangle2I constrain; constrain.Set( 0, 0, lilith3d::VERTEXSIZE-1, lilith3d::VERTEXSIZE-1 ); bounds.DoIntersection( constrain ); // Do the center first. if ( height <= altitude ) last = true; if ( tmesh->Height( terrainX, terrainY ) > altitude ) { tmesh->StartHeightChange(); tmesh->SetHeight( terrainX, terrainY, height ); for( int j=bounds.min.y; j<=bounds.max.y; ++j ) { for( int i=bounds.min.x; i<=bounds.max.x; ++i ) { if ( i != terrainX || j != terrainY ) { float len = Length( float( terrainX-i ), float( terrainY-j ), 0.0f ); float setHeight = len*SLOPE + height; if ( setHeight < tmesh->Height( i, j ) ) tmesh->SetHeight( i, j, setHeight ); } } } tmesh->EndHeightChange(); } for( Publisher< PitMorphListener >::const_iterator it = publish.begin(); it != publish.end(); ++it ) { if ( last ) (*it)->PitDone( this, terrainX, terrainY, height ); else (*it)->PitSink( this, terrainX, terrainY, height ); } return last; }
bool VolcanoMorph::DoSequence( TimeClock* timeClock ) { const float SPEED = 4.0f * ( TERRAIN_HEIGHT / 10.0f ); // m term in y = mx+b const float SLOPE = -1.3f; const float SMOOTH_RADIUS = 3.0f; bool last = false; Lilith3D* lilith = Lilith3D::Instance(); TerrainMesh* tmesh = lilith->GetTerrainMesh(); float height = tmesh->Height( terrainX, terrainY ) + timeClock->CalcVelocity( SPEED ); height = Clamp( height, TERRAIN_MIN, TERRAIN_MAX ); // essentially the x-intecept: // y = mx + b, b=height // x = -height / m float radius = -( height - TERRAIN_MIN ) / SLOPE; int irad = int( radius ) + 1; Rectangle2I bounds; bounds.Set( terrainX - irad, terrainY - irad, terrainX + irad, terrainY + irad ); Rectangle2I constrain; constrain.Set( 0, 0, lilith3d::VERTEXSIZE-1, lilith3d::VERTEXSIZE-1 ); bounds.DoIntersection( constrain ); // Do the center first. if ( height >= altitude ) last = true; if ( tmesh->Height( terrainX, terrainY ) < altitude ) { tmesh->StartHeightChange(); tmesh->SetHeight( terrainX, terrainY, height ); for( int j=bounds.min.y; j<=bounds.max.y; ++j ) { for( int i=bounds.min.x; i<=bounds.max.x; ++i ) { if ( i != terrainX || j != terrainY ) { float len = Length( float( terrainX-i ), float( terrainY-j ) ); float setHeight = len*SLOPE + height; if ( len < SMOOTH_RADIUS ) setHeight += fuzzHeight[ (j*lilith3d::VERTEXSIZE+i) % FUZZ_SIZE ]; if ( setHeight > tmesh->Height( i, j ) ) tmesh->SetHeight( i, j, setHeight ); } } } tmesh->EndHeightChange(); } // Inform listeners of changes. for( Publisher< VolcanoMorphListener >::const_iterator it = publish.begin(); it != publish.end(); ++it ) { if ( !last ) (*it)->VolcanoGrow( this, terrainX, terrainY, height ); else (*it)->VolcanoDone( this, terrainX, terrainY, height ); } // And now the particles and lights! const float FUZZ = 4.0f; const float FUZZMORE = 8.0f; float alt = tmesh->Height( terrainX, terrainY ); GravParticles* grav = lilith->GetGravParticles(); TimeClock* tc = Lilith3D::GetTimeClock(); while ( tc->Msec() > lastParticleTime ) { lastParticleTime += 50; Vector3F loc = { (float)terrainX, (float)terrainY, alt }; Vector3F vel = { -FUZZ / 2 + rand.FRand( FUZZ ), // velocity -FUZZ / 2 + rand.FRand( FUZZ ), SPEED * 1.3f }; Color3F col = { 0.8f + rand.FRand(0.2f), // coloring 0.6f + rand.FRand(0.2f), 0.0f }; grav->Create( loc, vel, col, GravParticles::GRAVITY, 1.0f, GravParticles::FLAME0 ); for( int i=0; i<3; ++i ) { Vector3F vel2 = { -FUZZMORE / 2 + rand.FRand( FUZZMORE ), -FUZZMORE / 2 + rand.FRand( FUZZMORE ), SPEED * 0.8f }; Color3F col2 = { 1.0f, 0.0f, 0.0f }; grav->Create( loc, vel2, col2, GravParticles::GRAVITY, GravParticles::MEDIUM ); } } // glowLight->SetPos( (float)terrainX, (float)terrainY, alt ); if ( decal ) { decal->SetAlpha( 1.0f ); const float FADEOUT = 0.8f * altitude; if ( alt > FADEOUT ) { decal->SetAlpha( Interpolate( FADEOUT, 1.0f, altitude, 0.0f, alt )); } } return last; }
void KrEngine::Draw( bool updateRect, std::vector< Rectangle2I >* _rectangles ) { std::vector< Rectangle2I > rectArrayOnStack; #if defined( DRAWDEBUG_RLE ) || defined( DRAWDEBUG_BLTRECTS ) debugFrameCounter++; #endif // GLOUTPUT( "Engine::Draw Walk\n" ); tree->Walk(); // We either use the passed in rectangles, // or the one here on the stack. Set the pointer // rectArray to the right thing. std::vector< Rectangle2I >* rectArray = ( _rectangles ) ? _rectangles : &rectArrayOnStack; rectArray->resize(0); if ( !paintInfo.openGL ) { // Queue up the rectangles that will be used to blit to screen: if ( needFullScreenUpdate ) { needFullScreenUpdate = false; Rectangle2I rect; rect.Set( 0, 0, screen->w-1, screen->h-1 ); rectArray->push_back( rect ); } else { for ( int i=0; i<nWindows; ++i ) { for ( int j=0; j<dirtyRectangle[i].NumRect(); ++j ) { rectArray->push_back( dirtyRectangle[i].Rect(j) ); } } } } if ( paintInfo.openGL ) { #ifdef KYRA_SUPPORT_OPENGL // OpenGL drawing glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); for( int j=0; j<nWindows; ++j ) { if ( fillBackground[j] ) { glBindTexture( GL_TEXTURE_2D, 0 ); glColor4f( backgroundColor[j].Redf(), backgroundColor[j].Greenf(), backgroundColor[j].Bluef(), 1.0f ); glBegin( GL_QUADS ); { glVertex3i( screenBounds[j].min.x, screenBounds[j].min.y, 0 ); glVertex3i( screenBounds[j].min.x + screenBounds[j].Width(), screenBounds[j].min.y, 0 ); glVertex3i( screenBounds[j].min.x + screenBounds[j].Width(), screenBounds[j].min.y + screenBounds[j].Height(), 0 ); glVertex3i( screenBounds[j].min.x, screenBounds[j].min.y + screenBounds[j].Height(), 0 ); } glEnd(); } bool clipping = ( screenBounds[j] != windowBounds ); if ( clipping ) { glEnable(GL_CLIP_PLANE0); glEnable(GL_CLIP_PLANE1); glEnable(GL_CLIP_PLANE2); glEnable(GL_CLIP_PLANE3); double plane0[4] = { 1.0, 0.0, 0.0, -screenBounds[j].min.x }; double plane1[4] = { -1.0, 0.0, 0.0, (screenBounds[j].min.x + screenBounds[j].Width() ) }; double plane2[4] = { 0.0, 1.0, 0.0, -screenBounds[j].min.y }; double plane3[4] = { 0.0, -1.0, 0.0, (screenBounds[j].min.y + screenBounds[j].Height() ) }; glClipPlane( GL_CLIP_PLANE0, plane0 ); glClipPlane( GL_CLIP_PLANE1, plane1 ); glClipPlane( GL_CLIP_PLANE2, plane2 ); glClipPlane( GL_CLIP_PLANE3, plane3 ); } tree->DrawWalk( screenBounds[j], &paintInfo, j ); if ( clipping ) { glDisable(GL_CLIP_PLANE0); glDisable(GL_CLIP_PLANE1); glDisable(GL_CLIP_PLANE2); glDisable(GL_CLIP_PLANE3); } dirtyRectangle[j].Clear(); } UpdateScreen( 0 ); #else // No openGl support, but openGl surface used GLASSERT( 0 ); #endif } else { // Bitmap drawing. // Draw the background, if necessary. Then // do a draw walk for every DR. for( int win=0; win<nWindows; ++win ) { for( int i=0; i<dirtyRectangle[win].NumRect(); ++i ) { const Rectangle2I& rect = dirtyRectangle[win].Rect( i ); // Draw the background. //GLASSERT( fillBackground[j] ); if ( fillBackground[win] ) { SDL_Rect sdlrect = { rect.min.x, rect.min.y, rect.Width(), rect.Height() }; U32 sdlColor = SDL_MapRGB( screen->format, backgroundColor[win].c.red, backgroundColor[win].c.green, backgroundColor[win].c.blue ); //GLASSERT( sdlColor == 0 ); SDL_FillRect( screen, &sdlrect, sdlColor ); } tree->DrawWalk( rect, &paintInfo, win ); /* #ifdef DRAWDEBUG_BLTRECTS KrPainter painter( &paintInfo ); painter.DrawBox( rect.xmin, rect.ymin, rect.Width(), rect.Height(), 200, 0, 0 ); #endif */ } #ifdef DRAWDEBUG_BLTRECTS dirtyRectangle[win].DrawRects( screen ); #endif dirtyRectangle[win].Clear(); } // The windows and DRs have been walked. Now transfer to physical screen. if ( updateRect ) { // Use the composite list of rectangles. UpdateScreen( rectArray ); } } }
Chit* LumosChitBag::NewBuilding(const Vector2I& pos, const char* name, int team) { const ChitContext* context = Context(); Chit* chit = NewChit(); const GameItem& rootItem = ItemDefDB::Instance()->Get(name); GameItem* item = rootItem.Clone(); // Hack...how to do this better?? if (item->IResourceName() == "pyramid0") { CStr<32> str; str.Format("pyramid%d", random.Rand(3)); item->SetResource(str.c_str()); } if (item->IResourceName() == ISC::kiosk) { switch (random.Rand(4)) { case 0: item->SetResource("kiosk.n"); break; case 1: item->SetResource("kiosk.m"); break; case 2: item->SetResource("kiosk.s"); break; default:item->SetResource("kiosk.c"); break; } } int size = 1; rootItem.keyValues.Get(ISC::size, &size); int porch = 0; rootItem.keyValues.Get(ISC::porch, &porch); const int circuit = 0; // Should be pre-cleared. But recover from weird situations by clearing. // Note that water is a real problem. Rectangle2I r; r.Set(pos.x, pos.y, pos.x + size - 1, pos.y + size - 1); for (Rectangle2IIterator it(r); !it.Done(); it.Next()) { const WorldGrid& wg = context->worldMap->GetWorldGrid(it.Pos()); GLASSERT(wg.IsLand()); (void)wg; context->worldMap->SetRock(it.Pos().x, it.Pos().y, 0, false, 0); context->worldMap->SetPlant(it.Pos().x, it.Pos().y, 0, 0); } MapSpatialComponent* msc = new MapSpatialComponent(); msc->SetBuilding(size, porch != 0, circuit); msc->SetBlocks((rootItem.flags & GameItem::PATH_NON_BLOCKING) ? false : true); chit->Add(msc); MapSpatialComponent::SetMapPosition(chit, pos.x, pos.y); chit->Add(new RenderComponent(item->ResourceName())); chit->Add(new HealthComponent()); AddItem(item, chit, context->engine, team, 0); IString script = rootItem.keyValues.GetIString("script"); if (!script.empty()) { Component* s = ComponentFactory::Factory(script.c_str(), &chitContext); GLASSERT(s); chit->Add(s); } IString proc = rootItem.keyValues.GetIString("procedural"); if (!proc.empty()) { ProcRenderInfo info; AssignProcedural(chit->GetItem(), &info); chit->GetRenderComponent()->SetProcedural(0, info); } IString consumes = rootItem.keyValues.GetIString(ISC::zone); if (!consumes.empty()) { Component* s = ComponentFactory::Factory("EvalBuildingScript", &chitContext); GLASSERT(s); chit->Add(s); } IString nameGen = rootItem.keyValues.GetIString( "nameGen"); if ( !nameGen.empty() ) { IString p = Context()->chitBag->NameGen(nameGen.c_str(), chit->random.Rand()); chit->GetItem()->SetProperName( p ); } #if 0 // debugging SectorPort sp; sp.sector.Set( pos.x/SECTOR_SIZE, pos.y/SECTOR_SIZE ); sp.port = 1; worldMap->SetRandomPort( sp ); #endif context->engine->particleSystem->EmitPD( ISC::constructiondone, ToWorld3F( pos ), V3F_UP, 0 ); if (XenoAudio::Instance()) { Vector3F pos3 = ToWorld3F(pos); XenoAudio::Instance()->PlayVariation(ISC::rezWAV, random.Rand(), &pos3); } return chit; }
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 }
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); } } } }
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; }