Added BC7 compression.
@@ -0,0 +1,198 @@
|
||||
/***********************************************************************
|
||||
|
||||
weapon_soulcube.def
|
||||
|
||||
***********************************************************************/
|
||||
|
||||
export fred {
|
||||
// view model soulCube
|
||||
options -prefix SOUL_ -keep guilight toplftnub toprtnub midnub botlftnub botrtnub -rename origin blah
|
||||
mesh models/weapons/soulcube/cycles/idle.mb -dest models/md5/weapons/soulcube_view/soulcube
|
||||
anim models/weapons/soulcube/cycles/raise.mb -dest models/md5/weapons/soulcube_view/raise
|
||||
anim models/weapons/soulcube/cycles/idle.mb -xyzprecision .01 -dest models/md5/weapons/soulcube_view/idle
|
||||
anim models/weapons/soulcube/cycles/idle2.mb -xyzprecision .01 -dest models/md5/weapons/soulcube_view/idle2
|
||||
anim models/weapons/soulcube/cycles/fire2.mb -dest models/md5/weapons/soulcube_view/fire
|
||||
anim models/weapons/soulcube/cycles/dryfire.mb -dest models/md5/weapons/soulcube_view/dryfire
|
||||
anim models/weapons/soulcube/cycles/lower.mb -dest models/md5/weapons/soulcube_view/lower
|
||||
}
|
||||
|
||||
export fred {
|
||||
// world model soulCube
|
||||
|
||||
options -ignorescale -prefix PLAYER_SOUL_ -rename ATTACHER origin -clearorigin -keepmesh soulfx1 -keepmesh soulfx2 -keepmesh soulfx3 -keepmesh soulfx4 -keepmesh soulfx5 -keepmesh soulknife -keepmesh soulcube
|
||||
|
||||
mesh models/characters/male_npc/cycles/tweakedplayermoves/soulcube_idle.mb -dest models/md5/weapons/soulcube_world/worldsoulcube
|
||||
anim models/characters/male_npc/cycles/tweakedplayermoves/soulcube_idle.mb -dest models/md5/weapons/soulcube_world/soulcube_idle
|
||||
|
||||
}
|
||||
|
||||
entityDef weapon_soulcube {
|
||||
"editor_color" ".3 .3 1"
|
||||
"editor_mins" "-16 -16 0"
|
||||
"editor_maxs" "16 16 32"
|
||||
"editor_usage" "Soul Cube"
|
||||
"editor_rotatable" "1"
|
||||
"weapon_class" "rvmWeaponSoulCube"
|
||||
|
||||
"spawnclass" "idItem"
|
||||
"size" "32 32 32"
|
||||
"model" "models/weapons/soulcube/w_soulcube.lwo"
|
||||
"model_world" "worldmodel_soulcube"
|
||||
"model_view" "viewmodel_soulcube"
|
||||
"joint_attach" "SOUL_ATTACHER"
|
||||
|
||||
"inv_name" "Soulcube"
|
||||
"inv_weapon" "weapon_soulcube"
|
||||
"inv_item" "5"
|
||||
"icon" "guis/assets/hud/wpn_11"
|
||||
|
||||
"mtr_guiLightShader" "lights/viewWeaponGuiLight"
|
||||
"weapon_scriptobject" "weapon_soulcube"
|
||||
"def_projectile" "projectile_soulblast"
|
||||
"ammoType" "ammo_souls"
|
||||
"ammoRequired" "5"
|
||||
"clipSize" "0"
|
||||
"mtr_flashShader" "muzzleflash"
|
||||
"flashColor" "1 0.8 0.4"
|
||||
"flashRadius" "120"
|
||||
"silent_fire" "0"
|
||||
|
||||
"hide_time" "0.3" // time to hide weapon when talking to npcs or looking at guis
|
||||
"hide_distance" "-15" // distance to offset weapon when talking to npcs or looking at guis
|
||||
|
||||
"spread" "0"
|
||||
|
||||
"weaponAngleOffsetAverages" "10"
|
||||
"weaponAngleOffsetScale" ".5"
|
||||
"weaponAngleOffsetMax" "400"
|
||||
"weaponOffsetTime" "400"
|
||||
"weaponOffsetScale" "0.005"
|
||||
|
||||
"snd_acquire" "sound_weapon_acquire"
|
||||
"snd_respawn" "sound_weapon_respawn"
|
||||
"snd_raise" "soulcube_raise"
|
||||
"snd_lower" "soulcube_putaway"
|
||||
"snd_hum" "soulcube_idle"
|
||||
"snd_fire_start" "soulcube_fire_start"
|
||||
}
|
||||
|
||||
model viewmodel_soulcube {
|
||||
mesh models/md5/weapons/soulcube_view/soulcube.md5mesh
|
||||
offset ( 12 0 -63 )
|
||||
skin skins/models/weapons/soulcubeidle
|
||||
|
||||
anim raise models/md5/weapons/soulcube_view/raise.md5anim {
|
||||
frame 1 skin skins/models/weapons/soulcubeidle
|
||||
frame 1 sound_weapon soulcube_raise
|
||||
}
|
||||
anim idle models/md5/weapons/soulcube_view/idle2.md5anim
|
||||
anim spin models/md5/weapons/soulcube_view/idle.md5anim {
|
||||
frame 95 sound_weapon soulcube_idlespin
|
||||
}
|
||||
anim fire models/md5/weapons/soulcube_view/fire.md5anim {
|
||||
frame 1 skin skins/models/weapons/soulcubeidle
|
||||
frame 1 sound_weapon soulcube_fire_start
|
||||
frame 23 skin skins/models/weapons/soulcubefire.skin
|
||||
frame 23 object_call Launch
|
||||
}
|
||||
anim putaway models/md5/weapons/soulcube_view/lower.md5anim
|
||||
}
|
||||
|
||||
model worldmodel_soulcube {
|
||||
mesh models/md5/weapons/soulcube_world/worldsoulcube.md5mesh
|
||||
|
||||
anim raise models/md5/weapons/soulcube_world/soulcube_idle.md5anim
|
||||
anim idle models/md5/weapons/soulcube_world/soulcube_idle.md5anim
|
||||
anim aim models/md5/weapons/soulcube_world/soulcube_idle.md5anim
|
||||
anim fire models/md5/weapons/soulcube_world/soulcube_idle.md5anim
|
||||
anim reload models/md5/weapons/soulcube_world/soulcube_idle.md5anim
|
||||
anim putaway models/md5/weapons/soulcube_world/soulcube_idle.md5anim
|
||||
}
|
||||
|
||||
|
||||
entityDef damage_soulSplash {
|
||||
"damage" "40"
|
||||
"radius" "100"
|
||||
"knockback" "80"
|
||||
"push" "10000"
|
||||
"gib" "1"
|
||||
"smoke_wound_flesh" "bloodwound.prt"
|
||||
}
|
||||
|
||||
entityDef damage_soulblast {
|
||||
"damage" "1000"
|
||||
"gib" "1"
|
||||
}
|
||||
|
||||
entityDef projectile_soulblast {
|
||||
"spawnclass" "idSoulCubeMissile"
|
||||
"mins" "-4 -4 -4"
|
||||
"maxs" "4 4 4"
|
||||
"cone" "3"
|
||||
"model" "models/weapons/soulcube/w_soulcube3.lwo"
|
||||
|
||||
"def_damage" "damage_soulblast"
|
||||
//"def_splash_damage" "damage_soulSplash"
|
||||
|
||||
"launchFromBarrel" "1"
|
||||
"health" "0" // amount of damage projectile can take if damaged (0 means it can't be destroyed)
|
||||
"velocity" "55 0 0" // how fast the projectile leaves the gun (or distance if fuse is 0)
|
||||
"angular_velocity" "0 0 0" // how the projectile is rotating when it leaves the gun
|
||||
"thrust" "0" // the rate of acceleration (always in the direction of the projectiles model)
|
||||
"thrust_start" "0" // when to start accelerating
|
||||
"thrust_end" "0" // when to stop accelerating
|
||||
"linear_friction" "0" // "air" friction
|
||||
"angular_friction" "0"
|
||||
"contact_friction" "0"
|
||||
"bounce" "0" // how much speed a projectile retains when it bounces off of objects (coefficient of restitution). 0 means no bounce.
|
||||
"mass" "5"
|
||||
"gravity" "0" // how much gravity affects the trajectory. gravity direction is same as the entity that fired it.
|
||||
"fuse" "30" // how long before the projectile is removed or self-detonates. Use 0 for beam weapons (velocity == distance).
|
||||
"random" "0 0 0"
|
||||
"clamp_dist" "64"
|
||||
"turn_max" "1000"
|
||||
"startingVelocity" "250"
|
||||
"endingVelocity" "700"
|
||||
"accelTime" "3"
|
||||
|
||||
"special_damage" "1" // for bosses that only show pain for certain types of projectiles
|
||||
|
||||
"launchDist" "30"
|
||||
"launchOffset" "0 0 -2"
|
||||
|
||||
|
||||
"detonate_on_fuse" "0" // whether projectile should detonate when it's fuse runs out
|
||||
"detonate_on_death" "0" // whether projectile should detonate when it's "killed" (health runs out)
|
||||
"detonate_on_world" "0" // whether projectile should detonate when it hits an obstacle
|
||||
"detonate_on_actor" "1" // whether projectile should detonate when it hits a character in the game
|
||||
|
||||
"model_detonate" "grenadeExplosion.prt" // parametric particles -- temp
|
||||
"model_dissolve" "soulcubedissolve.prt"
|
||||
|
||||
"impact_damage_effect" "0" // whether projectile causes blood splats from characters that bleed
|
||||
"impact_gib" "1" // whether projectile can gib a character or not (for example: bullets don't gib, but rockets do)
|
||||
|
||||
"mtr_detonate" "" // decal to leave on world when detonates
|
||||
|
||||
"smoke_kill" "soulcubedamage3.prt"
|
||||
|
||||
"smoke_fly" "soulcubetrail.prt"
|
||||
"smoke_detonate" "" // particle effect when detonates
|
||||
"smoke_bounce" "" // particle effect when bounces
|
||||
"smoke_fuse" "" // particle effect when removed from fuse expiring
|
||||
|
||||
"mtr_light_shader" ""
|
||||
"light_color" "0 0 0.8"
|
||||
"light_radius" "160"
|
||||
"light_offset" "-8 0 0"
|
||||
|
||||
"snd_present" "soulcube_idlespin"
|
||||
"snd_fly" "soulcube_fire_loop" // sound effect while in the air
|
||||
"snd_explode" "soulcube_shred"
|
||||
"snd_return" "soulcube_fire_end"
|
||||
|
||||
"mtr_explode_light_shader" "muzzleflash"
|
||||
"explode_light_color" "0 0 1"
|
||||
"explode_light_radius" "160"
|
||||
"explode_light_fadetime" "0.25"
|
||||
}
|
||||
|
Before Width: | Height: | Size: 1.0 MiB After Width: | Height: | Size: 1.0 MiB |
|
Before Width: | Height: | Size: 1.0 MiB After Width: | Height: | Size: 1.0 MiB |
|
Before Width: | Height: | Size: 1.0 MiB After Width: | Height: | Size: 1.0 MiB |
|
Before Width: | Height: | Size: 1.0 MiB After Width: | Height: | Size: 1.0 MiB |
|
Before Width: | Height: | Size: 1.0 MiB After Width: | Height: | Size: 1.0 MiB |
|
Before Width: | Height: | Size: 1.0 MiB After Width: | Height: | Size: 1.0 MiB |
|
Before Width: | Height: | Size: 1.0 MiB After Width: | Height: | Size: 1.0 MiB |
|
Before Width: | Height: | Size: 1.0 MiB After Width: | Height: | Size: 1.0 MiB |
|
Before Width: | Height: | Size: 1.0 MiB After Width: | Height: | Size: 1.0 MiB |
|
Before Width: | Height: | Size: 1.0 MiB After Width: | Height: | Size: 1.0 MiB |
|
Before Width: | Height: | Size: 1.0 MiB After Width: | Height: | Size: 1.0 MiB |
|
Before Width: | Height: | Size: 1.0 MiB After Width: | Height: | Size: 1.0 MiB |
|
Before Width: | Height: | Size: 1.0 MiB After Width: | Height: | Size: 1.0 MiB |
@@ -477,6 +477,7 @@ typeinfo.exe</Command>
|
||||
<ClCompile Include="game\weapons\Weapon_plasmagun.cpp" />
|
||||
<ClCompile Include="game\weapons\Weapon_rocketlauncher.cpp" />
|
||||
<ClCompile Include="game\weapons\Weapon_shotgun.cpp" />
|
||||
<ClCompile Include="game\weapons\Weapon_soulcube.cpp" />
|
||||
<ClCompile Include="game\WorldSpawn.cpp" />
|
||||
<ClCompile Include="..\engine\idlib\precompiled.cpp">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader>
|
||||
@@ -614,6 +615,7 @@ typeinfo.exe</Command>
|
||||
<ClInclude Include="game\weapons\Weapon_plasmagun.h" />
|
||||
<ClInclude Include="game\weapons\Weapon_rocketlauncher.h" />
|
||||
<ClInclude Include="game\weapons\Weapon_shotgun.h" />
|
||||
<ClInclude Include="game\weapons\Weapon_soulcube.h" />
|
||||
<ClInclude Include="game\WorldSpawn.h" />
|
||||
<ClInclude Include="..\engine\idlib\precompiled.h" />
|
||||
</ItemGroup>
|
||||
@@ -631,4 +633,4 @@ typeinfo.exe</Command>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
|
||||
@@ -216,6 +216,9 @@
|
||||
<ClCompile Include="game\weapons\Weapon_shotgun.cpp">
|
||||
<Filter>Weapons</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="game\weapons\Weapon_soulcube.cpp">
|
||||
<Filter>Weapons</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="game\gamesys\State.cpp">
|
||||
<Filter>GameSys</Filter>
|
||||
</ClCompile>
|
||||
@@ -558,6 +561,9 @@
|
||||
<ClInclude Include="game\weapons\Weapon_shotgun.h">
|
||||
<Filter>Weapons</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="game\weapons\Weapon_soulcube.h">
|
||||
<Filter>Weapons</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="game\gamesys\State.h">
|
||||
<Filter>GameSys</Filter>
|
||||
</ClInclude>
|
||||
@@ -706,4 +712,4 @@
|
||||
<ItemGroup>
|
||||
<None Include="game\Game.def" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
|
||||
@@ -800,6 +800,7 @@ const int CINEMATIC_SKIP_DELAY = SEC2MS( 2.0f );
|
||||
#include "weapons/Weapon_chaingun.h"
|
||||
#include "weapons/Weapon_rocketlauncher.h"
|
||||
#include "weapons/Weapon_bfg.h"
|
||||
#include "weapons/Weapon_soulcube.h"
|
||||
#include "weapons/Weapon_handgrenade.h"
|
||||
#include "weapons/Weapon_chainsaw.h"
|
||||
#include "weapons/Weapon_grabber.h"
|
||||
|
||||
@@ -3779,8 +3779,7 @@ void idPlayer::Weapon_Combat( void ) {
|
||||
if ( weapon.GetEntity()->IsReloading() ) {
|
||||
if ( !AI_RELOAD ) {
|
||||
AI_RELOAD = true;
|
||||
SetState( "ReloadWeapon" );
|
||||
UpdateScript();
|
||||
SetAnimState( ANIMCHANNEL_TORSO, "Torso_Reload", 4 );
|
||||
}
|
||||
} else {
|
||||
AI_RELOAD = false;
|
||||
|
||||
@@ -1763,6 +1763,11 @@ NOTE: this is only for impulse-triggered reload, auto reload is scripted
|
||||
*/
|
||||
void idWeapon::Reload()
|
||||
{
|
||||
if (!CanReload())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
OutOfAmmo = false;
|
||||
|
||||
// Reload and go back to idle.
|
||||
@@ -1770,6 +1775,34 @@ void idWeapon::Reload()
|
||||
currentWeaponObject->AppendState("Idle");
|
||||
}
|
||||
|
||||
/*
|
||||
================
|
||||
idWeapon::CanReload
|
||||
================
|
||||
*/
|
||||
bool idWeapon::CanReload() const
|
||||
{
|
||||
if (!currentWeaponObject || currentWeaponObject->IsReloading())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (clipSize <= 0 || AmmoInClip() >= clipSize)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return owner && (owner->inventory.HasAmmo(ammoType, ammoRequired) != 0);
|
||||
}
|
||||
|
||||
/*
|
||||
================
|
||||
idWeapon::AutoReload
|
||||
================
|
||||
*/
|
||||
bool idWeapon::AutoReload() const
|
||||
{
|
||||
return owner && !gameLocal.isClient && gameLocal.userInfo[owner->entityNumber].GetBool("ui_autoReload");
|
||||
}
|
||||
|
||||
/*
|
||||
================
|
||||
idWeapon::LowerWeapon
|
||||
@@ -1938,7 +1971,7 @@ idWeapon::isReady
|
||||
*/
|
||||
bool idWeapon::IsReady() const
|
||||
{
|
||||
return !hide && !IsHidden() && !currentWeaponObject->IsRunning() && !IsHolstered() && !currentWeaponObject->IsStateRunning("Lower");
|
||||
return !hide && !IsHidden() && ( !currentWeaponObject->IsRunning() || currentWeaponObject->IsStateRunning("Idle") ) && !IsHolstered() && !currentWeaponObject->IsStateRunning("Lower");
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -2709,6 +2742,12 @@ void idWeapon::ExitCinematic()
|
||||
{
|
||||
disabled = false;
|
||||
|
||||
if (currentWeaponObject && currentWeaponObject->HasNativeFunction("ExitCinematic"))
|
||||
{
|
||||
currentWeaponObject->Invoke("ExitCinematic", NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
RaiseWeapon();
|
||||
}
|
||||
|
||||
@@ -3133,8 +3172,6 @@ idWeapon::Event_WeaponReady
|
||||
void idWeapon::Event_WeaponReady()
|
||||
{
|
||||
//status = WP_READY;
|
||||
currentWeaponObject->SetState("Rising");
|
||||
currentWeaponObject->AppendState("Idle");
|
||||
// idLib::PrintfIf( g_debugWeapon.GetBool(), "Weapon Status WP_READY \n" );
|
||||
//if ( isLinked ) {
|
||||
// WEAPON_RAISEWEAPON = false;
|
||||
|
||||
@@ -221,6 +221,8 @@ public:
|
||||
int LowAmmo() const;
|
||||
int AmmoRequired() const;
|
||||
int AmmoCount() const;
|
||||
bool CanReload() const;
|
||||
bool AutoReload() const;
|
||||
int GetGrabberState() const;
|
||||
|
||||
// Flashlight
|
||||
@@ -502,4 +504,4 @@ ID_INLINE idPlayer* idWeapon::GetOwner()
|
||||
return owner;
|
||||
}
|
||||
|
||||
#endif /* !__GAME_WEAPON_H__ */
|
||||
#endif /* !__GAME_WEAPON_H__ */
|
||||
|
||||
@@ -3726,6 +3726,12 @@ intptr_t idWeapon::Invoke(const char *functionName, void *param1) {
|
||||
if(functionNameHash == 112768) { // AmmoCount
|
||||
return (intptr_t)AmmoCount();
|
||||
};
|
||||
if(functionNameHash == 107539) { // CanReload
|
||||
return (intptr_t)CanReload();
|
||||
};
|
||||
if(functionNameHash == 124568) { // AutoReload
|
||||
return (intptr_t)AutoReload();
|
||||
};
|
||||
if(functionNameHash == 188573) { // GetGrabberState
|
||||
return (intptr_t)GetGrabberState();
|
||||
};
|
||||
@@ -4012,6 +4018,12 @@ bool idWeapon::HasNativeFunction(const char *functionName) {
|
||||
if(functionNameHash == 112768) { // AmmoCount
|
||||
return true;
|
||||
};
|
||||
if(functionNameHash == 107539) { // CanReload
|
||||
return true;
|
||||
};
|
||||
if(functionNameHash == 124568) { // AutoReload
|
||||
return true;
|
||||
};
|
||||
if(functionNameHash == 188573) { // GetGrabberState
|
||||
return true;
|
||||
};
|
||||
@@ -9239,6 +9251,9 @@ intptr_t rvmMonsterBase::Invoke(const char *functionName, void *param1) {
|
||||
follow_alternate_path3();
|
||||
return 0;
|
||||
};
|
||||
if(functionNameHash == 210361) { // SpawnFromSpawner
|
||||
return (intptr_t)SpawnFromSpawner();
|
||||
};
|
||||
return __super::Invoke(functionName, param1);
|
||||
|
||||
};
|
||||
@@ -9392,6 +9407,9 @@ bool rvmMonsterBase::HasNativeFunction(const char *functionName) {
|
||||
if(functionNameHash == 295747) { // follow_alternate_path3
|
||||
return true;
|
||||
};
|
||||
if(functionNameHash == 210361) { // SpawnFromSpawner
|
||||
return true;
|
||||
};
|
||||
return __super::HasNativeFunction(functionName);
|
||||
|
||||
};
|
||||
@@ -12541,6 +12559,10 @@ bool rvmMonsterDemonTrite::HasNativeFunction(const char *functionName) {
|
||||
|
||||
intptr_t rvmMonsterDemonWraith::Invoke(const char *functionName, void *param1) {
|
||||
int functionNameHash = idStr::Hash(functionName);
|
||||
if(functionNameHash == 91190) { // Destroy
|
||||
Destroy();
|
||||
return 0;
|
||||
};
|
||||
if(functionNameHash == 124942) { // Torso_Idle
|
||||
Torso_Idle((stateParms_t *)param1);
|
||||
return 0;
|
||||
@@ -12619,6 +12641,9 @@ intptr_t rvmMonsterDemonWraith::Invoke(const char *functionName, void *param1) {
|
||||
|
||||
bool rvmMonsterDemonWraith::HasNativeFunction(const char *functionName) {
|
||||
int functionNameHash = idStr::Hash(functionName);
|
||||
if(functionNameHash == 91190) { // Destroy
|
||||
return true;
|
||||
};
|
||||
if(functionNameHash == 124942) { // Torso_Idle
|
||||
return true;
|
||||
};
|
||||
@@ -13407,6 +13432,9 @@ intptr_t rvmMonsterBossVagary::Invoke(const char *functionName, void *param1) {
|
||||
check_attacks();
|
||||
return 0;
|
||||
};
|
||||
if(functionNameHash == 248112) { // ChooseObjectToThrow
|
||||
return (intptr_t)ChooseObjectToThrow();
|
||||
};
|
||||
return __super::Invoke(functionName, param1);
|
||||
|
||||
};
|
||||
@@ -13464,6 +13492,9 @@ bool rvmMonsterBossVagary::HasNativeFunction(const char *functionName) {
|
||||
if(functionNameHash == 169136) { // check_attacks
|
||||
return true;
|
||||
};
|
||||
if(functionNameHash == 248112) { // ChooseObjectToThrow
|
||||
return true;
|
||||
};
|
||||
return __super::HasNativeFunction(functionName);
|
||||
|
||||
};
|
||||
@@ -14823,6 +14854,69 @@ bool rvmWeaponBFG::HasNativeFunction(const char *functionName) {
|
||||
|
||||
};
|
||||
|
||||
intptr_t rvmWeaponSoulCube::Invoke(const char *functionName, void *param1) {
|
||||
int functionNameHash = idStr::Hash(functionName);
|
||||
if(functionNameHash == 60556) { // Raise
|
||||
return (intptr_t)Raise((stateParms_t *)param1);
|
||||
};
|
||||
if(functionNameHash == 63107) { // Lower
|
||||
return (intptr_t)Lower((stateParms_t *)param1);
|
||||
};
|
||||
if(functionNameHash == 46077) { // Idle
|
||||
return (intptr_t)Idle((stateParms_t *)param1);
|
||||
};
|
||||
if(functionNameHash == 47046) { // Fire
|
||||
return (intptr_t)Fire((stateParms_t *)param1);
|
||||
};
|
||||
if(functionNameHash == 72819) { // Reload
|
||||
return (intptr_t)Reload((stateParms_t *)param1);
|
||||
};
|
||||
if(functionNameHash == 219305) { // UpdateShaderParms
|
||||
UpdateShaderParms();
|
||||
return 0;
|
||||
};
|
||||
if(functionNameHash == 73334) { // Launch
|
||||
Launch();
|
||||
return 0;
|
||||
};
|
||||
if(functionNameHash == 165047) { // ExitCinematic
|
||||
ExitCinematic();
|
||||
return 0;
|
||||
};
|
||||
return __super::Invoke(functionName, param1);
|
||||
|
||||
};
|
||||
|
||||
bool rvmWeaponSoulCube::HasNativeFunction(const char *functionName) {
|
||||
int functionNameHash = idStr::Hash(functionName);
|
||||
if(functionNameHash == 60556) { // Raise
|
||||
return true;
|
||||
};
|
||||
if(functionNameHash == 63107) { // Lower
|
||||
return true;
|
||||
};
|
||||
if(functionNameHash == 46077) { // Idle
|
||||
return true;
|
||||
};
|
||||
if(functionNameHash == 47046) { // Fire
|
||||
return true;
|
||||
};
|
||||
if(functionNameHash == 72819) { // Reload
|
||||
return true;
|
||||
};
|
||||
if(functionNameHash == 219305) { // UpdateShaderParms
|
||||
return true;
|
||||
};
|
||||
if(functionNameHash == 73334) { // Launch
|
||||
return true;
|
||||
};
|
||||
if(functionNameHash == 165047) { // ExitCinematic
|
||||
return true;
|
||||
};
|
||||
return __super::HasNativeFunction(functionName);
|
||||
|
||||
};
|
||||
|
||||
intptr_t rvmWeaponHandgrenade::Invoke(const char *functionName, void *param1) {
|
||||
int functionNameHash = idStr::Hash(functionName);
|
||||
if(functionNameHash == 110318) { // OwnerDied
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
1200 constants
|
||||
135 enums
|
||||
624 classes/structs/unions
|
||||
625 classes/structs/unions
|
||||
38 templates
|
||||
7 max inheritance level for 'idAI_Vagary'
|
||||
|
||||
@@ -8233,6 +8233,14 @@ classVariableInfo_t rvmMonsterBase::typeInfo[] = {
|
||||
{ "bool", "ignore_lostcombat", (intptr_t)(&((rvmMonsterBase *)0)->ignore_lostcombat), sizeof( ((rvmMonsterBase *)0)->ignore_lostcombat ) },
|
||||
{ "bool", "blocked", (intptr_t)(&((rvmMonsterBase *)0)->blocked), sizeof( ((rvmMonsterBase *)0)->blocked ) },
|
||||
{ "bool", "ignore_sight", (intptr_t)(&((rvmMonsterBase *)0)->ignore_sight), sizeof( ((rvmMonsterBase *)0)->ignore_sight ) },
|
||||
{ "bool", "monsterBeginStartActive", (intptr_t)(&((rvmMonsterBase *)0)->monsterBeginStartActive), sizeof( ((rvmMonsterBase *)0)->monsterBeginStartActive ) },
|
||||
{ "int", "monsterBeginTeleportType", (intptr_t)(&((rvmMonsterBase *)0)->monsterBeginTeleportType), sizeof( ((rvmMonsterBase *)0)->monsterBeginTeleportType ) },
|
||||
{ "int", "monsterBeginMoveType", (intptr_t)(&((rvmMonsterBase *)0)->monsterBeginMoveType), sizeof( ((rvmMonsterBase *)0)->monsterBeginMoveType ) },
|
||||
{ "int", "spawnerTriggerCount", (intptr_t)(&((rvmMonsterBase *)0)->spawnerTriggerCount), sizeof( ((rvmMonsterBase *)0)->spawnerTriggerCount ) },
|
||||
{ "idStr", "monsterBeginActionName", (intptr_t)(&((rvmMonsterBase *)0)->monsterBeginActionName), sizeof( ((rvmMonsterBase *)0)->monsterBeginActionName ) },
|
||||
{ "stateParms_t", "monsterBeginActionParms", (intptr_t)(&((rvmMonsterBase *)0)->monsterBeginActionParms), sizeof( ((rvmMonsterBase *)0)->monsterBeginActionParms ) },
|
||||
{ "idStr", "pathActionName", (intptr_t)(&((rvmMonsterBase *)0)->pathActionName), sizeof( ((rvmMonsterBase *)0)->pathActionName ) },
|
||||
{ "stateParms_t", "pathActionParms", (intptr_t)(&((rvmMonsterBase *)0)->pathActionParms), sizeof( ((rvmMonsterBase *)0)->pathActionParms ) },
|
||||
{ NULL, 0 }
|
||||
};
|
||||
|
||||
@@ -8747,6 +8755,11 @@ classVariableInfo_t rvmWeaponBFG::typeInfo[] = {
|
||||
{ NULL, 0 }
|
||||
};
|
||||
|
||||
classVariableInfo_t rvmWeaponSoulCube::typeInfo[] = {
|
||||
{ "int", "idleCount", (intptr_t)(&((rvmWeaponSoulCube *)0)->idleCount), sizeof( ((rvmWeaponSoulCube *)0)->idleCount ) },
|
||||
{ NULL, 0 }
|
||||
};
|
||||
|
||||
classVariableInfo_t rvmWeaponHandgrenade::typeInfo[] = {
|
||||
{ "float", "spread", (intptr_t)(&((rvmWeaponHandgrenade *)0)->spread), sizeof( ((rvmWeaponHandgrenade *)0)->spread ) },
|
||||
{ "float", "fuse_start", (intptr_t)(&((rvmWeaponHandgrenade *)0)->fuse_start), sizeof( ((rvmWeaponHandgrenade *)0)->fuse_start ) },
|
||||
@@ -9673,6 +9686,7 @@ static classTypeInfo_t classTypeInfo[] = {
|
||||
{ "rvmWeaponChainGun", "rvmWeaponObject", sizeof(rvmWeaponChainGun), rvmWeaponChainGun::typeInfo },
|
||||
{ "rvmWeaponRocketLauncher", "rvmWeaponObject", sizeof(rvmWeaponRocketLauncher), rvmWeaponRocketLauncher::typeInfo },
|
||||
{ "rvmWeaponBFG", "rvmWeaponObject", sizeof(rvmWeaponBFG), rvmWeaponBFG::typeInfo },
|
||||
{ "rvmWeaponSoulCube", "rvmWeaponObject", sizeof(rvmWeaponSoulCube), rvmWeaponSoulCube::typeInfo },
|
||||
{ "rvmWeaponHandgrenade", "rvmWeaponObject", sizeof(rvmWeaponHandgrenade), rvmWeaponHandgrenade::typeInfo },
|
||||
{ "rvmWeaponChainsaw", "rvmWeaponObject", sizeof(rvmWeaponChainsaw), rvmWeaponChainsaw::typeInfo },
|
||||
{ "rvmWeaponGrabber", "rvmWeaponObject", sizeof(rvmWeaponGrabber), rvmWeaponGrabber::typeInfo },
|
||||
|
||||
@@ -1712,6 +1712,9 @@ END_CLASS
|
||||
CLASS_DECLARATION( rvmWeaponObject, rvmWeaponBFG )
|
||||
END_CLASS
|
||||
|
||||
CLASS_DECLARATION( rvmWeaponObject, rvmWeaponSoulCube )
|
||||
END_CLASS
|
||||
|
||||
CLASS_DECLARATION( rvmWeaponObject, rvmWeaponHandgrenade )
|
||||
END_CLASS
|
||||
|
||||
|
||||
@@ -210,5 +210,6 @@ void idClass::RegisterClasses( void ) {
|
||||
REGISTER(rvmWeaponPlasmaGun);
|
||||
REGISTER(rvmWeaponRocketLauncher);
|
||||
REGISTER(rvmWeaponShotgun);
|
||||
REGISTER(rvmWeaponSoulCube);
|
||||
#undef REGISTER
|
||||
}
|
||||
|
||||
@@ -167,7 +167,7 @@ stateResult_t rvmWeaponBFG::Fire( stateParms_t* parms )
|
||||
FIRE_DONE
|
||||
};
|
||||
|
||||
if( ammoClip == 0 && owner->AmmoAvailable() && parms->stage == 0 )
|
||||
if( ammoClip == 0 && owner->AmmoAvailable() && owner->AutoReload() && parms->stage == 0 )
|
||||
{
|
||||
//owner->WeaponState( WP_RELOAD, PISTOL_IDLE_TO_RELOAD );
|
||||
owner->Reload();
|
||||
@@ -184,21 +184,17 @@ stateResult_t rvmWeaponBFG::Fire( stateParms_t* parms )
|
||||
return SRESULT_WAIT;
|
||||
|
||||
case FIRE_FUSING:
|
||||
if( !owner->IsFiring() )
|
||||
if( ( gameLocal.time > fuse_start + SEC2MS( BFG_MINRELEASETIME ) ) && !owner->IsFiring() )
|
||||
{
|
||||
parms->stage = FIRE_FIRE;
|
||||
}
|
||||
else if( gameLocal.time > gameLocal.time + SEC2MS( BFG_MINRELEASETIME ) && !owner->IsFiring() )
|
||||
{
|
||||
parms->stage = FIRE_SHORTFUSE;
|
||||
}
|
||||
else if( gameLocal.time >= fuse_end )
|
||||
{
|
||||
parms->stage = FIRE_SHORTFUSE;
|
||||
}
|
||||
else
|
||||
{
|
||||
powerLevel = ( gameLocal.time - fuse_start ) / BFG_FUSE;
|
||||
powerLevel = (float)( gameLocal.time - fuse_start ) / SEC2MS( BFG_FUSE );
|
||||
owner->Event_SetColor( powerLevel, powerLevel, powerLevel );
|
||||
owner->Event_SetGuiFloat( "powerlevel", powerLevel );
|
||||
}
|
||||
@@ -219,7 +215,7 @@ stateResult_t rvmWeaponBFG::Fire( stateParms_t* parms )
|
||||
case FIRE_SHORTFUSEWAIT:
|
||||
if( !owner->IsFiring() || gameLocal.time >= fuse_end )
|
||||
{
|
||||
powerLevel = ( gameLocal.time - fuse_start ) / BFG_FUSE;
|
||||
powerLevel = (float)( gameLocal.time - fuse_start ) / SEC2MS( BFG_FUSE );
|
||||
owner->Event_SetGuiFloat( "powerlevel", powerLevel );
|
||||
parms->stage = FIRE_FIRE;
|
||||
}
|
||||
@@ -233,7 +229,8 @@ stateResult_t rvmWeaponBFG::Fire( stateParms_t* parms )
|
||||
}
|
||||
else
|
||||
{
|
||||
time_held = gameLocal.time - fuse_start;
|
||||
time_held = MS2SEC( gameLocal.time - fuse_start );
|
||||
power = BFG_MAXPOWER * ( time_held / BFG_FUSE );
|
||||
if( power > BFG_MAXPOWER )
|
||||
{
|
||||
power = BFG_MAXPOWER;
|
||||
@@ -242,7 +239,7 @@ stateResult_t rvmWeaponBFG::Fire( stateParms_t* parms )
|
||||
owner->Event_PlayAnim( ANIMCHANNEL_ALL, "fire", false );
|
||||
fire_time = gameLocal.time;
|
||||
|
||||
owner->Event_LaunchProjectiles( BFG_NUMPROJECTILES, spread, 0, 1, 1 );
|
||||
owner->Event_LaunchProjectiles( BFG_NUMPROJECTILES, spread, time_held, 1, power );
|
||||
parms->stage = FIRE_FIRE_ANIMWAIT;
|
||||
}
|
||||
return SRESULT_WAIT;
|
||||
@@ -254,7 +251,7 @@ stateResult_t rvmWeaponBFG::Fire( stateParms_t* parms )
|
||||
}
|
||||
else
|
||||
{
|
||||
intensity = 1 - ( ( gameLocal.time - fire_time ) / 0.5 );
|
||||
intensity = 1.0f - ( (float)( gameLocal.time - fire_time ) / SEC2MS( 0.5f ) );
|
||||
if( intensity < 0 )
|
||||
{
|
||||
intensity = 0;
|
||||
@@ -270,7 +267,7 @@ stateResult_t rvmWeaponBFG::Fire( stateParms_t* parms )
|
||||
}
|
||||
else
|
||||
{
|
||||
powerLevel -= SEC2MS( 0.05 ); // TODO: DELTA TIME THIS!!
|
||||
powerLevel -= 0.05f;
|
||||
owner->Event_SetGuiFloat( "powerlevel", powerLevel );
|
||||
}
|
||||
return SRESULT_WAIT;
|
||||
@@ -350,4 +347,4 @@ stateResult_t rvmWeaponBFG::Reload( stateParms_t* parms )
|
||||
return SRESULT_WAIT;
|
||||
}
|
||||
return SRESULT_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,7 +173,7 @@ stateResult_t rvmWeaponDoubleShotgun::Fire( stateParms_t* parms )
|
||||
FIRE_WAIT
|
||||
};
|
||||
|
||||
if( ammoClip == 0 && owner->AmmoAvailable() && parms->stage == 0 )
|
||||
if( ammoClip == 0 && owner->AmmoAvailable() && owner->AutoReload() && parms->stage == 0 )
|
||||
{
|
||||
//owner->WeaponState( WP_RELOAD, PISTOL_IDLE_TO_RELOAD );
|
||||
owner->Reload();
|
||||
@@ -247,4 +247,4 @@ rvmWeaponDoubleShotgun::EjectBrass
|
||||
void rvmWeaponDoubleShotgun::EjectBrass( void )
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,7 +143,7 @@ stateResult_t rvmWeaponMachineGun::Fire( stateParms_t* parms )
|
||||
FIRE_WAIT
|
||||
};
|
||||
|
||||
if( ammoClip == 0 && owner->AmmoAvailable() && parms->stage == 0 )
|
||||
if( ammoClip == 0 && owner->AmmoAvailable() && owner->AutoReload() && parms->stage == 0 )
|
||||
{
|
||||
//owner->WeaponState( WP_RELOAD, PISTOL_IDLE_TO_RELOAD );
|
||||
owner->Reload();
|
||||
@@ -153,7 +153,7 @@ stateResult_t rvmWeaponMachineGun::Fire( stateParms_t* parms )
|
||||
switch( parms->stage )
|
||||
{
|
||||
case FIRE_NOTSET:
|
||||
next_attack = gameLocal.realClientTime + MS2SEC( MACHINEGUN_FIRERATE );
|
||||
next_attack = gameLocal.realClientTime + SEC2MS( MACHINEGUN_FIRERATE );
|
||||
owner->Event_LaunchProjectiles( MACHINEGUN_NUMPROJECTILES, spread, 0, 1, 1 );
|
||||
|
||||
owner->Event_PlayAnim( ANIMCHANNEL_ALL, "fire", false );
|
||||
@@ -200,4 +200,4 @@ stateResult_t rvmWeaponMachineGun::Reload( stateParms_t* parms )
|
||||
return SRESULT_WAIT;
|
||||
}
|
||||
return SRESULT_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,7 +147,7 @@ stateResult_t rvmWeaponPistol::Fire( stateParms_t* parms )
|
||||
FIRE_WAIT
|
||||
};
|
||||
|
||||
if( ammoClip == 0 && owner->AmmoAvailable() && parms->stage == 0 )
|
||||
if( ammoClip == 0 && owner->AmmoAvailable() && owner->AutoReload() && parms->stage == 0 )
|
||||
{
|
||||
//owner->WeaponState( WP_RELOAD, PISTOL_IDLE_TO_RELOAD );
|
||||
owner->Reload();
|
||||
@@ -212,4 +212,4 @@ stateResult_t rvmWeaponPistol::Reload( stateParms_t* parms )
|
||||
return SRESULT_WAIT;
|
||||
}
|
||||
return SRESULT_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,7 +141,7 @@ stateResult_t rvmWeaponPlasmaGun::Fire( stateParms_t* parms )
|
||||
FIRE_WAIT
|
||||
};
|
||||
|
||||
if( ammoClip == 0 && owner->AmmoAvailable() && parms->stage == 0 )
|
||||
if( ammoClip == 0 && owner->AmmoAvailable() && owner->AutoReload() && parms->stage == 0 )
|
||||
{
|
||||
//owner->WeaponState( WP_RELOAD, PISTOL_IDLE_TO_RELOAD );
|
||||
owner->Reload();
|
||||
@@ -200,4 +200,4 @@ stateResult_t rvmWeaponPlasmaGun::Reload( stateParms_t* parms )
|
||||
}
|
||||
|
||||
return SRESULT_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,7 +171,7 @@ stateResult_t rvmWeaponRocketLauncher::Fire( stateParms_t* parms )
|
||||
FIRE_WAIT
|
||||
};
|
||||
|
||||
if( ammoClip == 0 && owner->AmmoAvailable() && parms->stage == 0 )
|
||||
if( ammoClip == 0 && owner->AmmoAvailable() && owner->AutoReload() && parms->stage == 0 )
|
||||
{
|
||||
//owner->WeaponState( WP_RELOAD, PISTOL_IDLE_TO_RELOAD );
|
||||
owner->Reload();
|
||||
@@ -230,4 +230,4 @@ stateResult_t rvmWeaponRocketLauncher::Reload( stateParms_t* parms )
|
||||
return SRESULT_WAIT;
|
||||
}
|
||||
return SRESULT_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,7 +146,7 @@ stateResult_t rvmWeaponShotgun::Fire( stateParms_t* parms )
|
||||
FIRE_WAIT
|
||||
};
|
||||
|
||||
if( ammoClip == 0 && owner->AmmoAvailable() && parms->stage == 0 )
|
||||
if( ammoClip == 0 && owner->AmmoAvailable() && owner->AutoReload() && parms->stage == 0 )
|
||||
{
|
||||
//owner->WeaponState( WP_RELOAD, PISTOL_IDLE_TO_RELOAD );
|
||||
owner->Reload();
|
||||
@@ -197,6 +197,7 @@ stateResult_t rvmWeaponShotgun::Reload( stateParms_t* parms )
|
||||
enum RELOAD_State
|
||||
{
|
||||
RELOAD_NOTSET = 0,
|
||||
RELOAD_START_WAIT,
|
||||
RELOAD_WAIT,
|
||||
RELOAD_END
|
||||
};
|
||||
@@ -207,6 +208,15 @@ stateResult_t rvmWeaponShotgun::Reload( stateParms_t* parms )
|
||||
switch( parms->stage )
|
||||
{
|
||||
case RELOAD_NOTSET:
|
||||
owner->Event_PlayAnim( ANIMCHANNEL_ALL, "reload_start", false );
|
||||
parms->stage = RELOAD_START_WAIT;
|
||||
return SRESULT_WAIT;
|
||||
|
||||
case RELOAD_START_WAIT:
|
||||
if( !owner->Event_AnimDone( ANIMCHANNEL_ALL, 0 ) )
|
||||
{
|
||||
return SRESULT_WAIT;
|
||||
}
|
||||
owner->Event_PlayAnim( ANIMCHANNEL_ALL, "reload_loop", false );
|
||||
parms->stage = RELOAD_WAIT;
|
||||
return SRESULT_WAIT;
|
||||
@@ -216,8 +226,8 @@ stateResult_t rvmWeaponShotgun::Reload( stateParms_t* parms )
|
||||
{
|
||||
if( ( ammoClip < clip_size ) && ( ammoClip < ammoAvail ) )
|
||||
{
|
||||
parms->stage = RELOAD_NOTSET;
|
||||
owner->Event_AddToClip( SHOTGUN_RELOADRATE );
|
||||
owner->Event_PlayAnim( ANIMCHANNEL_ALL, "reload_loop", false );
|
||||
return SRESULT_WAIT;
|
||||
}
|
||||
else
|
||||
@@ -240,4 +250,4 @@ stateResult_t rvmWeaponShotgun::Reload( stateParms_t* parms )
|
||||
|
||||
|
||||
return SRESULT_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,9 +15,9 @@
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<LocalDebuggerCommand>D:\projects\Doom3\Doom3.exe</LocalDebuggerCommand>
|
||||
<DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>
|
||||
<LocalDebuggerCommandArguments>+set r_fullscreen 0 +set r_mode 11 +set sv_pure 0</LocalDebuggerCommandArguments>
|
||||
<LocalDebuggerCommandArguments>+set r_fullscreen 0 +set r_mode 11 +set sv_pure 0 </LocalDebuggerCommandArguments>
|
||||
<LocalDebuggerWorkingDirectory>D:\projects\Doom3</LocalDebuggerWorkingDirectory>
|
||||
<LocalDebuggerCommandArgumentsHistory>+set r_fullscreen 0 +set r_mode 11 +set sv_pure 0 |+set r_fullscreen 0 +set r_mode 9 +set sv_pure 0 |+set r_fullscreen 0 +set r_mode 9 +set sv_pure 0 +set fs_game rage|+set r_fullscreen 0 +set r_mode 9 +set sv_pure 0|+set r_fullscreen 0 +set r_mode 11 +set sv_pure 0|</LocalDebuggerCommandArgumentsHistory>
|
||||
<LocalDebuggerCommandArgumentsHistory>+set r_fullscreen 0 +set r_mode 9 +set sv_pure 0 +set fs_game rage|+set r_fullscreen 0 +set r_mode 9 +set sv_pure 0|+set r_fullscreen 0 +set r_mode 11 +set sv_pure 0|+set r_fullscreen 0 +set r_mode 11 +set sv_pure 0 +set fs_game e3|+set r_fullscreen 0 +set r_mode 11 +set sv_pure 0 |</LocalDebuggerCommandArgumentsHistory>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Q4 Debug|x64'">
|
||||
<LocalDebuggerCommand>D:\projects\Doom3\Quake4.exe</LocalDebuggerCommand>
|
||||
|
||||
@@ -805,6 +805,10 @@ void idKeyInput::Init( void ) {
|
||||
cmdSystem->AddCommand( "unbind", Key_Unbind_f, CMD_FL_SYSTEM, "unbinds any command from a key", idKeyInput::ArgCompletion_KeyName );
|
||||
cmdSystem->AddCommand( "unbindall", Key_Unbindall_f, CMD_FL_SYSTEM, "unbinds any commands from all keys" );
|
||||
cmdSystem->AddCommand( "listBinds", Key_ListBinds_f, CMD_FL_SYSTEM, "lists key bindings" );
|
||||
|
||||
#if !defined( QUAKE4 ) && !defined( PREY )
|
||||
SetBinding( 'r', "_impulse13" );
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -210,6 +210,8 @@ userCmdString_t userCmdStrings[] = {
|
||||
{ "_impulse11", UB_IMPULSE11 },
|
||||
{ "_impulse12", UB_IMPULSE12 },
|
||||
{ "_impulse13", UB_IMPULSE13 },
|
||||
{ "_reload", UB_IMPULSE13 },
|
||||
{ "reload", UB_IMPULSE13 },
|
||||
{ "_impulse14", UB_IMPULSE14 },
|
||||
{ "_impulse15", UB_IMPULSE15 },
|
||||
{ "_impulse16", UB_IMPULSE16 },
|
||||
@@ -935,6 +937,12 @@ void idUsercmdGenLocal::Key( int keyNum, bool down ) {
|
||||
|
||||
int action = idKeyInput::GetUsercmdAction( keyNum );
|
||||
|
||||
#if !defined( QUAKE4 ) && !defined( PREY )
|
||||
if ( action == UB_NONE && keyNum == 'r' && idKeyInput::GetBinding( keyNum )[0] == '\0' ) {
|
||||
action = UB_IMPULSE13;
|
||||
}
|
||||
#endif
|
||||
|
||||
if ( down ) {
|
||||
|
||||
buttonState[ action ]++;
|
||||
|
||||
@@ -104,6 +104,12 @@ using Microsoft::WRL::ComPtr;
|
||||
#ifndef GL_COMPRESSED_RGBA_S3TC_DXT5_EXT
|
||||
#define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83F3
|
||||
#endif
|
||||
#ifndef GL_COMPRESSED_RGBA_BPTC_UNORM_ARB
|
||||
#define GL_COMPRESSED_RGBA_BPTC_UNORM_ARB 0x8E8C
|
||||
#endif
|
||||
#ifndef GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB
|
||||
#define GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB 0x8E8D
|
||||
#endif
|
||||
#ifndef GL_COMPRESSED_ALPHA_ARB
|
||||
#define GL_COMPRESSED_ALPHA_ARB 0x84E9
|
||||
#endif
|
||||
@@ -705,6 +711,13 @@ struct NeuralPOMResource
|
||||
std::vector<uint8_t> latentRGBA16FBytes;
|
||||
};
|
||||
|
||||
struct CompressedTextureMip
|
||||
{
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
std::vector<uint8_t> data;
|
||||
};
|
||||
|
||||
struct TextureResource
|
||||
{
|
||||
GLuint glId = 0;
|
||||
@@ -749,6 +762,7 @@ struct TextureResource
|
||||
|
||||
std::vector<uint8_t> sysmem;
|
||||
std::vector<std::vector<uint8_t>> pendingMipChain;
|
||||
std::vector<CompressedTextureMip> compressedMips;
|
||||
|
||||
// Optional trained neural replacement for shader POM, usually attached to the
|
||||
// normal-map texture. If this is absent or not resident, the old path runs.
|
||||
@@ -7069,6 +7083,8 @@ static bool QD3D12_IsCompressedTextureFormat(GLenum internalFormat)
|
||||
case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT:
|
||||
case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT:
|
||||
case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
|
||||
case GL_COMPRESSED_RGBA_BPTC_UNORM_ARB:
|
||||
case GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
@@ -7086,6 +7102,10 @@ static DXGI_FORMAT QD3D12_MapCompressedTextureFormat(GLenum internalFormat)
|
||||
return DXGI_FORMAT_BC2_UNORM;
|
||||
case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
|
||||
return DXGI_FORMAT_BC3_UNORM;
|
||||
case GL_COMPRESSED_RGBA_BPTC_UNORM_ARB:
|
||||
return DXGI_FORMAT_BC7_UNORM;
|
||||
case GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB:
|
||||
return DXGI_FORMAT_BC7_UNORM_SRGB;
|
||||
default:
|
||||
return DXGI_FORMAT_UNKNOWN;
|
||||
}
|
||||
@@ -7100,6 +7120,8 @@ static UINT QD3D12_CompressedTextureBlockBytes(GLenum internalFormat)
|
||||
return 8;
|
||||
case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT:
|
||||
case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
|
||||
case GL_COMPRESSED_RGBA_BPTC_UNORM_ARB:
|
||||
case GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB:
|
||||
return 16;
|
||||
default:
|
||||
return 0;
|
||||
@@ -12531,10 +12553,13 @@ static UINT QD3D12_CalcMipCount(int width, int height)
|
||||
|
||||
static UINT QD3D12_DesiredTextureMipLevels(const TextureResource& tex)
|
||||
{
|
||||
// Compressed uploads stay at one level in this shim because we do not encode
|
||||
// generated BC/DXT blocks. Uncompressed texture storage gets a full CPU-built
|
||||
// mip chain whenever level 0 sysmem exists.
|
||||
if (tex.compressed || tex.width <= 0 || tex.height <= 0 || tex.sysmem.empty())
|
||||
if (tex.width <= 0 || tex.height <= 0)
|
||||
return 1;
|
||||
|
||||
if (tex.compressed)
|
||||
return std::max<UINT>(1u, (UINT)tex.compressedMips.size());
|
||||
|
||||
if (tex.sysmem.empty())
|
||||
return 1;
|
||||
|
||||
return QD3D12_CalcMipCount(tex.width, tex.height);
|
||||
@@ -12561,6 +12586,9 @@ static UINT QD3D12_TextureVisibleMipLevels(const TextureResource& tex)
|
||||
if (!QD3D12_TextureWantsMipSampling(tex))
|
||||
return 1;
|
||||
|
||||
if (tex.compressed)
|
||||
return std::max<UINT>(1u, std::min<UINT>(tex.mipLevels, (UINT)tex.compressedMips.size()));
|
||||
|
||||
if (!tex.mipChainResident || tex.mipChainResidentGeneration != tex.cpuGeneration)
|
||||
return 1;
|
||||
|
||||
@@ -12574,6 +12602,12 @@ static void EnsureTextureResource(TextureResource& tex)
|
||||
if (tex.width <= 0 || tex.height <= 0 || dxgiFormat == DXGI_FORMAT_UNKNOWN)
|
||||
return;
|
||||
|
||||
// Texture definitions can arrive while materials are being parsed, before the
|
||||
// D3D12 window/device descriptor state is fully ready. Keep the CPU-side image
|
||||
// data resident and defer real resource creation until the first draw/use.
|
||||
if (!g_gl.device || !g_gl.srvHeap)
|
||||
return;
|
||||
|
||||
const UINT desiredMipLevels = QD3D12_DesiredTextureMipLevels(tex);
|
||||
|
||||
auto EnsureSrvDescriptor = [&]()
|
||||
@@ -13450,31 +13484,6 @@ static void UploadTexture(TextureResource& tex, ID3D12GraphicsCommandList* comma
|
||||
return;
|
||||
}
|
||||
|
||||
const UINT blocksWide = QD3D12_CompressedTextureBlocksWide(tex.width);
|
||||
const UINT blocksHigh = QD3D12_CompressedTextureBlocksHigh(tex.height);
|
||||
const UINT srcRowBytes = blocksWide * blockBytes;
|
||||
const UINT rowPitch = (srcRowBytes + 255u) & ~255u;
|
||||
const UINT uploadSize = rowPitch * blocksHigh;
|
||||
|
||||
UploadAlloc alloc = QD3D12_AllocUpload(uploadSize, D3D12_TEXTURE_DATA_PLACEMENT_ALIGNMENT);
|
||||
uint8_t* dstBase = (uint8_t*)alloc.cpu;
|
||||
memset(dstBase, 0, uploadSize);
|
||||
|
||||
if (!tex.sysmem.empty() && blockBytes != 0)
|
||||
{
|
||||
const uint8_t* srcBase = tex.sysmem.data();
|
||||
const size_t available = tex.sysmem.size();
|
||||
for (UINT row = 0; row < blocksHigh; ++row)
|
||||
{
|
||||
const size_t srcOff = (size_t)row * (size_t)srcRowBytes;
|
||||
if (srcOff >= available)
|
||||
break;
|
||||
|
||||
const size_t copyBytes = std::min<size_t>((size_t)srcRowBytes, available - srcOff);
|
||||
memcpy(dstBase + (size_t)row * (size_t)rowPitch, srcBase + srcOff, copyBytes);
|
||||
}
|
||||
}
|
||||
|
||||
if (tex.state != D3D12_RESOURCE_STATE_COPY_DEST)
|
||||
{
|
||||
D3D12_RESOURCE_BARRIER toCopy{};
|
||||
@@ -13487,22 +13496,51 @@ static void UploadTexture(TextureResource& tex, ID3D12GraphicsCommandList* comma
|
||||
tex.state = D3D12_RESOURCE_STATE_COPY_DEST;
|
||||
}
|
||||
|
||||
D3D12_TEXTURE_COPY_LOCATION srcLoc{};
|
||||
srcLoc.pResource = g_currentWindow->upload.resource[g_currentWindow->frameIndex].Get();
|
||||
srcLoc.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT;
|
||||
srcLoc.PlacedFootprint.Offset = alloc.offset;
|
||||
srcLoc.PlacedFootprint.Footprint.Format = tex.dxgiFormat;
|
||||
srcLoc.PlacedFootprint.Footprint.Width = tex.width;
|
||||
srcLoc.PlacedFootprint.Footprint.Height = tex.height;
|
||||
srcLoc.PlacedFootprint.Footprint.Depth = 1;
|
||||
srcLoc.PlacedFootprint.Footprint.RowPitch = rowPitch;
|
||||
const UINT mipCount = std::max<UINT>(1u, std::min<UINT>((UINT)tex.compressedMips.size(), tex.mipLevels));
|
||||
for (UINT mip = 0; mip < mipCount; ++mip)
|
||||
{
|
||||
const CompressedTextureMip* mipData = (mip < tex.compressedMips.size()) ? &tex.compressedMips[mip] : nullptr;
|
||||
const int mipWidth = (mipData && mipData->width > 0) ? mipData->width : std::max<int>(1, tex.width >> (int)mip);
|
||||
const int mipHeight = (mipData && mipData->height > 0) ? mipData->height : std::max<int>(1, tex.height >> (int)mip);
|
||||
const UINT blocksWide = QD3D12_CompressedTextureBlocksWide(mipWidth);
|
||||
const UINT blocksHigh = QD3D12_CompressedTextureBlocksHigh(mipHeight);
|
||||
const UINT srcRowBytes = blocksWide * blockBytes;
|
||||
const UINT rowPitch = (srcRowBytes + 255u) & ~255u;
|
||||
const UINT uploadSize = rowPitch * blocksHigh;
|
||||
|
||||
D3D12_TEXTURE_COPY_LOCATION dstLoc{};
|
||||
dstLoc.pResource = tex.texture.Get();
|
||||
dstLoc.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;
|
||||
dstLoc.SubresourceIndex = 0;
|
||||
UploadAlloc alloc = QD3D12_AllocUpload(uploadSize, D3D12_TEXTURE_DATA_PLACEMENT_ALIGNMENT);
|
||||
uint8_t* dstBase = (uint8_t*)alloc.cpu;
|
||||
memset(dstBase, 0, uploadSize);
|
||||
|
||||
cl->CopyTextureRegion(&dstLoc, 0, 0, 0, &srcLoc, nullptr);
|
||||
const uint8_t* srcBase = (mipData && !mipData->data.empty()) ? mipData->data.data() : nullptr;
|
||||
const size_t available = mipData ? mipData->data.size() : 0;
|
||||
for (UINT row = 0; row < blocksHigh && srcBase; ++row)
|
||||
{
|
||||
const size_t srcOff = (size_t)row * (size_t)srcRowBytes;
|
||||
if (srcOff >= available)
|
||||
break;
|
||||
|
||||
const size_t copyBytes = std::min<size_t>((size_t)srcRowBytes, available - srcOff);
|
||||
memcpy(dstBase + (size_t)row * (size_t)rowPitch, srcBase + srcOff, copyBytes);
|
||||
}
|
||||
|
||||
D3D12_TEXTURE_COPY_LOCATION srcLoc{};
|
||||
srcLoc.pResource = g_currentWindow->upload.resource[g_currentWindow->frameIndex].Get();
|
||||
srcLoc.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT;
|
||||
srcLoc.PlacedFootprint.Offset = alloc.offset;
|
||||
srcLoc.PlacedFootprint.Footprint.Format = tex.dxgiFormat;
|
||||
srcLoc.PlacedFootprint.Footprint.Width = mipWidth;
|
||||
srcLoc.PlacedFootprint.Footprint.Height = mipHeight;
|
||||
srcLoc.PlacedFootprint.Footprint.Depth = 1;
|
||||
srcLoc.PlacedFootprint.Footprint.RowPitch = rowPitch;
|
||||
|
||||
D3D12_TEXTURE_COPY_LOCATION dstLoc{};
|
||||
dstLoc.pResource = tex.texture.Get();
|
||||
dstLoc.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;
|
||||
dstLoc.SubresourceIndex = mip;
|
||||
|
||||
cl->CopyTextureRegion(&dstLoc, 0, 0, 0, &srcLoc, nullptr);
|
||||
}
|
||||
|
||||
D3D12_RESOURCE_BARRIER toSrv{};
|
||||
toSrv.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
|
||||
@@ -16335,9 +16373,6 @@ void APIENTRY glCompressedTexImage2DARB(GLenum target, GLint level, GLenum inter
|
||||
return;
|
||||
}
|
||||
|
||||
if (level > 0)
|
||||
return;
|
||||
|
||||
if (width <= 0 || height <= 0 || border != 0 || imageSize < 0)
|
||||
{
|
||||
g_gl.lastError = GL_INVALID_VALUE;
|
||||
@@ -16358,25 +16393,44 @@ void APIENTRY glCompressedTexImage2DARB(GLenum target, GLint level, GLenum inter
|
||||
}
|
||||
|
||||
TextureResource& tex = it->second;
|
||||
tex.width = width;
|
||||
tex.height = height;
|
||||
tex.format = internalFormat;
|
||||
tex.compressed = true;
|
||||
tex.compressedInternalFormat = internalFormat;
|
||||
tex.compressedBlockBytes = QD3D12_CompressedTextureBlockBytes(internalFormat);
|
||||
tex.compressedImageSize = expectedSize;
|
||||
tex.forceOpaqueAlpha = (internalFormat == GL_COMPRESSED_RGB_S3TC_DXT1_EXT);
|
||||
tex.dxgiFormat = QD3D12_MapCompressedTextureFormat(internalFormat);
|
||||
tex.mipLevels = 1;
|
||||
tex.mipChainRequested = false;
|
||||
QD3D12_InvalidateTextureMipChain(tex, true);
|
||||
if (level == 0)
|
||||
{
|
||||
tex.width = width;
|
||||
tex.height = height;
|
||||
tex.format = internalFormat;
|
||||
tex.compressed = true;
|
||||
tex.compressedInternalFormat = internalFormat;
|
||||
tex.compressedBlockBytes = QD3D12_CompressedTextureBlockBytes(internalFormat);
|
||||
tex.compressedImageSize = expectedSize;
|
||||
tex.forceOpaqueAlpha = (internalFormat == GL_COMPRESSED_RGB_S3TC_DXT1_EXT);
|
||||
tex.dxgiFormat = QD3D12_MapCompressedTextureFormat(internalFormat);
|
||||
tex.mipLevels = 1;
|
||||
tex.mipChainRequested = false;
|
||||
tex.sysmem.clear();
|
||||
tex.compressedMips.clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!tex.compressed || internalFormat != tex.compressedInternalFormat)
|
||||
{
|
||||
g_gl.lastError = GL_INVALID_OPERATION;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
tex.sysmem.resize(expectedSize);
|
||||
if ((int)tex.compressedMips.size() <= level)
|
||||
tex.compressedMips.resize((size_t)level + 1u);
|
||||
|
||||
CompressedTextureMip& mip = tex.compressedMips[level];
|
||||
mip.width = width;
|
||||
mip.height = height;
|
||||
mip.data.resize(expectedSize);
|
||||
if (data && expectedSize > 0)
|
||||
memcpy(tex.sysmem.data(), data, expectedSize);
|
||||
memcpy(mip.data.data(), data, expectedSize);
|
||||
else if (expectedSize > 0)
|
||||
memset(tex.sysmem.data(), 0, expectedSize);
|
||||
memset(mip.data.data(), 0, expectedSize);
|
||||
|
||||
QD3D12_InvalidateTextureMipChain(tex, true);
|
||||
EnsureTextureResource(tex);
|
||||
}
|
||||
|
||||
@@ -16467,8 +16521,13 @@ void APIENTRY glCompressedTexSubImage2DARB(GLenum target, GLint level,
|
||||
}
|
||||
|
||||
const size_t fullSize = (size_t)QD3D12_CompressedTextureImageSize(tex.width, tex.height, tex.compressedInternalFormat);
|
||||
if (tex.sysmem.size() != fullSize)
|
||||
tex.sysmem.resize(fullSize, 0);
|
||||
if (tex.compressedMips.empty())
|
||||
tex.compressedMips.resize(1);
|
||||
CompressedTextureMip& baseMip = tex.compressedMips[0];
|
||||
baseMip.width = tex.width;
|
||||
baseMip.height = tex.height;
|
||||
if (baseMip.data.size() != fullSize)
|
||||
baseMip.data.resize(fullSize, 0);
|
||||
|
||||
const uint8_t* src = static_cast<const uint8_t*>(data);
|
||||
if (!src && expectedSize > 0)
|
||||
@@ -16483,7 +16542,7 @@ void APIENTRY glCompressedTexSubImage2DARB(GLenum target, GLint level,
|
||||
{
|
||||
const size_t dstOff = ((size_t)(dstBlockY + row) * (size_t)fullBlocksWide + (size_t)dstBlockX) * (size_t)blockBytes;
|
||||
const size_t srcOff = (size_t)row * srcRowBytes;
|
||||
memcpy(tex.sysmem.data() + dstOff, src + srcOff, srcRowBytes);
|
||||
memcpy(baseMip.data.data() + dstOff, src + srcOff, srcRowBytes);
|
||||
}
|
||||
|
||||
tex.compressedImageSize = (UINT)fullSize;
|
||||
@@ -16513,7 +16572,7 @@ void APIENTRY glGetCompressedTexImageARB(GLenum target, GLint level, GLvoid* img
|
||||
return;
|
||||
}
|
||||
|
||||
if (level > 0 || !img)
|
||||
if (level < 0 || !img)
|
||||
return;
|
||||
|
||||
auto it = g_gl.textures.find(g_gl.boundTexture[g_gl.activeTextureUnit]);
|
||||
@@ -16524,8 +16583,8 @@ void APIENTRY glGetCompressedTexImageARB(GLenum target, GLint level, GLvoid* img
|
||||
}
|
||||
|
||||
TextureResource& tex = it->second;
|
||||
if (!tex.sysmem.empty())
|
||||
memcpy(img, tex.sysmem.data(), tex.sysmem.size());
|
||||
if ((size_t)level < tex.compressedMips.size() && !tex.compressedMips[level].data.empty())
|
||||
memcpy(img, tex.compressedMips[level].data.data(), tex.compressedMips[level].data.size());
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -893,6 +893,12 @@ typedef uint32_t glRaytracingSceneHandle_t;
|
||||
#ifndef GL_COMPRESSED_RGBA_S3TC_DXT5_EXT
|
||||
#define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83F3
|
||||
#endif
|
||||
#ifndef GL_COMPRESSED_RGBA_BPTC_UNORM_ARB
|
||||
#define GL_COMPRESSED_RGBA_BPTC_UNORM_ARB 0x8E8C
|
||||
#endif
|
||||
#ifndef GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB
|
||||
#define GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB 0x8E8D
|
||||
#endif
|
||||
|
||||
#ifndef GL_MAP_READ_BIT
|
||||
#define GL_MAP_READ_BIT 0x0001
|
||||
|
||||
@@ -117,6 +117,17 @@ typedef struct
|
||||
unsigned long dwReserved2[3];
|
||||
} ddsFileHeader_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
unsigned long dxgiFormat;
|
||||
unsigned long resourceDimension;
|
||||
unsigned long miscFlag;
|
||||
unsigned long arraySize;
|
||||
unsigned long miscFlags2;
|
||||
} ddsFileHeaderDX10_t;
|
||||
|
||||
const unsigned long DDS_DIMENSION_TEXTURE2D = 3;
|
||||
const unsigned long DDS_DXGI_FORMAT_BC7_UNORM = 98;
|
||||
|
||||
// increasing numeric values imply more information is stored
|
||||
typedef enum {
|
||||
@@ -203,6 +214,8 @@ public:
|
||||
void WritePrecompressedImage();
|
||||
bool CheckPrecompressedImage( bool fullLoad );
|
||||
void UploadPrecompressedImage( byte *data, int len );
|
||||
bool CheckGeneratedBC7Image( bool fullLoad, ID_TIME_T sourceTimestamp );
|
||||
bool CookGeneratedBC7Image( const byte *pic, int width, int height, ID_TIME_T sourceTimestamp );
|
||||
void ActuallyLoadImage( bool checkForPrecompressed, bool fromBackEnd );
|
||||
void StartBackgroundImageLoad();
|
||||
int BitsForInternalFormat( int internalFormat ) const;
|
||||
@@ -376,6 +389,9 @@ public:
|
||||
static idCVar image_useAllFormats; // allow alpha/intensity/luminance/luminance+alpha
|
||||
static idCVar image_usePrecompressedTextures; // use .dds files if present
|
||||
static idCVar image_writePrecompressedTextures; // write .dds files if necessary
|
||||
static idCVar image_useBC7GeneratedTextures; // use generated BC7 DDS files if present
|
||||
static idCVar image_cookBC7GeneratedTextures; // cook generated BC7 DDS files from source data
|
||||
static idCVar image_bc7NormalMaps; // include normal maps in BC7 generated texture path
|
||||
static idCVar image_writeNormalTGA; // debug tool to write out .tgas of the final normal maps
|
||||
static idCVar image_writeNormalTGAPalletized; // debug tool to write out palletized versions of the final normal maps
|
||||
static idCVar image_writeTGA; // debug tool to write out .tgas of the non normal maps
|
||||
|
||||
@@ -54,6 +54,9 @@ idCVar idImageManager::image_useAllFormats( "image_useAllFormats", "1", CVAR_REN
|
||||
idCVar idImageManager::image_useNormalCompression( "image_useNormalCompression", "2", CVAR_RENDERER | CVAR_ARCHIVE | CVAR_INTEGER, "2 = use rxgb compression for normal maps, 1 = use 256 color compression for normal maps if available" );
|
||||
idCVar idImageManager::image_usePrecompressedTextures( "image_usePrecompressedTextures", "1", CVAR_RENDERER | CVAR_ARCHIVE | CVAR_BOOL, "use .dds files if present" );
|
||||
idCVar idImageManager::image_writePrecompressedTextures( "image_writePrecompressedTextures", "0", CVAR_RENDERER | CVAR_BOOL, "write .dds files if necessary" );
|
||||
idCVar idImageManager::image_useBC7GeneratedTextures( "image_useBC7GeneratedTextures", "1", CVAR_RENDERER | CVAR_ARCHIVE | CVAR_BOOL, "use generated BC7 DDS files if present" );
|
||||
idCVar idImageManager::image_cookBC7GeneratedTextures( "image_cookBC7GeneratedTextures", "1", CVAR_RENDERER | CVAR_ARCHIVE | CVAR_BOOL, "cook generated BC7 DDS files from source data when missing or stale" );
|
||||
idCVar idImageManager::image_bc7NormalMaps( "image_bc7NormalMaps", "1", CVAR_RENDERER | CVAR_ARCHIVE | CVAR_BOOL, "include normal maps in the generated BC7 path" );
|
||||
idCVar idImageManager::image_writeNormalTGA( "image_writeNormalTGA", "0", CVAR_RENDERER | CVAR_BOOL, "write .tgas of the final normal maps for debugging" );
|
||||
idCVar idImageManager::image_writeNormalTGAPalletized( "image_writeNormalTGAPalletized", "0", CVAR_RENDERER | CVAR_BOOL, "write .tgas of the final palletized normal maps for debugging" );
|
||||
idCVar idImageManager::image_writeTGA( "image_writeTGA", "0", CVAR_RENDERER | CVAR_BOOL, "write .tgas of the non normal maps for debugging" );
|
||||
|
||||
@@ -43,6 +43,26 @@ static bool FormatIsDXT( int internalFormat ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool FormatIsBC7( int internalFormat ) {
|
||||
return internalFormat == GL_COMPRESSED_RGBA_BPTC_UNORM_ARB || internalFormat == GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB;
|
||||
}
|
||||
|
||||
static bool FormatIsBlockCompressed( int internalFormat ) {
|
||||
return FormatIsDXT( internalFormat ) || FormatIsBC7( internalFormat );
|
||||
}
|
||||
|
||||
static int BlockBytesForInternalFormat( int internalFormat ) {
|
||||
if ( internalFormat == GL_COMPRESSED_RGB_S3TC_DXT1_EXT || internalFormat == GL_COMPRESSED_RGBA_S3TC_DXT1_EXT ) {
|
||||
return 8;
|
||||
}
|
||||
if ( internalFormat == GL_COMPRESSED_RGBA_S3TC_DXT3_EXT ||
|
||||
internalFormat == GL_COMPRESSED_RGBA_S3TC_DXT5_EXT ||
|
||||
FormatIsBC7( internalFormat ) ) {
|
||||
return 16;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void R_SetImageAverageColor( idImage *image, const byte *pic, int width, int height ) {
|
||||
if ( !image || !pic || width <= 0 || height <= 0 ) {
|
||||
return;
|
||||
@@ -140,6 +160,9 @@ int idImage::BitsForInternalFormat( int internalFormat ) const {
|
||||
return 8;
|
||||
case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
|
||||
return 8;
|
||||
case GL_COMPRESSED_RGBA_BPTC_UNORM_ARB:
|
||||
case GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB:
|
||||
return 8;
|
||||
case GL_RGBA4:
|
||||
return 16;
|
||||
case GL_RGB5:
|
||||
@@ -1006,6 +1029,498 @@ void idImage::ImageProgramStringToCompressedFileName( const char *imageProg, cha
|
||||
strcat( fileName, ".dds" );
|
||||
}
|
||||
|
||||
static const char *R_BC7DepthFolder( textureDepth_t depth ) {
|
||||
switch ( depth ) {
|
||||
case TD_SPECULAR:
|
||||
return "specular";
|
||||
case TD_BUMP:
|
||||
return "normal";
|
||||
case TD_HIGH_QUALITY:
|
||||
return "high";
|
||||
case TD_DIFFUSE:
|
||||
return "diffuse";
|
||||
case TD_DEFAULT:
|
||||
default:
|
||||
return "default";
|
||||
}
|
||||
}
|
||||
|
||||
static bool R_DepthWantsGeneratedBC7( textureDepth_t depth ) {
|
||||
if ( depth == TD_HIGH_QUALITY ) {
|
||||
return false;
|
||||
}
|
||||
if ( depth == TD_BUMP && !globalImages->image_bc7NormalMaps.GetBool() ) {
|
||||
return false;
|
||||
}
|
||||
return depth == TD_DIFFUSE || depth == TD_DEFAULT || depth == TD_SPECULAR || depth == TD_BUMP;
|
||||
}
|
||||
|
||||
static void R_ImageProgramStringToGeneratedBC7FileName( const char *imageProg, textureDepth_t depth, char *fileName ) {
|
||||
const char *s;
|
||||
char *f;
|
||||
|
||||
idStr::snPrintf( fileName, MAX_IMAGE_NAME, "generated/images/bc7/%s/", R_BC7DepthFolder( depth ) );
|
||||
f = fileName + strlen( fileName );
|
||||
|
||||
int folderDepth = 0;
|
||||
for ( s = imageProg ; *s && ( f - fileName ) < MAX_IMAGE_NAME - 6 ; s++ ) {
|
||||
if ( *s == '/' || *s == '\\' || *s == '(' ) {
|
||||
if ( folderDepth < 4 ) {
|
||||
*f = '/';
|
||||
folderDepth++;
|
||||
} else {
|
||||
*f = '_';
|
||||
}
|
||||
f++;
|
||||
} else if ( *s == '<' || *s == '>' || *s == ':' || *s == '|' || *s == '"' || *s == '.' ) {
|
||||
*f++ = '_';
|
||||
} else if ( *s == ' ' && f > fileName && *( f - 1 ) == '/' ) {
|
||||
} else if ( *s == ')' || *s == ',' ) {
|
||||
} else {
|
||||
*f++ = *s;
|
||||
}
|
||||
}
|
||||
*f = 0;
|
||||
idStr::Copynz( f, ".dds", MAX_IMAGE_NAME - (int)( f - fileName ) );
|
||||
}
|
||||
|
||||
static void R_WriteBitsLSB( byte *block, int &bit, unsigned int value, int count ) {
|
||||
for ( int i = 0; i < count; i++ ) {
|
||||
if ( value & ( 1u << i ) ) {
|
||||
block[bit >> 3] |= (byte)( 1u << ( bit & 7 ) );
|
||||
}
|
||||
bit++;
|
||||
}
|
||||
}
|
||||
|
||||
static int R_BC7Luma( const byte *rgba ) {
|
||||
return rgba[0] * 54 + rgba[1] * 183 + rgba[2] * 19;
|
||||
}
|
||||
|
||||
static byte *R_MipMapNormalMap( const byte *in, int width, int height, bool preserveBorder ) {
|
||||
if ( width < 1 || height < 1 || ( width + height == 2 ) ) {
|
||||
common->FatalError( "R_MipMapNormalMap called with size %i,%i", width, height );
|
||||
}
|
||||
|
||||
const byte border[4] = { in[0], in[1], in[2], in[3] };
|
||||
const int newWidth = ( width > 1 ) ? width >> 1 : 1;
|
||||
const int newHeight = ( height > 1 ) ? height >> 1 : 1;
|
||||
byte *out = (byte *)R_StaticAlloc( newWidth * newHeight * 4 );
|
||||
|
||||
for ( int y = 0; y < newHeight; y++ ) {
|
||||
for ( int x = 0; x < newWidth; x++ ) {
|
||||
float nx = 0.0f;
|
||||
float ny = 0.0f;
|
||||
float nz = 0.0f;
|
||||
int alpha = 0;
|
||||
int samples = 0;
|
||||
|
||||
for ( int yy = 0; yy < 2; yy++ ) {
|
||||
const int sy = ( height == 1 ) ? 0 : idMath::ClampInt( 0, height - 1, y * 2 + yy );
|
||||
for ( int xx = 0; xx < 2; xx++ ) {
|
||||
const int sx = ( width == 1 ) ? 0 : idMath::ClampInt( 0, width - 1, x * 2 + xx );
|
||||
const byte *p = in + ( sy * width + sx ) * 4;
|
||||
nx += p[0] * ( 2.0f / 255.0f ) - 1.0f;
|
||||
ny += p[1] * ( 2.0f / 255.0f ) - 1.0f;
|
||||
nz += p[2] * ( 2.0f / 255.0f ) - 1.0f;
|
||||
alpha += p[3];
|
||||
samples++;
|
||||
}
|
||||
}
|
||||
|
||||
float len = idMath::Sqrt( nx * nx + ny * ny + nz * nz );
|
||||
if ( len > 1.0e-6f ) {
|
||||
len = 1.0f / len;
|
||||
nx *= len;
|
||||
ny *= len;
|
||||
nz *= len;
|
||||
} else {
|
||||
nx = 0.0f;
|
||||
ny = 0.0f;
|
||||
nz = 1.0f;
|
||||
}
|
||||
|
||||
byte *dst = out + ( y * newWidth + x ) * 4;
|
||||
dst[0] = (byte)idMath::ClampInt( 0, 255, (int)( ( nx * 0.5f + 0.5f ) * 255.0f + 0.5f ) );
|
||||
dst[1] = (byte)idMath::ClampInt( 0, 255, (int)( ( ny * 0.5f + 0.5f ) * 255.0f + 0.5f ) );
|
||||
dst[2] = (byte)idMath::ClampInt( 0, 255, (int)( ( nz * 0.5f + 0.5f ) * 255.0f + 0.5f ) );
|
||||
dst[3] = (byte)idMath::ClampInt( 0, 255, ( alpha + samples / 2 ) / samples );
|
||||
}
|
||||
}
|
||||
|
||||
if ( preserveBorder ) {
|
||||
R_SetBorderTexels( out, newWidth, newHeight, border );
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
static byte *R_MipMapForBC7Depth( const byte *in, int width, int height, bool preserveBorder, textureDepth_t depth ) {
|
||||
return ( depth == TD_BUMP ) ? R_MipMapNormalMap( in, width, height, preserveBorder ) : R_MipMap( in, width, height, preserveBorder );
|
||||
}
|
||||
|
||||
static float R_NormalProjection( const byte *rgba, const idVec3 &axis ) {
|
||||
return ( rgba[0] * ( 2.0f / 255.0f ) - 1.0f ) * axis.x +
|
||||
( rgba[1] * ( 2.0f / 255.0f ) - 1.0f ) * axis.y +
|
||||
( rgba[2] * ( 2.0f / 255.0f ) - 1.0f ) * axis.z;
|
||||
}
|
||||
|
||||
static void R_EncodeBC7Mode6Block( const byte *rgba, int width, int height, int x, int y, bool normalMap, byte *block ) {
|
||||
memset( block, 0, 16 );
|
||||
|
||||
byte minColor[4] = { 255, 255, 255, 255 };
|
||||
byte maxColor[4] = { 0, 0, 0, 0 };
|
||||
int minLuma = 2147483647;
|
||||
int maxLuma = -2147483648;
|
||||
float minProjection = idMath::INFINITY;
|
||||
float maxProjection = -idMath::INFINITY;
|
||||
idVec3 averageNormal( 0.0f, 0.0f, 0.0f );
|
||||
idVec3 variance( 0.0f, 0.0f, 0.0f );
|
||||
idVec3 axis( 1.0f, 0.0f, 0.0f );
|
||||
|
||||
if ( normalMap ) {
|
||||
for ( int by = 0; by < 4; by++ ) {
|
||||
const int sy = ( y + by < height ) ? y + by : height - 1;
|
||||
for ( int bx = 0; bx < 4; bx++ ) {
|
||||
const int sx = ( x + bx < width ) ? x + bx : width - 1;
|
||||
const byte *p = rgba + ( sy * width + sx ) * 4;
|
||||
averageNormal.x += p[0] * ( 2.0f / 255.0f ) - 1.0f;
|
||||
averageNormal.y += p[1] * ( 2.0f / 255.0f ) - 1.0f;
|
||||
averageNormal.z += p[2] * ( 2.0f / 255.0f ) - 1.0f;
|
||||
}
|
||||
}
|
||||
averageNormal *= 1.0f / 16.0f;
|
||||
for ( int by = 0; by < 4; by++ ) {
|
||||
const int sy = ( y + by < height ) ? y + by : height - 1;
|
||||
for ( int bx = 0; bx < 4; bx++ ) {
|
||||
const int sx = ( x + bx < width ) ? x + bx : width - 1;
|
||||
const byte *p = rgba + ( sy * width + sx ) * 4;
|
||||
const float nx = p[0] * ( 2.0f / 255.0f ) - 1.0f;
|
||||
const float ny = p[1] * ( 2.0f / 255.0f ) - 1.0f;
|
||||
const float nz = p[2] * ( 2.0f / 255.0f ) - 1.0f;
|
||||
variance.x += ( nx - averageNormal.x ) * ( nx - averageNormal.x );
|
||||
variance.y += ( ny - averageNormal.y ) * ( ny - averageNormal.y );
|
||||
variance.z += ( nz - averageNormal.z ) * ( nz - averageNormal.z );
|
||||
}
|
||||
}
|
||||
if ( variance.y > variance.x && variance.y >= variance.z ) {
|
||||
axis.Set( 0.0f, 1.0f, 0.0f );
|
||||
} else if ( variance.z > variance.x && variance.z > variance.y ) {
|
||||
axis.Set( 0.0f, 0.0f, 1.0f );
|
||||
}
|
||||
}
|
||||
|
||||
for ( int by = 0; by < 4; by++ ) {
|
||||
const int sy = ( y + by < height ) ? y + by : height - 1;
|
||||
for ( int bx = 0; bx < 4; bx++ ) {
|
||||
const int sx = ( x + bx < width ) ? x + bx : width - 1;
|
||||
const byte *p = rgba + ( sy * width + sx ) * 4;
|
||||
if ( normalMap ) {
|
||||
const float projection = R_NormalProjection( p, axis );
|
||||
if ( projection < minProjection ) {
|
||||
minProjection = projection;
|
||||
memcpy( minColor, p, 4 );
|
||||
}
|
||||
if ( projection > maxProjection ) {
|
||||
maxProjection = projection;
|
||||
memcpy( maxColor, p, 4 );
|
||||
}
|
||||
continue;
|
||||
}
|
||||
const int luma = R_BC7Luma( p );
|
||||
if ( luma < minLuma ) {
|
||||
minLuma = luma;
|
||||
memcpy( minColor, p, 4 );
|
||||
}
|
||||
if ( luma > maxLuma ) {
|
||||
maxLuma = luma;
|
||||
memcpy( maxColor, p, 4 );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int bit = 0;
|
||||
R_WriteBitsLSB( block, bit, 0x40, 7 ); // BC7 mode 6: six zero mode bits followed by one.
|
||||
|
||||
const byte *ep[2] = { minColor, maxColor };
|
||||
for ( int c = 0; c < 4; c++ ) {
|
||||
R_WriteBitsLSB( block, bit, ep[0][c] >> 1, 7 );
|
||||
R_WriteBitsLSB( block, bit, ep[1][c] >> 1, 7 );
|
||||
}
|
||||
R_WriteBitsLSB( block, bit, ep[0][0] & 1, 1 );
|
||||
R_WriteBitsLSB( block, bit, ep[1][0] & 1, 1 );
|
||||
|
||||
const int range = maxLuma - minLuma;
|
||||
const float projectionRange = maxProjection - minProjection;
|
||||
for ( int i = 0; i < 16; i++ ) {
|
||||
const int bx = i & 3;
|
||||
const int by = i >> 2;
|
||||
const int sx = ( x + bx < width ) ? x + bx : width - 1;
|
||||
const int sy = ( y + by < height ) ? y + by : height - 1;
|
||||
const byte *p = rgba + ( sy * width + sx ) * 4;
|
||||
int index = 0;
|
||||
if ( normalMap && projectionRange > 1.0e-6f ) {
|
||||
index = idMath::ClampInt( 0, 15, (int)( ( ( R_NormalProjection( p, axis ) - minProjection ) / projectionRange ) * 15.0f + 0.5f ) );
|
||||
} else if ( range > 0 ) {
|
||||
index = idMath::ClampInt( 0, 15, ( ( R_BC7Luma( p ) - minLuma ) * 15 + range / 2 ) / range );
|
||||
}
|
||||
|
||||
if ( i == 0 ) {
|
||||
R_WriteBitsLSB( block, bit, index & 7, 3 );
|
||||
} else {
|
||||
R_WriteBitsLSB( block, bit, index, 4 );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void R_EncodeBC7Image( const byte *rgba, int width, int height, textureDepth_t depth, idList<byte> &out ) {
|
||||
const int blocksWide = ( width + 3 ) / 4;
|
||||
const int blocksHigh = ( height + 3 ) / 4;
|
||||
out.SetNum( blocksWide * blocksHigh * 16 );
|
||||
|
||||
const bool normalMap = ( depth == TD_BUMP );
|
||||
byte *dst = out.Ptr();
|
||||
for ( int by = 0; by < blocksHigh; by++ ) {
|
||||
for ( int bx = 0; bx < blocksWide; bx++ ) {
|
||||
R_EncodeBC7Mode6Block( rgba, width, height, bx * 4, by * 4, normalMap, dst );
|
||||
dst += 16;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void R_AppendBytes( idList<byte> &dst, const void *src, int bytes ) {
|
||||
const int oldNum = dst.Num();
|
||||
dst.SetNum( oldNum + bytes );
|
||||
memcpy( dst.Ptr() + oldNum, src, bytes );
|
||||
}
|
||||
|
||||
static unsigned long R_GeneratedBC7CookVersion() {
|
||||
return DDS_MAKEFOURCC( 'B', '7', 'C', '3' );
|
||||
}
|
||||
|
||||
static bool R_GetDDSAverageColor( const byte *data, int len, float averageColor[4] ) {
|
||||
if ( len < 4 + (int)sizeof( ddsFileHeader_t ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const ddsFileHeader_t *header = (const ddsFileHeader_t *)( data + 4 );
|
||||
if ( LittleLong( header->dwReserved1[0] ) != DDS_MAKEFOURCC( 'A', 'V', 'G', '1' ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for ( int i = 0; i < 4; i++ ) {
|
||||
unsigned long bits = LittleLong( header->dwReserved1[1 + i] );
|
||||
memcpy( &averageColor[i], &bits, sizeof( float ) );
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool idImage::CheckGeneratedBC7Image( bool fullLoad, ID_TIME_T sourceTimestamp ) {
|
||||
if ( !globalImages->image_useBC7GeneratedTextures.GetBool() ||
|
||||
!R_DepthWantsGeneratedBC7( depth ) ||
|
||||
fileSystem->PerformingCopyFiles() ||
|
||||
!renderSystem->IsOpenGLRunning() ||
|
||||
cubeFiles != CF_2D ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
char filename[MAX_IMAGE_NAME];
|
||||
R_ImageProgramStringToGeneratedBC7FileName( imgName, depth, filename );
|
||||
|
||||
ID_TIME_T generatedTimestamp;
|
||||
fileSystem->ReadFile( filename, NULL, &generatedTimestamp );
|
||||
if ( generatedTimestamp == FILE_NOT_FOUND_TIMESTAMP ) {
|
||||
return false;
|
||||
}
|
||||
if ( sourceTimestamp != FILE_NOT_FOUND_TIMESTAMP && generatedTimestamp < sourceTimestamp ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
idFile *f = fileSystem->OpenFileRead( filename );
|
||||
if ( !f ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int len = f->Length();
|
||||
if ( len < 4 + (int)sizeof( ddsFileHeader_t ) + (int)sizeof( ddsFileHeaderDX10_t ) ) {
|
||||
fileSystem->CloseFile( f );
|
||||
return false;
|
||||
}
|
||||
if ( !fullLoad && len > globalImages->image_cacheMinK.GetInteger() * 1024 ) {
|
||||
len = globalImages->image_cacheMinK.GetInteger() * 1024;
|
||||
}
|
||||
|
||||
byte *data = (byte *)R_StaticAlloc( len );
|
||||
f->Read( data, len );
|
||||
fileSystem->CloseFile( f );
|
||||
|
||||
if ( LittleLong( *(unsigned long *)data ) != DDS_MAKEFOURCC( 'D', 'D', 'S', ' ' ) ) {
|
||||
R_StaticFree( data );
|
||||
return false;
|
||||
}
|
||||
const ddsFileHeader_t *header = (const ddsFileHeader_t *)( data + 4 );
|
||||
if ( LittleLong( header->dwReserved1[5] ) != R_GeneratedBC7CookVersion() ) {
|
||||
R_StaticFree( data );
|
||||
return false;
|
||||
}
|
||||
|
||||
UploadPrecompressedImage( data, len );
|
||||
timestamp = generatedTimestamp;
|
||||
|
||||
R_StaticFree( data );
|
||||
return true;
|
||||
}
|
||||
|
||||
bool idImage::CookGeneratedBC7Image( const byte *pic, int width, int height, ID_TIME_T sourceTimestamp ) {
|
||||
if ( !globalImages->image_cookBC7GeneratedTextures.GetBool() ||
|
||||
!globalImages->image_useBC7GeneratedTextures.GetBool() ||
|
||||
!R_DepthWantsGeneratedBC7( depth ) ||
|
||||
globalImages->keepPixelsResident ||
|
||||
cubeFiles != CF_2D ||
|
||||
!pic || width <= 0 || height <= 0 ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
R_SetImageAverageColor( this, pic, width, height );
|
||||
float sourceAverageColor[4];
|
||||
for ( int i = 0; i < 4; i++ ) {
|
||||
sourceAverageColor[i] = averageColor[i];
|
||||
}
|
||||
|
||||
bool preserveBorder = ( repeat == TR_CLAMP_TO_ZERO );
|
||||
int scaled_width = width;
|
||||
int scaled_height = height;
|
||||
GetDownsize( scaled_width, scaled_height );
|
||||
|
||||
byte *scaledBuffer = NULL;
|
||||
if ( scaled_width == width && scaled_height == height ) {
|
||||
scaledBuffer = (byte *)R_StaticAlloc( sizeof( unsigned ) * scaled_width * scaled_height );
|
||||
memcpy( scaledBuffer, pic, width * height * 4 );
|
||||
} else {
|
||||
scaledBuffer = R_MipMapForBC7Depth( pic, width, height, preserveBorder, depth );
|
||||
width >>= 1;
|
||||
height >>= 1;
|
||||
if ( width < 1 ) {
|
||||
width = 1;
|
||||
}
|
||||
if ( height < 1 ) {
|
||||
height = 1;
|
||||
}
|
||||
while ( width > scaled_width || height > scaled_height ) {
|
||||
byte *shrunk = R_MipMapForBC7Depth( scaledBuffer, width, height, preserveBorder, depth );
|
||||
R_StaticFree( scaledBuffer );
|
||||
scaledBuffer = shrunk;
|
||||
width >>= 1;
|
||||
height >>= 1;
|
||||
if ( width < 1 ) {
|
||||
width = 1;
|
||||
}
|
||||
if ( height < 1 ) {
|
||||
height = 1;
|
||||
}
|
||||
}
|
||||
scaled_width = width;
|
||||
scaled_height = height;
|
||||
}
|
||||
|
||||
if ( repeat == TR_CLAMP_TO_ZERO ) {
|
||||
byte rgba[4] = { 0, 0, 0, 255 };
|
||||
if ( depth == TD_BUMP ) {
|
||||
rgba[0] = 128;
|
||||
rgba[1] = 128;
|
||||
rgba[2] = 255;
|
||||
}
|
||||
R_SetBorderTexels( scaledBuffer, scaled_width, scaled_height, rgba );
|
||||
}
|
||||
if ( repeat == TR_CLAMP_TO_ZERO_ALPHA ) {
|
||||
byte rgba[4] = { 255, 255, 255, 0 };
|
||||
if ( depth == TD_BUMP ) {
|
||||
rgba[0] = 128;
|
||||
rgba[1] = 128;
|
||||
rgba[2] = 255;
|
||||
}
|
||||
R_SetBorderTexels( scaledBuffer, scaled_width, scaled_height, rgba );
|
||||
}
|
||||
|
||||
for ( int i = 0; i < 4; i++ ) {
|
||||
averageColor[i] = sourceAverageColor[i];
|
||||
}
|
||||
|
||||
idList<byte> fileBytes;
|
||||
fileBytes.SetGranularity( 1024 );
|
||||
const unsigned long magic = DDS_MAKEFOURCC( 'D', 'D', 'S', ' ' );
|
||||
|
||||
const int numLevels = NumLevelsForImageSize( scaled_width, scaled_height );
|
||||
ddsFileHeader_t header;
|
||||
memset( &header, 0, sizeof( header ) );
|
||||
header.dwSize = sizeof( header );
|
||||
header.dwFlags = DDSF_CAPS | DDSF_PIXELFORMAT | DDSF_WIDTH | DDSF_HEIGHT | DDSF_LINEARSIZE;
|
||||
header.dwHeight = scaled_height;
|
||||
header.dwWidth = scaled_width;
|
||||
header.dwPitchOrLinearSize = ( ( scaled_width + 3 ) / 4 ) * 16;
|
||||
header.dwMipMapCount = numLevels;
|
||||
header.dwFlags |= DDSF_MIPMAPCOUNT;
|
||||
header.dwCaps1 = DDSF_TEXTURE | DDSF_MIPMAP | DDSF_COMPLEX;
|
||||
header.ddspf.dwSize = sizeof( header.ddspf );
|
||||
header.ddspf.dwFlags = DDSF_FOURCC;
|
||||
header.ddspf.dwFourCC = DDS_MAKEFOURCC( 'D', 'X', '1', '0' );
|
||||
header.dwReserved1[0] = DDS_MAKEFOURCC( 'A', 'V', 'G', '1' );
|
||||
for ( int i = 0; i < 4; i++ ) {
|
||||
memcpy( &header.dwReserved1[1 + i], &averageColor[i], sizeof( float ) );
|
||||
}
|
||||
header.dwReserved1[5] = R_GeneratedBC7CookVersion();
|
||||
|
||||
ddsFileHeaderDX10_t header10;
|
||||
memset( &header10, 0, sizeof( header10 ) );
|
||||
header10.dxgiFormat = DDS_DXGI_FORMAT_BC7_UNORM;
|
||||
header10.resourceDimension = DDS_DIMENSION_TEXTURE2D;
|
||||
header10.arraySize = 1;
|
||||
|
||||
R_AppendBytes( fileBytes, &magic, sizeof( magic ) );
|
||||
R_AppendBytes( fileBytes, &header, sizeof( header ) );
|
||||
R_AppendBytes( fileBytes, &header10, sizeof( header10 ) );
|
||||
|
||||
byte *mipBuffer = scaledBuffer;
|
||||
int mipWidth = scaled_width;
|
||||
int mipHeight = scaled_height;
|
||||
for ( int level = 0; level < numLevels; level++ ) {
|
||||
idList<byte> encoded;
|
||||
R_EncodeBC7Image( mipBuffer, mipWidth, mipHeight, depth, encoded );
|
||||
R_AppendBytes( fileBytes, encoded.Ptr(), encoded.Num() );
|
||||
|
||||
if ( level + 1 < numLevels ) {
|
||||
byte *shrunk = R_MipMapForBC7Depth( mipBuffer, mipWidth, mipHeight, preserveBorder, depth );
|
||||
if ( mipBuffer != scaledBuffer ) {
|
||||
R_StaticFree( mipBuffer );
|
||||
}
|
||||
mipBuffer = shrunk;
|
||||
mipWidth >>= 1;
|
||||
mipHeight >>= 1;
|
||||
if ( mipWidth < 1 ) {
|
||||
mipWidth = 1;
|
||||
}
|
||||
if ( mipHeight < 1 ) {
|
||||
mipHeight = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ( mipBuffer != scaledBuffer ) {
|
||||
R_StaticFree( mipBuffer );
|
||||
}
|
||||
R_StaticFree( scaledBuffer );
|
||||
|
||||
char filename[MAX_IMAGE_NAME];
|
||||
R_ImageProgramStringToGeneratedBC7FileName( imgName, depth, filename );
|
||||
fileSystem->WriteFile( filename, fileBytes.Ptr(), fileBytes.Num(), "fs_savepath" );
|
||||
|
||||
UploadPrecompressedImage( fileBytes.Ptr(), fileBytes.Num() );
|
||||
if ( texnum != TEXTURE_NOT_LOADED ) {
|
||||
glTextureAverageColorQD3D12( texnum, averageColor[0], averageColor[1], averageColor[2], averageColor[3] );
|
||||
}
|
||||
timestamp = sourceTimestamp;
|
||||
precompressedFile = true;
|
||||
return texnum != TEXTURE_NOT_LOADED;
|
||||
}
|
||||
|
||||
/*
|
||||
==================
|
||||
NumLevelsForImageSize
|
||||
@@ -1430,6 +1945,8 @@ has completed
|
||||
*/
|
||||
void idImage::UploadPrecompressedImage( byte *data, int len ) {
|
||||
ddsFileHeader_t *header = (ddsFileHeader_t *)(data + 4);
|
||||
float ddsAverageColor[4];
|
||||
const bool hasDDSAverageColor = R_GetDDSAverageColor( data, len, ddsAverageColor );
|
||||
|
||||
// ( not byte swapping dwReserved1 dwReserved2 )
|
||||
header->dwSize = LittleLong( header->dwSize );
|
||||
@@ -1453,8 +1970,16 @@ void idImage::UploadPrecompressedImage( byte *data, int len ) {
|
||||
|
||||
// generate the texture number
|
||||
glGenTextures( 1, &texnum );
|
||||
if ( hasDDSAverageColor ) {
|
||||
for ( int i = 0; i < 4; i++ ) {
|
||||
averageColor[i] = ddsAverageColor[i];
|
||||
}
|
||||
glTextureAverageColorQD3D12( texnum, averageColor[0], averageColor[1], averageColor[2], averageColor[3] );
|
||||
}
|
||||
|
||||
int externalFormat = 0;
|
||||
const ddsFileHeaderDX10_t *header10 = NULL;
|
||||
int dataOffset = sizeof( ddsFileHeader_t ) + 4;
|
||||
|
||||
precompressedFile = true;
|
||||
|
||||
@@ -1478,6 +2003,22 @@ void idImage::UploadPrecompressedImage( byte *data, int len ) {
|
||||
case DDS_MAKEFOURCC( 'R', 'X', 'G', 'B' ):
|
||||
internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
|
||||
break;
|
||||
case DDS_MAKEFOURCC( 'D', 'X', '1', '0' ):
|
||||
if ( len < (int)( sizeof( ddsFileHeader_t ) + 4 + sizeof( ddsFileHeaderDX10_t ) ) ) {
|
||||
common->Warning( "Invalid DX10 DDS header\n" );
|
||||
return;
|
||||
}
|
||||
header10 = (const ddsFileHeaderDX10_t *)( data + sizeof( ddsFileHeader_t ) + 4 );
|
||||
switch ( LittleLong( header10->dxgiFormat ) ) {
|
||||
case DDS_DXGI_FORMAT_BC7_UNORM:
|
||||
internalFormat = GL_COMPRESSED_RGBA_BPTC_UNORM_ARB;
|
||||
break;
|
||||
default:
|
||||
common->Warning( "Unsupported DX10 DDS format %i\n", LittleLong( header10->dxgiFormat ) );
|
||||
return;
|
||||
}
|
||||
dataOffset += sizeof( ddsFileHeaderDX10_t );
|
||||
break;
|
||||
default:
|
||||
common->Warning( "Invalid compressed internal format\n" );
|
||||
return;
|
||||
@@ -1525,13 +2066,13 @@ void idImage::UploadPrecompressedImage( byte *data, int len ) {
|
||||
int skipMip = 0;
|
||||
GetDownsize( uploadWidth, uploadHeight );
|
||||
|
||||
byte *imagedata = data + sizeof(ddsFileHeader_t) + 4;
|
||||
byte *imagedata = data + dataOffset;
|
||||
|
||||
for ( int i = 0 ; i < numMipmaps; i++ ) {
|
||||
int size = 0;
|
||||
if ( FormatIsDXT( internalFormat ) ) {
|
||||
if ( FormatIsBlockCompressed( internalFormat ) ) {
|
||||
size = ( ( uw + 3 ) / 4 ) * ( ( uh + 3 ) / 4 ) *
|
||||
(internalFormat <= GL_COMPRESSED_RGBA_S3TC_DXT1_EXT ? 8 : 16);
|
||||
BlockBytesForInternalFormat( internalFormat );
|
||||
} else {
|
||||
size = uw * uh * (header->ddspf.dwRGBBitCount / 8);
|
||||
}
|
||||
@@ -1539,7 +2080,7 @@ void idImage::UploadPrecompressedImage( byte *data, int len ) {
|
||||
if ( uw > uploadWidth || uh > uploadHeight ) {
|
||||
skipMip++;
|
||||
} else {
|
||||
if ( FormatIsDXT( internalFormat ) ) {
|
||||
if ( FormatIsBlockCompressed( internalFormat ) ) {
|
||||
glCompressedTexImage2DARB( GL_TEXTURE_2D, i - skipMip, internalFormat, uw, uh, 0, size, imagedata );
|
||||
} else {
|
||||
glTexImage2D( GL_TEXTURE_2D, i - skipMip, internalFormat, uw, uh, 0, externalFormat, GL_UNSIGNED_BYTE, imagedata );
|
||||
@@ -1612,9 +2153,21 @@ void idImage::ActuallyLoadImage( bool checkForPrecompressed, bool fromBackEnd )
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ID_TIME_T sourceTimestamp = FILE_NOT_FOUND_TIMESTAMP;
|
||||
const bool bc7GeneratedWanted = globalImages->image_useBC7GeneratedTextures.GetBool() && R_DepthWantsGeneratedBC7( depth );
|
||||
if ( bc7GeneratedWanted ) {
|
||||
R_LoadImageProgram( imgName, NULL, NULL, NULL, &sourceTimestamp, NULL );
|
||||
if ( CheckGeneratedBC7Image( true, sourceTimestamp ) ) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// see if we have a pre-generated image file that is
|
||||
// already image processed and compressed
|
||||
if ( checkForPrecompressed && globalImages->image_usePrecompressedTextures.GetBool() ) {
|
||||
const bool shouldCookBC7FromSource = bc7GeneratedWanted &&
|
||||
globalImages->image_cookBC7GeneratedTextures.GetBool() &&
|
||||
sourceTimestamp != FILE_NOT_FOUND_TIMESTAMP;
|
||||
if ( !shouldCookBC7FromSource && checkForPrecompressed && globalImages->image_usePrecompressedTextures.GetBool() ) {
|
||||
if ( CheckPrecompressedImage( true ) ) {
|
||||
// we got the precompressed image
|
||||
return;
|
||||
@@ -1647,6 +2200,18 @@ void idImage::ActuallyLoadImage( bool checkForPrecompressed, bool fromBackEnd )
|
||||
// may not be strictly necessary, but some code uses it, so let's leave it in
|
||||
imageHash = MD4_BlockChecksum( pic, width * height * 4 );
|
||||
|
||||
if ( CookGeneratedBC7Image( pic, width, height, timestamp ) ) {
|
||||
if ( globalImages->keepPixelsResident ) {
|
||||
if ( residentPixels ) {
|
||||
R_StaticFree( residentPixels );
|
||||
}
|
||||
residentPixels = pic;
|
||||
} else {
|
||||
R_StaticFree( pic );
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
GenerateImage( pic, width, height, filter, allowDownSize, repeat, depth );
|
||||
timestamp = timestamp;
|
||||
precompressedFile = false;
|
||||
@@ -2173,6 +2738,12 @@ void idImage::Print() const {
|
||||
case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
|
||||
common->Printf( "DXT5 " );
|
||||
break;
|
||||
case GL_COMPRESSED_RGBA_BPTC_UNORM_ARB:
|
||||
common->Printf( "BC7 " );
|
||||
break;
|
||||
case GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB:
|
||||
common->Printf( "BC7s " );
|
||||
break;
|
||||
case GL_RGBA4:
|
||||
common->Printf( "RGBA4 " );
|
||||
break;
|
||||
|
||||