• 🏆 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] VectorBase

Level 6
Joined
Jan 9, 2019
Messages
102
JASS:
library VectorBase requires Alloc
//== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ====
//  _______________
//   #  VectorBase
//          v4.1b, by Overfrost
//	----------------------------
//
//    - basically implements Cartesian coordinate system (x,y,z)
//
//    - but also spherical system (s,p,t) - sphere-radius, azimuth, zenith angle
//
//    - and also cylindrical system (r,p,z) - radius, azimuth, z
//
//    - for 2d, both Cartesian(x,y) and polar(r,p) systems can be used
//
//    - only (x,y,z) are actually stored, all others are computed on the fly
//
//    - has two available struct-modules for implementing vector-usage
//      and one struct for basic vector uses
//
//	_______________________
//	 #	Technical Details:
//
//	  - as mentioned above, only stores (x,y,z) values, but also the boolean .inverted
//		which helps in storing (theta) that gets within (PI, 2*PI) range, because
//		(x,y,z) alone can only handle (theta) within [0, PI] range and not outside that
//
//	  - only (theta) assignments can automatically change .inverted, this includes .rotate
//
//	  - other assignments do change (theta), but they always change (theta) to a value
//		within [0, PI] range, therefore never triggering any inversions, moreover,
//		if .inverted is already true, these assignments will not revert .inverted to false
//
//	  - angles are in radians
//
//	  - more info at (hiveworkshop.com/threads/vectorbase.311604)
//
//  ______________
//   #  Requires:
//
//    - Alloc (Best)
//          github.com/nestharus/JASS/tree/master/jass/Systems/Alloc/Standard
//
//== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ====
//! novjass

    //
    module VectorBase
        
        //--------------
        // Coordinates
        //
        real x
        real y
        real z
		//

        //-------------
		// Magnitudes
		//
        real s  // sigma, spherical radius, 3d length
        real r  // rho, polar radius, hypotenuse of (x,y)
		//

		//-------------
		// Directions
		//
        real p  // phi, azimuth, xy-angle
        real t  // theta, declination angle, zr-angle
        // - .t is 0 if the vector is directly facing towards the sky
        // - both are in radians, and preserves 3d-magnitude
        //
		boolean inverted
		// - only affects .p and .t and doesn't affect (x,y,z)
		// - if .inverted is true:
		//	 - .p returns the opposite direction of what it's supposed to return
		//	 - .t refers to the inverted .p instead of the actual .p of (x,y,z)
		// - .inverted automatically becomes true when .t gets within (PI, 2*PI) range
		//	 and automatically becomes false when it gets out from that range
		//

	//---------------
	// Helper Macro
	//
	textmacro VECTOR_BASE_RESET takes INSTANCE
	// - sets .x, .y, .z of INSTANCE to 0, and .inverted to false
	// - the other fields are pseudo fields, so there's no need to reset them
	// - to be used inside a function
	// - (this is completely optional and here for ease-of-use only)
	//

    //
    module VectorExt
        implement VectorBase

        //---------------------
        // 3D Multi-assigners
        //
        method xyz takes real x, real y, real z returns thistype(this)
        method spt takes real s, real p, real t returns thistype(this)
        method rpz takes real r, real p, real z returns thistype(this)
        // - spt and rpz are faster than their separated counterparts
        //

        //---------------------
        // 2D Multi-assigners
        //
        method xy takes real x, real y returns thistype(this)
        method rp takes real r, real p returns thistype(this)
        // - both ignores z completely
        //

		//------------------
		// Quick-assigners
		//
		method revert takes nothing returns thistype(this)
		// - sets .inverted to false
		//
		method invert takes nothing returns thistype(this)
		// - inverts .inverted (e.g. true -> false or false -> true)
		//

        //----------
        // Rotator
        //
        method rotate takes real p, real t returns thistype(this)
        // - rotates the vector by both phi and theta
        // - if t = 0, the rotation will only affect (x,y)
        //

    //
    struct Vector extends array
        implement VectorExt

		//----------------
		// Static Fields
		//
		readonly static thistype temp
		// - a global instance to be used locally
		// - must not be deallocated
		//

        //-------------
        // Allocators
        //
        static method create takes nothing returns thistype
        //
        static method unit2 takes real p returns thistype
        static method unit3 takes real p, real t returns thistype
        // - both creates and returns a vector unit
        // - the vector unit has phi/theta as directions and 1 as sphere-radius
        //

		//---------------
		// Deallocators
		//
        method lock takes nothing returns thistype(this)
        method unlock takes nothing returns thistype
        // - unlock replaces destroy, it returns 0 if it destroys, (this) otherwise
        //

