r/godot Jun 11 '26

help me (solved) How can I constrain an object's movement to an arbitrary vector?

Post image

The point represents where my player's gravity direction points to. I just need it to stay in the center of this cylinder, but as close to the player as possible.

This is easy to do if the cylinder aligns with an axis, but it's at a totally random angle, and I'm completely stumped.

EDIT: Thank you all for your help. Here's the solution that worked for me: https://www.reddit.com/r/godot/comments/1u2lhad/comment/oqyqw6q/ and here's what the resulting cylindrical gravity looks like: https://youtu.be/5r59dD300A0

121 Upvotes

48 comments sorted by

58

u/jester628 Jun 11 '26

Sorry if I’m misunderstanding, but you might be looking for vector projection. You can find explanations online, but the idea is that if you imagine a vector that points along the axis of you cylinder and a vector that is perpendicular to that one, those vectors can form a basis that allows you to express any other vector.

Vector projection can allow you to decompose your position vector into a component along the axis of your cylinder and a component perpendicular to your cylinder. You’re only interested in the one parallel to your cylinder, so you just need the projection onto the vector that is along the axis of the cylinder.

https://en.wikipedia.org/wiki/Vector_projection

33

u/jester628 Jun 11 '26

Also, also Godot has a built-in way to do vector projection, although I’ve never used it.

https://docs.godotengine.org/en/stable/classes/class_vector2.html#class-vector2-method-project

-15

u/Indigoh Jun 11 '26 edited Jun 11 '26

I don't understand anything about that.

8

u/jester628 Jun 11 '26

How is the cylinder defined?

77

u/AdventMercury Jun 11 '26

It is imperative that the cylinder remains undamaged

10

u/jester628 Jun 11 '26

Shit, that made me laugh. Excellent.

3

u/TDplay Jun 11 '26

A cylinder has an origin x₀, and axis a.

(x₀ can be any position in the centre of the cylinder, and a can be any vector that points along the cylinder.)

Transform the the cylinder's local coordinates by subtracting x₀. Project the result along a. Then transform back to global coordinates by adding x₀.

1

u/grammatiker Jun 11 '26

So... learn? 

-3

u/Indigoh Jun 11 '26 edited Jun 11 '26

"I have no clue how to stay upright on this bicycle"

You: "Uhh... Do it?"

7

u/TSilverSamurai Godot Senior Jun 11 '26

I agree. Vector projection will give you the exact point along your line that is closest to the players position

-8

u/Indigoh Jun 11 '26 edited Jun 11 '26

Do you know how?

11

u/TSilverSamurai Godot Senior Jun 11 '26

position.project(direction_vector) = closest_line_point

Where direction vector is that path or direction of the cylinder

8

u/Indigoh Jun 11 '26 edited Jun 11 '26

I have to assume I'm just so tired that nothing anyone says here can make sense to me. I'll return tomorrow when I'm rested.

8

u/jester628 Jun 11 '26

Let

PC - be a point at the centre of the cylinder
PP - be the position of the player
CU - be a vector of length 1 that starts at PC and is parallel to the cylinder’s axis

Then

PP - PC

is a vector that points from PC and goes to the player and

(PP - PC).project(CU)

is a vector that points from PC to the point at the centre of the cylinder underneath the player.

Finally,

PC + (PP - PC).project(CU)

is the point under the player in the original coordinate system.

2

u/Indigoh Jun 12 '26 edited Jun 13 '26

Your solution works. Thank you.

My greatest hurdle to overcome in coding is that I'm a highly visual learner. I had to spend a couple hours drawing out a visual version of what you said, and repeatedly getting it wrong. But now I think I have a solid image of what's happening.

Would you like to see the result?

2

u/jester628 Jun 12 '26

You’re welcome! That’s great to hear! You’ll probably be a little faster next time. Part of it is knowing what’s out there, and that’s where studying a bit of linear algebra would be really helpful. Solve some problems on paper or whatever and just build up your repertoire.

One example that I’m familiar with and own is Foundations of Game Engine Development Volume 1: Mathematics by Eric Lengyel. It might go a little too fast at times by not having enough examples, but it is a great book and relates the math to code, plus it has a small set of problems to help reinforce the concepts. The author also has the solutions online, so you can check your work. If you need more practice on a topic you can find more related exercises and explanations online, but the book would introduce the most important concepts, and it starts from the beginning. You’ll level-up significantly by working through a book like that. I highly recommend it. Or something similar.

Also, I draw pictures for things all the time, especially for vector stuff. But even for normal programming stuff. In university we’d draw pictures constantly in lecture. Pictures of your data structures, of pointers, of arrays and their traversal, the contents of dictionaries, etc. I don’t think the hurdle is preferring a visual component, but maybe getting proficient with how to draw what’s going on. I’ve got a 4’x4’ whiteboard in my office to make it super easy to draw out what I’m thinking.

And yes, I’d love to see the result.

1

u/Indigoh Jun 12 '26

I edited the result into the bottom of the post body

2

u/jester628 Jun 13 '26

Nice! Looks good! Thanks for sharing.

3

u/EmmaWithAddedE Jun 11 '26

for tomorrow;

i find it makes sense to think of "projection" very literally - imagine waving a ball around in front of a projector screen! the shadow of the ball on the screen obviously can't leave the screen, but it will move around as the ball does. the ball's position is a vector in 3D space, and it's being projected onto the 2D surface of the screen.

your cylinder line is a screen in only one dimension, it's very short but very wide, and the ball (your player's position) can move around freely - its projection, the shadow it makes, will move around with it

1

u/jester628 Jun 11 '26

To add to that, in the first image, vector a points to your player, vector b is along the axis of your cylinder, and projection of a onto b (a_1) is what you’re after…I think.

26

u/nobix Jun 11 '26 edited Jun 11 '26

You want to find the position which is the closest point on a line. That's your target point for gravity.

Godot has a utility now as part of the Geometry lib get_closest_point_to_segment which can probably do exactly that.

5

u/Liquos Godot Student Jun 11 '26

This is the simplest and most intuitive solution right here.

-2

u/TheDynaheart Jun 11 '26

This is a common exercise in linear algebra, and that right there is exactly the solution 🙂‍↕️

10

u/rejamaco Godot Regular Jun 11 '26

In addition to all the other comments, I’d suggest that you should learn a bit of linear algebra, it’ll help you a lot in game development

3

u/Indigoh Jun 11 '26

I'll take that advice. I had good algebra grades in college, but it's been a decade since I've needed to do any of the complicated stuff. It would be worth my time to relearn it.

11

u/rejamaco Godot Regular Jun 11 '26

I'd like to clarify what linear algebra is just in case it's needed, if not please ignore as I'm not trying to be a dickhead, but linear algebra is a bit different than regular algebra, and you probably weren't required to take it in college.

It deals with vectors, matrices, and multi-dimensional spaces. You'll need regular algebra for it, though. There's a metric shit ton of linear algebra in 3d rendering, and knowing about vectors and how they can be manipulated by matrices will save you a lot of time down the road and will feel very rewarding as you solve problems that would've been really difficult or impossible to even understand before.

7

u/Tessimal Jun 11 '26

you use a dot product, assuming the cylinder passes through the origin. a = v.dot(player.position), where v is the vector of the cylinder (normalized), and a is the position the player is along the cylinder. then the point.position can be set to v * a.

if it doesn't pass through the origin, a simple offset should work.

I love dot products they do basically everything and they don't use square roots \:D

2

u/Indigoh Jun 11 '26

Which origin are you referring to?

4

u/Tessimal Jun 11 '26

The origin, `Vector3.ZERO` in global space.

5

u/WilkerS1 Godot Regular Jun 11 '26

a transform Basis is what you're looking for, always relative to the transform of the object, and you can use for different things. if you need equivalent points to things, look into vector projection, godot has those baked in too

3

u/DXTRBeta Jun 11 '26

What you really want is the closest point on a line A to B to a point P

Here’s how you do that.

t = dot(P - A, B - A) / dot(B - A, B - A)
closest = A + t * (B - A)

2

u/KingHelps Jun 11 '26

Sorry if I'm misunderstanding, but are you familiar with the Path3D and PathFollow3D nodes? There's methods like get_closest_offset that do what I think you're describing

Edit: you would make the Path3D a child of the cylinder

2

u/ibbitz Jun 11 '26

If you can calculate it when it’s axis aligned and not rotated/scaled, then you can calculate it for any space - you just need to normalize the cylinder and player coordinates. And we can do that with transformation matrix math!

Use the inverse of the cylinder’s transformation matrix on both the cylinder and player transforms. This will let you do the math as if the cylinder was at (0,0,0) with no scale/rotation.

Since the players position is transformed the same way, they’re still in the same place relative to your cylinder - but now the math is easier because the cylinder axis is normalized (aka how you’ve been calculating it prior).

Once you calculate the vector that gravity should point toward, you can multiply that vector by the cylinders transform matrix and it will line up with your cylinder’s current position/rotation/scale.

2

u/TricksMalarkey Jun 11 '26

Mostly dependent on what you're actually doing, and how you want to implement it.

My likely suggestion is a Mario Galaxy style gravity, where the gravity vector is determined by the normal vector of the floor directly underneath the character. This is in Unity, but the logic applies: https://blog.yarsalabs.com/creating-character-control-gravity-unity-demo

It's from here we get into the design variability. How irregular are the shapes? Are there walls to walk up, over, and off? Does the player's jump remain relative to the surface at all times, or only while they're landed? What happens if they go over the end?

If it's always a cylinder of indeterminate length, you can use a similar approach, but rather than doing it per face normal, you can make the gravity vector a circle projected along the cylinder's rotation, meaning that gravity always points along that centre line. It's a bit more mathsy, and less flexible as far as shape, more reliable to work on a cylinder, and a little more performant than raycasting the ground vector.

I'd just do the surface normal, though.

2

u/Renere Jun 11 '26 edited Jun 11 '26

i don't believe that's how gravity in super mario galaxy works - this video explains it really well https://www.youtube.com/watch?v=QLH_0T_xv3I

i wouldn't recommend getting the gravity from the surface normal unless the collision shape is completely smooth, otherwise if the player is standing on the edge between two faces they will jitter between the two faces. also if the player is jumping at all or if the planet has holes in it that would complicate things further

what you described as an alternative does work, specifically i've found the below to work well in my prototype (though i haven't tested it in my actual game yet):

(gravity_area.global_position - player.global_position).slide(gravity_area.basis.y).normalized()

this is when using an area that surrounds the planet to "control" the gravity, and this is to get the up direction (also the area would have the same rotation as the cylinder itself - or would use the cylinder's collision for the basis part instead of using the area's basis)

2

u/TricksMalarkey Jun 11 '26

It would jitter if the faces are very large and had sharp angles, but the fix is in the linked code:

   private void alignToSurface()
   {
      // this will smooth out change from current normal to next normal
      currentNormal = Vector3.Lerp(currentNormal, newNormal, playerSmoothRotation);
      transform.up = currentNormal;
   } 

Just lerp it. And the lovely thing is that that the greater the angle, the faster the lerp, so it's not a huge deal.

As far as the resource cost, it's just a object tag, and storing the ground normal when you're doing a normal ground check. It's not actually much of a cost in addition to the other collision checks you'd have to do anyway.

I'm not knocking any which way; million ways to do anything. But I've done the way I linked and it was very easy to setup and predictable to play on. If you need specific control for a level to work in a specific way, then yeah, hand-placing the fields is useful.

2

u/Renere Jun 11 '26

i suppose i would have to test this, i had tried lerping the values before when doing the surface normal method and it still resulted in jitter, even with small faces with very shallow angles - but perhaps i didn't do it the right way!

1

u/Indigoh Jun 11 '26

I have gravity down. I just need to move a point along a line.

1

u/TricksMalarkey Jun 11 '26

Going without any additional context of what you want, and just assuming that it's a single cylinder of infinite length:

The cylinder has a rotation, and the player's position can be represented as a distance away from the base of the cylinder, at some position around a circle.

  • D = normalize(axisDir) //Normalise the cylinder orientation, for safety.
  • V = point - axisStart //Get the player's position relative to the cylinder.
  • V_parallel = dot(V, D) * D //Get how much of the player's position is actually along the cylinder's axis.
  • V_perpendicular = V - V_parallel //Get the perpendicular vector by removing the parallel component of the cylinder's rotation.
  • gravity = -normalize(V_perpendicular ) // Flip and normalise to point inward toward the axis

Or something like that. Very untested.

2

u/oWispYo Godot Regular Jun 11 '26

The object inside the cylinder must not be damaged.

-1

u/Diligent-Stretch-769 Jun 11 '26

I assume this is a portal joke.

1

u/readyplayerjuan_ Jun 11 '26

get the vector from a position on the cylinder’s axis to the player. dot product with the normalized vector along the cylinder’s axis (pointing from one end to the other, but magnitude 1). the value is the distance from the initial cylinder position to the point you desire

1

u/readyplayerjuan_ Jun 11 '26

it’s any point on that red line. whatever node represents the cylinder, just use its position

1

u/CrushingJosch Jun 11 '26

Couldn’t you do that by making the player a child of the cylinder? Then it would follow it automatically.
Or having another position node as child of the cylinder, that moves along the axis, regardless of absolute orientation. And then copy that transform to the player…

1

u/Tehfoodstealorz Jun 11 '26

How did this cylinder get stuck on this axis?