Jump to content

Performant approach to check distance to ground/obstacle


Recommended Posts

Well, first off, this weekend I devised an approach to distance gauging similarly to real life sonar or infrared approaches.

I'm casting a spell from an invisible activator from below my feet down to the ground (another invisible activator 200 units further down actually, but the projectile passes that one and goes on until it hits the ground or an obstacle),
then I'm regularly searching for the obstacle (GetFirstRef, GetNextRef loop, with additional checks for the "right" projectile),
then I track it and continuously store the distance it traveled (GetProjectileDistanceTraveled), until it hits the ground/vanishes (IsFormValid + not IsRefDeleted),
then the last stored distance traveled is the distance to ground/obstacle (or sufficiently close to it).

It's working splendidly under lab conditions,
a visible activator following me around in short distance and above my head, casting the spell on command, and I quickly get the result every time.

But under gaming conditions, me actually flying around, at a higher speed, with constant scans running with a small delay, it often takes ages for even the projectile to be found, and then ages more until it hits the ground.
While actively flying around the distance to ground updates roughly once every 5 minutes, which, needless to say, is less than ideal (read: horribly useless).

 

if 0 < fDistanceCheckDelay
    let fDistanceCheckDelay -= GetSecondsPassed
else
    if 0 == iDistanceCheckStep
        let fDebugPosX := fPosX
        let fDebugPosY := fPosY
        let fDebugPosZ := fPosZ - 50
        rDistanceCaster.SetPos X fDebugPosX
        rDistanceCaster.SetPos Y fDebugPosY
        rDistanceCaster.SetPos Z fDebugPosZ
        let fDebugPosX := fPosX
        let fDebugPosY := fPosY
        let fDebugPosZ := fPosZ - 250
        rDistanceTarget.SetPos X fDebugPosX
        rDistanceTarget.SetPos Y fDebugPosY
        rDistanceTarget.SetPos Z fDebugPosZ
        rDistanceCaster.Cast DrakeDragonDistanceCheckSpell rDistanceTarget
        PrintToConsole "%n (%.0f %.0f %.0f) casted sonar spell down towards %n (%.0f %.0f %.0f)" rDistanceCaster fPosX fPosY fPosZ rDistanceTarget fDebugPosX fDebugPosY fDebugPosZ
        let iDistanceCheckStep := 1
    elseif 1 == iDistanceCheckStep
        if 0 == rDistanceProjectile
            PrintToConsole "find projectile down"
            let rDistanceProjectileTemp := GetFirstRef 34 1
            Label
            if rDistanceProjectileTemp
                if (rDistanceCaster == rDistanceProjectileTemp.GetProjectileSource) && (DrakeDragonDistanceCheckSpell == rDistanceProjectileTemp.GetMagicProjectileSpell)
                    let rDistanceProjectile := rDistanceProjectileTemp
                else
                    let rDistanceProjectileTemp := GetNextRef
                    Goto
                endif
            endif
            if rDistanceProjectile
                let fDistanceCheckTimeout := 0
            else
                let fDistanceCheckTimeout += GetSecondsPassed
            endif
            if 2 < fDistanceCheckTimeout
                let rDistanceProjectile := 0
                let iDistanceCheckStep := 0
                PrintToConsole "projectile down find timeout"
            endif
        endif
        if (IsFormValid rDistanceProjectile) && (0 == IsRefDeleted rDistanceProjectile)
            let fDistanceBottomTemp := rDistanceProjectile.GetProjectileDistanceTraveled
            PrintToConsole "projectile down found, current distance: %.2f" fDistanceBottomTemp
            if 2000 < fDistanceBottomTemp
                let fDistanceBottomTemp := 2000
                let rDistanceProjectile := 0
            endif
        else
            let rDistanceProjectile := 0
        endif
        if 0 == rDistanceProjectile && 0 < fDistanceBottomTemp
            let fDistanceBottom := fDistanceBottomTemp
            let fDistanceBottomTemp := 0
            let iDistanceCheckStep := 2
        endif

    else
        let fDistanceCheckDelay := fDelayForDistanceCheck
        let iDistanceCheckStep := 0
    endif
endif

 



I already contemplated slapping a Kalman filter onto it all to smoothen it out, but a little (read: massive) bit faster scanning to begin with would still be preferred.


Isn't there any more performant/faster approach to achieve the same thing? It takes way too long for the projectile to even come into being, before it can be tracked.
So, if anybody'd know of a better alternative, it'd be more than appreciated. Thanks in advance!

Link to comment
Share on other sites

I once had this problem and used GetLOS to detect the distance, with pretty good results (Youtube video) (code).

 

If memory serves, I used two micro actors, actor A near the player and actor B positioned at a distance and checked if A can see B. The nice thing about it is that you don't need to position actor B gradatively farther. You can start from a random distance (previous distance would be a good start) and move it farther or closer as necessary and quickly find the distance with a few successive approximations.

 