//! endnovjass
//== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ====

//
module VectorBase

	//----------------
    // stored fields
    real x
    real y
    real z
	//
	boolean inverted

	//-------------
	// macroscope
//! textmacro P_VECTOR_BASE_MACROSCOPE takes S, R, P, T
		//----------------
		// pseudo fields
		method operator s takes nothing returns real
			return $S$
		endmethod
		method operator r takes nothing returns real
			return $R$
		endmethod
		//
		method operator p takes nothing returns real
			if (inverted) then
				return $P$ + 3.141592654
			endif
			return $P$
		endmethod
		method operator t takes nothing returns real
			if (inverted) then
				return 6.283185307 - $T$
			endif
			return $T$
		endmethod

		//----------------------
		// magnitude assigners
		method operator s= takes real aS returns nothing
			local real lP = $P$
			local real lT = $T$
			//
			set x = aS*Sin(lT)*Cos(lP)
			set y = aS*Sin(lT)*Sin(lP)
			set z = aS*Cos(lT)
		endmethod
		method operator r= takes real aR returns nothing
			local real lS = $S$
			local real lP = $P$
			//
			set x = aR*Cos(lP)
			set y = aR*Sin(lP)
			set z = SquareRoot(lS*lS - aR*aR)
		endmethod

		//----------------------
		// direction assigners
		method operator p= takes real aP returns nothing
			local real lR = $R$
			//
			if (inverted) then
				set aP = aP + 3.141592654
			endif
			//
			set x = lR*Cos(aP)
			set y = lR*Sin(aP)
		endmethod
		method operator t= takes real aT returns nothing
			local real lS = $S$
			local real lP
			//
			if (inverted) then
				set lP = $P$ + 3.141592654
			else
				set lP = $P$
			endif
			//
		//
//! endtextmacro
//! runtextmacro P_VECTOR_BASE_MACROSCOPE("SquareRoot(x*x + y*y + z*z)", "SquareRoot(x*x + y*y)", "Atan2(y, x)", "Atan2(SquareRoot(x*x + y*y), z)")
		//
        set x = lS*Sin(aT)*Cos(lP)
        set y = lS*Sin(aT)*Sin(lP)
        set z = lS*Cos(aT)
		//
	//! textmacro P_VECTOR_BASE_INVERT takes T
			//
			set $T$ = $T$ - R2I($T$*.159154943)*6.283185307  // .159154943 = 1 / 2*PI
			if ($T$ < 0) then
				set $T$ = $T$ + 6.283185307
			endif
			//
			if ($T$ > 3.141592654) then
				set inverted = true
			else
				set inverted = false
			endif
			//
	//! endtextmacro
	//! runtextmacro P_VECTOR_BASE_INVERT("aT")
    endmethod
