Skip to content

Instantly share code, notes, and snippets.

@L-e-x-o-n
Created June 24, 2023 19:28
Show Gist options
  • Save L-e-x-o-n/1d585fa46c5827501407b081d54f2aa8 to your computer and use it in GitHub Desktop.
Save L-e-x-o-n/1d585fa46c5827501407b081d54f2aa8 to your computer and use it in GitHub Desktop.
Draws the spherical commblast range onto the ground updated for comupdate
function widget:GetInfo()
return {
name = "Commblast Range GL4",
desc = "Draws the spherical commblast range onto the ground",
author = "Beherith", --Updated for commander update by Lexon
date = "2022.08.27",
license = "Lua code: GNU GPL, v2 or later, Shader GLSL code: (c) Beherith ([email protected])",
layer = -1,
enabled = true,
}
end
local commblastSphereShader = nil
local glTexture = gl.Texture
local glCulling = gl.Culling
local glDepthTest = gl.DepthTest
local spGetUnitDefID = Spring.GetUnitDefID
local spGetUnitPosition = Spring.GetUnitPosition
local spGetGroundHeight = Spring.GetGroundHeight
local spGetUnitsInCylinder = Spring.GetUnitsInCylinder
local spIsGUIHidden = Spring.IsGUIHidden
local spIsSphereInView = Spring.IsSphereInView
local spGetMyTeamID = Spring.GetMyTeamID
local diag = math.diag
---- Config stuff ------------------
local commanders = {} -- keyed as {unitID : {draw = bool, oldopacity = 0.0, newopacity = 1.0}}
local commDefIds = { [UnitDefNames['corcom'].id] = true, [UnitDefNames['armcom'].id] = true}
local blastRadius = 350 -- com explosion
local showOnEnemyDistance = 370
local fadeInDistance = 160
---- GL4 Config stuff ----------------
local shaderConfig = {
FULLRADIUS = 500,
SPHERESEGMENTS = 8,
BLASTRADIUS = blastRadius,
OPACITYMULTIPLIER = 0.75,
TEAMCOLORED = 1,
BLASTDMG = 5000,
}
---- Object intersection test http://www.realtimerendering.com/intersections.html
-- Shaders
local vsSrc = [[
#version 420
#extension GL_ARB_uniform_buffer_object : require
#extension GL_ARB_shader_storage_buffer_object : require
#extension GL_ARB_shading_language_420pack: require
// This shader is (c) Beherith ([email protected])
#line 5000
layout (location = 0) in vec4 position; // l w rot and maxalpha
layout (location = 1) in vec3 normals;
layout (location = 2) in vec2 uvs;
layout (location = 3) in vec4 params_alphastart_alphaend_gameframe;
layout (location = 4) in uvec4 instData;
//__ENGINEUNIFORMBUFFERDEFS__
//__DEFINES__
layout(std140, binding = 0) readonly buffer MatrixBuffer {
mat4 mat[];
};
struct SUniformsBuffer {
uint composite; // u8 drawFlag; u8 unused1; u16 id;
uint unused2;
uint unused3;
uint unused4;
float maxHealth;
float health;
float unused5;
float unused6;
vec4 speed;
vec4[5] userDefined; //can't use float[20] because float in arrays occupies 4 * float space
};
layout(std140, binding=1) readonly buffer UniformsBuffer {
SUniformsBuffer uni[];
};
#line 10000
#define SNORM2NORM(value) (value * 0.5 + 0.5)
out DataVS {
flat vec3 v_centerpos; // xyz and radius?
flat vec4 v_teamcolor; // red or teamcolor, and alpha modifier
noperspective vec2 v_screenUV;
};
out float hp;
void main()
{
uint teamIndex = (instData.z & 0x000000FFu); //leftmost ubyte is teamIndex
vec4 teamCol = teamColor[teamIndex];
mat4 worldMatrix = mat[instData.x];
// if the unit is not visible, then just transform the sphere to 0
if ((uni[instData.y].composite & 0x00001fu) == 0u ) worldMatrix = mat4(0.0);
v_centerpos = worldMatrix[3].xyz;
vec4 worldPos = vec4(1.0);
worldPos.xyz = position.xyz * FULLRADIUS;
worldPos = worldMatrix * worldPos;
gl_Position = cameraViewProj * worldPos;
v_screenUV = SNORM2NORM(gl_Position.xy / gl_Position.w);
vec3 camPos = cameraViewInv[3].xyz;
// TODO:
// modulate alpha based on time from params_alpha_health
float time = timeInfo.x + timeInfo.w ;
float alphasmooth = clamp((time - params_alphastart_alphaend_gameframe.z) / 15.0, 0.0, 1.0);
alphasmooth = mix(params_alphastart_alphaend_gameframe.x, params_alphastart_alphaend_gameframe.y, alphasmooth);
// modulate alpha based on health
float damagedness = 1.0 - clamp( uni[instData.y].health/ uni[instData.y].maxHealth, 0, 1);
// modulate alpha based on distance from camera
float distanceToCamera = length(camPos - worldMatrix[3].xyz);
distanceToCamera = clamp((distanceToCamera -2000)/1000,0,1);
v_teamcolor.rgb = vec3(1.0, 0.0, 0.0);
#if (TEAMCOLORED == 1)
v_teamcolor.rgb = teamCol.rgb;
#endif
hp = uni[instData.y].health;
v_teamcolor.a = alphasmooth + damagedness - distanceToCamera;
}
]]
local fsSrc = [[
#version 420
#extension GL_ARB_uniform_buffer_object : require
#extension GL_ARB_shading_language_420pack: require
// This shader is (c) Beherith ([email protected])
//__ENGINEUNIFORMBUFFERDEFS__
//__DEFINES__
#line 30000
uniform float iconDistance;
in DataVS {
flat vec3 v_centerpos; // xyz and radius?
flat vec4 v_teamcolor; // red or teamcolor, and alpha modifier
noperspective vec2 v_screenUV;
};
uniform sampler2D mapDepths;
out vec4 fragColor;
float distancetocomm = 0;
float lineblast(float radius, float width, float strength) {
float linestrength = clamp(width - abs(distancetocomm - radius),0,1);
return linestrength * strength;
}
in float hp;
#line 31000
void main(void)
{
float mapdepth = texture(mapDepths, v_screenUV).x;
// Transform screen-space depth to world-space position
vec4 mapWorldPos = vec4( vec3(v_screenUV.xy * 2.0 - 1.0, mapdepth), 1.0);
mapWorldPos = cameraViewProjInv * mapWorldPos;
mapWorldPos.xyz = mapWorldPos.xyz / mapWorldPos.w; // YAAAY this works!
vec3 mapToComm = v_centerpos.xyz - mapWorldPos.xyz;
distancetocomm = length(mapToComm);
float radius = BLASTRADIUS - ((hp * BLASTRADIUS) / BLASTDMG);
if (distancetocomm > BLASTRADIUS) fragColor.a = 0.0 ;
fragColor.rgba = vec4(1.0, 0.0, 0.0, 0.0);
fragColor.rgb = v_teamcolor.rgb;
fragColor.a += lineblast(radius, 2, 0.55);
fragColor.a *= OPACITYMULTIPLIER;
fragColor.a *= v_teamcolor.a;
}
]]
local luaShaderDir = "LuaUI/Widgets/Include/"
local LuaShader = VFS.Include(luaShaderDir.."LuaShader.lua")
VFS.Include(luaShaderDir.."instancevbotable.lua")
local commblastSphereVBO
local function initFogGL4(shaderConfig, DPATname)
if commblastSphereShader then
commblastSphereShader:Finalize()
end
local engineUniformBufferDefs = LuaShader.GetEngineUniformBufferDefs()
vsSrc = vsSrc:gsub("//__ENGINEUNIFORMBUFFERDEFS__", engineUniformBufferDefs)
vsSrc = vsSrc:gsub("FULLRADIUS", shaderConfig.FULLRADIUS)
vsSrc = vsSrc:gsub("TEAMCOLORED", shaderConfig.TEAMCOLORED)
fsSrc = fsSrc:gsub("//__ENGINEUNIFORMBUFFERDEFS__", engineUniformBufferDefs)
fsSrc = fsSrc:gsub("BLASTRADIUS", shaderConfig.BLASTRADIUS)
fsSrc = fsSrc:gsub("OPACITYMULTIPLIER", shaderConfig.OPACITYMULTIPLIER)
fsSrc = fsSrc:gsub("BLASTDMG", shaderConfig.BLASTDMG)
commblastSphereShader = LuaShader(
{
vertex = vsSrc:gsub("//__DEFINES__", "#define MYGRAVITY " .. tostring(Game.gravity + 0.1)),
fragment = fsSrc:gsub("//__DEFINES__", "#define USE_STIPPLE " .. tostring(0)),
uniformInt = {
mapDepths = 0,
},
uniformFloat = {
fadeDistance = 300000,
},
shaderConfig = shaderConfig
},
"Commblast Range GL4"
)
commblastSphereShader:Initialize()
if not commblastSphereShader then
Spring.Echo("Error: Commblast Range GL4 shader not initialized")
widgetHandler:RemoveWidget()
return
end
local sphereVBO, _, sphereIndexVBO, _ = makeSphereVBO(shaderConfig.SPHERESEGMENTS, shaderConfig.SPHERESEGMENTS/2, 1)
DrawPrimitiveAtUnitVBO = makeInstanceVBOTable(
{
{id = 3, name = 'params_alphastart_alphaend_gameframe', size = 4},
{id = 4, name = 'instData', type = GL.UNSIGNED_INT, size = 4},
},
64, -- maxelements
DPATname .. "VBO", -- name,
4 --unitIDattribID
)
DrawPrimitiveAtUnitVBO:makeVAOandAttach(sphereVBO,DrawPrimitiveAtUnitVBO.instanceVBO, sphereIndexVBO)
return DrawPrimitiveAtUnitVBO, commblastSphereShader
end
local cacheTable = {0,0,0,0,0,0,0,0}
local function AddBlastSphere(unitID, noUpload, oldopacity, newopacity, gameframe)
--Spring.Echo("Added a unit")
cacheTable[1] = oldopacity
cacheTable[2] = newopacity
cacheTable[3] = gameframe
return pushElementInstance(commblastSphereVBO,
cacheTable,
unitID, true, noUpload, unitID)
end
function widget:RecvLuaMsg(msg, playerID)
--Spring.Echo("widget:RecvLuaMsg",msg)
if msg:sub(1,18) == 'LobbyOverlayActive' then
chobbyInterface = (msg:sub(1,19) == 'LobbyOverlayActive1')
end
end
function widget:DrawWorldPreUnit()
if chobbyInterface then return end
if spIsGUIHidden() then return end
if commblastSphereShader.shaderObj ~= nil and commblastSphereVBO.usedElements > 0 then
--Spring.Echo(commblastSphereVBO.usedElements)
glCulling(GL.FRONT)
glDepthTest(GL.LEQUAL)
glDepthTest(false)
gl.DepthMask(false)
gl.Texture(0, "$map_gbuffer_zvaltex")
commblastSphereShader:Activate()
commblastSphereVBO:Draw()
commblastSphereShader:Deactivate()
glTexture(0, false)
glCulling(false)
glDepthTest(false)
end
end
local myTeamID
--- Look how easy api_unit_tracker is to use!
function widget:VisibleUnitAdded(unitID, unitDefID, unitTeam)
--Spring.Echo("VisibleUnitAdded", unitID, unitDefID, unitTeam)
if commDefIds[unitDefID] and unitTeam == myTeamID then
commanders[unitID] = {draw = false, oldopacity = 0.0, newopacity = 0.0}
end
end
function widget:VisibleUnitsChanged(extVisibleUnits, extNumVisibleUnits)
clearInstanceTable(commblastSphereVBO) -- clear all instances
for unitID, unitDefID in pairs(extVisibleUnits) do
widget:VisibleUnitAdded(unitID, unitDefID)
end
end
function widget:VisibleUnitRemoved(unitID) -- remove the corresponding ground plate if it exists
commanders[unitID] = nil
if commblastSphereVBO.instanceIDtoIndex[unitID] then
popElementInstance(commblastSphereVBO, unitID)
end
end
function widget:GameFrame(n)
-- This is where we update the alphas of the spheres
-- also based on health!
-- we gotta do this every 15 frames, and do interpolation in-shader for optimum efficiency
-- check com movement, also try to draw as little as possible!
-- api_unit_tracker will take care of removing invisible commanders for us
-- but we have to add/update them here manually!
if n%15 ~= 0 then return end
-- in the first pass, identify which ones we want to draw, and set their new opacities accordingly
-- do not draw if any:
-- comm above ground
-- not in view
-- no enemies in range
for unitID, params in pairs(commanders) do
local x, y, z = spGetUnitPosition(unitID)
local draw = false
local newopacity = 0
if x then
local groundHeight = spGetGroundHeight(x,z)
draw = true
if y - groundHeight > 10 then
draw = false
elseif not spIsSphereInView(x,y,z,blastRadius) then
draw = false
end
-- Spring.Echo(draw, groundHeight)
if draw then
draw = false
local nearbyEnemyUnits = spGetUnitsInCylinder(x, z, showOnEnemyDistance + fadeInDistance, Spring.ENEMY_UNITS)
if nearbyEnemyUnits then
for _, v in pairs(nearbyEnemyUnits) do
if commDefIds[spGetUnitDefID(v)] then
local ex,ey,ez = spGetUnitPosition(v)
if ex then
local distance = diag(x-ex, y-ey, z-ez)
if distance < (showOnEnemyDistance + fadeInDistance) then
draw = true
newopacity = 1.0 - math.min(1.0, math.max(0.0, (distance - showOnEnemyDistance) / fadeInDistance))
end
end
end
break
end
end
end
end
params.draw = draw
params.oldopacity = params.newopacity
params.newopacity = newopacity
end
clearInstanceTable(commblastSphereVBO)
local gameframe = Spring.GetGameFrame()
for unitID, params in pairs(commanders) do
if params.draw then
--Spring.Echo("Drawing blastsphere for", unitID, params.draw,params.newopacity, params.oldopacity)
AddBlastSphere(unitID, true, params.oldopacity, params.newopacity, gameframe)
end
end
uploadAllElements(commblastSphereVBO)
end
function widget:Initialize()
myTeamID = spGetMyTeamID()
commblastSphereVBO, commblastSphereShader = initFogGL4(shaderConfig, "commblastSpheres")
--Spring.Echo(Spring.HaveShadows(),"advshad",Spring.HaveAdvShading())
if WG['unittrackerapi'] and WG['unittrackerapi'].visibleUnits then
widget:VisibleUnitsChanged(WG['unittrackerapi'].visibleUnits, nil)
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment