Uncle
Warcraft Moderator
- Joined
- Aug 10, 2018
- Messages
- 6,658
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/
SpecialEffectShadow.mdl
flaresimple_bw.blp
=============================================================================================================================================================
Allows the creation of objects represented by special effects that cast shadows. Shadows are themselves represented by special effects. 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.
If the shadowWidth is greater than the shadowHeight, the shadow will be stretched in x-direction. If the shadowHeight is greater, the shadow will be stretched
in 45° direction.
=============================================================================================================================================================
Functions
=============================================================================================================================================================
AddShadowedEffect(modelPath, x, y, shadowWidth, shadowHeight) -> effect
DestroyShadowedEffect(whichEffect)
SetShadowedEffectPosition(whichEffect, x, y, z)
SetShadowedEffectX(whichEffect, x)
SetShadowedEffectY(whichEffect, y)
SetShadowedEffectZ(whichEffect, z)
SetShadowedEffectAlpha(whichEffect, alpha)
SetShadowedEffectScale(whichEffect, scale)
=============================================================================================================================================================
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 SHADOW_OFFSET_X = 0.25 ---@type number
--Displacement of the shadow-center in x-direction as a fraction of shadow size when the object casting the shadow is directly above the ground.
local SHADOW_OFFSET_Y = 0.15 ---@type number
--Displacement of the shadow-center in y-direction as a fraction of shadow size when the object casting the shadow is directly above the ground.
local SHADOW_OFFSET_Z = 0.1 ---@type number
--Displacement of the shadow in z-direction as a fraction of shadow size. A higher value prevents clipping on jagged terrain.
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 shadow = {} ---@type effect[]
local currentX = {} ---@type number[]
local currentY = {} ---@type number[]
local currentZ = {} ---@type number[]
local shadowOffsetX = {} ---@type number[]
local shadowOffsetY = {} ---@type number[]
local shadowOffsetZ = {} ---@type number[]
local shadowAttenuation = {} ---@type number[]
local currentAlpha = {} ---@type integer[]
local currentWidth = {} ---@type number[]
local currentHeight = {} ---@type number[]
local currentScale = {} ---@type number[]
local heightOffset = {} ---@type number[]
local zenithOffsetX = math.tan(bj_DEGTORAD*SUN_ZENITH_ANGLE)*math.cos(bj_DEGTORAD*SUN_AZIMUTH_ANGLE)
local zenithOffsetY = math.tan(bj_DEGTORAD*SUN_ZENITH_ANGLE)*math.sin(bj_DEGTORAD*SUN_AZIMUTH_ANGLE)
local GetLocZ = nil ---@type function
local moveableLoc = nil ---@type location
local atan2 = math.atan
---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 shadowWidth number
---@param shadowHeight number
---@param zOffset? number
function AddShadowedEffect(modelPath, x, y, shadowWidth, shadowHeight, zOffset)
local effect = AddSpecialEffect(modelPath, x, y)
shadow[effect] = AddSpecialEffect("SpecialEffectShadow.mdl", x, y)
BlzSetSpecialEffectTimeScale(shadow[effect], 0)
if shadowHeight > shadowWidth then
BlzSetSpecialEffectTime(shadow[effect], 1 + 0.25*(shadowHeight/shadowWidth - 1))
BlzSetSpecialEffectScale(shadow[effect], shadowWidth/200)
shadowOffsetY[effect] = SHADOW_OFFSET_Y*shadowWidth
shadowOffsetX[effect] = SHADOW_OFFSET_X*shadowWidth
shadowOffsetZ[effect] = SHADOW_OFFSET_Z*shadowHeight
shadowAttenuation[effect] = SHADOW_ATTENUATION/shadowWidth
else
BlzSetSpecialEffectTime(shadow[effect], 1 - 0.25*(shadowWidth/shadowHeight - 1))
BlzSetSpecialEffectScale(shadow[effect], shadowHeight/200)
shadowOffsetY[effect] = SHADOW_OFFSET_Y*shadowHeight
shadowOffsetX[effect] = SHADOW_OFFSET_X*shadowHeight
shadowOffsetZ[effect] = SHADOW_OFFSET_Z*shadowWidth
shadowAttenuation[effect] = SHADOW_ATTENUATION/shadowHeight
end
currentX[effect], currentY[effect] = x + shadowOffsetX[effect], y + shadowOffsetY[effect]
currentZ[effect] = GetLocZ(x, y)
BlzSetSpecialEffectPosition(shadow[effect], currentX[effect], currentY[effect], currentZ[effect])
heightOffset[effect] = zOffset or 0
currentAlpha[effect] = 255
currentScale[effect] = 1
currentWidth[effect] = shadowWidth
currentHeight[effect] = shadowHeight
return effect
end
---@param whichEffect effect
function DestroyShadowedEffect(whichEffect)
DestroyEffect(whichEffect)
DestroyEffect(shadow[whichEffect])
shadow[whichEffect] = nil
currentX[whichEffect], currentY[whichEffect], currentZ[whichEffect] = nil, nil, nil
shadowOffsetX[whichEffect], shadowOffsetY[whichEffect], shadowOffsetZ[whichEffect] = nil, nil, nil
currentAlpha[whichEffect], currentScale[whichEffect], currentWidth[whichEffect], currentHeight[whichEffect] = nil, nil, nil, nil
heightOffset[whichEffect], shadowAttenuation[whichEffect] = nil, nil
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
BlzSetSpecialEffectAlpha(shadow[whichEffect], alpha)
x = x + shadowOffsetX[whichEffect] + zenithOffsetX*(dz - heightOffset[whichEffect])
y = y + shadowOffsetY[whichEffect] + zenithOffsetY*(dz - heightOffset[whichEffect])
terrainZ = GetLocZ(x, y)
BlzSetSpecialEffectPosition(shadow[whichEffect], x, y, terrainZ + shadowOffsetZ[whichEffect])
local gradientX = (GetLocZ(x + 16, y) - GetLocZ(x - 16, y))*0.0313
local gradientY = (GetLocZ(x, y + 16) - GetLocZ(x, y - 16))*0.0313
BlzSetSpecialEffectPitch(shadow[whichEffect], -atan2(gradientX, 1))
BlzSetSpecialEffectRoll(shadow[whichEffect], atan2(gradientY, 1))
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
BlzSetSpecialEffectAlpha(shadow[whichEffect], alpha)
end
---@param whichEffect effect
---@param scale number
function SetShadowedEffectScale(whichEffect, scale)
local x = currentX[whichEffect]
local y = currentY[whichEffect]
local z = currentZ[whichEffect]
local terrainZ = GetLocZ(x, y)
BlzSetSpecialEffectScale(whichEffect, scale)
currentWidth[whichEffect] = currentWidth[whichEffect]*scale/currentScale[whichEffect]
currentHeight[whichEffect] = currentHeight[whichEffect]*scale/currentScale[whichEffect]
currentScale[whichEffect] = scale
if currentHeight[whichEffect] > currentWidth[whichEffect] then
BlzSetSpecialEffectScale(shadow[whichEffect], currentWidth[whichEffect]/200)
shadowOffsetY[whichEffect] = SHADOW_OFFSET_Y*currentWidth[whichEffect]
shadowOffsetX[whichEffect] = SHADOW_OFFSET_X*currentWidth[whichEffect]
shadowOffsetZ[whichEffect] = SHADOW_OFFSET_Z*currentHeight[whichEffect]
shadowAttenuation[whichEffect] = SHADOW_ATTENUATION/currentWidth[whichEffect]
else
BlzSetSpecialEffectScale(shadow[whichEffect], currentHeight[whichEffect]/200)
shadowOffsetY[whichEffect] = SHADOW_OFFSET_Y*currentHeight[whichEffect]
shadowOffsetX[whichEffect] = SHADOW_OFFSET_X*currentHeight[whichEffect]
shadowOffsetZ[whichEffect] = SHADOW_OFFSET_Z*currentWidth[whichEffect]
shadowAttenuation[whichEffect] = SHADOW_ATTENUATION/currentHeight[whichEffect]
end
local dz = (z - terrainZ)
local alpha = (currentAlpha[whichEffect]*(1 - (shadowAttenuation[whichEffect]*dz))) // 1
if alpha < 0 then
alpha = 0
end
BlzSetSpecialEffectAlpha(shadow[whichEffect], alpha)
x = x + shadowOffsetX[whichEffect] + zenithOffsetX*(dz - heightOffset[whichEffect])
y = y + shadowOffsetY[whichEffect] + zenithOffsetY*(dz - heightOffset[whichEffect])
BlzSetSpecialEffectPosition(shadow[whichEffect], x, y, GetLocZ(x, y) + shadowOffsetZ[whichEffect])
end
OnInit.main(function()
local precomputedHeightMap = Require.optionally "PrecomputedHeightMap"
if precomputedHeightMap then
GetLocZ = GetCliffAdjustedZ
else
moveableLoc = Location(0, 0)
GetLocZ = function(x, y)
MoveLocation(moveableLoc, x, y)
return GetLocationZ(moveableLoc)
end
end
end)
end