Being a Fool trying to be Cool with Quaternions [WORKED AROUND]

Ask about ACS, DECORATE, ZScript, or any other scripting questions here!
Forum rules
Before asking on how to use a ZDoom feature, read the ZDoom wiki first. If you still don't understand how to use a feature, then ask here.

Please bear in mind that the people helping you do not automatically know how much you know. You may be asked to upload your project file to look at. Don't be afraid to ask questions about what things mean, but also please be patient with the people trying to help you. (And helpers, please be patient with the person you're trying to help!)
User avatar
kinker31
Posts: 70
Joined: Mon Feb 05, 2018 12:18 am
Operating System Version (Optional): Arch, Linux-Zen
Graphics Processor: ATI/AMD with Vulkan/Metal Support
Location: Does anyone actually put anything serious here?

Being a Fool trying to be Cool with Quaternions [WORKED AROUND]

Post by kinker31 »

Context/Main Problem
I'm currently trying to make a replacement for A_FireBullets and A_FireProjectile that doesn't deal with any gimbal-locking business.
While the method posted below does work for player hitscans (though I might've forgotten to add the flash function?), projectile aiming is extremely wonky when I test it out.

I don't know if it's because I'm applying the calculations wrong when it comes to projectiles, or if I'm not using A_FireProjectile right, or if it's something else entirely.
I'll see if I can figure out a way to get the function to work in the meantime, but help on this would be mundo-appreciated.

Also, on a lesser note, is there a better, more efficient way to go about the implementations below?

Relevant Code Blocks
A quaternion called playerAngle exists outside the two functions to save on copy/pasting.

Code: Select all

//pDir: Player Direction, amplitude: Random Devation Mode
static vector3, vector3 GimbalUnlocker(quat pDir, vector2 direction, vector2 offset, bool amplitude = false)
{
	quat offy;
	vector3 up = (0, 0, 1), forwards = (1, 0, 0), left = (0, 1, 0), finalDir, finalOff, offTemp;
	if(amplitude)
	{
		double temppyX = frandom(-direction.x, direction.x);
		double temppyY = frandom(-direction.y, direction.y);
		offy = (Quat.AxisAngle(up, temppyX)) * (Quat.AxisAngle(left, temppyY));
	}
	else
	{
		offy = (Quat.AxisAngle(up, direction.x)) * (Quat.AxisAngle(left, direction.y));
	}
	offTemp = pDir * (offset.x, offset.y, 0);
	finalOff = level.Vec3Offset((0, 0, 0), offTemp);
	finalDir = pDir * offy * forwards;
	return finalDir, finalOff;
}

Code: Select all

action void WeponBoolet(int num, uint dam, uint modu, 
					vector2 deviation, bool nomAmmo = true, 
					uint ammoNom = 1, bool explicit = false,
					bool flashy = true, bool soundy = true, 
					vector2 offsets = (0, 0))
{
	if(nomAmmo && !invoker.DepleteAmmo(invoker.bAltFire, true, ammoNom)) {return;}
		
	if(flashy) {PlayerPawn(self).PlayAttacking2();}
	if(soundy) {A_StartSound(player.ReadyWeapon.AttackSound, CHAN_WEAPON);}

	uint iterations = abs(num); if(num == 0) {iterations = 1;}
		
	double autoPitch = BulletSlope(), ying, yang;
	invoker.playerAngle = Quat.FromAngles(angle, autoPitch, roll);
	vector3 dirry, ofsetty;
		
	bool firstOne = (num == 1) && (!player.refire);
		
	for(uint i = 0; i < iterations; i++)
	{
		int finalDam = dam * random(1, modu);
		ying = 0; yang = 0;
		bool applyS = ((i != 0) && !firstOne && (num != 0)) && !explicit;
		[dirry, ofsetty] = GimbalUnlocker(invoker.playerAngle, deviation, offsets, applyS);
		ying = atan2(dirry.y, dirry.x); yang = -asin(dirry.z);
		LineAttack(ying, PLAYERMISSILERANGE, yang, finalDam, 'Hitscan', 
				"BulletPuff", 0, null, ofsetty.z, ofsetty.x, ofsetty.y);
	}
}

Code: Select all

action void WeponProj(class<Actor> flingy, vector2 eulerAng = (0, 0), vector2 offsets = (0, 0), 
					bool nomAmmo = true, EFireCustomMissileFlags customFlags = FPF_NOAUTOAIM)
{
	vector3 dirry, ofsetty;
	invoker.playerAngle = Quat.FromAngles(angle, pitch, roll);
	[dirry, ofsetty] = GimbalUnlocker(invoker.playerAngle, eulerAng, offsets, false);
	double ying = atan2(dirry.y, dirry.x), yang = -asin(dirry.z);
	A_FireProjectile(flingy, ying, nomAmmo, ofsetty.y, ofsetty.z, customFlags, yang);
}
Last edited by kinker31 on Tue Mar 24, 2026 2:59 am, edited 1 time in total.
User avatar
kinker31
Posts: 70
Joined: Mon Feb 05, 2018 12:18 am
Operating System Version (Optional): Arch, Linux-Zen
Graphics Processor: ATI/AMD with Vulkan/Metal Support
Location: Does anyone actually put anything serious here?

Re: Being a Fool trying to be Cool with Quaternions

Post by kinker31 »

Eh, I was able to work around it by doing 'ying/yang -= angle/pitch', not entirely sure that's gonna work out longterm for me but at least the projectiles fire like they should for now. I'll go ahead and mark this topic.
On a completely unrelated note, if you're making wrappers for A_FireProjectile, always make sure to accept two actors pointers like the parent function; It'll come in handy.

Return to “Scripting”