Again, if memory serves, using GetLOS from the player does not work well in third person view, so I used actor A near the player.

I used actors because I read somewhere that GetLOS is not CPU efficient with inanimate objects.

 

Once more, if memory serves, there is a maximum distance beyond which GetLOS will always return false.

Link to comment
Share on other sites

Oh, yeah, I was already considering giving a GetLOS-based approach a try next. But now I got all the facts and won't even have to start from scratch anymore. Thank you very much!

 

I do recall having seen this video before. Man, does it look smooth indeed. That's definitely whole leagues faster than any spell or arrow projectile flies. I'll give it a go right up next.

 

I knew GetLOS will go through actors like in your video from the WiKi talk, but that won't be a problem for me at all. It's distance to ground, not distance to the next dumbo staying in your way while you fly, after all, and I don't expect there to be much updrift coming from a person/creature standing around.

The limit in range will also not be a problem to me. I'm already limiting it to 2000 units top, as only at distances closer than that it starts making sense to check. "Oh, hey! There's some rock mass at around 5 miles below you. Quick! Start gaining height or you'll collide!"... Na-ah.

 

And for all those occasional/random occurrences of it temporarily failing, for whatever reason, I could still slap a Kalman filter onto it at any time, if need be, to reduce the noise.

 

Can't wait to try it out now!

Link to comment
Share on other sites

I checked the code in my files and I do not see some things I do remember, like the two actors I mentioned.

My code will not help much, as it is pre-OBSE (still uses SaveIP and GoTo as loop control).

I probably exercised GetLOS further some other time later.

 

The nice thing about GetLOS is that you can move the target and check its visibility several times in the same frame. But it has a limit. If memory serves, after around 20 times, it starts to return erratic results. I clearly remember implementing some control to limit the number of loops per frame to avoid this glitch, interrupting the probing and continuing in the next frame(s).

 

Another thing I remember is that Oblivion ground is at different depths, depending on the detection method. For collision, it is where it seems to be, but for visibility it is about one foot deeper, as can be noticed in the video when targeting the ground.

Link to comment
Share on other sites

Hmm, I can't seem to get it to work.
Maybe it's because your checks have been into the direction the player's looking, while mine are straight down, and making a creature look down (via SetAngle X) doesn't seem to stick really, nor does keeping it in place via SetPos.

I'm keeping the source creature in place right under my feet and facing down constantly (or that's the plan), as the quest script currently runs all 0.001 seconds.
The target creature is put into place at increasing distance in steps of 200 units and no farther away than 2000 units. So it should be only 10 steps, i.e. 10 GetLOS calls, max. per frame.

It correctly reports a 0 distance while I'm no further than 200 units from ground. But as soon as I get above that height it continuously starts spamming either 0 as the result of the loop or 2000, as it supposedly sees the target at all 10 steps, even while already deep behind a street mesh. Not even once did it see the target at only a part of the 2000 range, always all or none, and it's continuously alternating.

Here's the code I'm currently using:

if 1 == checkdistance
    if 0 == DragonDistanceSource.GetInSameCell player
        DragonDistanceSource.MoveTo player
    endif
    if 0 == DragonDistanceTarget.GetInSameCell player
        DragonDistanceTarget.MoveTo player
    endif
 
    let posXsrc := player.GetPos X
    let posYsrc := player.GetPos Y
    let posZsrc := player.GetPos Z
    let posXtrg := posXsrc
    let posYtrg := posYsrc
    let posZtrg := posZsrc - 200
    Label 2
    if posZsrc - posZtrg <= 2000
        DragonDistanceSource.SetPos X posXsrc
        DragonDistanceSource.SetPos Y posYsrc
        DragonDistanceSource.SetPos Z posZsrc
        DragonDistanceSource.SetAngle X 90
        DragonDistanceSource.SetAngle Y 0
        DragonDistanceSource.SetAngle Z 0
        PrintToConsole "LOS source Z: %.2f" posZsrc
        DragonDistanceTarget.SetPos X posXtrg
        DragonDistanceTarget.SetPos Y posYtrg
        DragonDistanceTarget.SetPos Z posZtrg
        PrintToConsole "LOS target Z: %.2f" posZtrg
        if DragonDistanceSource.GetLOS DragonDistanceTarget
            let losdistance := posZtrg - posZsrc
            let losdistance := Abs losdistance
            PrintToConsole "source sees target at distance: %.4f" losdistance
            let posZtrg -= 200
            Goto 2
        endif
    endif
    ;DragonDistanceSource.MoveTo DragonDistanceHoldingMarker
    ;DragonDistanceTarget.MoveTo DragonDistanceHoldingMarker
    PrintToConsole "LOS distance: %.4f" losdistance
endif


