• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

[vJASS] Tree transparency

Status
Not open for further replies.
Level 25
Joined
Feb 2, 2006
Messages
1,683
For my mod/map I want to make trees or other big doodads (could also be destructibles if I had to replace them) transparent when a unit stands behind/near them.

What I have found about it is this and this.

In my map the trees are doodads and not destructables at the moment. Since the occlusion doesn't have anything to do with the transparency according to one of the posts and the occlusion works only for destructibles I am thinking about doodad animations.

My questions are now:
Is it the best way to make trees transparent when units get near it by adding a transparency animation to the tree model and playing it when the unit gets near?
If so how do I easily add such animations to a large number of tree models?
If so how do I write the vJass code? Periodic checks and
JASS:
SetDoodadAnimation()
for all trees or any range events?

What alternatives or existing systems are there?

edit:
Related JASS functions I've found:
JASS:
native          ShowDestructable            takes destructable d, boolean flag returns nothing

// these have nothing to do with transparency?
native          EnableOcclusion              takes boolean flag returns nothing
native          GetDestructableOccluderHeight takes destructable d returns real
native          SetDestructableOccluderHeight takes destructable d, real height returns nothing

native SetDoodadAnimation       takes real x, real y, real radius, integer doodadID, boolean nearestOnly, string animName, boolean animRandom returns nothing
native SetDoodadAnimationRect   takes rect r, integer doodadID, string animName, boolean animRandom returns nothing

// if I use units which I should not according to a post
native          SetUnitVertexColor  takes unit whichUnit, integer red, integer green, integer blue, integer alpha returns nothing
 
Yep, use SetDoodadAnimation.

Occlusion has to do with hiding surfaces/culling, as far as I know:
https://en.wikipedia.org/wiki/Hidden_surface_determination#Occlusion_culling

So it won't give that transparent effect you're looking for.

Assuming you have doodads with the necessary animations, it should be pretty easy to get a system working for it. Run a periodic timer and just set the animations of the doodads within his range. The main issue is resetting the animation of those doodads that are no longer in range, but it actually isn't that much of an issue (if the animation is instant!):
  • Run a periodic timer every X seconds. Store the unit's current coordinates (for each player).
  • In the timer callback, use call SetDoodadAnimation(...) on that stored value to reset the animation to the opaque tree.
  • Get the unit's new coordinates, store it, and use SetDoodadAnimation() to make the doodads in range become transparent.

I assume by "transparent" you mean to make them have some alpha value--a ghost-like effect. If you mean for them to be completely "hidden", then you can actually just input "show" or "hide" into SetDoodadAnimation().

If you have an animation that isn't instant or near-instant, then you'll have to make them destructables or units and store them between each tick, compare the two groups, and determine which ones are no longer in range.
 

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
Yeah native SetDoodadAnimation takes real x, real y, real radius, integer doodadID, boolean nearestOnly, string animName, boolean animRandom returns nothing works great.

The problem is that you need to revert the animation back afterwards.

So I recommend splitting the map up in tiles and then store those tiles in which you played a certain animation to revert it back afterwards if no unit is in these tiles.

Also, I coded a similar thing in the past. Let me see if I can find it.

EDIT: here you go.

This system uses destructables instead of doodads, though. Maybe you can use the doodad replacement tool in the editor to change your doodad trees into destructables? Should work, I did that in the past.

The system is set up so stand "stand" is the normal opaque state of the model. "stand alternate" is the transparent one. "birth alternate" is the transit animation from "stand" to "stand alternate" and "birth" turns it back to "stand" (so you can make it that trees fade out gradually instead of instantly).
Check out the model I attached to see how I made these animations.

The system hides trees within "fadedist" of the camera eye position (not target position; as you want trees in front transparent, not those in the back). For the default camera, this means trees at the screen borders are mostly opaque and trees at the center of the view are transparent. It also only updates 30 trees per interval, which leads to a neat little visual effect in which not all trees turn transparent at once. Imho, that looks and feels cooler.

