VR picking up object is weird

For your use case, you could probably do something like this with RunService to figure out the velocity:

local part = --part being cframed

-- doesn't have to be heartbeat
local lastPosition
local connection
connection = RunService.Heartbeat:Connect(function(dt)
    local part.Position = --whatever you want the position to be
    if NeedsToBeThrown and lastPosition then
        --disengage part
        part.Velocity = (part.Position - lastPosition)/dt
        connection:Disconnect()
    end
    lastPosition = part.Position
end)

This might throw the item backwards, and if so, reverse the sign on the velocity assignment.

You can also use a while loop to do this:

while true do
    local dt = task.wait()
    part.Position = --input
    if NeedsToBeThrown and lastPosition then
        --disengage
        part.Velocity = (part.Position - lastPosition)/dt
        break
    end
    lastPosition = part.Position
end
1 Like

Thank you. I’ll test this out and see if this works.

1 Like

What makes CollectionService complicated? Using CollectionService means one instance less for each object that can be picked up, potentially amounting to thousands of less instances used. (less memory used)

1 Like

I can think about adding CollectionService later, because I am trying to work on the picking up and throwing, so your suggestion will be noted.

You might also want to detect where to position the brick differently then. Maybe you are not calculating the position(s) correctly then?

I can’t show you any math equation(s) / “Position-calculator-code”, because I am not familiar with VR, and I am not the person to ask, and this is not a request catagory.

The post below me is a pretty clever too.

1 Like

What I would do to correctly position the part is use a WeldConstraint (not a Weld, as that changes the part’s position unrealistically, I think), and just destroy the weld when you want to throw it. That way, it looks like you’re holding it in your hand and the physics engine does the holding (and throwing!) for you.

2 Likes

I did try using a WeldConstraint before with the unanchored part (not massless), but it seemed to just flop when I tried to throw it.

1 Like

I should’ve guessed this since the hands are not simulated by the physics engine, I guess you should try that loop I was talking about earlier then, but just recording the last position instead of setting it.

1 Like

I put your position detecting inside of my repeat loop (renderstep) and it seems to work good, I’d say.
https://gyazo.com/1dbb690cb4e5403f975cf6d31eed44a6

2 Likes

Big thanks to everyone that has helped me with this, and their suggestions.

2 Likes

I always recommend ContextActionService over UserInputService for cases where you only to an event to fire whenever a specific key is pressed. InputBegan fires whenever an input is began, so it will fire when not needed. ContextActionService also accounts for if the player is selecting a textbox so you don’t need to use GetFocusedTextBox.

This would create a lot of lag with lots of instances under workspace, so using GetTagged could give you a significant performance gain if you have a lot of parts under workspace.

I am not worried about optimization currently. When I finish working on the basics, I will optimize my script.

RenderStepped should be used instead of heartbeat for the positioning. This will prevent the movement from becoming choppy and set the part’s position every frame.

Also, if RenderStepped is used then there is no need to anchor the part. The part will be in the correct position every frame. Anchoring it may prevent the physics engine from doing any extra though.

The average weighted change of the last few positions should be used. This is because if the hand position changes at the last moment perhaps due to an artifact of position precision, the object would be thrown in a very different direction. Not too many positions should be used otherwise it would be unresponsive. The changes should also be weighted according to how close they are to the release since changes closer to the release probably mean more to the desired direction.

I get that, but the reason I anchor it is because whenever I do throw it, it just bounces back fast. The way it gets thrown is fine with the solution that was used. Plus, I already am using RenderStepped.

So, implementation of your suggestion into code, using an array:

local lastPositions = {}