endmodule
module VectorExt
    implement VectorBase

	//---------------------
    // 3d multi-assigners
    method xyz takes real aX, real aY, real aZ returns thistype
        set x = aX
        set y = aY
        set z = aZ
        //
        return this
    endmethod
    //
    method spt takes real aS, real aP, real aT returns thistype
	//! textmacro P_VECTOR_BASE_SET_SPT takes S_MUL, P, T
			//
			set x = $S_MUL$Sin($T$)*Cos($P$)
			set y = $S_MUL$Sin($T$)*Sin($P$)
			set z = $S_MUL$Cos($T$)
			//
	//! endtextmacro
	//! runtextmacro P_VECTOR_BASE_SET_SPT("aS*", "aP", "aT")
        //
	//! runtextmacro P_VECTOR_BASE_INVERT("aT")
		//
        return this
    endmethod
    //
    method rpz takes real aR, real aP, real aZ returns thistype
	//! textmacro P_VECTOR_BASE_SET_RPZ takes R_MUL, P, Z
			//
			set x = $R_MUL$Cos($P$)
			set y = $R_MUL$Sin($P$)
			set z = $Z$
			//
	//! endtextmacro
	//! runtextmacro P_VECTOR_BASE_SET_RPZ("aR*", "aP", "aZ")
        //
        return this
    endmethod

	//---------------------
    // 2d multi-assigners
    method xy takes real aX, real aY returns thistype
        set x = aX
        set y = aY
        //
        return this
    endmethod
    //
    method rp takes real aR, real aP returns thistype
		set x = aR*Cos(aP)
		set y = aR*Sin(aP)
        //
        return this
    endmethod

	//----------------------------
	// inversion quick-assigners
	method revert takes nothing returns thistype
		set inverted = false
		//
		return this
	endmethod
	method invert takes nothing returns thistype
		set inverted = (not inverted)
		//
		return this
	endmethod

	//----------
    // rotator
    method rotate takes real aP, real aT returns thistype
        local real lZ = z
        local real lR = SquareRoot(x*x + y*y)
        local real lP = Atan2(y, x) + aP
		//
		if (inverted) then
			set aT = -aT
		endif
		//
        set  z = lZ*Cos(aT) - lR*Sin(aT)
        set lR = lZ*Sin(aT) + lR*Cos(aT)
        //
        set x = lR*Cos(lP)
        set y = lR*Sin(lP)
        //
		if (inverted) then
			set lP = lP - R2I((lP + 3.141592654)*.159154943)*6.283185307 + 3.141592654
		else
			set lP = lP - R2I(lP*.159154943)*6.283185307
		endif
		//
		set lZ = Atan2(y, x)
		if (lZ == lP or (lZ + 6.283185307) == lP or (lZ - 6.283185307) == lP) then
			set inverted = false
		else
			set inverted = true
		endif
		//
        return this
    endmethod
endmodule

//
//! textmacro VECTOR_BASE_RESET takes INSTANCE
		//
		set $INSTANCE$.x = 0
		set $INSTANCE$.y = 0
		set $INSTANCE$.z = 0
		//
		set $INSTANCE$.inverted = false
		//
//! endtextmacro

//
struct Vector extends array
    implement Alloc
    implement VectorExt

	//----------------
	// temp instance
	static method operator temp takes nothing returns thistype
		return 0
	endmethod //inlines

    //
    private integer pLock

    //
    static method create takes nothing returns thistype
	//! textmacro P_VECTOR_BASE_CREATE
			//
			local thistype this = allocate()
			set pLock = 0
			//
	//! endtextmacro
	//! runtextmacro P_VECTOR_BASE_CREATE()
		//
	//! runtextmacro VECTOR_BASE_RESET("this")
        //
        return this
    endmethod
    //
    static method unit2 takes real aP returns thistype
	//! runtextmacro P_VECTOR_BASE_CREATE()
		//
	//! runtextmacro P_VECTOR_BASE_SET_RPZ("", "aP", "0")
		//
        return this
    endmethod
    static method unit3 takes real aP, real aT returns thistype
	//! runtextmacro P_VECTOR_BASE_CREATE()
		//
	//! runtextmacro P_VECTOR_BASE_SET_SPT("", "aP", "aT")
	//! runtextmacro P_VECTOR_BASE_INVERT("aT")
		//
        return this
    endmethod

    //
    method lock takes nothing returns thistype
        set pLock = pLock + 1
		//
		return this
    endmethod
    method unlock takes nothing returns thistype
        set pLock = pLock - 1
        if (pLock < 0) then
            call deallocate()
            return 0
        endif
        return this
    endmethod
endstruct

endlibrary
JASS:
scope WeirdElementalOddity initializer pgInit

//
globals
	private timer pgTimer = CreateTimer()
	//
	private effect pgElec
	private effect pgCold  // note that this one's effect jumps around on its own already
	private effect pgFire
	//
	private Vector pgBase
	private Vector pgMoon
	private Vector pgLune
	//
	private real pgPhi = 0
endglobals