JASS:
library TreeCrownSystem
    
globals
    private constant integer destructableID1 = 'B000'
    private constant integer destructableID2 = 'B001'
    private constant integer destructableID3 = 'B00R'
    private constant real fadedist = 900
    private constant real tick = 0.2
    private constant integer deltacount = 30
    private constant integer maxtrees = 200
    private destructable array d[maxtrees]
    private boolean array b[maxtrees]
    
    private integer maxcount = 0
    private integer lastcount = 0
endglobals    

private function filter takes nothing returns boolean
    return (GetDestructableTypeId(GetFilterDestructable()) == destructableID1 or GetDestructableTypeId(GetFilterDestructable()) == destructableID2 or GetDestructableTypeId(GetFilterDestructable()) == destructableID3)
endfunction
    
private function periodic takes nothing returns nothing
    local integer i = lastcount
    local integer j = lastcount+deltacount
    local real dx = 0
    local real dy = 0
    local real x = 0
    local real y = 0
    local real dist = 0
    if j<maxcount then
        set x = GetCameraEyePositionX()
        set y = GetCameraEyePositionY()
        loop
            exitwhen i>j
            set dx=GetDestructableX(d[i])-x
            set dy=GetDestructableY(d[i])-y
            if SquareRoot(dx*dx+dy*dy) > fadedist then
                if b[i] then //is faded out
                    set b[i] = false
                    call SetDestructableAnimation(d[i], "birth")
                    call QueueDestructableAnimation(d[i], "stand")
                endif
            else
                if not b[i] then //is faded in
                    set b[i] = true
                    call SetDestructableAnimation(d[i], "birth alternate")
                    call QueueDestructableAnimation(d[i], "stand alternate")
                endif
            endif
            set i=i+1
        endloop
    else
        set x = GetCameraEyePositionX()
        set y = GetCameraEyePositionY()
        loop
            exitwhen i>maxcount
            set dx=GetDestructableX(d[i])-x
            set dy=GetDestructableY(d[i])-y
            if SquareRoot(dx*dx+dy*dy) > fadedist then
                if b[i] then //is faded out
                    set b[i] = false
                    call SetDestructableAnimation(d[i], "birth")
                    call QueueDestructableAnimation(d[i], "stand")
                endif
            else
                if not b[i] then //is faded in
                    set b[i] = true
                    call SetDestructableAnimation(d[i], "birth alternate")
                    call QueueDestructableAnimation(d[i], "stand alternate")
                endif
            endif
            set i=i+1
        endloop
    endif
endfunction

private function register takes nothing returns nothing
    if maxcount < maxtrees then
        set d[maxcount] = GetEnumDestructable()
        set b[maxcount] = false
        set maxcount = maxcount + 1
    else
        call BJDebugMsg("ERROR: Maximum number of tree crowns for Tree Fade System reached!")
    endif
endfunction

public function initialize takes nothing returns nothing
    call EnumDestructablesInRect(bj_mapInitialPlayableArea, Condition(function filter), function register)
    call TimerStart(CreateTimer(), tick, true, function periodic)
endfunction    
    
endlibrary
 

Attachments

  • NewWorldTree.mdx
    62.1 KB · Views: 95
Last edited:
Level 25
Joined
Feb 2, 2006
Messages
1,683
Looks great but does it work for multiplayer (per player) as well?


For the reverting I think your first suggestion would be enough, to reset all transparencies of the last rect/radius and then to set the transparency for the current rect of doodads.
Then there would be no fading but I would not have to use destructables. But why should
JASS:
SetDoodadAnimation()
be slower than iterating through destructables?

I will have a look into your new model. How did you add the animation? Is there an easy copy/paste way for all tree MDL files?

