Skip to content

Commit 10300a8

Browse files
committed
Enhance RopePhysics with improved collision handling and constraint application
Enhances rope physics for collision and constraints. Improves collision detection by using precise raycasting for all rope segments and updates the response to accurately halt segments at impact. Introduces an iterative constraint relaxation method that ensures the rope maintains its target total length. This method correctly anchors rope ends and handles coincident segment points for greater stability. Refactors and enhances rope physics simulation Refactors rope physics logic, centralizing it into a dedicated module. Enhances collision detection with precise raycasting for all segments, ensuring segments halt accurately at impact and rebound to prevent phasing. Introduces an iterative constraint relaxation method to maintain target rope length. This includes spring damping to prevent extreme tension forces on actors and improve stability, particularly for anchored ends. Adds light rope smoothing to reduce jaggedness and refines rope length management. Rope rendering now features a color gradient, and debug information is updated. Adjusts various physics parameters, including segment limits and pull ratios, for improved grappling behavior.
1 parent 9e0e520 commit 10300a8

File tree

3 files changed

+383
-179
lines changed

3 files changed

+383
-179
lines changed

Data/Base.rte/Devices/Tools/GrappleGun/Grapple.lua

Lines changed: 56 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ function Create(self)
2727

2828
self.limitReached = false
2929
self.stretchMode = false -- Alternative elastic pull mode a là Liero
30-
self.stretchPullRatio = 0.1
30+
self.stretchPullRatio = 0.01 -- How much the rope stretches when pulling in stretch mode
3131
self.pieSelection = 0 -- 0 is nothing, 1 is full retract, 2 is partial retract, 3 is partial extend, 4 is full extend
3232

3333
self.climbDelay = 8 -- Faster climbing for shorter rope
@@ -48,8 +48,8 @@ function Create(self)
4848
self.cablespring = 0.15 -- VelvetGrapple constraint stiffness
4949

5050
-- Dynamic rope segment calculation variables
51-
self.minSegments = 4 -- Minimum number of segments
52-
self.maxSegments = 50 -- Maximum number of segments
51+
self.minSegments = 1 -- Minimum number of segments
52+
self.maxSegments = 500 -- Maximum number of segments
5353
self.segmentLength = 12 -- Target length per segment (increased for better performance)
5454
self.currentSegments = self.minSegments -- Current number of segments
5555

@@ -137,11 +137,17 @@ function Update(self)
137137
self.lineLength = self.lineVec.Magnitude
138138
self.currentLineLength = self.lineLength
139139

140-
-- Update rope anchor points directly
140+
-- Update rope anchor points directly for flight mode
141141
self.apx[0] = self.parent.Pos.X
142142
self.apy[0] = self.parent.Pos.Y
143143
self.apx[self.currentSegments] = self.Pos.X
144144
self.apy[self.currentSegments] = self.Pos.Y
145+
146+
-- Set all lastX/lastY positions to prevent velocity inheritance from previous mode
147+
for i = 0, self.currentSegments do
148+
self.lastX[i] = self.apx[i]
149+
self.lastY[i] = self.apy[i]
150+
end
145151
end
146152

147153
-- Calculate optimal number of segments based on rope length using our module function
@@ -155,75 +161,46 @@ function Update(self)
155161
end
156162
end
157163

158-
-- Rope physics simulation using VelvetGrapple approach
159-
local cablelength = self.currentLineLength / math.max(1, self.currentSegments) -- Dynamic per-segment length
164+
-- Proper rope physics simulation using the RopePhysics module
165+
local endPos = self.Pos
160166

