From 1f2f6896e16cc65eb62f20664794ed1cedf20aea Mon Sep 17 00:00:00 2001 From: Robert Beckebans Date: Sat, 20 Feb 2021 16:24:45 +0100 Subject: [PATCH] Merged Quake 1 lightstyle support from Iced-Hellfire --- neo/d3xp/Entity.cpp | 4 +- neo/d3xp/Entity.h | 22 +++- neo/d3xp/Game_local.cpp | 20 ++- neo/d3xp/Game_local.h | 2 + neo/d3xp/Light.cpp | 132 ++++++++++++++++++++ neo/d3xp/Light.h | 45 ++++++- neo/d3xp/gamesys/Class.h | 1 + neo/renderer/Material.h | 34 +++++ neo/tools/imgui/lighteditor/LightEditor.cpp | 130 ++++++++++++++++--- neo/tools/imgui/lighteditor/LightEditor.h | 10 +- 10 files changed, 379 insertions(+), 21 deletions(-) diff --git a/neo/d3xp/Entity.cpp b/neo/d3xp/Entity.cpp index 4e096e6f..00e17184 100644 --- a/neo/d3xp/Entity.cpp +++ b/neo/d3xp/Entity.cpp @@ -1692,7 +1692,7 @@ int idEntity::GetModelDefHandle() idEntity::UpdateRenderEntity ================ */ -bool idEntity::UpdateRenderEntity( renderEntity_s* renderEntity, const renderView_t* renderView ) +bool idEntity::UpdateRenderEntity( renderEntity_t* renderEntity, const renderView_t* renderView ) { if( gameLocal.inCinematic && gameLocal.skipCinematic ) { @@ -1721,7 +1721,7 @@ idEntity::ModelCallback NOTE: may not change the game state whatsoever! ================ */ -bool idEntity::ModelCallback( renderEntity_s* renderEntity, const renderView_t* renderView ) +bool idEntity::ModelCallback( renderEntity_t* renderEntity, const renderView_t* renderView ) { idEntity* ent; diff --git a/neo/d3xp/Entity.h b/neo/d3xp/Entity.h index 1eb45a22..9a6f3a9d 100644 --- a/neo/d3xp/Entity.h +++ b/neo/d3xp/Entity.h @@ -302,8 +302,8 @@ public: // animation virtual bool UpdateAnimationControllers(); - bool UpdateRenderEntity( renderEntity_s* renderEntity, const renderView_t* renderView ); - static bool ModelCallback( renderEntity_s* renderEntity, const renderView_t* renderView ); + bool UpdateRenderEntity( renderEntity_t* renderEntity, const renderView_t* renderView ); + static bool ModelCallback( renderEntity_t* renderEntity, const renderView_t* renderView ); virtual idAnimator* GetAnimator(); // returns animator object used by this entity // sound @@ -571,6 +571,14 @@ private: void UpdatePVSAreas(); // events +public: +// jmarshall + idVec3 GetOrigin( void ); + float DistanceTo( idEntity* ent ); + float DistanceTo( const idVec3& pos ) const; + idStr GetNextKey( const char* prefix, const char* lastMatch ); +// jmarshall end + void Event_GetName(); void Event_SetName( const char* name ); void Event_FindTargets(); @@ -641,6 +649,16 @@ private: void Event_GuiNamedEvent( int guiNum, const char* event ); }; +ID_INLINE float idEntity::DistanceTo( idEntity* ent ) +{ + return DistanceTo( ent->GetPhysics()->GetOrigin() ); +} + +ID_INLINE float idEntity::DistanceTo( const idVec3& pos ) const +{ + return ( pos - GetPhysics()->GetOrigin() ).LengthFast(); +} + /* =============================================================================== diff --git a/neo/d3xp/Game_local.cpp b/neo/d3xp/Game_local.cpp index 6e528ec0..f2bd8edc 100644 --- a/neo/d3xp/Game_local.cpp +++ b/neo/d3xp/Game_local.cpp @@ -2590,6 +2590,22 @@ void idGameLocal::RunEntityThink( idEntity& ent, idUserCmdMgr& userCmdMgr ) idCVar g_recordTrace( "g_recordTrace", "0", CVAR_BOOL, "" ); +// jmarshall +/* +================ +idGameLocal::RunSharedThink +================ +*/ +void idGameLocal::RunSharedThink( void ) +{ + idEntity* ent; + for( ent = activeEntities.Next(); ent != NULL; ent = ent->activeNode.Next() ) + { + ent->SharedThink(); + } +} +// jmarshall end + /* ================ idGameLocal::RunFrame @@ -2766,7 +2782,9 @@ void idGameLocal::RunFrame( idUserCmdMgr& cmdMgr, gameReturn_t& ret ) } RunTimeGroup2( cmdMgr ); - +// jmarshall + RunSharedThink(); +// jmarshall end // Run catch-up for any client projectiles. // This is done after the main think so that all projectiles will be up-to-date // when snapshots are created. diff --git a/neo/d3xp/Game_local.h b/neo/d3xp/Game_local.h index 4cd346f3..9513732d 100644 --- a/neo/d3xp/Game_local.h +++ b/neo/d3xp/Game_local.h @@ -718,6 +718,8 @@ private: void ShowTargets(); void RunDebugInfo(); + void RunSharedThink(); + void InitScriptForMap(); void SetScriptFPS( const float com_engineHz ); void SpawnPlayer( int clientNum ); diff --git a/neo/d3xp/Light.cpp b/neo/d3xp/Light.cpp index ab77e04f..c9955997 100644 --- a/neo/d3xp/Light.cpp +++ b/neo/d3xp/Light.cpp @@ -166,6 +166,7 @@ void idGameEdit::ParseSpawnArgsToRenderLight( const idDict* args, renderLight_t* args->GetBool( "parallel", "0", renderLight->parallel ); args->GetString( "texture", "lights/squarelight1", &texture ); + // allow this to be NULL renderLight->shader = declManager->FindMaterial( texture, false ); } @@ -177,6 +178,12 @@ idLight::UpdateChangeableSpawnArgs */ void idLight::UpdateChangeableSpawnArgs( const idDict* source ) { +// jmarshall + lightStyleFrameTime = spawnArgs.GetInt( "ls_frametime", "100" ); + lightStyle = spawnArgs.GetInt( "style", -1 ); + + lightStyleState.Reset(); +// jmarshall end idEntity::UpdateChangeableSpawnArgs( source ); @@ -336,6 +343,9 @@ void idLight::Spawn() // do the parsing the same way dmap and the editor do gameEdit->ParseSpawnArgsToRenderLight( &spawnArgs, &renderLight ); +// jmarshall: Store the original light radius for the light style. + lightStyleBase = renderLight.lightRadius; +// jmarshall end // we need the origin and axis relative to the physics origin/axis localLightOrigin = ( renderLight.origin - GetPhysics()->GetOrigin() ) * GetPhysics()->GetAxis().Transpose(); @@ -397,6 +407,30 @@ void idLight::Spawn() spawnArgs.GetBool( "break", "0", breakOnTrigger ); spawnArgs.GetInt( "count", "1", count ); +// jmarshall + lightStyleFrameTime = spawnArgs.GetInt( "ls_frametime", "100" ); + lightStyle = spawnArgs.GetInt( "style", -1 ); + + int numStyles = spawnArgs.GetInt( "num_styles", "0" ); + if( numStyles > 0 ) + { + for( int i = 0; i < numStyles; i++ ) + { + idStr style = spawnArgs.GetString( va( "light_style%d", i ) ); + light_styles.Append( style ); + } + } + else + { + // RB: it's not defined in entityDef light so use predefined Quake 1 table + for( int i = 0; i < 12; i++ ) + { + idStr style = spawnArgs.GetString( va( "light_style%d", i ), predef_lightstyles[ i ] ); + light_styles.Append( style ); + } + } +// jmarshall end + triggercount = 0; fadeFrom.Set( 1, 1, 1, 1 ); @@ -886,6 +920,104 @@ void idLight::Think() Present(); } +/* +================ +idLight::SharedThink +================ +*/ +// jmarshall +void idLight::SharedThink() +{ + float lightval; + int stringlength; + float offset; + int offsetwhole; + int otime; + int lastch, nextch; + + if( lightStyle == -1 ) + { + return; + } + + if( lightStyle > light_styles.Num() ) + { + //gameLocal.Error( "Light style out of range\n" ); + return; + } + + idStr dl_stylestring = light_styles[lightStyle]; + + otime = gameLocal.time - lightStyleState.dl_time; + stringlength = dl_stylestring.Length(); + + // it's been a long time since you were updated, lets assume a reset + if( otime > 2 * lightStyleFrameTime ) + { + otime = 0; + lightStyleState.dl_frame = lightStyleState.dl_oldframe = 0; + lightStyleState.dl_backlerp = 0; + } + + lightStyleState.dl_time = gameLocal.time; + + offset = ( ( float )otime ) / lightStyleFrameTime; + offsetwhole = ( int )offset; + + lightStyleState.dl_backlerp += offset; + + + if( lightStyleState.dl_backlerp > 1 ) // we're moving on to the next frame + { + lightStyleState.dl_oldframe = lightStyleState.dl_oldframe + ( int )lightStyleState.dl_backlerp; + lightStyleState.dl_frame = lightStyleState.dl_oldframe + 1; + if( lightStyleState.dl_oldframe >= stringlength ) + { + lightStyleState.dl_oldframe = ( lightStyleState.dl_oldframe ) % stringlength; + //if (cent->dl_oldframe < 3 && cent->dl_sound) { // < 3 so if an alarm comes back into the pvs it will only start a sound if it's going to be closely synced with the light, otherwise wait till the next cycle + // engine->S_StartSound(NULL, cent->currentState.number, CHAN_AUTO, cgs.gameSounds[cent->dl_sound]); + //} + } + + if( lightStyleState.dl_frame >= stringlength ) + { + lightStyleState.dl_frame = ( lightStyleState.dl_frame ) % stringlength; + } + + lightStyleState.dl_backlerp = lightStyleState.dl_backlerp - ( int )lightStyleState.dl_backlerp; + } + + + lastch = dl_stylestring[lightStyleState.dl_oldframe] - 'a'; + nextch = dl_stylestring[lightStyleState.dl_frame] - 'a'; + + lightval = ( lastch * ( 1.0 - lightStyleState.dl_backlerp ) ) + ( nextch * lightStyleState.dl_backlerp ); + + // ydnar: dlight values go from 0-1.5ish +#if 0 + lightval = ( lightval * ( 1000.0f / 24.0f ) ) - 200.0f; // they want 'm' as the "middle" value as 300 + lightval = max( 0.0f, lightval ); + lightval = min( 1000.0f, lightval ); +#else + lightval *= 0.071429f; + lightval = Max( 0.0f, lightval ); + lightval = Min( 20.0f, lightval ); +#endif + + renderLight.lightRadius.x = lightval * lightStyleBase.x; + renderLight.lightRadius.y = lightval * lightStyleBase.y; + renderLight.lightRadius.z = lightval * lightStyleBase.z; + + + if( !common->IsClient() ) + { + BecomeActive( TH_THINK ); + } + + PresentLightDefChange(); +} +// jmarshall end + /* ================ idLight::ClientThink diff --git a/neo/d3xp/Light.h b/neo/d3xp/Light.h index 7192c7cf..7e307e1e 100644 --- a/neo/d3xp/Light.h +++ b/neo/d3xp/Light.h @@ -41,6 +41,37 @@ extern const idEventDef EV_Light_GetLightParm; extern const idEventDef EV_Light_SetLightParm; extern const idEventDef EV_Light_SetLightParms; +// jmarshall +struct rvmLightStyleState_t +{ + rvmLightStyleState_t(); + + int dl_frame; + float dl_framef; + int dl_oldframe; + int dl_time; + float dl_backlerp; + + void Reset(); +}; + +ID_INLINE rvmLightStyleState_t::rvmLightStyleState_t() +{ + Reset(); +} + +ID_INLINE void rvmLightStyleState_t::Reset() +{ + dl_frame = 0; + dl_framef = 0; + dl_oldframe = 0; + dl_time = 0; + dl_backlerp = 0; +} +// jmarshall end + + + class idLight : public idEntity { public: @@ -60,7 +91,9 @@ public: virtual void FreeLightDef(); virtual bool GetPhysicsToSoundTransform( idVec3& origin, idMat3& axis ); void Present(); - +// jmarshall + virtual void SharedThink(); +// jmarshall end void SaveState( idDict* args ); virtual void SetColor( float red, float green, float blue ); virtual void SetColor( const idVec4& color ); @@ -122,6 +155,11 @@ private: bool breakOnTrigger; int count; +// jmarshall + int lightStyle; + int lightStyleFrameTime; + idVec3 lightStyleBase; +// jmarshall end int triggercount; idEntity* lightParent; idVec4 fadeFrom; @@ -148,6 +186,11 @@ private: void Event_SetSoundHandles(); void Event_FadeOut( float time ); void Event_FadeIn( float time ); + +// jmarshall + idList light_styles; + rvmLightStyleState_t lightStyleState; +// jmarshall end }; #endif /* !__GAME_LIGHT_H__ */ diff --git a/neo/d3xp/gamesys/Class.h b/neo/d3xp/gamesys/Class.h index e08c52f3..dbef0773 100644 --- a/neo/d3xp/gamesys/Class.h +++ b/neo/d3xp/gamesys/Class.h @@ -246,6 +246,7 @@ public: const char* GetSuperclass() const; void FindUninitializedMemory(); + virtual void SharedThink() { } void Save( idSaveGame* savefile ) const {}; void Restore( idRestoreGame* savefile ) {}; diff --git a/neo/renderer/Material.h b/neo/renderer/Material.h index dceaa8fc..c4c0566b 100644 --- a/neo/renderer/Material.h +++ b/neo/renderer/Material.h @@ -367,6 +367,40 @@ typedef enum class idSoundEmitter; +// RB: predefined Quake 1 light styles +static char* predef_lightstyles[] = +{ + "m", + "mmnmmommommnonmmonqnmmo", + "abcdefghijklmnopqrstuvwxyzyxwvutsrqponmlkjihgfedcba", + "mmmmmaaaaammmmmaaaaaabcdefgabcdefg", + "mamamamamama", + "jklmnopqrstuvwxyzyxwvutsrqponmlkj", + "nmonqnmomnmomomno", + "mmmaaaabcdefgmmmmaaaammmaamm", + "mmmaaammmaaammmabcdefaaaammmmabcdefmmmaaaa", + "aaaaaaaazzzzzzzz", + "mmamammmmammamamaaamammma", + "abcdefghijklmnopqrrqponmlkjihgfedcba" +}; + +static char* predef_lightstylesinfo[] = +{ + "Normal", + "Flicker A", + "Slow Strong Pulse", + "Candle A", + "Fast Strobe", + "Gentle Pulse", + "Flicker B", + "Candle B", + "Candle C", + "Slow Strobe", + "Fluorescent Flicker", + "Slow Pulse (no black)" +}; +// RB end + class idMaterial : public idDecl { public: diff --git a/neo/tools/imgui/lighteditor/LightEditor.cpp b/neo/tools/imgui/lighteditor/LightEditor.cpp index d8526e46..96ef4cd0 100644 --- a/neo/tools/imgui/lighteditor/LightEditor.cpp +++ b/neo/tools/imgui/lighteditor/LightEditor.cpp @@ -4,6 +4,7 @@ Doom 3 GPL Source Code Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company. Copyright (C) 2015 Daniel Gibson +Copyright (C) 2020-2021 Robert Beckebans This file is part of the Doom 3 GPL Source Code ("Doom 3 Source Code"). @@ -68,9 +69,9 @@ void LightInfo::Defaults() lightRadius.Zero(); castShadows = true; castSpecular = true; - castDiffuse = true; hasCenter = false; isParallel = false; + lightStyle = -1; } @@ -95,7 +96,6 @@ void LightInfo::DefaultProjected() void LightInfo::FromDict( const idDict* e ) { - lightRadius.Zero(); lightTarget.Zero(); lightRight.Zero(); @@ -106,7 +106,6 @@ void LightInfo::FromDict( const idDict* e ) castShadows = !e->GetBool( "noshadows" ); castSpecular = !e->GetBool( "nospecular" ); - castDiffuse = !e->GetBool( "nodiffuse" ); fallOff = e->GetFloat( "falloff" ); strTexture = e->GetString( "texture" ); @@ -176,6 +175,9 @@ void LightInfo::FromDict( const idDict* e ) hasCenter = true; } } + + // RB: Quake 1 light styles + lightStyle = e->GetInt( "style", -1 ); } // the returned idDict is supposed to be used by idGameEdit::EntityChangeSpawnArgs() @@ -190,7 +192,6 @@ void LightInfo::ToDict( idDict* e ) e->Set( "noshadows", ( !castShadows ) ? "1" : "0" ); e->Set( "nospecular", ( !castSpecular ) ? "1" : "0" ); - e->Set( "nodiffuse", ( !castDiffuse ) ? "1" : "0" ); e->SetFloat( "falloff", fallOff ); @@ -265,6 +266,16 @@ void LightInfo::ToDict( idDict* e ) e->Set( "light_center", DELETE_VAL ); e->Set( "parallel", DELETE_VAL ); } + + // RB: Quake 1 light styles + if( lightStyle != -1 ) + { + e->SetInt( "style", lightStyle ); + } + else + { + e->Set( "style", DELETE_VAL ); + } } LightInfo::LightInfo() @@ -303,10 +314,14 @@ void LightEditor::Init( const idDict* dict, idEntity* light ) LoadLightTextures(); } + if( styleNames.Num() == 0 ) + { + LoadLightStyles(); + } + if( dict ) { original.FromDict( dict ); - //current = original; cur.FromDict( dict ); const char* name = dict->GetString( "name", NULL ); @@ -319,6 +334,7 @@ void LightEditor::Init( const idDict* dict, idEntity* light ) { idassert( 0 && "LightEditor::Init(): Given entity has no 'name' property?!" ); entityName = ""; // TODO: generate name or handle gracefully or something? + title.Format( "Light Editor: light" ); } currentTextureIndex = 0; @@ -336,6 +352,12 @@ void LightEditor::Init( const idDict* dict, idEntity* light ) } } } + + // RB: light styles + if( original.lightStyle >= 0 ) + { + currentStyleIndex = original.lightStyle + 1; + } } this->lightEntity = light; } @@ -348,6 +370,8 @@ void LightEditor::Reset() lightEntity = NULL; currentTextureIndex = 0; currentTexture = NULL; + + currentStyleIndex = 0; } namespace @@ -365,37 +389,45 @@ public: void LightEditor::LoadLightTextures() { textureNames.Clear(); + int count = declManager->GetNumDecls( DECL_MATERIAL ); - const idMaterial* mat; + for( int i = 0; i < count; i++ ) { - mat = declManager->MaterialByIndex( i, false ); + const idMaterial* mat = declManager->MaterialByIndex( i, false ); + idStr str = mat->GetName(); str.ToLower(); // FIXME: why? (this is from old doom3 code) + if( str.Icmpn( "lights/", strlen( "lights/" ) ) == 0 || str.Icmpn( "fogs/", strlen( "fogs/" ) ) == 0 ) { textureNames.Append( str ); } } + textureNames.SortWithTemplate( idSort_textureNames() ); } // static -bool LightEditor::TextureItemsGetter( void* data, int idx, const char** out_text ) +bool LightEditor::TextureItemsGetter( void* data, int idx, const char** outText ) { LightEditor* self = static_cast( data ); if( idx == 0 ) { - *out_text = ""; + *outText = ""; return true; } - --idx; // as index 0 has special purpose, the "real" index is one less + + // as index 0 has special purpose, the "real" index is one less + --idx; + if( idx < 0 || idx >= self->textureNames.Num() ) { - *out_text = ""; + *outText = ""; return false; } - *out_text = self->textureNames[idx].c_str(); + + *outText = self->textureNames[idx].c_str(); return true; } @@ -403,6 +435,7 @@ bool LightEditor::TextureItemsGetter( void* data, int idx, const char** out_text void LightEditor::LoadCurrentTexture() { currentTexture = NULL; + if( currentTextureIndex > 0 && cur.strTexture.Length() > 0 ) { const idMaterial* mat = declManager->FindMaterial( cur.strTexture, false ); @@ -413,12 +446,67 @@ void LightEditor::LoadCurrentTexture() } } +void LightEditor::LoadLightStyles() +{ + styleNames.Clear(); + + const idDeclEntityDef* decl = static_cast( declManager->FindType( DECL_ENTITYDEF, "light", false ) ); + if( decl == NULL ) + { + return; + } + + int numStyles = decl->dict.GetInt( "num_styles", "0" ); + if( numStyles > 0 ) + { + for( int i = 0; i < numStyles; i++ ) + { + idStr style = decl->dict.GetString( va( "light_style%d", i ) ); + styleNames.Append( style ); + } + } + else + { + // RB: it's not defined in entityDef light so use predefined Quake 1 table + for( int i = 0; i < 12; i++ ) + { + idStr style( predef_lightstylesinfo[ i ] ); + styleNames.Append( style ); + } + } +} + +// static +bool LightEditor::StyleItemsGetter( void* data, int idx, const char** outText ) +{ + LightEditor* self = static_cast( data ); + if( idx == 0 ) + { + *outText = ""; + return true; + } + + // as index 0 has special purpose, the "real" index is one less + --idx; + + if( idx < 0 || idx >= self->styleNames.Num() ) + { + *outText = ""; + return false; + } + + *outText = self->styleNames[idx].c_str(); + + return true; +} + void LightEditor::TempApplyChanges() { if( lightEntity != NULL ) { idDict d; cur.ToDict( &d ); + gameEdit->EntityChangeSpawnArgs( lightEntity, &d ); gameEdit->EntityUpdateChangeableSpawnArgs( lightEntity, NULL ); } @@ -448,6 +536,7 @@ void LightEditor::SaveChanges() gameEdit->MapAddEntity( &d ); #endif // 0 } + gameEdit->MapSave(); } @@ -457,6 +546,7 @@ void LightEditor::CancelChanges() { idDict d; original.ToDict( &d ); + gameEdit->EntityChangeSpawnArgs( lightEntity, &d ); gameEdit->EntityUpdateChangeableSpawnArgs( lightEntity, NULL ); } @@ -476,8 +566,7 @@ void LightEditor::DrawWindow() bool changes = false; changes |= ImGui::Checkbox( "Cast Shadows", &cur.castShadows ); - ImGui::SameLine(); - changes |= ImGui::Checkbox( "Cast Diffuse", &cur.castDiffuse ); + //ImGui::SameLine(); changes |= ImGui::Checkbox( "Cast Specular", &cur.castSpecular ); ImGui::Spacing(); @@ -573,9 +662,20 @@ void LightEditor::DrawWindow() ImGui::Unindent(); + if( ImGui::Combo( "Style", ¤tStyleIndex, StyleItemsGetter, this, styleNames.Num() + 1 ) ) + { + changes = true; + + // -1 because 0 is "" + cur.lightStyle = ( currentStyleIndex > 0 ) ? currentStyleIndex - 1 : -1; + } + + ImGui::Spacing(); + if( ImGui::Combo( "Texture", ¤tTextureIndex, TextureItemsGetter, this, textureNames.Num() + 1 ) ) { changes = true; + // -1 because 0 is "" cur.strTexture = ( currentTextureIndex > 0 ) ? textureNames[currentTextureIndex - 1] : ""; LoadCurrentTexture(); @@ -592,6 +692,8 @@ void LightEditor::DrawWindow() // then only the changed attribute (e.g. color) would be set to all lights, // but they'd keep their other individual properties (eg radius) + ImGui::Spacing(); + if( ImGui::Button( "Save to .map" ) ) { SaveChanges(); diff --git a/neo/tools/imgui/lighteditor/LightEditor.h b/neo/tools/imgui/lighteditor/LightEditor.h index 1627b63c..edf57b48 100644 --- a/neo/tools/imgui/lighteditor/LightEditor.h +++ b/neo/tools/imgui/lighteditor/LightEditor.h @@ -70,9 +70,9 @@ public: idVec3 lightRadius; bool castShadows; bool castSpecular; - bool castDiffuse; bool hasCenter; bool isParallel; + int lightStyle; LightInfo(); @@ -85,6 +85,7 @@ public: class LightEditor { +private: idStr title; idStr entityName; LightInfo original; @@ -96,6 +97,13 @@ class LightEditor int currentTextureIndex; idImage* currentTexture; + // RB: light style support + idList styleNames; + int currentStyleIndex; + + void LoadLightStyles(); + static bool StyleItemsGetter( void* data, int idx, const char** out_text ); + void Init( const idDict* dict, idEntity* light ); void Reset();