edit:
I have added another sequence for transparency named "Stand Alternative". As in your model I have added two new layers using filter mode blend:
Code:
Sequences 2 {
	Anim "Stand" {
		Interval { 0, 999 },
	}
	Anim "Stand Alternate" {
		Interval { 1000, 2000 },
	}
}
Textures 2 {
	Bitmap {
		Image "Textures\Birkenrinde.blp",
	}
	Bitmap {
		Image "Textures\Birkenlaub.blp",
	}
}
Materials 2 {
	Material {
		Layer {
			FilterMode Blend,
			Alpha 2 {
				Linear,
				0: 1,
				1000: 0.15,
			}
			static TextureID 0,
		}
		Layer {
			FilterMode Transparent,
			Alpha 2 {
				Linear,
				0: 1,
				1000: 0,
			}
			TwoSided,
			static TextureID 0,
		}
	}
	Material {
		Layer {
			FilterMode Blend,
			Alpha 2 {
				Linear,
				0: 1,
				1000: 0.15,
			}
			static TextureID 1,
		}
		Layer {
			FilterMode Transparent,
			Alpha 2 {
				Linear,
				0: 1,
				1000: 0,
			}
			TwoSided,
			static TextureID 1,
		}
	}
}
It seems to work in the War3ModelEditor.
 
Last edited:

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
Looks great but does it work for multiplayer (per player) as well?
Yes.

For the reverting I think your first suggestion would be enough, to reset all transparencies of the last rect/radius and then to set the transparency for the current rect of doodads.
Then there would be no fading but I would not have to use destructables. But why should
JASS:
SetDoodadAnimation()
be slower than iterating through destructables?
SetDoodadAnimation is basicly a non-configurable Enumeration. Also, it will only set an animation for one doodad type. So you basicly have to do this for every different doodad type; which will add more and more stress on the game the more doodads have these animations. A destructable enumeration allows defining a custom filter (that can be designed to be O(1) via a lookup table), so the performance hit will be constant in your game, no matter how much of these models you have.
However, if you have only a low number of units (like 10-20) and a low number of doodad types you want to hide, then this approach would probably be the most performant:

Have a periodic timer; at each iteration:
- loop through all units that should trigger the transparency
- for each of those units:
--> if GetUnitX() and GetUnitY() != stored X and Y then:
----> use SetDoodadAnimation on the stored X and Y coordinates of the last iteration cycle and trigger the "stand" animation there
----> set X and Y coordinates = Position of the unit for use in the next cycle
----> use SetDoodadAnimation on the new X and Y coordinates to trigger the "stand alternate" animation there
--> endif
 
Level 25
Joined
Feb 2, 2006
Messages
1,683
Thanks a lot. Somehow I cannot increase your reputation :D

Does it work in multiplayer since the local values and animation do not cause any desyncs? GetCameraEyePositionX() and GetCameraEyePositionY() does return a different value for each player locally or does it not? And SetDestructableAnimation() can be called locally?
 
Animations can be done locally, yes (most purely visual updates can be done locally).

The thing you should test in your implementation is whether the animations are always performed. Zwiebelchen's method using the camera target position works fine because animations are guaranteed to be performed if they are in our field of view. If you are doing it based on unit position, then you may want to run tests to make sure it is performing properly regardless of whether the doodad/destructible is in your field of view (some engines will optimize to not render them at all. some engines might store them as states).

You can also do it based on both camera target and unit position (i.e. only run the script if the unit is in your field of view). Camera values are indeed local so just be careful with what you do with them.
 

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
You can also do it based on both camera target and unit position (i.e. only run the script if the unit is in your field of view). Camera values are indeed local so just be careful with what you do with them.
That is actually pretty clever. I haven't thought of that yet. I'll write a quick snippet based on that idea; give me a minute.


EDIT: Here you go

Register all the units you want to occlude doodads via:
  • Custom Script: call AddUnitOcclusion(unit)
Then register all the doodads you want to be occluded via the following custom script (enter the correct rawcodes):
  • Custom Script: call AddDoodadOcclusion('D000')