//
private function pgOnExpire takes nothing returns nothing
	set pgPhi = pgPhi + 0.01
	call Vector.temp.rpz(400, pgPhi, 0)
	//
	set pgBase.t = pgBase.t + 0.02
	set pgBase.p = pgBase.p + 0.3
	//
	call pgMoon.rotate( 0.4, 0.03)  // these and above produce the same result
	call pgLune.rotate(-0.5, 0.04)
	//
	call BlzSetSpecialEffectPosition(pgElec, pgBase.x + Vector.temp.x, pgBase.y + Vector.temp.y, pgBase.z + 200)
	call BlzSetSpecialEffectPosition(pgCold, pgMoon.x + Vector.temp.x, pgMoon.y + Vector.temp.y, pgMoon.z + 200)
	call BlzSetSpecialEffectPosition(pgFire, pgLune.x + Vector.temp.x, pgLune.y + Vector.temp.y, pgLune.z + 200)
endfunction

//
private function pgInit takes nothing returns nothing
	set pgElec = AddSpecialEffect("Abilities\\Weapons\\FarseerMissile\\FarseerMissile.mdl", 32256, 32256)
	set pgCold = AddSpecialEffect("Abilities\\Weapons\\ZigguratFrostMissile\\ZigguratFrostMissile.mdl", 32256, 32256)
	set pgFire = AddSpecialEffect("Abilities\\Spells\\Other\\BreathOfFire\\BreathOfFireDamage.mdl", 32256, 32256)
	//
	set pgBase = Vector.create().spt(40, 0, 0)
	set pgMoon = Vector.create().rp(60, 0)
	set pgLune = Vector.create().rp(50, 3.141592654)
	//
	call TimerStart(pgTimer, 0.03125, true, function pgOnExpire)
	//
	call FogEnable(false)
	call FogMaskEnable(false)
endfunction

endscope
Changelog:
  • v4.1b: Optimized t assignment a little.
  • v4.1a:
    - Re-optimized the script by using textmacros instead of function calls.
    - Optimized temp.
  • v4.0a:
    - Swapped the terms phi and theta, and also rho and side. This includes renaming most of the API.
    - Renamed VECTOR_BASE_INSTANCE_RESET to VECTOR_BASE_RESET.
    - Removed getR, getT, getP, and getS.
    - Added temp as a shared local instance.
    - Changed the return type of lock and unlock to thistype.
  • v3.0b: Fixed t and p assignment methods.
  • v3.0a:
    - Added inverted, invert, and revert.
    - Added phi's directional inversion system to allow more practical uses of rotate.
    - Also added an optional textmacro for initializing VectorBase's default values.
    - Rewritten some of the documentation and comments.
  • v2.2c: Added important section in documentation.
  • v2.2b: Renamed private/local variables to longer names.
  • v2.2a: Changed requirement from AllocFast to Alloc.
  • v2.1a: Added static methods getR, getT, getP, and getS.
  • v2.0a: Removed all module methods but rotate and multi-assigners.
  • v1.0b: Reworded some terms in documentation to better clarify things.
References:
 
Last edited:

Jampion

Code Reviewer
Level 15
Joined
Mar 25, 2016
Messages
1,327
I noticed some problems with changing phi. I tried rotate, p= and rtp. Only rtp seems to work without issues.
I used the following code to test it:
JASS:
scope Test initializer Init

   globals
       private effect fx
       private Vector origin
       private Vector spherePos
       private unit target
       
       private real currentR
       private real currentT
       private real currentP

   endglobals
   
   private function Test takes nothing returns nothing
       local real x
       local real y
       local real z

       
       call origin.xyz(GetUnitX(target), GetUnitY(target), BlzGetLocalUnitZ(target) + GetUnitFlyHeight(target))
       
       set currentP = currentP + 0.1
       
       //rotate
       //call spherePos.rotate(0, 0.1)
       
       //operator
       //set spherePos.p = currentP
       
       //rtp
       call spherePos.rtp(currentR, currentT, currentP)
       
       
       
       set x = origin.x + spherePos.x
       set y = origin.y + spherePos.y
       set z = origin.z + spherePos.z
       call BlzSetSpecialEffectPosition(fx, x, y, z)
       call BJDebugMsg(R2S(x) + ", " + R2S(y) + ", " + R2S(z))
   endfunction

   private function Init takes nothing returns nothing
       set origin = Vector.create()
       set spherePos = Vector.create()
       set target = CreateUnit(Player(0), 'hgry', 0, 0, 0)
       call origin.xyz(0,0,0)
       set fx = AddSpecialEffect("Units\\NightElf\\Wisp\\Wisp.mdx", 0, 0)
       call TimerStart(CreateTimer(), 0.03, true, function Test)
       set currentR = 100
       set currentT = 90 * bj_DEGTORAD
       set currentP = 90 * bj_DEGTORAD
       call spherePos.rtp(currentR, currentT, currentP)
   endfunction