161-
-- Set anchor points and update physics for all segments
162-
local i = self.currentSegments
163-
-- HANDLE ALL LINE JOINTS (from n down to 0)
164-
while i > -1 do
165-
if i == 0 or (i == self.currentSegments and self.limitReached == false and (self.actionMode == 1 or self.actionMode > 1)) then
166-
-- Anchor points: 0 (player) and n (hook)
167-
if i == 0 then -- POINT 0: ANCHOR TO GUN
168-
local usepos = self.parent.Pos
169-
self.apx[i] = usepos.X
170-
self.apy[i] = usepos.Y
171-
self.lastX[i] = self.lastPos.X
172-
self.lastY[i] = self.lastPos.Y
173-
else -- POINT n: ANCHOR TO GRAPPLE if IN FLIGHT
174-
local usepos = self.Pos
175-
self.apx[i] = usepos.X
176-
self.apy[i] = usepos.Y
177-
self.lastX[i] = usepos.X
178-
self.lastY[i] = usepos.Y
179-
end
180-
else
181-
if not (i == self.currentSegments and self.actionMode == 2) then
182-
-- CALCULATE BASIC PHYSICS
183-
local accX = 0
184-
local accY = 0.05
185-
186-
local velX = self.apx[i] - self.lastX[i]
187-
local velY = self.apy[i] - self.lastY[i]
188-
189-
local ufriction = self.usefriction
190-
if i == self.currentSegments then
191-
ufriction = 0.99
192-
accY = 0.5
193-
end
194-
195-
local nextX = (velX + accX) * ufriction
196-
local nextY = (velY + accY) * ufriction
197-
198-
self.lastX[i] = self.apx[i]
199-
self.lastY[i] = self.apy[i]
200-
201-
-- Use physics module for collision handling
202-
RopePhysics.verletCollide(self, i, nextX, nextY)
203-
end
204-
205-
if i == self.currentSegments and self.actionMode == 3 then
206-
if self.target and self.target.ID ~= rte.NoMOID then
207-
local target = self.target
208-
if target.ID ~= target.RootID then
209-
local mo = target:GetRootParent()
210-
if mo.ID ~= rte.NoMOID and IsAttachable(target) then
211-
target = mo
212-
end
213-
end
214-
215-
self.lastX[i] = self.apx[i]-target.Vel.X
216-
self.lastY[i] = self.apy[i]-target.Vel.Y
217-
else -- Our MO has been destroyed, return hook
218-
self.ToDelete = true
219-
end
167+
-- Choose physics simulation based on rope mode
168+
if self.actionMode == 1 then
169+
-- Flight mode - keep rope tight and straight
170+
RopePhysics.updateRopeFlightPath(self)
171+
else
172+
-- Attached mode - run full physics simulation
173+
RopePhysics.updateRopePhysics(self, startPos, endPos, self.currentLineLength)
174+
175+
-- Apply constraints to maintain rope structure and length
176+
RopePhysics.applyRopeConstraints(self, self.currentLineLength)
177+
end
178+
179+
-- Special handling for attached targets (MO grabbing)
180+
if self.actionMode == 3 and self.target and self.target.ID ~= rte.NoMOID then
181+
local target = self.target
182+
if target.ID ~= target.RootID then
183+
local mo = target:GetRootParent()
184+
if mo.ID ~= rte.NoMOID and IsAttachable(target) then
185+
target = mo
220186
end
221187
end
222188

223-
-- Get optimized iteration count based on rope conditions
224-
local maxIterations = RopePhysics.optimizePhysicsIterations(self)
225-
226-
i = i-1
189+
-- Update hook position to follow the target
190+
self.Pos = target.Pos
191+
self.apx[self.currentSegments] = target.Pos.X
192+
self.apy[self.currentSegments] = target.Pos.Y
193+
194+
-- Apply target velocity to the hook anchor for physics continuity
195+
self.lastX[self.currentSegments] = self.apx[self.currentSegments] - target.Vel.X
196+
self.lastY[self.currentSegments] = self.apy[self.currentSegments] - target.Vel.Y
197+
else
198+
-- Update hook position from rope physics when not attached to MO
199+
if self.actionMode > 1 then -- Hook is stuck to terrain
200+
-- Let the rope physics determine hook position constraints
201+
self.Pos.X = self.apx[self.currentSegments]
202+
self.Pos.Y = self.apy[self.currentSegments]
203+
end
227204
end
228205

229206
-- Draw the rope using the renderer module
@@ -236,19 +213,23 @@ function Update(self)
236213
local debugPos = self.Pos + Vector(10, -30) -- Define a position for debug text
237214
RopeRenderer.showDebugInfo(self, player, debugPos)
238215

239-
-- Update hook position based on rope physics
216+
-- Update lineVec and lineLength based on current positions
217+
self.lineVec = SceneMan:ShortestDistance(self.parent.Pos, self.Pos, self.mapWrapsX)
218+
self.lineLength = self.lineVec.Magnitude
219+
220+
-- Update hook position if length limit is reached during flight
240221
if self.actionMode == 1 and self.limitReached == true then
241222
self.Pos.X = self.apx[self.currentSegments]
242223
self.Pos.Y = self.apy[self.currentSegments]
243224
end
244225

245-
self.lineVec = SceneMan:ShortestDistance(self.parent.Pos, self.Pos, self.mapWrapsX)
246-
self.lineLength = self.lineVec.Magnitude
247-
248-
-- Update current line length
226+
-- Update current line length based on action mode
249227
if self.actionMode == 1 and self.limitReached == false then
250-
-- Always update rope length while in flight
228+
-- Always update rope length while in flight - rope should be tight
251229
self.currentLineLength = self.lineLength
230+
elseif self.actionMode > 1 then
231+
-- When attached, maintain set line length for physics constraints
232+
-- currentLineLength should only be updated by player input or automatic climbing
252233
end
253234

254235
-- Check if line length exceeds maximum

0 commit comments

Comments
 (0)