JASS:
library TreeOcclusion initializer init

globals
     private constant real OCCLUSION_RADIUS = 150 //defines the radius around the unit in which doodads are occluded
     private constant real CAMERA_TARGET_RADIUS = 1000 //defines the radius around the camera target in which doodads are occluded

     private integer array raw
     private integer rawcount = 0
     private real array X
     private real array Y
     private unit array U
     private integer unitcount = 0
endglobals

function AddDoodadOcclusion takes integer rawcode returns nothing
     set raw[rawcount] = rawcode
     set rawcount = rawcount+1
endfunction

function AddUnitOcclusion takes unit u returns nothing
     set U[unitcount] = u
     set unitcount = unitcount+1
endfunction

private function periodic takes nothing returns nothing
     local integer i = 0
     local integer j
     local real camX = GetCameraTargetPositionX()
     local real camY = GetCameraTargetPositionY()
     loop
          exitwhen i >= unitcount
          set j = 0
          loop
               exitwhen j >= rawcount
               call SetDoodadAnimation(X[i], Y[i], OCCLUSION_RADIUS, raw[j], "stand", false)
               set j = j+1
          endloop
          set i = i+1
     endloop
     set i = 0
     loop
          exitwhen i >= unitcount
          if GetUnitTypeId(U[i]) == 0 then
               //clean up removed units
               set unitcount = unitcount-1
               set U[i] = U[unitcount]
               set X[i] = X[unitcount]
               set Y[i] = Y[unitcount]
               set U[unitcount] = null
               set X[unitcount] = 0
               set Y[unitcount] = 0
               set i = i-1
          else
               if IsUnitInRangeXY(U[i], camX, camY, CAMERA_TARGET_RADIUS) then
                    if GetUnitX(U[i]) != X[i] or GetUnitY(U[i]) != Y[i] then
                         set X[i] = GetUnitX[U[i]]
                         set Y[i] = GetUnitY[U[i]]
                         set j = 0
                         loop
                              exitwhen j >= rawcount
                              call SetDoodadAnimation(X[i], Y[i], OCCLUSION_RADIUS, raw[j], "stand alternate", false)
                              set j = j+1
                         endloop
                    endif
               endif
          endif
          set i = i+1
     endloop
endfunction

private function init takes nothing returns nothing
     call TimerStart(CreateTimer(), 0.1, true, function periodic)
endfunction

endlibrary
 
Last edited:
Level 6
Joined
Jun 18, 2004
Messages
119
This last example will leave a tree transparent if the unit leaves the range because the "stand animation" also depends on the units range. If the set stand animation is removed from the if statement then the script will perform correctly.

You could however store whether a unit is in the camerafield and that way remove the need to always perform the stand animation, but im not sure if it is worth it performancewise.
 

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
This last example will leave a tree transparent if the unit leaves the range because the "stand animation" also depends on the units range. If the set stand animation is removed from the if statement then the script will perform correctly.

You could however store whether a unit is in the camerafield and that way remove the need to always perform the stand animation, but im not sure if it is worth it performancewise.
You're right. Didn't think of that case. I edited the code above to fix this particular case.

EDIT: Damn, there is another problem, though: multiple units will mess this approach up, as it will always try to rehide first, then show these doodads within the same unit enumeration; hiding and unhiding must be done on seperate unit loops, otherwise the visibility state will be messed up with multiple units on the map. I will try to change the concept a little to fix this.

EDIT2: Okay, the new version should definitely work (edited my post above). It's also way more performant now, as it uses an unordered unit stack instead of a group. Also, I got rid of the unit indexer dependency.
 
Last edited:

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
edit:
Shouldn't it be "Stand alternate" somewhere. You could define the animations as constants as well.
Yes, correct. The lower one should be "stand alternate". I missed that.

