Fri Jan 27, 2017 5:49 am

I can do this http://i.imgur.com/QjzNbfm.png
But not sure how to also apply random angle to that.
I know that to rotate it by 90 degrees, I need to swap smth with smth, I think roll with angle. But whatevs.
What order does Doom apply these angles in?

Code currently looks like this
Code:
`class SlopedThing : Candelabra{    Default    {        +FLATSPRITE;        //+ROLLCENTER;    }        private void SetToSlope(double dang)    {        vector3 fnormal = CurSector.floorplane.normal;        if (!CurSector.floorplane.isSlope())            fnormal = (0, 0, 1);        vector2 fnormalp1 = (fnormal.x != 0 || fnormal.y != 0) ? (fnormal.x, fnormal.y).Unit() : (0, 0);        vector2 fnormalp2 = ((fnormal.x, fnormal.y).Length(), fnormal.z);        self.roll = 0;        self.angle = atan2(fnormalp1.y, fnormalp1.x);        self.pitch = -90+atan2(fnormalp2.x, fnormalp2.y);        //Console.Printf("angles = %.2f, %.2f, %.2f\n", angle, pitch, roll);    }        override void PostBeginPlay()    {        Super.PostBeginPlay();    }        override void Tick()    {        double dang = (level.time*3)%360;        //dang = 45;        SetToSlope(dang);        Super.Tick();    }}`

Fri Jan 27, 2017 9:02 am

For starters it should affect the roll, not the angle. Will experiment.

Graf, does that help?

Fri Jan 27, 2017 9:44 am

viewtopic.php?f=3&t=50334

@ZZYZX: I made a little demo here of combining pitch and roll for the playercamera to (kind of) defeat gimbal-lock and make it appear as if the level were "tilted". It's likely very crude compared to what you're doing but perhaps there's a little bit of code in there that would help.

Fri Jan 27, 2017 9:56 am

What we're trying to do is this with the green stickmen.

Fri Jan 27, 2017 10:02 am

I know. I'm just offering some math that worked for my use case, which seemed somewhat similar in the general purpose. If it's helpful, great. If not, disregard.

Fri Jan 27, 2017 11:41 am

Ok so I got this horrible geometrical hack based on observing how it rotates
Has nothing to do with Caligari's solution though (in fact I didn't even understand what's going on there and why the /50).
http://pastebin.com/6qs9jviW (nevermind Sign, it's unused)

It's a little tiny bit off though (almost unnoticeable). I believe it needs to be upscaled/downscaled by 1.2 or smth.
Also, you probably can make it a shadow by giving it pitch of 90/-90. But it's a little tiny bit off too.

Fri Jan 27, 2017 11:48 am

I'm not worried about the shadows. In fact I probably might exclude them because that's going to be a bit costly -- I'd rather wait on GZDoom to support a real light/sun system for that.

Also it works, so I'm not complaining.

Fri Jan 27, 2017 12:59 pm

ZZYZX wrote:Ok so I got this horrible geometrical hack based on observing how it rotates :)
Has nothing to do with Caligari's solution though (in fact I didn't even understand what's going on there and why the /50).
http://pastebin.com/6qs9jviW (nevermind Sign, it's unused)

It's a little tiny bit off though (almost unnoticeable). I believe it needs to be upscaled/downscaled by 1.2 or smth.
Also, you probably can make it a shadow by giving it pitch of 90/-90. But it's a little tiny bit off too.

Very cool! Can you make a version that would work for things like flat blood splats, etc? I tried messing around by compensating the angle, pitch and roll but can't seem to come up with anything that looks correct...

EDIT:

Code:
`class Z_BloodSpot : Z_BloodBase{    Default    {        Radius 8;        RenderStyle "Shaded";    }    private void SetToSlope(double dAng)    {        vector3 fNormal = CurSector.FloorPlane.Normal;        if (!CurSector.FloorPlane.isSlope())        fNormal = (0, 0, 1);        vector2 fNormalP1 = (fNormal.X != 0 || fNormal.Y != 0) ? (fNormal.X, fNormal.Y).Unit() : (0, 0);        vector2 fNormalP2 = ((fNormal.X, fNormal.Y).Length(), fNormal.Z);        vector2 angNormal1 = (cos(dAng - 90), sin(dAng - 90)).Unit();        vector2 angNormal2 = (cos(dAng), sin(dAng)).Unit();        double dDiff1 = (angNormal1.X * fNormalP1.X + angNormal1.Y * fNormalP1.Y); // dot product        double dDiff2 = (angNormal2.X * fNormalP1.X + angNormal2.Y * fNormalP1.Y); // dot product        Self.Pitch = -atan2(fNormalP2.X, fNormalP2.Y) * dDiff2;        Self.Roll = atan2(fNormalP2.X, fNormalP2.Y);        Self.Roll *= dDiff1;        Self.Angle = dAng;    }    override void PostBeginPlay()    {        Super.PostBeginPlay();    }    override void Tick()    {        SetToSlope(Angle);        Super.Tick();    }    States    {    Spawn:        TNT1 A 0 NoDelay A_SpawnItemEx("Z_BloodDrop", flags: BLOOD_FLAGS);        TNT1 A 1 A_Jump(256, "BloodSpot1", "BloodSpot2", "BloodSpot3", "BloodSpot4");        Stop;    BloodSpot1:        BSPT A 0 A_SetTics(BLOODSPOT_TICS);        Goto FadeOut;    BloodSpot2:        BSPT B 0 A_SetTics(BLOODSPOT_TICS);        Goto FadeOut;    BloodSpot3:        BSPT C 0 A_SetTics(BLOODSPOT_TICS);        Goto FadeOut;    BloodSpot4:        BSPT D 0 A_SetTics(BLOODSPOT_TICS);        Goto FadeOut;    FadeOut:        "####" "#" 2 A_FadeOut(BLOODSPOT_FADEOUTSPEED);        Loop;    }} `