What's also quite unexpected, is that the creatures "both" seem to constantly drop down, but their distance increases, as coded, and at a certain distance (I couldn't yet figure out what exactly's the cause or when/where it happens) they just switch back. And to confuse the hell out of me, all the time the script still states them at the exact positions they should be at, not at all where I "see" them.

And I also don't see them ever looking down, despite me directly setting their angle via script.
They seem to randomly spin around though, so maybe that's why they see the target intermittently.

oblivion20181027_23_05_39_by_drakethedra

Link to comment
Share on other sites

Puzzling, as the code seems OK to me.

Some comments...

What's also quite unexpected, is that the creatures "both" seem to constantly drop down, but their distance increases, as coded, and at a certain distance (I couldn't yet figure out what exactly's the cause or when/where it happens) they just switch back. And to confuse the hell out of me, all the time the script still states them at the exact positions they should be at, not at all where I "see" them.


One thing I deduced along the years is that the engine has a 'Script' phase, a 'Physics' and a 'Rendering' phase, one after the other in each frame.

This accounts for the difference between the script position and the visual position: (1) you position the actor in the script phase, (2) in the physics phase the engine applies the gravity pull and moves the actor down and (3) in the rendering phase the actor is rendered in this new position.

I remember my first attempts tries with flying actors where I kept them in the air with SetPos and the result was a heavy flickering up and down.

([EDIT] Maybe SetVelocity would solve the visuals, but it did not exist at the time.)

In my esp, I used a dog scaled down to the size of a flea, so I could not really see it to notice any movement.

Also, during the script phase there is no physics involved, so you may use SetPos just once before the loop,

And I also don't see them ever looking down, despite me directly setting their angle via script.
They seem to randomly spin around though, so maybe that's why they see the target intermittently.


I don't think so. As mentioned above, by the time the engine renders the creature, the GetLOS computation has long been done.

I do not remember if setting angleX forces the actor to move the head. Maybe it doesn't. The "Look" function certainly does.

Maybe it's because your checks have been into the direction the player's looking, while mine are straight down,


Yes, maybe. But I suppose you have tried with the player looking down, which would solve the problem (if this was the problem).
The Wiki says that the field of view for GetLOS is 95 degrees.

I'm keeping the source creature in place right under my feet and facing down constantly


The only practical suggestion I have is that you place the source creature a little below the player Z position. Maybe the player himself is considered in the way.

For debugging purpose, you might keep the loop running always 10 times and printing the results each time.

Something like this:

    Label 2
    if posZsrc - posZtrg <= 2000
        PrintToConsole "LOS source Z: %.2f" posZsrc
        DragonDistanceTarget.SetPos X posXtrg
        DragonDistanceTarget.SetPos Y posYtrg
        DragonDistanceTarget.SetPos Z posZtrg
        PrintToConsole "LOS target Z: %.2f" posZtrg

        let losdistance := posZtrg - posZsrc
        let losdistance := Abs losdistance
        if DragonDistanceSource.GetLOS DragonDistanceTarget
             PrintToConsole "source sees target at distance: %.4f" losdistance
        else
            PrintToConsole "source does not see target at distance: %.4f" losdistance
        endif
 
        let posZtrg -= 200
        Goto 2
    endif
 
Edited by QQuix
Link to comment
Share on other sites

going out on a limb here

but when you give the command for....

DragonDistanceSource.SetAngle X 90

 

 

does it keep adding the 90 to that axis each time the code fires off?

as in....

first time it runs it adds 90 degrees and makes it look down

then you added 90 again and now its looking up

then you added 90 again and now it looking down again

 

 

looking closer at the wiki for GetLOS

"The field of view for LOS is +/- 95 degrees from the 0 axis line in which the observer (non-player actor, player, or camera) is looking straight ahead."

I may be thinking of this wrong, but looks like you don't need to set your 'activator' to 'look down' since its already 'looking' for anything up to 95 degrees anyway.

Link to comment
Share on other sites

Ah, yeah, I've read that article/discussion before. Don't know if the "set actor below ground, wait a frame, then it'll be moved up the on-ground position automatically" approach will make much sense at the speed I'm flying at. But them talking of water reminds me, the other methods will all fail when above water, so I'll have to check distance to water plane as well anyways.

There's also an OBSE function now to obtain ground mesh height at a specific position, but the ground mesh isn't the "only" geometry that can make up the ground, especially not when inside a city and the street model is floating leagues above ground (see IC).

I basically want to know the distance to ground while flying, so when you get too close I can switch animation into landing pose, shortly before you actually land. And I want it to be a little easier to stay afloat when close to the ground, or to even gain some height while gliding above an elevating ground, also at cliffs and the like.

In a way close/similar to how it is in this video: https://youtu.be/NOyGBUxEaPY

But with the controls and maneuverability from/like in this (current state of my scripts, recorded yesterday):

Link to comment
Share on other sites

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...