The system should work flawless for your case; however, there is still something that could be changed, for example, there is no need hiding a tree that is close to a unit but actually behind that unit. In other words; the system could be improved by taking the viewing angle of the camera into account. But it depends on what you want to do, honestly.

The system could actually be modified pretty easy for that, since all you have to do is polar-offseting the X and Y coordinates a bit into camera direction.
These offset values are simple mathematics:
JASS:
                         set dX = camX-GetCameraEyePositionX()
                         set dY = camY-GetCameraEyePositionY()
                         set camdist = GetCameraField(CAMERA_FIELD_TARGET_DISTANCE)
                         set XOffset = AVERAGE_DOODAD_HEIGHT*dX/(SquareRoot(camdist*camdist-dX*dX))
                         set YOffset = AVERAGE_DOODAD_HEIGHT*dY/(SquareRoot(camdist*camdist-dY*dY))

Here is a modified system that takes the camera angle into account:

JASS:
library TreeOcclusion initializer init

globals
     private constant real OCCLUSION_RADIUS = 150 //defines the radius around the unit in which doodads are occluded
     private constant real CAMERA_TARGET_RADIUS = 1000 //defines the radius around the camera target in which doodads are occluded
     private constant real AVERAGE_DOODAD_HEIGHT = 300

     private integer array raw
     private integer rawcount = 0
     private real array X
     private real array Y
     private unit array U
     private integer unitcount = 0
endglobals

function AddDoodadOcclusion takes integer rawcode returns nothing
     set raw[rawcount] = rawcode
     set rawcount = rawcount+1
endfunction

function AddUnitOcclusion takes unit u returns nothing
     set U[unitcount] = u
     set unitcount = unitcount+1
endfunction

private function periodic takes nothing returns nothing
     local integer i = 0
     local integer j
     local real camX = GetCameraTargetPositionX()
     local real camY = GetCameraTargetPositionY()
     local real dX = camX-GetCameraEyePositionX()
     local real dY = camY-GetCameraEyePositionY()
     local real camdist = GetCameraField(CAMERA_FIELD_TARGET_DISTANCE)
     local real XOffset = AVERAGE_DOODAD_HEIGHT*dX/(SquareRoot(camdist*camdist-dX*dX))
     local real YOffset = AVERAGE_DOODAD_HEIGHT*dY/(SquareRoot(camdist*camdist-dY*dY))
     loop
          exitwhen i >= unitcount
          set j = 0
          loop
               exitwhen j >= rawcount
               call SetDoodadAnimation(X[i], Y[i], OCCLUSION_RADIUS, raw[j], "stand", false)
               set j = j+1
          endloop
          set i = i+1
     endloop
     set i = 0
     loop
          exitwhen i >= unitcount
          if GetUnitTypeId(U[i]) == 0 then
               //clean up removed units
               set unitcount = unitcount-1
               set U[i] = U[unitcount]
               set X[i] = X[unitcount]
               set Y[i] = Y[unitcount]
               set U[unitcount] = null
               set X[unitcount] = 0
               set Y[unitcount] = 0
               set i = i-1
          else
               if IsUnitInRangeXY(U[i], camX, camY, CAMERA_TARGET_RADIUS) then
                    if GetUnitX(U[i]) != X[i] or GetUnitY(U[i]) != Y[i] then
                         set X[i] = GetUnitX[U[i]]+XOffset
                         set Y[i] = GetUnitY[U[i]]+YOffset
                         set j = 0
                         loop
                              exitwhen j >= rawcount
                              call SetDoodadAnimation(X[i], Y[i], OCCLUSION_RADIUS, raw[j], "stand alternate", false)
                              set j = j+1
                         endloop
                    endif
               endif
          endif
          set i = i+1
     endloop
endfunction

private function init takes nothing returns nothing
     call TimerStart(CreateTimer(), 0.1, true, function periodic)
endfunction

endlibrary
 
Last edited:
Status
Not open for further replies.
Top