connection = RunService.RenderStepped:Connect(function(dt)
	part.Position = --input
	if NeedsToBeThrown and #lastPositions > 0 then
		--disengage
		local sumVelocity = part.Position - lastPositions[1]
		part.Velocity = sumVelocity / (#lastPositions * dt)
		connection:Disconnect()
	end

	table.insert(lastPositions,part.Position)
	if #lastPositions > 5 then --really a variable
		table.remove(lastPositions,1)
	end
end)

Also, reasoning for not taking the smaller instantaneous velocities for each iteration and taking the “true” average is shown here, sort of:

local sumVelocity = Vector3.new()

for i = 1, #lastPositions - 1 do
	sumVelocity = sumVelocity + lastPositions[i+1] - lastPositions[i]
end
-- this becomes ([2] - [1]) + ([3] - [2]) + ([4] - [3]) + ([5] - [4])
-- which evaluates to [2] - [1] + [3] - [2] + [4] - [3] + [5] - [4]
-- which is resorted into [5] - [4] + [4] - [3] + [3] - [2] + [2] - [1]
-- which makes everything cancel out except [5] and -[1]

-- this becomes the next line:
sumVelocity = sumVelocity + lastPositions[#lastPositions] - lastPositions[1]
sumVelocity = sumVelocity + part.Position - lastPositions[#lastPositions]
-- this makes lastPositions[#lastPositions] cancel out too!
-- which creates [current] - [1]

I suggest tuning how large the length of the array can be, which is dictated by this snippet at the end:

if #lastPositions > 5 then
	table.remove(lastPositions,1)
end

Please reply if you think taking the sum of dt is important too!

1 Like

The simplification of the average depends on not weighting the positions according to their closeness to the release event. It also requires you to disregard the spacing of the positions in time (assumes they are all the same).

Here is a full implementation of weighting the velocities and keeping track of the spacing:


local RunService = game:GetService 'RunService'

-- << INTERFACE >>
local function getHandCF()
	return CFrame.new() -- TODO: edit to get the hand position
end

local function didLetGo()
	return false -- TODO: edit to return true when the hand let go
end

local function setCF(cf)
	-- TODO: edit to set the part's CFrame
end

local function setV(v)
	-- TODO: edit to set the part's Velocity
end

-- << FIFO stack >>
-- implemented as a linked list
local push, clean, toArray, reset
do
	local first, last

	function push(t, v)
		local record = {
			t = t;
			v = t;
		}

		if first then
			first = record
			last = record
		else
			last.next = record
			last = record
		end
	end

	function clean(t, freshness)
		while first and t - first.t > freshness do
			first = first.next
		end

		if first == nil then
			last = nil
		end
	end

	function toArray()
		local arr = {}
		local cur = first
		while cur do
			arr[#arr + 1] = cur.v
			cur = cur.next
		end
		return arr
	end

	function reset()
		first = nil
		last = nil
	end
end

-- Custom reduce function. Goes through the array
-- accumilating the results of the given callback.
-- Call the callback with the previous and current
-- values in the array. Both values must be defined
local function reduce(arr, func, acc)
	local i = 1
	local prv, cur = nil, arr[1]

	while cur do
		acc = func(acc, prv, cur)
		prv = cur
		i = i + 1
		cur = arr[i]
	end

	return acc
end

-- Calculate the area under the curve of sqrt
-- multiplied by 3/2 to make total area between
-- 0 and 1 equal to 1. This makes the percent of
-- the total area equal to the partial area.
-- The integral was used to calculate this formula.
local p = 3 / 2
local function AUC(x)
	return x ^ p
end

local t = 0
local freshness = 0.1

-- Calculate the weight for the given time period
local function getWeight(ct, dt)
	-- percent of time at start of period since start
	local a = 1 - ((t - ct) / freshness)

	-- percent of time at end of period since start
	local b = a + dt / freshness
	return AUC(b) - AUC(a)
end

local function toVelocity(curV, prv, cur)
	local prv_ct
	if prv then
		prv_ct = prv.ct
	else
		prv_ct = t - freshness
	end
	return curV + getWeight(prv_ct, cur.ct - prv_ct) * cur.cv
end

local connection
local lastClean = t
local cleanPeriod = 5 -- clean cache every 5 seconds
local lastP = Vector3.new()

-- TODO: keep track of last CF to calculate rotational velocity
local function onRenderStepped(dt)
	t = t + dt

	local cf = getHandCF()
	setCF(cf)
	push(t, {
		ct = t; -- absolute time of cf start
		cv = cf.p - lastP;
	})
	lastP = cf.P

	if didLetGo() then
		clean(t, freshness)
		connection:Disconnect()
		connection = nil
		setV(reduce(
			toArray(),
			toVelocity,
			Vector3.new()
		))
		reset()
	elseif t - lastClean > cleanPeriod then
		clean(t, freshness)
	end
end

connection = RunService.RenderStepped:Connect(onRenderStepped)

Since my VR headset isn’t detected by Roblox, I’ll leave testing to the OP. It uses 3/2 * sqrt(x) as the weight function, meaning that positions closer to the moment of release all have a reasonable influence on the velocity, quickly dropping off near the maximum freshness (0.1 seconds ago in this case).

4 Likes

It has already been solved, so I do not need any more help for these physics as the current one works fine.

1 Like

Hi i am also making a Vr game Does Anyone Know Where To Get this Script?
Cuz I need a Grab Script for My Vr Game

Thanks! -syntax