1

I'm trying my best to describe it:

I've got a 3D sloped surface, and I wanna know the height(Z) it is at any given point inside of it. I have the surface's vertices' 3D positions, and the point's 2D position. How to know what should be the height it would collide with while at the surface?Example

I'm using GDScript, making a 3D/2.5D Doom-like engine in Godot's 2D engine. Flat floors and walls work perfectly, but slopes? I've tried everything I could think of for months and nothing worked.

weg
  • 27
  • 3

1 Answers1

1

This is what you would do:

  • We define a line that is vertical and passes through your 2D point.

    Let us say our 2D point is position. Then:

    • A point on the line is position augmented with 0 on the height component. I'll call it v0:

      var v0 = Vector3(position.x, position.y, 0.0)
      
    • And the direction of the line is vertical, so zero on both 2D components, and 1 on the height component. I'll call it dir:

      var dir = Vector3(0.0, 0.0, 1.0) 
      
  • We define a plane by the three points that make up your 3D triangle.

    Let us say our 3D points are v1, v2 and v3.

    • By definition they are all on the plane.

    • We can find the normal of the plane using cross product, like this:

      var normal := (v2 - v1).cross(v3 - v1)
      

      For this use case we don't care if we got the normal flipped.

      Ah, but it will be convenient to ensure it is of unit length:

      var normal := (v2 - v1).cross(v3 - v1).normalized()
      
  • We find the interception of the line and the plane. That is our solution.

    We want to find a point on the plane, I'll call it r. That is a point such that the vector that goes from one of the points on the plane to it is perpendicular to the normal of the plane. Meaning that (r - v1).dot(normal) == 0.

    Please notice that I'm using ==, which is the equality comparison operator. I'm using that because it is an equation, not an assigment.

    The point r must also belong to the line. We can use the parametric form of the line, which is like this: r = v0 + dir * t. And we will have to first find what parameter t must be, so we can compute r.

    So we replace r here:

    (r - v1).dot(normal) == 0
    

    Which gives us this:

    ((v0 + dir * t) - v1).dot(normal) == 0
    

    Rearrange:

    (dir * t + v0 - v1).dot(normal) == 0
    

    By distributive property of the dot product:

    (dir * t).dot(normal) + (v0 - v1).dot(normal) == 0
    

    Since t is an scalar we can take it out of the dot product:

    t * dir.dot(normal) + (v0 - v1).dot(normal) == 0
    

    Subtract (v0 - v1).dot(normal) on both sides:

    t * dir.dot(normal) == - (v0 - v1).dot(normal)
    

    We can move that negative sign inside the dot product:

    t * dir.dot(normal) == (v1 - v0).dot(normal)
    

    And divide both sides by dir.dot(normal):

    t == (v1 - v0).dot(normal) / dir.dot(normal)
    

    Thus, our point r is:

    var r := v0 + dir * (v1 - v0).dot(normal) / dir.dot(normal)
    

    Then you can read the height component of r, which is your answer.

    By the way Godot has a method that is almost this. But it intersects a ray, not a line: intersect_ray of the Plane class.

Theraot
  • 31,890
  • 5
  • 57
  • 86
  • Forgive me but I'm having a really hard time understanding this. v0 is player's position, v1, v2, & v3 are the vertices', that's okay. "A point on the line is v augmented with 0 on the height component. I'll call it v0."; "And the direction of the line is vertical, so zero on both 2D components, and 1 on the height component. I'll call it dir." What does that mean? What is dir defined as? Then comes r, first it's equal to zero, then it's not... do I just use the last one? I really wasn't prepared for such a complex answer! What I understood + the error: https://i.imgur.com/Hf7cUsV.png – weg Feb 01 '23 at 12:10
  • 1
    @weg I have modified the answer. – Theraot Feb 01 '23 at 17:43
  • Thank you so much! This is what it looks like for me, is it correct?: https://i.imgur.com/9Uh27tW.png It seems like the ascending and descending is working, but the final result can either be right, or randomly too low. Looks to be directly related to the vertices' position order. (0,100,50 - 0,0,0 - 100,100,100) works perfectly, (0,0,0 - 100,100,100 - 100,0,50) is a -1400 units off, for example – weg Feb 01 '23 at 21:37
  • 1
    @weg I tried those values with `(50, 50, 0)` and it works for me. Changing the order would flip the normal of the plane, but the solution should be the same. However, seeing your code I notice something I overlooked: using `var d = v1.dot(normal)` can give you a negative but `var d = v1.project(normal).length()` will always be positive. so use `var d = v1.dot(normal)` instead, I amend that on the answer. Edit: I decided to remove `d` from the answer to avoid the confusion. – Theraot Feb 01 '23 at 21:58
  • For a while it was around 20-40 units off, negative or positive, but in the middle of adding a solution (I was gonna get the new height at the lowest point, and subtract it to the player's new height), it decided to work outta nowhere perfectly! Idk why. Anyway, I've added credit for you at my project! Thank you so much for your help. Your solution is incredibly concise and sophisticated. You must be a wonderful coder! The final code for those interested: https://i.imgur.com/fJCloUR.png – weg Feb 02 '23 at 10:50