endscope

With rotate(0,0.1) the effect jumps around, but doesn't rotate. Using rotate with only theta it works without issues.
With p= the wisp goes from p=0 to p=180 down normally. Then when going from p=180 to p=360 it "splits" into two wisps. Basically some function seems to change the sign of the y coordinate that makes the effect jump from -y to y really fast.
I haven't seen any issues with .rtp().


Some other things:
Do you plan to add a function to add two vectors?
I think it would be a good idea to note that angles use radians, even if that's the standard.
 
Level 6
Joined
Jan 9, 2019
Messages
102
With rotate(0,0.1) the effect jumps around, but doesn't rotate.
It's not due to a bug in rotate, it's because of the fact that the system stores Spherical coordinates only in (x,y,z), therefore it can't store any excess phi, nor any inversion. rotate always rotates phi downwards due to this, rotating upwards can be done by using negative values. After a vector is rotated to (phi > 3.14..), only (x,y,z) values of the coordinate is stored, therefore the next time it is rotated, it will forget the fact that it previously had (phi > 3.14..), causing the continuous back-and-forth in the bottom of the orbit.

When I first tested the system, I encountered that too. But I figured I shouldn't change anything there. Though adding a fourth hidden boolean that state that the vector's phi is inverted might be a good workaround. I have thought about adding this, and still don't fancy this at all, because theta would be inverted along with it which produces unnecessary complications and inaccuracy. I believe that spherical coordinate system itself doesn't rely on (phi > 3.14..), so can I say no in "fixing" rotate that doesn't even need any fixing?

Do you plan to add a function to add two vectors?
I think it would be a good idea to note that angles use radians, even if that's the standard.
I personally think that add/sub/scale/flip/copy are highly unnecessary. One can simply do .xyz/etc instead of those, with better flexibility. They were around though.

EDIT:
Updated to v2.2c. (2/2/2019)
 
Last edited:

Jampion

Code Reviewer
Level 15
Joined
Mar 25, 2016
Messages
1,327
so can I say no in "fixing" rotate that doesn't even need any fixing?
If there is a good reason to have a function not work in certain situations, it's ok to have it like that. You should however make sure to explain when it won't work as expected. It would also be nice to suggest a way to get the desired effect.
I assume the p= operator has a similar issue, when you have phi > 3.14.
 
Level 6
Joined
Jan 9, 2019
Messages
102
I assume the p= operator has a similar issue, when you have phi > 3.14.
Yes, it will flip the vector just like how rotate would.

You should however make sure to explain when it won't work as expected.
I'll try re-explaining this mess later.

EDIT:
After thinking it through for the last time, I finally implemented the inversion system. While theta can get affected, as long as phi-manipulations are under control, there should be less harm than benefit from it.

Updated to v3.0b. (5/2/2019)
 
Last edited:

Jampion

Code Reviewer
Level 15
Joined
Mar 25, 2016
Messages
1,327
From the tests I made I couldn't find any problems. The rotations now work as one would expect.
I'm not familiar enough with all the maths used to comment on it, but I haven't found any issues or odd behaviour, so it should be correct.

I find some of the text macros make the system hard to read, because they are defined inside a function body, but called from another function, that does not reference the function in which the text macro is defined.
e.g. unit2 runs P_VECTOR_BASE_SET_RPZ, which is defined inside the rpz method
I assume you do it that way, because it saves you the function call of rpz in that case.
 
Level 6
Joined
Jan 9, 2019
Messages
102
Yeah I shouldn't have written it that way, CnP should work better for it.

This one is quite outdated though, and I didn't even bother updating it lol. Here's the latest version of this, Vector. I haven't written public docs for it, but its API list exists, and quite long at it.

I haven't bothered uploading any of my github stuffs here because as you can see, no proper docs yet.
 
Top