-- right trigger: teleport-jump to targeted location
-- right thumbstick: rotate the view horizontally
local motion = {
pose = lovr.math.newMat4(), -- Transformation in VR initialized to origin (0,0,0) looking down -Z
thumbstickDeadzone = 0.4, -- Smaller thumbstick displacements are ignored (too much noise)
-- Snap motion parameters
snapTurnAngle = 2 * math.pi / 12,
thumbstickCooldownTime = 0.3,
thumbstickCooldown = 0,
-- Teleport motion parameters
teleportDistance = 12,
blinkTime = 0.5,
blinkStopwatch = math.huge,
teleportValid = false,
targetPosition = lovr.math.newVec3(),
teleportCurve = lovr.math.newCurve(3),
}
lovr.graphics.setBackgroundColor(0.1, 0.1, 0.1)
function motion.teleport(dt)
-- Teleportation determining target position and executing jump when triggered
local handPose = mat4(motion.pose):mul(mat4(lovr.headset.getPose('hand/right/point')))
local handPosition = vec3(handPose)
local handDirection = quat(handPose):direction()
-- Intersect with ground plane
local ratio = vec3(handPose).y / handDirection.y
local intersectionDistance = math.sqrt(handPosition.y^2 + (handDirection.x * ratio)^2 + (handDirection.z * ratio)^2)
motion.targetPosition:set(handPose:translate(0, 0, -intersectionDistance))
-- Check if target position is a valid teleport target
motion.teleportValid = motion.targetPosition.y < handPosition.y and
(handPosition - motion.targetPosition):length() < motion.teleportDistance
-- Construct teleporter visualization curve
local midPoint = vec3(handPosition):lerp(motion.targetPosition, 0.3)
if motion.teleportValid then
midPoint:add(vec3(0, 0.1 * intersectionDistance, 0)) -- Fake a parabola
end
motion.teleportCurve:setPoint(1, handPosition)
motion.teleportCurve:setPoint(2, midPoint)
motion.teleportCurve:setPoint(3, motion.targetPosition)
-- Start timer when jump is triggered, preform jump on half-blink
if lovr.headset.wasPressed('right', 'trigger') and motion.teleportValid then
motion.blinkStopwatch = -motion.blinkTime / 2
end
-- Preform jump with VR pose offset by relative distance between here and there
if motion.blinkStopwatch < 0 and
motion.blinkStopwatch + dt >= 0 then
local headsetXZ = vec3(lovr.headset.getPosition())
headsetXZ.y = 0
local newPosition = motion.targetPosition - headsetXZ
motion.pose:set(newPosition, vec3(1,1,1), quat(motion.pose)) -- ZAAPP
end
-- Snap horizontal turning (often combined with teleport mechanics)
if lovr.headset.isTracked('right') then
local x, y = lovr.headset.getAxis('right', 'thumbstick')
if math.abs(x) > motion.thumbstickDeadzone and motion.thumbstickCooldown < 0 then
local angle = -x / math.abs(x) * motion.snapTurnAngle
motion.pose:rotate(angle, 0, 1, 0)
motion.thumbstickCooldown = motion.thumbstickCooldownTime
end
end
motion.blinkStopwatch = motion.blinkStopwatch + dt
motion.thumbstickCooldown = motion.thumbstickCooldown - dt
end
function motion.drawTeleport(pass)
-- Teleport target and curve
pass:setColor(1, 1, 1, 0.1)
if motion.teleportValid then
pass:setColor(1, 1, 0)
pass:cylinder(motion.targetPosition, 0.4,0.05, math.pi/2, 1,0,0)
pass:setColor(1, 1, 1)
end
--lovr.graphics.setLineWidth(4)
pass:line(motion.teleportCurve:render(30))
-- Teleport blink, modeled as gaussian function
local blinkAlpha = math.exp(-(motion.blinkStopwatch/ 0.25 / motion.blinkTime)^2)
pass:setColor(0,0,0, blinkAlpha)
pass:fill()
end
function lovr.update(dt)
motion.teleport(dt)
end
function lovr.draw(pass)
pass:transform(mat4(motion.pose):invert())
-- Render hands
pass:setColor(1,1,1)
local radius = 0.04
for _, hand in ipairs(lovr.headset.getHands()) do
-- Whenever pose of hand or head is used, need to account for VR movement
local poseRW = mat4(lovr.headset.getPose(hand))
local poseVR = mat4(motion.pose):mul(poseRW)
poseVR:scale(radius)
pass:sphere(poseVR)
end
-- Some scenery
lovr.math.setRandomSeed(0)
local goldenRatio = (math.sqrt(5) + 1) / 2
local goldenAngle = (2 - goldenRatio) * (2 * math.pi)
local k = 1.8
for i = 1, 500 do
local r = math.sqrt(i) * k
local x = math.cos(goldenAngle * i) * r
local y = math.sin(goldenAngle * i) * r
if lovr.math.random() < 0.05 then
pass:setColor(0.8, 0.5, 0)
else
local shade = 0.1 + 0.3 * lovr.math.random()
pass:setColor(shade, shade, shade)
end
pass:cylinder(x, -0.01, y, 1,0.02, math.pi / 2, 1,0,0)
end
motion.drawTeleport(pass)
end