This works for 3D blood splats. The only difference is I flipped the atan signs for Self.Roll and Self.Pitch, and removed the -90 from the Self.Pitch. Very nice!! Thank you ZZYZX-senpai

Fri Jan 27, 2017 1:38 pm

http://pastebin.com/hNGuPrQQ an optimized (?) version without dot product, replaced with AngDiffAs10. That function would compare two angles and return 1 for matching, and -1 for opposite.

Fri Jan 27, 2017 1:49 pm

That's actually de-optimized. If you are out for speed, do not use scripted subfunctions - they are really costly - my profiling showed they take 30x as long to call as a native function call.

Fri Jan 27, 2017 1:52 pm

Ok, minus two scripted calls and minus four native calls http://pastebin.com/pmfTPmAq, because 30x slower function call here means noticeably smaller FPS with large usage counts.
Anyway, why do scripted functions lag that much?

Fri Jan 27, 2017 2:04 pm

Where's scripted function calls in there? All those math functions are actually intrinsics. Sure, the operation they perform costs time but they are single opcodes. That function actually has no 'call' opcode whatsoever.

Fri Jan 27, 2017 2:10 pm

Nowhere, hence minus two
I was asking about the previous one, why does scripted vs native CALL (not execution even!) cost more?

Fri Jan 27, 2017 2:50 pm

Graf, which would be healthier for speed?

One single function call all the time? (Calls ProcessCooldowns which only performs its stuff if the timer is %3, %2 or %10, all = 0)
Code:
`   override void Tick()   {      if (isVoodoo() || health <= 0)      {         Super.Tick();         return;      }            if (PentaNoiseWait > 0)   PentaNoiseWait--;      if (level.maptime <= 1)   InitPlayer();            ProcessCooldowns();      if (GetClass() == "Doom4Player")      UpdateHAM();      Super.Tick();   }      void ProcessCooldowns()   {                // Use common denominator.      int timer = level.maptime % 30;       // No need to check for StaticChargeDrain since the upgrade alone      // shuts this off.      if (!(timer % 3))      {         if (!CountInv("StaticUpgrade4"))         {                if (CountInv("StaticUpgrade3"))            {   A_TakeInventory("StaticRifleChargingToken",1);   }            else if (CountInv("StaticUpgrade2"))            {   A_TakeInventory("StaticRifleChargingToken",2);   }            else            {   A_TakeInventory("StaticRifleChargingToken",4);   }         }         A_TakeInventory("PlasmaStunBombCounter",1);         user_lasthp = health + CountInv("BasicArmor");      }            if (!(timer % 2))      {         A_TakeInventory("SGTripleShotTimer",1);         A_TakeInventory("SGGrenadeTimer",1);      }      if (!(timer % 10))      {         if (CountInv("GrenadeCooldown"))         {            A_TakeInventory("GrenadeCooldown",1);            if (!CountInv("GrenadeCooldown"))            {               A_PlaySound("Doom4/Weapon/Grenade/GrenadeRegen");            }         }      }   }`

Or multiple functions split up over time?

In this version, ProcessCooldown1, 2, and 3 are only called after 3, 2, and 10 tics passed respectively.

Code:
`   int CooldownTimer[3];   override void Tick()   {      if (isVoodoo() || health <= 0)      {         Super.Tick();         return;      }            if (PentaNoiseWait > 0)   PentaNoiseWait--;      if (level.maptime <= 1)   InitPlayer();            static const int CooldownCaps[] = { 3, 2, 10 };      for (int i = 0; i < 3; i++)      {         CooldownTimer[i] = (CooldownTimer[i] + 1) % CooldownCaps[i];      }            if (!CooldownTimer[0])   ProcessCooldown1();      if (!CooldownTimer[1])   ProcessCooldown2();      if (!CooldownTimer[2])   ProcessCooldown3();         if (GetClass() == "Doom4Player")      UpdateHAM();      Super.Tick();   }      void ProcessCooldown1()   {      // No need to check for StaticChargeDrain since the upgrade alone      // shuts this off.      if (!CountInv("StaticUpgrade4"))      {             if (CountInv("StaticUpgrade3"))         {   A_TakeInventory("StaticRifleChargingToken",1);   }         else if (CountInv("StaticUpgrade2"))         {   A_TakeInventory("StaticRifleChargingToken",2);   }         else         {   A_TakeInventory("StaticRifleChargingToken",4);   }      }      A_TakeInventory("PlasmaStunBombCounter",1);      user_lasthp = health + CountInv("BasicArmor");   }      void ProcessCooldown2()   {      A_TakeInventory("SGTripleShotTimer",1);      A_TakeInventory("SGGrenadeTimer",1);   }      void ProcessCooldown3()   {      if (CountInv("GrenadeCooldown"))      {         A_TakeInventory("GrenadeCooldown",1);         if (!CountInv("GrenadeCooldown"))         {            A_PlaySound("Doom4/Weapon/Grenade/GrenadeRegen");         }      }   }`

Fri Jan 27, 2017 3:23 pm

In that case it really doesn't matter because the native code being called by those subfunctions requires a lot more time than the VM function calls.