Uncle
Warcraft Moderator
- Joined
- Aug 10, 2018
- Messages
- 6,714
That's a really cool idea, but I wonder if at this point it's more efficient to just rely on Units?
if Debug then Debug.beginFile "SpecialEffectShadows" end
do
--[[
=============================================================================================================================================================
Special Effect Shadows
by Antares
Requires:
PrecomputedHeightMap (optional) https://www.hiveworkshop.com/threads/precomputed-synchronized-terrain-height-map.353477/
TotalInitialization https://www.hiveworkshop.com/threads/total-initialization.317099/
=============================================================================================================================================================
Allows the creation of objects represented by special effects that cast shadows. Shadows are represented by images. The functions to manipulate shadow-casting
effects are analogous to the default special effect natives, for example BlzSetSpecialEffectPosition -> SetShadowedEffectPosition. All functions for which the
analog native is safe to use asynchronously can also safely be used asynchronously.
The AddShadowedEffect function requires a path for the shadow texture. There are two preset constants: UNIT_SHADOW_PATH is the standard shadow that ground
units use. FLYER_SHADOW_PATH is the shadow that flying units use. Building shadows have a weird format and cannot be used without converting them first.
=============================================================================================================================================================
Functions
=============================================================================================================================================================
AddShadowedEffect(modelPath, x, y, shadowPath, shadowWidth, shadowHeight, xOffset, yOffset) -> effect
DestroyShadowedEffect(whichEffect)
SetShadowedEffectPosition(whichEffect, x, y, z)
SetShadowedEffectX(whichEffect, x)
SetShadowedEffectY(whichEffect, y)
SetShadowedEffectZ(whichEffect, z)
SetShadowedEffectAlpha(whichEffect, alpha) Fades the effect as well as the shadow.
=============================================================================================================================================================
Config
=============================================================================================================================================================
]]
local SUN_ZENITH_ANGLE = 45 ---@type number
--Determines the direction of the sun light in degrees. 90 = sun at the zenith. Does not affect shadow shape.
local SUN_AZIMUTH_ANGLE = 45 ---@type number
--Determines the direction of the sun light in degrees. 0 = shadows cast in positive x-direction. Does not affect shadow shape.
local MAXIMUM_ALPHA = 200 ---@type integer
--The maximum opacity of the shadow image (0-255).
local SHADOW_ATTENUATION = 0.05 ---@type number
--How quickly a shadow becomes weakened as an object moves upwards. The shadow will fade completely at Z = shadowSize/SHADOW_ATTENUATION.
--===========================================================================================================================================================
local mt = {__mode = "k"}
local shadow = setmetatable({}, mt) ---@type image[]
local currentX = setmetatable({}, mt) ---@type number[]
local currentY = setmetatable({}, mt) ---@type number[]
local currentZ = setmetatable({}, mt) ---@type number[]
local shadowOffsetX = setmetatable({}, mt) ---@type number[]
local shadowOffsetY = setmetatable({}, mt) ---@type number[]
local shadowAttenuation = setmetatable({}, mt) ---@type number[]
local currentAlpha = setmetatable({}, mt) ---@type integer[]
local currentWidth = setmetatable({}, mt) ---@type number[]
local currentHeight = setmetatable({}, mt) ---@type number[]
local zenithOffsetX = 1/math.tan(bj_DEGTORAD*SUN_ZENITH_ANGLE)*math.cos(bj_DEGTORAD*SUN_AZIMUTH_ANGLE)
local zenithOffsetY = 1/math.tan(bj_DEGTORAD*SUN_ZENITH_ANGLE)*math.sin(bj_DEGTORAD*SUN_AZIMUTH_ANGLE)
local GetLocZ = nil ---@type function
local moveableLoc = nil ---@type location
UNIT_SHADOW_PATH = "ReplaceableTextures\\Shadows\\Shadow.blp"
FLYER_SHADOW_PATH = "ReplaceableTextures\\Shadows\\ShadowFlyer.blp"
---Creates a special effect and adds a shadow. shadowWidth/shadowHeight is the size at scale 1. zOffset is the z-coordinate at which the shadow-casting effect is considered on the ground. This value is used to determine the shadow position dependent on the sun's zenith angle.
---@param modelPath string
---@param x number
---@param y number
---@param shadowPath string
---@param shadowWidth number
---@param shadowHeight number
---@param xOffset? number
---@param yOffset? number
---@return effect
function AddShadowedEffect(modelPath, x, y, shadowPath, shadowWidth, shadowHeight, xOffset, yOffset)
local effect = AddSpecialEffect(modelPath, x, y)
shadow[effect] = CreateImage(shadowPath, shadowWidth, shadowHeight, 0, x + (xOffset or 0) - 0.5*shadowWidth, y + (yOffset or 0) - 0.5*shadowHeight, 0, 0, 0, 0, 1)
SetImageRenderAlways(shadow[effect], true)
SetImageColor(shadow[effect], 255, 255, 255, MAXIMUM_ALPHA)
SetImageAboveWater(shadow[effect], false, true)
currentWidth[effect] = shadowWidth
currentHeight[effect] = shadowHeight
shadowOffsetX[effect] = xOffset or 0
shadowOffsetY[effect] = yOffset or 0
shadowAttenuation[effect] = SHADOW_ATTENUATION/math.max(shadowHeight, shadowWidth)
currentAlpha[effect] = MAXIMUM_ALPHA
currentX[effect], currentY[effect] = x, y
currentZ[effect] = GetLocZ(x, y)
return effect
end
---@param whichEffect effect
function DestroyShadowedEffect(whichEffect)
DestroyEffect(whichEffect)
DestroyImage(shadow[whichEffect])
end
---@param whichEffect effect
---@param x number
---@param y number
---@param z number
function SetShadowedEffectPosition(whichEffect, x, y, z)
BlzSetSpecialEffectPosition(whichEffect, x, y, z)
currentX[whichEffect], currentY[whichEffect], currentZ[whichEffect] = x, y, z
local terrainZ = GetLocZ(x, y)
local dz = (z - terrainZ)
local alpha = (currentAlpha[whichEffect]*(1 - (shadowAttenuation[whichEffect]*dz))) // 1
if alpha < 0 then
alpha = 0
end
SetImageColor(shadow[whichEffect], 255, 255, 255, alpha)
x = x + shadowOffsetX[whichEffect] + zenithOffsetX*dz
y = y + shadowOffsetY[whichEffect] + zenithOffsetY*dz
SetImagePosition(shadow[whichEffect], x + shadowOffsetX[whichEffect] - 0.5*currentWidth[whichEffect], y + shadowOffsetY[whichEffect] - 0.5*currentHeight[whichEffect], 0)
end
---@param whichEffect effect
---@param x number
function SetShadowedEffectX(whichEffect, x)
SetShadowedEffectPosition(whichEffect, x, currentY[whichEffect], currentZ[whichEffect])
end
---@param whichEffect effect
---@param y number
function SetShadowedEffectY(whichEffect, y)
SetShadowedEffectPosition(whichEffect, currentX[whichEffect], y, currentZ[whichEffect])
end
---@param whichEffect effect
---@param z number
function SetShadowedEffectZ(whichEffect, z)
SetShadowedEffectPosition(whichEffect, currentX[whichEffect], currentY[whichEffect], z)
end
---@param whichEffect effect
---@param alpha integer
function SetShadowedEffectAlpha(whichEffect, alpha)
local x = currentX[whichEffect]
local y = currentY[whichEffect]
local z = currentZ[whichEffect]
local terrainZ = GetLocZ(x, y)
currentAlpha[whichEffect] = alpha
BlzSetSpecialEffectAlpha(whichEffect, alpha)
alpha = (currentAlpha[whichEffect]*(1 - (shadowAttenuation[whichEffect]*(z - terrainZ)))) // 1
if alpha < 0 then
alpha = 0
end
SetImageColor(shadow[whichEffect], 255, 255, 255, alpha)
end
OnInit.final(function()
local precomputedHeightMap = Require.optionally "PrecomputedHeightMap"
if precomputedHeightMap then
GetLocZ = _G.GetTerrainZ
else
moveableLoc = Location(0, 0)
GetLocZ = function(x, y)
MoveLocation(moveableLoc, x, y)
return GetLocationZ(moveableLoc)
end
end
end)
end