Uncle
Warcraft Moderator
- Joined
- Aug 10, 2018
- Messages
- 7,866
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
corresponding 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
if Debug then Debug.beginFile "SpecialEffectShadows" end
do
--[[
=============================================================================================================================================================
Special Effect Shadows (alternate version)
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/
Hook https://www.hiveworkshop.com/threads/hook.339153/
=============================================================================================================================================================
Allows the creation of objects represented by special effects that cast shadows. Shadows are represented by images. This version uses Hooks to modify the
BlzSetSpecialEffect natives to also affect the attached shadow, if it exists.
To add a shadow to an effect, use AddShadowToEffect. You need to know the coordinates of the special effect for the shadow to be created at the right position.
However, even if you don't know the location, any update of its location with BlzSetSpecialEffectPosition will update the shadow automatically.
The AddShadowToEffect 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
=============================================================================================================================================================
AddShadowToEffect(whichEffect, x, y, shadowPath, shadowWidth, shadowHeight, xOffset, yOffset)
=============================================================================================================================================================
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"
---Add a shadow to an existing special effect. 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 whichEffect effect
---@param x number
---@param y number
---@param shadowPath string
---@param shadowWidth number
---@param shadowHeight number
---@param xOffset? number
---@param yOffset? number
function AddShadowToEffect(whichEffect, x, y, shadowPath, shadowWidth, shadowHeight, xOffset, yOffset)
shadow[whichEffect] = 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[whichEffect], true)
SetImageColor(shadow[whichEffect], 255, 255, 255, MAXIMUM_ALPHA)
SetImageAboveWater(shadow[whichEffect], false, true)
currentWidth[whichEffect] = shadowWidth
currentHeight[whichEffect] = shadowHeight
shadowOffsetX[whichEffect] = xOffset or 0
shadowOffsetY[whichEffect] = yOffset or 0
shadowAttenuation[whichEffect] = SHADOW_ATTENUATION/math.max(shadowHeight, shadowWidth)
currentAlpha[whichEffect] = MAXIMUM_ALPHA
currentX[whichEffect], currentY[whichEffect] = x, y
currentZ[whichEffect] = GetLocZ(x, y)
end
---@param whichEffect effect
function Hook:DestroyEffect(whichEffect)
if shadow[whichEffect] then
DestroyImage(shadow[whichEffect])
end
self.old(whichEffect)
end
---@param whichEffect effect
---@param x number
---@param y number
---@param z number
function Hook:BlzSetSpecialEffectPosition(whichEffect, x, y, z)
self.old(whichEffect, x, y, z)
if shadow[whichEffect] then
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
end
---@param whichEffect effect
---@param x number
function Hook:BlzSetSpecialEffectX(whichEffect, x)
if shadow[whichEffect] then
BlzSetSpecialEffectPosition(whichEffect, x, currentY[whichEffect], currentZ[whichEffect])
else
self.old(whichEffect, x)
end
end
---@param whichEffect effect
---@param y number
function Hook:BlzSetSpecialEffectY(whichEffect, y)
if shadow[whichEffect] then
BlzSetSpecialEffectPosition(whichEffect, currentX[whichEffect], y, currentZ[whichEffect])
else
self.old(whichEffect, y)
end
end
---@param whichEffect effect
---@param z number
function Hook:BlzSetSpecialEffectZ(whichEffect, z)
if shadow[whichEffect] then
BlzSetSpecialEffectPosition(whichEffect, currentX[whichEffect], currentY[whichEffect], z)
else
self.old(whichEffect, z)
end
end
---@param whichEffect effect
---@param alpha integer
function Hook:BlzSetSpecialEffectAlpha(whichEffect, alpha)
BlzSetSpecialEffectAlpha(whichEffect, alpha)
if shadow[whichEffect] then
local x = currentX[whichEffect]
local y = currentY[whichEffect]
local z = currentZ[whichEffect]
local terrainZ = GetLocZ(x, y)
currentAlpha[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
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