Quellcode durchsuchen

Port docs to new vector API;

bjorn vor 4 Wochen
Ursprung
Commit
75c81ffb9d
30 geänderte Dateien mit 416 neuen und 395 gelöschten Zeilen
  1. 28 23
      examples/Animation/2_Bone_IK/main.lua
  2. 7 7
      examples/Effects/Billboards/main.lua
  3. 53 58
      examples/Effects/Cubemap_Generate/main.lua
  4. 69 64
      examples/Effects/Stencil/main.lua
  5. 43 18
      examples/Environment/Mirror/main.lua
  6. 11 10
      examples/Flatscreen/FPS_Controls/main.lua
  7. 4 1
      examples/Interaction/Controller_Models/main.lua
  8. 1 1
      examples/Interaction/Custom_Hand_Rig/main.lua
  9. 9 9
      examples/Interaction/Dragging/main.lua
  10. 13 14
      examples/Interaction/Dragging_with_Rotation/main.lua
  11. 5 6
      examples/Interaction/Hand_Gestures/main.lua
  12. 9 8
      examples/Interaction/Physics_Pointer/main.lua
  13. 2 2
      examples/Interaction/Pointer/main.lua
  14. 9 9
      examples/Interaction/Pointer_UI/main.lua
  15. 6 5
      examples/Lighting/Circular_Shadows/main.lua
  16. 10 8
      examples/Lighting/Shadows/main.lua
  17. 9 12
      examples/Locomotion/Basic_Thumbsticks/main.lua
  18. 17 21
      examples/Locomotion/Space_Stretch/main.lua
  19. 16 19
      examples/Locomotion/Teleportation_Colliders/main.lua
  20. 11 14
      examples/Locomotion/Teleportation_Flat/main.lua
  21. 4 6
      examples/Locomotion/Walking_In_Place/main.lua
  22. 5 5
      examples/Optimization/Instancing/main.lua
  23. 8 8
      examples/Optimization/Instancing_-_Blob/main.lua
  24. 9 9
      examples/Optimization/Instancing_-_Compute/main.lua
  25. 23 21
      examples/Physics/Hand_Physics/main.lua
  26. 6 6
      examples/Physics/Newtons_Cradle/main.lua
  27. 7 7
      examples/Physics/Saloon_Door/main.lua
  28. 2 1
      examples/Physics/Terrain/main.lua
  29. 15 17
      examples/Physics/Wrecking_Ball/main.lua
  30. 5 6
      examples/Physics/Zip_Line/main.lua

+ 28 - 23
examples/Animation/2_Bone_IK/main.lua

@@ -1,47 +1,50 @@
+local transform = lovr.math.newMat4()
+
 local function solve(root, target, control, lengths)
-  local T = vec3(target - root)
-  local C = vec3(control - root)
-  local R = vec3(0)
+  local T = target - root
+  local C = control - root
+  local R = vector(0)
 
   -- Basis
-  local bx = vec3(T):normalize()
-  local by = (C - bx * (C:dot(bx))):normalize()
-  local bz = vec3(bx):cross(by)
+  local bx = T:normalize()
+  local by = (C - bx * C:dot(bx)):normalize()
+  local bz = bx:cross(by)
 
   -- Matrix from basis
-  local transform = mat4(
-    bx.x, by.x, bz.x, 0,
-    bx.y, by.y, bz.y, 0,
-    bx.z, by.z, bz.z, 0,
+  transform:set(
+    bx.x, bx.y, bx.z, 0,
+    by.x, by.y, by.z, 0,
+    bz.x, bz.y, bz.z, 0,
     0, 0, 0, 1
-  ):transpose()
+  )
 
-  local distance = #T
+  local distance = T:length()
   local x = (distance + (lengths[1] ^ 2 - lengths[2] ^ 2) / distance) / 2
   local y = math.sqrt(lengths[1] ^ 2 - x ^ 2)
-  local solution = vec4(x, y, 0, 1)
-  return root + (transform * solution).xyz
+  local solution = vector(x, y, 0)
+  return root + transform * solution
 end
 
 function lovr.load()
   boneLengths = { .3, .3 }
-  root = lovr.math.newVec3(-.2, 1.5, -.5)
-  target = lovr.math.newVec3(.2, 1.5, -.7)
-  control = lovr.math.newVec3(0, 1.8, -.6)
+  points = {
+    root = vector(-.2, 1.5, -.5),
+    target = vector(.2, 1.5, -.7),
+    control = vector(0, 1.8, -.6)
+  }
   pointSize = .04
   drags = {}
 end
 
 function lovr.update(dt)
   -- Allow hands to drag any of the points
-  local points = { root, target, control }
   for i, hand in ipairs(lovr.headset.getHands()) do
-    local handPosition = vec3(lovr.headset.getPosition(hand .. '/point'))
+    local handPosition = vector(lovr.headset.getPosition(hand .. '/point'))
 
     if lovr.headset.wasPressed(hand, 'trigger') then
-      for j, point in ipairs(points) do
+      for k, point in pairs(points) do
         if handPosition:distance(point) < pointSize then
-          drags[hand] = point
+          drags[hand] = k
         end
       end
     elseif lovr.headset.wasReleased(hand, 'trigger') then
@@ -49,12 +52,13 @@ function lovr.update(dt)
     end
 
     if drags[hand] then
-      drags[hand]:set(handPosition)
+      points[drags[hand]] = handPosition
     end
   end
 end
 
 function lovr.draw(pass)
+  local root, target, control = points.root, points.target, points.control
 
   -- Draw the joints and the control point
   pass:setColor(0xff80ff)
@@ -66,7 +70,8 @@ function lovr.draw(pass)
   -- Draw the hand
   pass:setColor(0xffffff)
   for _, hand in ipairs(lovr.headset.getHands()) do
-    pass:cube(mat4(lovr.headset.getPose(hand .. '/point')):scale(.01))
+    local x, y, z, angle, ax, ay, az = lovr.headset.getPose(hand .. '/point')
+    pass:cube(x, y, z, .01, angle, ax, ay, az)
   end
 
   -- Draw a line from the root to the result from the IK solver, then to the target

+ 7 - 7
examples/Effects/Billboards/main.lua

@@ -8,10 +8,9 @@ function lovr.load()
   for x = 0, puffImageSize - 1 do
     for y = 0, puffImageSize - 1 do
       local radius = puffImageSize / 2 - 1
-      local distanceFromCenter = vec2(radius, radius):distance(vec2(x, y))
+      local distanceFromCenter = vector(radius, radius, 0):distance(vector(x, y, 0))
       local depth = math.sqrt(radius^2 - distanceFromCenter^2)
       puffImage:setPixel(x, y, 1, 1, 1, depth / radius)
-      lovr.math.drain()
     end
   end
   puffTexture = lovr.graphics.newTexture(puffImage)
@@ -21,7 +20,7 @@ function lovr.load()
   redPuffs = {}
   for i = 1, 100 do
     table.insert(redPuffs, {
-      position = lovr.math.newVec3(
+      position = vector(
         lovr.math.random() - .5,
         lovr.math.random() - .5 + 1.7,
         lovr.math.random() - .5
@@ -31,7 +30,7 @@ function lovr.load()
   bluePuffs = {}
   for i = 1, 100 do
     table.insert(bluePuffs, {
-      position = lovr.math.newVec3(
+      position = vector(
         lovr.math.random() - .5, 
         lovr.math.random() - .5 + 1.7,
         lovr.math.random() - .5
@@ -83,12 +82,14 @@ end
 function lovr.draw(pass)
   pass:setSampler('nearest')                                         -- try uncommenting
 
+  local head = vector(lovr.headset.getPosition())
+
   -- red puffs
   pass:setColor(1, .9, .9)
   pass:setMaterial(puffMaterial)
   pass:setDepthWrite(false)
   pass:setShader(redPuffShader)
-  pass:send('headPosition', { lovr.headset.getPosition('head') })
+  pass:send('headPosition', head)
   for _, puff in pairs(redPuffs) do
     pass:plane(puff.position, .1)
   end
@@ -97,7 +98,6 @@ function lovr.draw(pass)
   -- blue puffs
   pass:setColor(.9, 1, 1)
   for _, puff in pairs(bluePuffs) do
-    puff.orientation = quat(mat4():target(puff.position, vec3(lovr.headset.getPosition('head'))))
-    pass:plane(puff.position, .1, .1, puff.orientation)
+    pass:plane(puff.position, .1, .1, quaternion.lookdir(head - puff.position))
   end
 end

+ 53 - 58
examples/Effects/Cubemap_Generate/main.lua

@@ -10,56 +10,57 @@ local scene = {}
 function scene.load()
 	scene.floorSize = 6
 	scene.cubeCount = 60
-	scene.boundMin = lovr.math.newVec3(-10, -1, -10)
-	scene.boundMax = lovr.math.newVec3(10,   9,  10)
+	scene.boundMin = vector(-10, -1, -10)
+	scene.boundMax = vector(10,   9,  10)
 	scene.speed = 1
 	scene.rotateSpeed = 1
 	scene.cubeSize = 0.2
 	scene.cubes = {}
 
-	scene.sphereCenter = lovr.math.newVec3(0, 1.5, -0.5)
+	scene.sphereCenter = vector(0, 1.5, -0.5)
 	scene.sphereRad = 0.125
 
-	for i=1,scene.cubeCount do
+	for i = 1, scene.cubeCount do
 		scene.generate(i, true)
 	end
 end
 
 local function randomQuaternion()
-	-- Formula from http://planning.cs.uiuc.edu/node198.html
-	local u,v,w = math.random(), math.random(), math.random()
-	return lovr.math.newQuat( math.sqrt(1-u)*math.sin(2*v*math.pi),
-		        math.sqrt(1-u)*math.cos(2*v*math.pi),
-		        math.sqrt(u)*math.sin(2*w*math.pi),
-		        math.sqrt(u)*math.cos(2*w*math.pi),
-		        true ) -- Raw components
+	local u, v, w = math.random(), math.random(), math.random()
+	return quaternion.pack(
+		math.sqrt(1 - u) * math.sin(2 * v * math.pi),
+		math.sqrt(1 - u) * math.cos(2 * v * math.pi),
+		math.sqrt(u) * math.sin(2 * w * math.pi),
+		math.sqrt(u) * math.cos(2 * w * math.pi)
+	)
 end
 
 function scene.generate(i, randomZ) -- Generate each cube with random position and color and a random rotational velocity
 	local cube = {}
-	cube.at = lovr.math.newVec3()
-	cube.at.x = scene.boundMin.x + math.random()*(scene.boundMax.x-scene.boundMin.x)
-	cube.at.y = scene.boundMin.y + math.random()*(scene.boundMax.y-scene.boundMin.y)
+	local x = scene.boundMin.x + math.random() * (scene.boundMax.x - scene.boundMin.x)
+	local y = scene.boundMin.y + math.random() * (scene.boundMax.y - scene.boundMin.y)
+	local z
 	if randomZ then
-		cube.at.z = scene.boundMin.z + math.random()*(scene.boundMax.z-scene.boundMin.z)
+		z = scene.boundMin.z + math.random() * (scene.boundMax.z - scene.boundMin.z)
 	else
-		cube.at.z = scene.boundMin.z
+		z = scene.boundMin.z
 	end
+	cube.at = vector(x, y, z)
 	cube.rotateBasis = randomQuaternion()
-	cube.rotateTarget = lovr.math.newQuat(cube.rotateBasis:conjugate())
+	cube.rotateTarget = cube.rotateBasis:conjugate()
 	cube.rotate = cube.rotateBasis
-	cube.color = {math.random()*0.8, math.random()*0.8, math.random()*0.8}
+	cube.color = { math.random() * 0.8, math.random() * 0.8, math.random() * 0.8 }
 	scene.cubes[i] = cube
 end
 
 function scene.update(dt) -- On each frame, move each cube and spin it a little
-	for i,cube in ipairs(scene.cubes) do
-		cube.at.z = cube.at.z + scene.speed*dt
+	for i, cube in ipairs(scene.cubes) do
+		cube.at = cube.at + vector(0, 0, scene.speed * dt)
 		if cube.at.z > scene.boundMax.z then -- If cube left the scene bounds respawn it
 			scene.generate(i)
 		else
-			local rotateAmount = (cube.at.z - scene.boundMin.z)/(scene.boundMax.z-scene.boundMin.z)
-			cube.rotate = cube.rotateBasis:slerp( cube.rotateTarget, rotateAmount )
+			local rotateAmount = (cube.at.z - scene.boundMin.z) / (scene.boundMax.z - scene.boundMin.z)
+			cube.rotate = cube.rotateBasis:slerp(cube.rotateTarget, rotateAmount)
 		end
 	end
 end
@@ -67,20 +68,22 @@ end
 function scene.draw(pass)
 
 	-- First, draw a floor
-	local floorRecenter = scene.floorSize/2 + 0.5
-	for x=1,scene.floorSize do for y=1,scene.floorSize do
-		if (x+y)%2==0 then
-			pass:setColor(0.25,0.25,0.25)
-		else
-			pass:setColor(0.5,0.5,0.5)
+	local floorRecenter = scene.floorSize / 2 + 0.5
+	for x = 1, scene.floorSize do
+		for y = 1,scene.floorSize do
+			if (x + y) % 2==0 then
+				pass:setColor(0.25, 0.25, 0.25)
+			else
+				pass:setColor(0.5, 0.5, 0.5)
+			end
+			pass:plane(x - floorRecenter, 0, y - floorRecenter, 1, 1, math.pi / 2, 1, 0, 0)
 		end
-		pass:plane(x-floorRecenter,0,y-floorRecenter, 1,1, math.pi/2,1,0,0)
-	end end
+	end
 
 	-- Draw cubes
-	for _,cube in ipairs(scene.cubes) do
+	for _, cube in ipairs(scene.cubes) do
 		pass:setColor(unpack(cube.color))
-		pass:cube(cube.at.x, cube.at.y, cube.at.z, scene.cubeSize, cube.rotate:unpack())
+		pass:cube(cube.at, scene.cubeSize, cube.rotate:unpack())
 	end
 end
 
@@ -88,33 +91,25 @@ end
 
 local cubemap = {}
 
-local unitX = lovr.math.newVec3(1,0,0)
-local unitY = lovr.math.newVec3(0,1,0)
-local unitZ = lovr.math.newVec3(0,0,1)
+local unitX = vector(1, 0, 0)
+local unitY = vector(0, 1, 0)
+local unitZ = vector(0, 0, 1)
 
 function cubemap.load()
-	-- Create cubemap textures
-	local cubemapWidth, cubemapHeight = 256, 256
-	cubemap.texture = lovr.graphics.newTexture(cubemapWidth, cubemapHeight, 6, { type = "cube" })
-	cubemap.faces = {}
+	-- Create cubemap texture
+	cubemap.texture = lovr.graphics.newTexture(256, 256, 6, { type = "cube" })
 
 	-- Precalculate cubemap View-Projection matrices
 	local center = scene.sphereCenter
-	cubemap.facePerspective = lovr.math.newMat4():perspective(math.rad(90.0), 1, .1, 0)
-	for i,matrix in ipairs{
-		-- Not sure why the x flip is needed!
-		lovr.math.mat4():lookAt(center, center - unitX, vec3(0, 1, 0)),
-		lovr.math.mat4():lookAt(center, center + unitX, vec3(0, 1, 0)),
-		lovr.math.mat4():lookAt(center, center + unitY, vec3(0, 0, -1)),
-		lovr.math.mat4():lookAt(center, center - unitY, vec3(0, 0, 1)),
-		lovr.math.mat4():lookAt(center, center + unitZ, vec3(0, 1, 0)),
-		lovr.math.mat4():lookAt(center, center - unitZ, vec3(0, 1, 0))
-	} do
-		-- Each face will contain a matrix
-		local face = {}
-		face.matrix = lovr.math.newMat4(matrix)
-		cubemap.faces[i] = face
-	end
+	cubemap.facePerspective = lovr.math.newMat4():perspective(math.rad(90), 1, .1, 0)
+	cubemap.faces = {
+		lovr.math.newMat4():lookAt(center, center - unitX, vec3(0, 1, 0)),
+		lovr.math.newMat4():lookAt(center, center + unitX, vec3(0, 1, 0)),
+		lovr.math.newMat4():lookAt(center, center + unitY, vec3(0, 0, -1)),
+		lovr.math.newMat4():lookAt(center, center - unitY, vec3(0, 0, 1)),
+		lovr.math.newMat4():lookAt(center, center + unitZ, vec3(0, 1, 0)),
+		lovr.math.newMat4():lookAt(center, center - unitZ, vec3(0, 1, 0))
+	}
 
 	-- Create reflection shader
 	cubemap.shader = lovr.graphics.newShader('unlit', [[
@@ -140,7 +135,7 @@ function cubemap.draw()
 
 	for i = 1, 6 do
 		cubemap.pass:setProjection(i, cubemap.facePerspective)
-		cubemap.pass:setViewPose(i,cubemap.faces[i].matrix,true)
+		cubemap.pass:setViewPose(i, cubemap.faces[i], true)
 	end
 
 	scene.draw(cubemap.pass)
@@ -163,10 +158,10 @@ function lovr.draw(pass)
 	scene.draw(pass)
 
 	-- Draw sphere textured with cube map
-	pass:setColor(1,0.6,0.6)
+	pass:setColor(1, 0.6, 0.6)
 	pass:setShader(cubemap.shader)
-	pass:send("cubemap", cubemap.texture)
-	pass:sphere(scene.sphereCenter.x, scene.sphereCenter.y, scene.sphereCenter.z, scene.sphereRad)
+	pass:send('cubemap', cubemap.texture)
+	pass:sphere(scene.sphereCenter, scene.sphereRad)
 
 	return lovr.graphics.submit(cubemap.pass, pass)
 end

+ 69 - 64
examples/Effects/Stencil/main.lua

@@ -12,95 +12,100 @@ function scene.load()
 
   -- A series of sideways-drifting cubes (these will be stenciled)
   scene.driftCubeCount = 60
-  scene.boundMin = lovr.math.newVec3(-10, -1, -10)
-  scene.boundMax = lovr.math.newVec3( 10,  9,  10)
+  scene.boundMin = vector(-10, -1, -10)
+  scene.boundMax = vector( 10,  9,  10)
   scene.speed = 1
   scene.driftCubeSize = 0.6
   scene.driftCubes = {}
-  for i=1,scene.driftCubeCount do
+  for i = 1, scene.driftCubeCount do
     scene.generateDriftCube(i, true)
   end
 
   -- A 3x3 cube made of two different stencil types
-  scene.stencilCubeCenter = lovr.math.newVec3(0, 1.5, -0.5)
+  scene.stencilCubeCenter = vector(0, 1.5, -0.5)
   scene.stencilCubeSize = 0.25
   scene.stencilCubeRotate = 0
   scene.stencilCubeRotateSpeed = 1
   scene.stencilCubes = {}
-  for z=-1,1 do for y=-1,1 do for x=-1,1 do -- Iterate over every cube
-    if not (x==0 and y==0 and z==0) then -- Except the center
-      table.insert(scene.stencilCubes, {lovr.math.newVec3(x,y,z), math.random(1,2)}) -- Cube center and stencil type
+  for z = -1, 1 do
+    for y = -1, 1 do
+      for x = -1, 1 do -- Iterate over every cube
+        if not (x==0 and y==0 and z==0) then -- Except the center
+          table.insert(scene.stencilCubes, { vector(x,y,z), math.random(1,2) }) -- Cube center and stencil type
+        end
+      end
     end
-  end end end
+  end
 
   -- Three cubemap skyboxes, of different colors
   scene.skybox = {}
   local skyboxTextureSize = 32
   local bandSize=3
   for cube_index,colors in ipairs{
-    {{1, 0.5, 1}, {1,1,1}},     -- Fuschia and white
-    {{1, 1, 0.5},   {0,0,0}},   -- Yellow and black
-    {{1,1,1}, {0.9,0.9,0.9}},   -- White and silver
+    {{ 1, 0.5, 1 }, { 1, 1, 1 }}, -- Fuschia and white
+    {{ 1, 1, 0.5 }, { 0, 0, 0 }}, -- Yellow and black
+    {{ 1, 1, 1 }, { 0.9, 0.9, 0.9 }}, -- White and silver
   } do
     local layers = {}
-    for layer=1,6 do -- 6 layers to a cubemap
-      local data = lovr.data.newImage(skyboxTextureSize, skyboxTextureSize, "rgba8")
-      for y=1,skyboxTextureSize do for x=1,skyboxTextureSize do
-        local isBorder = x==1 or x==skyboxTextureSize or y==1 or y==skyboxTextureSize -- Solid color in corners
-        local direction = cube_index==3 and -1 or 1                          -- Reverse direction on third cubemap
-        local whichColor = (isBorder or ((x+direction*y-2)%(bandSize*2)>=bandSize)) and 1 or 2 -- Diagonal stripes
-        data:setPixel(x-1,y-1,unpack(colors[whichColor]))
-      end end
+    for layer = 1, 6 do -- 6 layers to a cubemap
+      local data = lovr.data.newImage(skyboxTextureSize, skyboxTextureSize, 'rgba8')
+      for y = 1, skyboxTextureSize do
+        for x = 1, skyboxTextureSize do
+          local isBorder = x == 1 or x == skyboxTextureSize or y == 1 or y == skyboxTextureSize -- Solid color in corners
+          local direction = cube_index==3 and -1 or 1  -- Reverse direction on third cubemap
+          local whichColor = (isBorder or ((x + direction * y - 2) % (bandSize * 2) >= bandSize)) and 1 or 2 -- Diagonal stripes
+          data:setPixel(x - 1, y - 1, unpack(colors[whichColor]))
+        end
+      end
       table.insert(layers, data)
     end
     table.insert(scene.skybox, lovr.graphics.newTexture(layers))
   end
 
-  scene.sampler = lovr.graphics.newSampler({filter="nearest"})
+  scene.sampler = lovr.graphics.newSampler({ filter = 'nearest' })
 end
 
-local function randomQuaternion() -- Generate one random rotation
-  -- Formula from http://planning.cs.uiuc.edu/node198.html
-  local u,v,w = math.random(), math.random(), math.random()
-  return lovr.math.newQuat(
-    math.sqrt(1-u)*math.sin(2*v*math.pi),
-    math.sqrt(1-u)*math.cos(2*v*math.pi),
-    math.sqrt(u)*math.sin(2*w*math.pi),
-    math.sqrt(u)*math.cos(2*w*math.pi),
-    true -- Raw components
-  )
+local function randomQuaternion()
+	local u, v, w = math.random(), math.random(), math.random()
+	return quaternion.pack(
+		math.sqrt(1 - u) * math.sin(2 * v * math.pi),
+		math.sqrt(1 - u) * math.cos(2 * v * math.pi),
+		math.sqrt(u) * math.sin(2 * w * math.pi),
+		math.sqrt(u) * math.cos(2 * w * math.pi)
+	)
 end
 
 function scene.generateDriftCube(i, randomX) -- Generate one cube with random position and color and a random rotational velocity
   local cube = {}
-  cube.at = lovr.math.newVec3()
+  local x, y, z
   if randomX then
-    cube.at.x = scene.boundMin.x + math.random()*(scene.boundMax.x-scene.boundMin.x)
+    x = scene.boundMin.x + math.random() * (scene.boundMax.x - scene.boundMin.x)
   else
-    cube.at.x = scene.boundMin.x
+    x = scene.boundMin.x
   end
-  cube.at.y = scene.boundMin.y + math.random()*(scene.boundMax.y-scene.boundMin.y)
-  cube.at.z = scene.boundMin.z + math.random()*(scene.boundMax.z-scene.boundMin.z)
+  y = scene.boundMin.y + math.random()*(scene.boundMax.y-scene.boundMin.y)
+  z = scene.boundMin.z + math.random()*(scene.boundMax.z-scene.boundMin.z)
 
+  cube.at = vector(x, y, z)
   cube.rotateBasis = randomQuaternion()
-  cube.rotateTarget = lovr.math.newQuat(cube.rotateBasis:conjugate())
+  cube.rotateTarget = cube.rotateBasis:conjugate()
   cube.rotate = cube.rotateBasis
   scene.driftCubes[i] = cube
 end
 
 function scene.update(dt) -- On each frame, move each cube and spin it a little
-  for i,cube in ipairs(scene.driftCubes) do
-    cube.at.x = cube.at.x + scene.speed*dt
+  for i, cube in ipairs(scene.driftCubes) do
+    cube.at = cube.at + vector(scene.speed * dt, 0, 0)
     if cube.at.x > scene.boundMax.x then -- If cube left the scene bounds respawn it
       scene.generateDriftCube(i)
     else
-      local rotateAmount = (cube.at.x - scene.boundMin.x)/(scene.boundMax.x-scene.boundMin.x)
-      cube.rotate = cube.rotateBasis:slerp( cube.rotateTarget, rotateAmount )
+      local rotateAmount = (cube.at.x - scene.boundMin.x) / (scene.boundMax.x - scene.boundMin.x)
+      cube.rotate = cube.rotateBasis:slerp(cube.rotateTarget, rotateAmount)
     end
   end
 
   -- Also rotate the center cube
-  scene.stencilCubeRotate = scene.stencilCubeRotate + dt*scene.stencilCubeRotateSpeed
+  scene.stencilCubeRotate = scene.stencilCubeRotate + dt * scene.stencilCubeRotateSpeed
 end
 
 function scene.draw(pass)
@@ -112,16 +117,18 @@ function scene.draw(pass)
   pass:skybox(scene.skybox[3])
 
   -- Next, draw a floor
-  local floorRecenter = scene.floorSize/2 + 0.5
-  for x=1,scene.floorSize do for y=1,scene.floorSize do
-    if (x+y)%2==0 then
-      pass:setColor(0.25,0.25,0.25)
-    else
-      pass:setColor(0.35,0.35,0.35)
+  local floorRecenter = scene.floorSize / 2 + 0.5
+  for x = 1, scene.floorSize do
+    for y = 1, scene.floorSize do
+      if (x + y) % 2 == 0 then
+        pass:setColor(0.25, 0.25, 0.25)
+      else
+        pass:setColor(0.35, 0.35, 0.35)
+      end
+      pass:plane(x - floorRecenter, 0, y - floorRecenter, 1, 1, -math.pi / 2, 1, 0, 0) -- Face up
     end
-    pass:plane(x-floorRecenter,0,y-floorRecenter, 1,1, -math.pi/2,1,0,0) -- Face up
-  end end
-  pass:setColor(1,1,1,1)
+  end
+  pass:setColor(1, 1, 1, 1)
 
   -- Stencils here
   -- Using stencils involves drawing twice, once with a stencil write set and once with a stencil test set.
@@ -137,23 +144,21 @@ function scene.draw(pass)
     local center, stencilValue = unpack(cube)
 
     -- Draw to stencil (but only when we pass the depth test)
-    pass:setStencilWrite({"keep", "keep", "replace"}, stencilValue)
-    pass:cube(center*scene.stencilCubeSize, scene.stencilCubeSize)
-
+    pass:setStencilWrite({'keep', 'keep', 'replace'}, stencilValue)
+    pass:cube(center * scene.stencilCubeSize, scene.stencilCubeSize)
   end
   pass:pop()
 
   pass:setStencilWrite() -- Reset stencil write
   pass:setColorWrite(true) -- Reset color write
 
-  -- Now that we've painted the stencil buffer, let's draw something with depth-- like a skybox
+  -- Now that we've painted the stencil buffer, let's draw something with depth, like a skybox
   pass:setDepthTest() -- Turn off depth test because the skybox is "behind" the cubes
-  for stencilValue=1,2 do
-    pass:setStencilTest("equal", stencilValue) -- Commands after here will only draw on pixels where the stencil value is right
-
+  for stencilValue = 1, 2 do
+    pass:setStencilTest('equal', stencilValue) -- Commands after here will only draw on pixels where the stencil value is right
     pass:skybox(scene.skybox[stencilValue])
   end
-  pass:setDepthTest("gequal") -- Turn depth test back on
+  pass:setDepthTest('gequal') -- Turn depth test back on
 
   -- Example 2: Using stencils to prevent collision
 
@@ -161,11 +166,11 @@ function scene.draw(pass)
   -- But we don't want any cubes to overlap each other. We want each cube to look like a "world of shadow".
   -- The cubes can darken the skybox and the 3x3 cube, but not any pixel where another cube has already drawn.
 
-  pass:setStencilTest("notequal", 3) -- We will write the value "3", but refuse to write any pixel where a 3 is already present.
-  pass:setStencilWrite("replace", 3) -- Note we haven't cleared the stencil buffer, so we can't reuse values 1 or 2.
-  for _,cube in ipairs(scene.driftCubes) do
-    pass:setColor(0.75,0.5,0.5,0.5)
-    pass:cube(cube.at.x, cube.at.y, cube.at.z, scene.driftCubeSize, cube.rotate:unpack())
+  pass:setStencilTest('notequal', 3) -- We will write the value "3", but refuse to write any pixel where a 3 is already present.
+  pass:setStencilWrite('replace', 3) -- Note we haven't cleared the stencil buffer, so we can't reuse values 1 or 2.
+  for _, cube in ipairs(scene.driftCubes) do
+    pass:setColor(0.75, 0.5, 0.5, 0.5)
+    pass:cube(cube.at, scene.driftCubeSize, cube.rotate)
   end
 
   -- The stencil state will reset at the end of this lovr.draw, but let's clear it anyway.
@@ -176,7 +181,7 @@ end
 -- Handle lovr
 
 function lovr.load()
-  lovr.graphics.setBackgroundColor(1,0,0) -- Red to show up clearly if something goes wrong
+  lovr.graphics.setBackgroundColor(1, 0, 0) -- Red to show up clearly if something goes wrong
   scene.load()
 end
 

+ 43 - 18
examples/Environment/Mirror/main.lua

@@ -1,41 +1,60 @@
 local mirrorPose = lovr.math.newMat4(0, 1.65, -0.75)
-local mirrorSize = lovr.math.newVec3(0.6, 1.2, 1)
+local inverseMirrorPose = lovr.math.newMat4(mirrorPose):invert()
+local mirrorSize = vector(0.6, 1.2, 1)
 
 local function draw_mirror(pass, outline)
-  local pose = mat4(mirrorPose)
-  pose:scale(mirrorSize)
-  pass:plane(pose, outline and 'line' or 'fill')
+  pass:push()
+  pass:transform(mirrorPose)
+  pass:scale(mirrorSize)
+  pass:plane(nil, outline and 'line' or 'fill')
+  pass:pop()
 end
 
 local function draw_scene(pass)
   pass:setColor(0x203c56)
   pass:box(0, 0, 0,  4, 0.05, 4) -- floor
+
   pass:setColor(0x544e68)
   pass:box(0, 1, -0.5,  1, 0.1, 0.4) -- a table
+
   pass:setColor(0xffaa5e)
   pass:cylinder(0.2, 1.2, -0.4,  .06,0.3,  math.pi/2, 1,0,0, true, 0,2*math.pi, 8) -- a vase
+
   pass:setColor(0xffd4a3)
   pass:box(0.1, 1.1, -0.6,  0.12, 0.1, 0.12) -- a jewelry box
-  local pose = mat4()
-  pose:set(lovr.headset.getPose('head')):scale(0.09, 0.12, 0.11)
+
+  pass:push()
+  pass:transform(lovr.headset.getPose())
+
+  pass:push()
+  pass:scale(.09, .12, .11)
   pass:setColor(0x8d697a)
-  pass:sphere(pose) -- head
+  pass:sphere() -- head
+  pass:pop()
+
+  pass:push()
+  pass:translate(0, -0.02, -0.15)
+  pass:rotate(-0.2, 1,0,0)
+  pass:scale(0.015, 0.02, 0.03)
   pass:setColor(0xd08159)
-  pose:set(lovr.headset.getPose('head'))
-  pose:translate(0, -0.02, -0.15)
-  pose:rotate(-0.2, 1,0,0)
-  pose:scale(0.015, 0.02, 0.03)
-  pass:cylinder(pose) -- nose
-  for _, handness in ipairs(lovr.headset.getHands()) do
-    local skeleton = lovr.headset.getSkeleton(handness)
+  pass:cylinder() -- nose
+  pass:pop()
+
+  pass:pop()
+
+  for _, hand in ipairs(lovr.headset.getHands()) do
+    local skeleton = lovr.headset.getSkeleton(hand)
     if skeleton then
       for _, bone in ipairs(skeleton) do
         local x, y, z, _, angle, ax, ay, az = unpack(bone)
-        pass:box(pose:set(x, y, z, angle, ax, ay, az):scale(0.017, 0.012, 0.019)) -- hand bone
+        pass:box(x, y, z, 0.017, 0.012, 0.019, angle, ax, ay, az) -- hand bone
       end
     else
-      pose:set(lovr.headset.getPose(handness)):scale(0.03, 0.07, 0.09)
-      pass:box(pose) -- palm
+      pass:push()
+      pass:transform(lovr.headset.getPose(hand))
+      pass:scale(.03, .07, .09)
+      pass:box() -- palm
+      pass:pop()
     end
   end
 end
@@ -46,10 +65,13 @@ end
 
 function lovr.draw(pass)
   pass:setCullMode('back')
+
   draw_scene(pass)
   pass:setColor(0xffecd6)
   draw_mirror(pass, true) -- mirror frame
+
   pass:push()
+
   pass:setStencilWrite('replace', 1)
   pass:setColorWrite(false)
   pass:setDepthWrite(false)
@@ -57,15 +79,18 @@ function lovr.draw(pass)
   pass:setDepthWrite(true)
   pass:setColorWrite(true)
   pass:setStencilWrite()
+
   pass:setStencilTest('equal', 1)
   pass:transform(mirrorPose)
   pass:scale(1, 1, -1) -- after Z-flip all triangles will change their windings
-  pass:transform(mat4(mirrorPose):invert())
+  pass:transform(inverseMirrorPose)
   pass:setWinding('clockwise') -- invert the winding to account for the flip
   draw_scene(pass)
   pass:setWinding('counterclockwise')
   pass:setStencilTest()
+
   pass:pop()
+
   pass:setColor(0x000000, 0.3)
   draw_mirror(pass, false) -- glass tint
 end

+ 11 - 10
examples/Flatscreen/FPS_Controls/main.lua

@@ -5,7 +5,7 @@ function lovr.load()
 
   camera = {
     transform = lovr.math.newMat4(),
-    position = lovr.math.newVec3(),
+    position = vector(),
     movespeed = 10,
     pitch = 0,
     yaw = 0
@@ -13,24 +13,25 @@ function lovr.load()
 end
 
 function lovr.update(dt)
-  local velocity = vec4()
+  local vx, vy, vz = 0, 0, 0
 
   if lovr.system.isKeyDown('w', 'up') then
-    velocity.z = -1
+    vz = -1
   elseif lovr.system.isKeyDown('s', 'down') then
-    velocity.z = 1
+    vz = 1
   end
 
   if lovr.system.isKeyDown('a', 'left') then
-    velocity.x = -1
+    vx = -1
   elseif lovr.system.isKeyDown('d', 'right') then
-    velocity.x = 1
+    vx = 1
   end
 
-  if #velocity > 0 then
-    velocity:normalize()
-    velocity:mul(camera.movespeed * dt)
-    camera.position:add(camera.transform:mul(velocity).xyz)
+  local velocity = vector(vx, vy, vz)
+
+  if velocity:length() > 0 then
+    velocity = velocity:normalize() * camera.movespeed * dt
+    camera.position = camera.position + camera.transform:mul(velocity, 0) -- Just apply transform's rotation to velocity
   end
 
   camera.transform:identity()

+ 4 - 1
examples/Interaction/Controller_Models/main.lua

@@ -3,13 +3,16 @@ function lovr.load()
     left = lovr.headset.newModel('hand/left'),
     right = lovr.headset.newModel('hand/right')
   }
+
+  transform = lovr.math.newMat4()
 end
 
 function lovr.draw(pass)
   for hand, model in pairs(models) do
     if lovr.headset.isTracked(hand) then
       lovr.headset.animate(model)
-      pass:draw(model, mat4(lovr.headset.getPose(hand)))
+      transform:set(lovr.headset.getPose(hand))
+      pass:draw(model, transform)
     end
   end
 

+ 1 - 1
examples/Interaction/Custom_Hand_Rig/main.lua

@@ -120,7 +120,7 @@ function lovr.draw(pass)
       pass:setDepthWrite(false)
       for i = 1, #hand.skeleton do
         local x, y, z, _, angle, ax, ay, az = unpack(hand.skeleton[i])
-        pass:sphere(mat4(x, y, z, angle, ax, ay, az):scale(.003))
+        pass:sphere(x, y, z, .003, angle, ax, ay, az)
       end
       pass:setDepthWrite(true)
 

+ 9 - 9
examples/Interaction/Dragging/main.lua

@@ -1,5 +1,5 @@
 box = {
-  position = lovr.math.newVec3(0, 1, -.25),
+  position = vector(0, 1, -.25),
   size = .25
 }
 
@@ -7,27 +7,26 @@ function lovr.load()
   drag = {
     active = false,
     hand = nil,
-    offset = lovr.math.newVec3()
+    offset = vector()
   }
 end
 
 function lovr.update(dt)
   for i, hand in ipairs(lovr.headset.getHands()) do
     if lovr.headset.wasPressed(hand, 'trigger') then
-      local offset = box.position - vec3(lovr.headset.getPosition(hand))
+      local offset = box.position - vector(lovr.headset.getPosition(hand))
       local halfSize = box.size / 2
       local x, y, z = offset:unpack()
       if math.abs(x) < halfSize and math.abs(y) < halfSize and math.abs(z) < halfSize then
         drag.active = true
         drag.hand = hand
-        drag.offset:set(offset)
+        drag.offset = offset
       end
     end
   end
 
   if drag.active then
-    local handPosition = vec3(lovr.headset.getPosition(drag.hand))
-    box.position:set(handPosition + drag.offset)
+    box.position = drag.offset + vector(lovr.headset.getPosition(drag.hand))
 
     if lovr.headset.wasReleased(drag.hand, 'trigger') then
       drag.active = false
@@ -37,10 +36,11 @@ end
 
 function lovr.draw(pass)
   pass:setColor(drag.active and 0x80ee80 or 0xee8080)
-  pass:cube(box.position, box.size, quat(), 'line')
+  pass:cube(box.position, box.size, nil, 'line')
 
+  pass:setColor(0xffffff)
   for i, hand in ipairs(lovr.headset.getHands()) do
-    pass:setColor(0xffffff)
-    pass:cube(mat4(lovr.headset.getPose(hand)):scale(.01))
+    local x, y, z, angle, ax, ay, az = lovr.headset.getPose(hand)
+    pass:cube(x, y, z, .01, angle, ax, ay, az)
   end
 end

+ 13 - 14
examples/Interaction/Dragging_with_Rotation/main.lua

@@ -1,21 +1,21 @@
 function lovr.load()
-  boxMatrix = lovr.math.newMat4() --inialize box
-  boxMatrix:set(
-    vec3(0,1,-1), --global position
-    vec3(0.25,0.25,0.25), --scale
-    quat(0,0,0,0) --global rotation
-  )
+  boxMatrix = lovr.math.newMat4()
+  boxMatrix:translate(0, 1, -1)
+  boxMatrix:scale(.25)
 
-  offset = lovr.math.newMat4() --initialize offset matrix
+  -- offset matrix represents transform from hand -> box
+  offset = lovr.math.newMat4()
 end
 
 function lovr.update(dt)
-  if lovr.headset.wasPressed('left','trigger') then
-    offset:set(mat4(lovr.headset.getPose('left')):invert() * boxMatrix)
-  end
+  for i, hand in ipairs(lovr.headset.getHands()) do
+    if lovr.headset.wasPressed(hand, 'trigger') then
+      offset:set(lovr.headset.getPose(hand)):invert():mul(boxMatrix)
+    end
 
-  if lovr.headset.isDown('left','trigger') then
-    boxMatrix:set(mat4(lovr.headset.getPose('left')) * (offset))
+    if lovr.headset.isDown(hand, 'trigger') then
+      boxMatrix:set(lovr.headset.getPose(hand)):mul(offset)
+    end
   end
 end
 
@@ -23,7 +23,6 @@ function lovr.draw(pass)
   pass:box(boxMatrix, 'line')
 
   for i, hand in ipairs(lovr.headset.getHands()) do
-    local x, y, z = lovr.headset.getPosition(hand)
-    pass:sphere(x, y, z, .01)
+    pass:sphere(vector(lovr.headset.getPosition(hand)), .01)
   end
 end

+ 5 - 6
examples/Interaction/Hand_Gestures/main.lua

@@ -1,5 +1,5 @@
 local function getJointDirection(skeleton, joint)
-  return quat(unpack(skeleton[joint], 5)):direction()
+  return quaternion(unpack(skeleton[joint], 5)):direction()
 end
 
 local function getCurl(skeleton, finger)
@@ -8,8 +8,7 @@ local function getCurl(skeleton, finger)
   local directions = {}
 
   repeat
-    local orientation = quat(unpack(skeleton[fingers[finger] + #directions], 5))
-    table.insert(directions, orientation:direction())
+    table.insert(directions, getJointDirection(skeleton, fingers[finger] + #directions))
   until #directions == jointCount
 
   local straightness = 0
@@ -53,7 +52,7 @@ local function drawSkeleton(pass, skeleton)
   pass:setColor(0xf0f0f0)
 
   for i, joint in ipairs(skeleton) do
-    pass:sphere(vec3(unpack(joint)), .01)
+    pass:sphere(joint, .01)
   end
 
   pass:setColor(0xb0b0b0)
@@ -62,8 +61,8 @@ local function drawSkeleton(pass, skeleton)
     local base = ({ 3, 7, 12, 17, 22 })[f]
     local length = f == 1 and 3 or 4
     for j = 1, length do
-      local from = vec3(unpack(skeleton[base + j - 1]))
-      local to = vec3(unpack(skeleton[base + j - 0]))
+      local from = vector(unpack(skeleton[base + j - 1]))
+      local to = vector(unpack(skeleton[base + j - 0]))
       pass:capsule(from, to, .002)
     end
   end

+ 9 - 8
examples/Interaction/Physics_Pointer/main.lua

@@ -1,9 +1,10 @@
 local random = lovr.math.random
 local boxes = {}
 local selectedBox = nil
-local hitpoint = lovr.math.newVec3()
+local hitpoint = nil
 local red = { 1, .5, .5 }
 local green = { .5, 1, .5 }
+local device = 'hand/left/point'
 
 function lovr.load()
   lovr.graphics.setBackgroundColor(.2, .2, .22)
@@ -26,13 +27,13 @@ function lovr.update(dt)
 
   world:update(dt)
 
-  local ox, oy, oz = lovr.headset.getPosition('hand/left/point')
-  local dx, dy, dz = quat(lovr.headset.getOrientation('hand/left/point')):direction():mul(50):unpack()
-  local collider, shape, x, y, z = world:raycast(ox, oy, oz, ox + dx, oy + dy, oz + dz)
+  local origin = vector(lovr.headset.getPosition(device))
+  local direction = vector(lovr.headset.getDirection(device))
+  local collider, shape, x, y, z = world:raycast(origin, origin + direction * 50)
 
   if collider then
     selectedBox = collider
-    hitpoint:set(x, y, z)
+    hitpoint = vector(x, y, z)
   end
 end
 
@@ -40,7 +41,7 @@ function lovr.draw(pass)
   -- Boxes
   for i, box in ipairs(boxes) do
     pass:setColor(box == selectedBox and green or red)
-    pass:cube(vec3(box:getPosition()), .28, quat(box:getOrientation()))
+    pass:cube(vector(box:getPosition()), .28, box:getOrientation())
   end
 
   -- Dot
@@ -50,8 +51,8 @@ function lovr.draw(pass)
   end
 
   -- Laser pointer
-  local hand = vec3(lovr.headset.getPosition('hand/left/point'))
-  local direction = quat(lovr.headset.getOrientation('hand/left/point')):direction()
+  local hand = vector(lovr.headset.getPosition(device))
+  local direction = vector(lovr.headset.getDirection(device))
   pass:setColor(1, 1, 1)
   pass:line(hand, selectedBox and hitpoint or (hand + direction * 50))
 end

+ 2 - 2
examples/Interaction/Pointer/main.lua

@@ -1,8 +1,8 @@
 function lovr.draw(pass)
   for i, hand in ipairs(lovr.headset.getHands()) do
     hand = hand .. '/point'
-    local position = vec3(lovr.headset.getPosition(hand))
-    local direction = quat(lovr.headset.getOrientation(hand)):direction()
+    local position = vector(lovr.headset.getPosition(hand))
+    local direction = vector(lovr.headset.getDirection(hand))
 
     pass:setColor(1, 1, 1)
     pass:sphere(position, .01)

+ 9 - 9
examples/Interaction/Pointer_UI/main.lua

@@ -16,7 +16,7 @@ local button = {
   text = 'Please click me',
   textSize = .1,
   count = 0,
-  position = lovr.math.newVec3(0, 1, -3),
+  position = vector(0, 1, -3),
   width = 1.0,
   height = .4,
   hover = false,
@@ -29,14 +29,14 @@ function lovr.update()
   button.hover, button.active = false, false
 
   for i, hand in ipairs(lovr.headset.getHands()) do
-    tips[hand] = tips[hand] or lovr.math.newVec3()
+    tips[hand] = tips[hand] or vector
 
     -- Ray info:
-    local rayPosition = vec3(lovr.headset.getPosition(hand .. '/point'))
-    local rayDirection = vec3(lovr.headset.getDirection(hand .. '/point'))
+    local rayPosition = vector(lovr.headset.getPosition(hand .. '/point'))
+    local rayDirection = vector(lovr.headset.getDirection(hand .. '/point'))
 
     -- Call the raycast helper function to get the intersection point of the ray and the button plane
-    local hit = raycast(rayPosition, rayDirection, button.position, vec3(0, 0, 1))
+    local hit = raycast(rayPosition, rayDirection, button.position, vector(0, 0, 1))
 
     local inside = false
     if hit then
@@ -61,7 +61,7 @@ function lovr.update()
 
     -- Set the end position of the pointer.  If the raycast produced a hit position then use that,
     -- otherwise extend the pointer's ray outwards by 50 meters and use it as the tip.
-    tips[hand]:set(inside and hit or (rayPosition + rayDirection * 50))
+    tips[hand] = inside and hit or (rayPosition + rayDirection * 50)
   end
 end
 
@@ -78,12 +78,12 @@ function lovr.draw(pass)
 
   -- Button text (add a small amount to the z to put the text slightly in front of button)
   pass:setColor(1, 1, 1)
-  pass:text(button.text, button.position + vec3(0, 0, .001), button.textSize)
-  pass:text('Count: ' .. button.count, button.position + vec3(0, .5, 0), .1)
+  pass:text(button.text, button.position + vector(0, 0, .001), button.textSize)
+  pass:text('Count: ' .. button.count, button.position + vector(0, .5, 0), .1)
 
   -- Pointers
   for hand, tip in pairs(tips) do
-    local position = vec3(lovr.headset.getPosition(hand))
+    local position = vector(lovr.headset.getPosition(hand))
 
     pass:setColor(1, 1, 1)
     pass:sphere(position, .01)

+ 6 - 5
examples/Lighting/Circular_Shadows/main.lua

@@ -59,9 +59,10 @@ function lovr.load()
 end
 
 function lovr.update()
-  left = vec3(lovr.headset.getPosition('left'))
-  right = vec3(lovr.headset.getPosition('right'))
-  head = vec3(mat4(lovr.headset.getPose('head')):translate(0, -.1, -.1))
+  left = vector(lovr.headset.getPosition('left'))
+  right = vector(lovr.headset.getPosition('right'))
+  local rotation = quaternion(lovr.headset.getOrientation('head'))
+  head = vector(lovr.headset.getPose('head')) + vector(0, -.1, -.1):rotate(rotation)
 end
 
 function lovr.draw(pass)
@@ -91,6 +92,6 @@ function lovr.draw(pass)
 
   -- hands
   pass:setColor(.9, .7, .1)
-  pass:sphere(vec3(lovr.headset.getPosition('left')), .05)
-  pass:sphere(vec3(lovr.headset.getPosition('right')), .05)
+  pass:sphere(vector(lovr.headset.getPosition('left')), .05)
+  pass:sphere(vector(lovr.headset.getPosition('right')), .05)
 end

+ 10 - 8
examples/Lighting/Shadows/main.lua

@@ -4,7 +4,7 @@
 ]]
 
 local z = -2
-local light_pos = lovr.math.newVec3(3.0, 4.0, z)
+local light_pos = vector(3.0, 4.0, z)
 local light_orthographic = false -- Use orthographic light
 local shadow_map_size = 2048
 
@@ -13,7 +13,9 @@ local debug_show_shadow_map = false -- Enable to view shadow map in overlap
 
 local shader, render_texture
 local shadow_map_texture, shadow_map_sampler
-local light_space_matrix
+local view_matrix = lovr.math.newMat4()
+local projection_matrix = lovr.math.newMat4()
+local light_space_matrix = lovr.math.newMat4()
 local shadow_map_pass, lighting_pass
 
 local function render_scene(pass)
@@ -111,18 +113,18 @@ local function render_shadow_map(draw)
   if light_orthographic then
     local radius = 3
     local far_plane = 15
-    projection = mat4():orthographic(-radius, radius, -radius, radius, near_plane, far_plane)
+    projection:orthographic(-radius, radius, -radius, radius, near_plane, far_plane)
   else
-    projection = mat4():perspective(math.pi / 3, 1, near_plane)
+    projection:perspective(math.pi / 3, 1, near_plane)
   end
 
-  local view = mat4():lookAt(light_pos, vec3(0, 1, z))
+  view_matrix:lookAt(light_pos, vector(0, 1, z))
 
-  light_space_matrix = mat4(projection):mul(view)
+  light_space_matrix:set(projection_matrix):mul(view_matrix)
 
   shadow_map_pass:reset()
-  shadow_map_pass:setProjection(1, projection)
-  shadow_map_pass:setViewPose(1, view, true)
+  shadow_map_pass:setProjection(1, projection_matrix)
+  shadow_map_pass:setViewPose(1, view_matrix, true)
   if light_orthographic then
     -- Note for ortho projection with a far plane the depth coord is reversed
     shadow_map_pass:setDepthTest('lequal')

+ 9 - 12
examples/Locomotion/Basic_Thumbsticks/main.lua

@@ -35,13 +35,13 @@ function motion.smooth(dt)
   end
   if lovr.headset.isTracked('left') then
     local x, y = lovr.headset.getAxis('left', 'thumbstick')
-    local direction = quat(lovr.headset.getOrientation(motion.directionFrom)):direction()
+    local direction = vector(lovr.headset.getDirection(motion.directionFrom))
     if not motion.flying then
-      direction.y = 0
+      direction = vector(direction.x, 0, direction.z)
     end
     -- Smooth strafe movement
     if math.abs(x) > motion.thumbstickDeadzone then
-      local strafeVector = quat(-math.pi / 2, 0,1,0):mul(vec3(direction))
+      local strafeVector = quaternion(-math.pi / 2, 0,1,0) * direction
       motion.pose:translate(strafeVector * x * motion.walkingSpeed * dt)
     end
     -- Smooth Forward/backward movement
@@ -68,12 +68,11 @@ function motion.snap(dt)
   if lovr.headset.isTracked('left') then
     local x, y = lovr.headset.getAxis('left', 'thumbstick')
     if math.abs(y) > motion.thumbstickDeadzone and motion.thumbstickCooldown < 0 then
-      local moveVector = quat(lovr.headset.getOrientation('head')):direction()
+      local moveVector = vector(lovr.headset.getDirection('head'))
       if not motion.flying then
-        moveVector.y = 0
+        moveVector = vector(moveVector.x, 0, moveVector.z)
       end
-      moveVector:mul(y / math.abs(y) * motion.dashDistance)
-      motion.pose:translate(moveVector)
+      motion.pose:translate(moveVector * (y / math.abs(y) * motion.dashDistance))
       motion.thumbstickCooldown = motion.thumbstickCooldownTime
     end
   end
@@ -86,7 +85,7 @@ function lovr.update(dt)
     motion.flying = true
   elseif lovr.headset.wasReleased('left', 'grip') then
     motion.flying = false
-    local height = vec3(motion.pose).y
+    local _, height = motion.pose:getPosition()
     motion.pose:translate(0, -height, 0)
   end
   if lovr.headset.isDown('right', 'grip') then
@@ -103,10 +102,8 @@ function lovr.draw(pass)
   local radius = 0.05
   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)
+    local position = motion.pose * vector(lovr.headset.getPosition(hand))
+    pass:sphere(position, radius)
   end
   -- Some scenery
   lovr.math.setRandomSeed(0)

+ 17 - 21
examples/Locomotion/Space_Stretch/main.lua

@@ -2,9 +2,7 @@
 -- left grip + right grip + right trigger: reset the view
 
 local motion = {
-  pose = lovr.math.newMat4(0,0,0, 1,1,1, 0, 0,1,0),
-  left_anchor_vr = lovr.math.newVec3(),
-  right_anchor_vr = lovr.math.newVec3(),
+  pose = lovr.math.newMat4()
 }
 
 local palette = {0x0d2b45, 0x203c56, 0x544e68, 0x8d697a, 0xd08159, 0xffaa5e, 0xffd4a3, 0xffecd6}
@@ -12,13 +10,13 @@ lovr.graphics.setBackgroundColor(palette[1])
 
 
 function lovr.update(dt)
-  local left_vr  = vec3(motion.pose:mul(lovr.headset.getPosition('hand/left')))
-  local right_vr = vec3(motion.pose:mul(lovr.headset.getPosition('hand/right')))
+  local left_vr  = motion.pose * vector(lovr.headset.getPosition('hand/left'))
+  local right_vr = motion.pose * vector(lovr.headset.getPosition('hand/right'))
   if lovr.headset.wasPressed('hand/left', 'grip') then
-    motion.left_anchor_vr:set(left_vr)
+    motion.left_anchor_vr = left_vr
   end
   if lovr.headset.wasPressed('hand/right', 'grip') then
-    motion.right_anchor_vr:set(right_vr)
+    motion.right_anchor_vr = right_vr
   end
   if lovr.headset.isDown('hand/left',  'grip') and
      lovr.headset.isDown('hand/right', 'grip') then
@@ -30,20 +28,20 @@ function lovr.update(dt)
     -- the change of scale must also affect the viewpoint location
     x, y, z = x * offset_scale, y * offset_scale, z * offset_scale
     -- Position: the mid-point of anchors is compared to midpoint of current controllers position
-    local midpoint_anchor     = vec3(motion.left_anchor_vr):lerp(motion.right_anchor_vr, 0.5)
-    local midpoint_controller = vec3(left_vr):lerp(right_vr, 0.5)
-    local offset_position = vec3(midpoint_anchor):sub(midpoint_controller)
+    local midpoint_anchor     = motion.left_anchor_vr:lerp(motion.right_anchor_vr, 0.5)
+    local midpoint_controller = left_vr:lerp(right_vr, 0.5)
+    local offset_position = midpoint_anchor - midpoint_controller
     x, y, z = x + offset_position.x, y + offset_position.y, z + offset_position.z
     motion.pose:set(x, y, z, scale, scale, scale, angle, ax, ay, az)  -- apply transition and scaling
     -- Rotation: get angle between current controllers and anchors in XZ
-    local l_to_r_anchor = vec3(motion.right_anchor_vr):sub(motion.left_anchor_vr)
-    local l_to_r_controller = vec3(right_vr):sub(left_vr)
+    local l_to_r_anchor = motion.right_anchor_vr - motion.left_anchor_vr
+    local l_to_r_controller = right_vr - left_vr
     local sign = 1
-    if vec3(l_to_r_controller):cross(vec3(l_to_r_anchor)):dot(vec3(0, 1, 0)) < 0 then
+    if l_to_r_controller:cross(l_to_r_anchor):dot(vector(0, 1, 0)) < 0 then
       sign = -1
     end
-    l_to_r_anchor = l_to_r_anchor.xz:normalize()
-    l_to_r_controller = l_to_r_controller.xz:normalize()
+    l_to_r_anchor = (l_to_r_anchor * vector(1, 0, 1)):normalize()
+    l_to_r_controller = (l_to_r_controller * vector(1, 0, 1)):normalize()
     local cos_angle = l_to_r_controller:dot(l_to_r_anchor)
     cos_angle = math.max(-1, math.min(1, cos_angle))
     local offset_rotation = math.acos(cos_angle) * sign
@@ -51,8 +49,8 @@ function lovr.update(dt)
     -- pose & anchor reset
     if lovr.headset.isDown('hand/right', 'trigger') then
       motion.pose:set()
-      motion.left_anchor_vr:set(left_vr)
-      motion.right_anchor_vr:set(right_vr)
+      motion.left_anchor_vr = left_vr
+      motion.right_anchor_vr = right_vr
     end
   end
 end
@@ -63,15 +61,13 @@ function lovr.draw(pass)
   -- Render hands
   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)
+    local position = motion.pose * vector(lovr.headset.getPosition(hand))
     if lovr.headset.isDown(hand, 'grip') then
       pass:setColor(palette[6])
     else
       pass:setColor(palette[8])
     end
-    poseVR:scale(0.02)
-    pass:sphere(poseVR)
+    pass:sphere(position, .02)
   end
   -- An example scene
   local t = lovr.timer.getTime()

+ 16 - 19
examples/Locomotion/Teleportation_Colliders/main.lua

@@ -16,7 +16,7 @@ local motion = {
   blinkTime = 0.05,
   blinkStopwatch = math.huge,
   teleportValid = false,
-  targetPosition = lovr.math.newVec3(),
+  targetPosition = vector(),
   teleportCurve = lovr.math.newCurve(3),
 }
 
@@ -25,35 +25,35 @@ 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()
+  local handPosition = vector(handPose:getPosition())
+  local handDirection = quaternion(handPose:getOrientation()):direction()
   -- Intersect with world geometry by casting a ray
-  local origin = vec3(handPose:mul(0, 0, -0.2))
-  local target = vec3(handPose:mul(0, 0, -motion.teleportDistance))
+  local origin = handPose * vector(0, 0, -0.2)
+  local target = handPose * vector(0, 0, -motion.teleportDistance)
   local intersectionDistance = math.huge
   local closestHit
   physicsWorld:raycast(origin, target, nil,
     function(collider, shape, x, y, z, nx, ny, nz)
-      local position = vec3(x, y, z)
-      local normal = vec3(nx, ny, nz)
+      local position = vector(x, y, z)
+      local normal = vector(nx, ny, nz)
       local distance = (origin - position):length()
       -- Teleportation is possible if distance is within range and if surface is roughly horizontal
-      if distance < intersectionDistance and normal:dot(vec3(0,1,0)) > 0.5 then
+      if distance < intersectionDistance and normal.y > 0.5 then
         intersectionDistance = distance
         closestHit = position
       end
     end)
   if closestHit then
     motion.teleportValid = true
-    motion.targetPosition:set(closestHit)
+    motion.targetPosition = closestHit
   else
     motion.teleportValid = false
-    motion.targetPosition:set(handPose:mul(0,0,-20))
+    motion.targetPosition = handPose * vector(0,0,-20)
   end
   -- Construct teleporter visualization curve 
-  local midPoint = vec3(handPosition):lerp(motion.targetPosition, 0.3)
+  local midPoint = handPosition:lerp(motion.targetPosition, 0.3)
   if motion.teleportValid then
-    midPoint:add(vec3(0, 0.1 * intersectionDistance, 0)) -- Fake a parabola
+    midPoint = midPoint + vector(0, 0.1 * intersectionDistance, 0) -- Fake a parabola
   end
   motion.teleportCurve:setPoint(1, handPosition)
   motion.teleportCurve:setPoint(2, midPoint)
@@ -65,10 +65,9 @@ function motion.teleport(dt)
   -- 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 headsetXZ = vector(lovr.headset.getPosition()) * vector(1, 0, 1)
     local newPosition = motion.targetPosition - headsetXZ
-    motion.pose:set(newPosition, vec3(1,1,1), quat(motion.pose)) -- ZAAPP
+    motion.pose:set(newPosition, 1,1,1, motion.pose:getOrientation()) -- ZAAPP
   end
   -- Snap horizontal turning (often combined with teleport mechanics)
   if lovr.headset.isTracked('right') then
@@ -150,10 +149,8 @@ function lovr.draw(pass)
   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)
+    local position = motion.pose * vector(lovr.headset.getPosition(hand))
+    pass:sphere(position, radius)
   end
   -- Render columns
   for i, column in ipairs(columns) do

+ 11 - 14
examples/Locomotion/Teleportation_Flat/main.lua

@@ -13,7 +13,7 @@ local motion = {
   blinkTime = 0.5,
   blinkStopwatch = math.huge,
   teleportValid = false,
-  targetPosition = lovr.math.newVec3(),
+  targetPosition = vector(),
   teleportCurve = lovr.math.newCurve(3),
 }
 
@@ -22,19 +22,19 @@ 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()
+  local handPosition = vector(handPose:getPosition())
+  local handDirection = quaternion(handPose:getOrientation()):direction()
   -- Intersect with ground plane
-  local ratio =  vec3(handPose).y / handDirection.y
+  local ratio =  handPosition.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))
+  motion.targetPosition = vector(handPose:translate(0, 0, -intersectionDistance):getPosition())
   -- 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)
+  local midPoint = handPosition:lerp(motion.targetPosition, 0.3)
   if motion.teleportValid then
-    midPoint:add(vec3(0, 0.1 * intersectionDistance, 0)) -- Fake a parabola
+    midPoint = midPoint + vector(0, 0.1 * intersectionDistance, 0) -- Fake a parabola
   end
   motion.teleportCurve:setPoint(1, handPosition)
   motion.teleportCurve:setPoint(2, midPoint)
@@ -46,10 +46,9 @@ function motion.teleport(dt)
   -- 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 headsetXZ = vector(lovr.headset.getPosition()) * vector(1, 0, 1)
     local newPosition = motion.targetPosition - headsetXZ
-    motion.pose:set(newPosition, vec3(1,1,1), quat(motion.pose)) -- ZAAPP
+    motion.pose:set(newPosition, 1,1,1, motion.pose:getOrientation()) -- ZAAPP
   end
   -- Snap horizontal turning (often combined with teleport mechanics)
   if lovr.headset.isTracked('right') then
@@ -93,10 +92,8 @@ function lovr.draw(pass)
   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)
+    local position = motion.pose * vector(lovr.headset.getPosition(hand))
+    pass:sphere(position, radius)
   end
   -- Some scenery
   lovr.math.setRandomSeed(0)

+ 4 - 6
examples/Locomotion/Walking_In_Place/main.lua

@@ -44,7 +44,7 @@ function motion.walkinplace(dt)
     motion.warmup = motion.warmup + dt
     return
   end
-  local direction = quat(lovr.headset.getOrientation('head')):direction()
+  local direction = vector(lovr.headset.getDirection())
   local pitch = direction.y
   -- Detect pitch changes (looking up/down) and inhibit walking until pitch motion ends
   motion.headPitch = ((pitch - motion.headPitch) * 0.1 + motion.headPitch)
@@ -58,7 +58,7 @@ function motion.walkinplace(dt)
   -- Apply estimated walk intensity to speed
   -- Simple lowpass IIR is applied for smoothness and inertia
   motion.speed = motion.speed * (1 - motion.damping) + walkIntensity * walkInhibit
-  direction.y = 0
+  direction = direction * vector(1, 0, 1)
   motion.pose:translate(direction * motion.speed * dt)
 end
 
@@ -76,10 +76,8 @@ function lovr.draw(pass)
   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)
+    local position = motion.pose * vector(lovr.headset.getPosition(hand))
+    pass:sphere(position, radius)
   end
   -- Some scenery
   lovr.math.setRandomSeed(0)

+ 5 - 5
examples/Optimization/Instancing/main.lua

@@ -7,10 +7,10 @@ function lovr.load()
   local transforms = {}
   local random, randomNormal = lovr.math.random, lovr.math.randomNormal
   for i = 1, MONKEYS do
-    local position = vec3(randomNormal(8), randomNormal(8), randomNormal(8))
-    local orientation = quat(random(2 * math.pi), random(), random(), random())
-    local scale = vec3(.75)
-    transforms[i] = mat4(position, scale, orientation)
+    local position = vector(randomNormal(8), randomNormal(8), randomNormal(8))
+    local orientation = quaternion(random(2 * math.pi), random(), random(), random())
+    local scale = vector(.75)
+    transforms[i] = lovr.math.newMat4(position, scale, orientation)
   end
 
   -- Put them in a buffer
@@ -36,5 +36,5 @@ function lovr.draw(pass)
   pass:setCullMode('back')
   pass:setShader(shader)
   pass:send('Transforms', transformBuffer)
-  pass:draw(monkey, mat4(), MONKEYS)
+  pass:draw(monkey, nil, MONKEYS)
 end

+ 8 - 8
examples/Optimization/Instancing_-_Blob/main.lua

@@ -11,13 +11,13 @@ function lovr.load()
   local pointer = ffi.cast("float*",transformBlob:getPointer())
   local random, randomNormal = lovr.math.random, lovr.math.randomNormal
   for i = 1, MONKEYS do
-    local position = vec3(randomNormal(8), randomNormal(8), randomNormal(8))
-    local orientation = quat(random(2 * math.pi), random(), random(), random())
-    local scale = vec3(.75)
-    local transform = mat4(position, scale, orientation)
-    local components = {transform:unpack(true)}
-    for i2,v in ipairs(components) do
-      pointer[(i-1)*16 + (i2-1)] = v
+    local position = vector(randomNormal(8), randomNormal(8), randomNormal(8))
+    local orientation = quaternion(random(2 * math.pi), random(), random(), random())
+    local scale = vector(.75)
+    local transform = lovr.math.newMat4(position, scale, orientation)
+    local components = { transform:unpack(true) }
+    for i2, v in ipairs(components) do
+      pointer[(i - 1) * 16 + (i2 - 1)] = v
     end
   end
 
@@ -41,5 +41,5 @@ function lovr.draw(pass)
   pass:setCullMode('back')
   pass:setBlendMode(nil)
   pass:send('TransformBuffer', buffer)
-  pass:draw(model, mat4(), MONKEYS)
+  pass:draw(model, nil, MONKEYS)
 end

+ 9 - 9
examples/Optimization/Instancing_-_Compute/main.lua

@@ -9,20 +9,20 @@ function lovr.load()
   local random, randomNormal = lovr.math.random, lovr.math.randomNormal
   local transforms = {}
   for i = 1, MONKEYS do
-    local position = vec3(randomNormal(8), randomNormal(8), randomNormal(8))
-    local orientation = quat(random(2 * math.pi), random(), random(), random())
-    local scale = vec3(.75)
-    transforms[i] = mat4(position, scale, orientation)
+    local position = vector(randomNormal(8), randomNormal(8), randomNormal(8))
+    local orientation = quaternion(random(2 * math.pi), random(), random(), random())
+    local scale = vector(.75)
+    transforms[i] = lovr.math.newMat4(position, scale, orientation)
   end
 
   -- More random transforms-- this will correspond to the transform applied to each monkey per frame
   local offsets = {}
   for i = 1, MONKEYS do
-    local position = vec3(randomNormal(1), randomNormal(8), randomNormal(8)):mul(ASSUME_FRAMERATE)
+    local position = vector(randomNormal(1), randomNormal(8), randomNormal(8)):mul(ASSUME_FRAMERATE)
     local radianSwing = ASSUME_FRAMERATE * math.pi / 2
-    local orientation = quat(random(-radianSwing, radianSwing), random(), random(), random())
-    local scale = vec3(1)
-    offsets[i] = mat4(position, scale, orientation)
+    local orientation = quaternion(random(-radianSwing, radianSwing), random(), random(), random())
+    local scale = vector(1)
+    offsets[i] = lovr.math.newMat4(position, scale, orientation)
   end
 
   -- Create a Buffer to store positions for lots of models
@@ -69,5 +69,5 @@ function lovr.draw(pass)
   pass:send('TransformBuffer', transformBuffer)
   pass:setCullMode('back')
   pass:setBlendMode(nil)
-  pass:draw(model, mat4(), MONKEYS)
+  pass:draw(model, nil, MONKEYS)
 end

+ 23 - 21
examples/Physics/Hand_Physics/main.lua

@@ -42,23 +42,24 @@ local hand_force = 30000
 function lovr.load()
   world = lovr.physics.newWorld(0, -2, 0, false) -- low gravity and no collider sleeping
   -- ground plane
-  local box = world:newBoxCollider(vec3(0, 0, 0), vec3(20, 0.1, 20))
+  local box = world:newBoxCollider(vector(0, 0, 0), vector(20, 0.1, 20))
   box:setKinematic(true)
   table.insert(boxes, box)
   -- create a fort of boxes
   lovr.math.setRandomSeed(0)
   for angle = 0, 2 * math.pi, 2 * math.pi / 12 do
     for height = 0.3, 1.5, 0.4 do
-      local pose = mat4():rotate(angle, 0,1,0):translate(0, height, -1)
-      local size = vec3(0.3, 0.4, 0.2)
-      local box = world:newBoxCollider(vec3(pose), size)
-      box:setOrientation(quat(pose))
+      local orientation = quaternion(angle, 0,1,0)
+      local position = orientation * vector(0, height, -1)
+      local size = vector(0.3, 0.4, 0.2)
+      local box = world:newBoxCollider(position, size)
+      box:setOrientation(orientation)
       table.insert(boxes, box)
     end
   end
   -- make colliders for two hands
   for i = 1, 2 do
-    hands.colliders[i] = world:newBoxCollider(vec3(0,2,0), vec3(0.04, 0.08, 0.08))
+    hands.colliders[i] = world:newBoxCollider(vector(0,2,0), vector(0.04, 0.08, 0.08))
     hands.colliders[i]:setLinearDamping(0.7)
     hands.colliders[i]:setAngularDamping(0.9)
     hands.colliders[i]:setMass(0.5)
@@ -88,21 +89,24 @@ function lovr.update(dt)
   -- hand updates - location, orientation, solidify on trigger button, grab on grip button
   for i, hand in pairs(lovr.headset.getHands()) do
     -- align collider with controller by applying force (position) and torque (orientation)
-    local rw = mat4(lovr.headset.getPose(hand))   -- real world pose of controllers
-    local vr = mat4(hands.colliders[i]:getPose()) -- vr pose of palm colliders
-    local angle, ax,ay,az = quat(rw):mul(quat(vr):conjugate()):unpack()
+    local handPosition = vector(lovr.headset.getPosition(hand))
+    local handOrientation = quaternion(lovr.headset.getOrientation(hand))
+    local colliderOrientation = quaternion(hands.colliders[i]:getOrientation())
+    local rotation = handOrientation * colliderOrientation:conjugate()
+    local angle, ax,ay,az = rotation:toangleaxis()
     angle = ((angle + math.pi) % (2 * math.pi) - math.pi) -- for minimal motion wrap to (-pi, +pi) range
-    hands.colliders[i]:applyTorque(vec3(ax, ay, az):mul(angle * dt * hand_torque))
-    hands.colliders[i]:applyForce((vec3(rw) - vec3(vr)):mul(dt * hand_force))
+    hands.colliders[i]:applyTorque(vector(ax, ay, az) * (angle * dt * hand_torque))
+    local delta = vector(lovr.headset.getPosition(hand)) - vector(hands.colliders[i]:getPosition())
+    hands.colliders[i]:applyForce(delta * dt * hand_force)
     -- solidify when trigger touched
     hands.solid[i] = lovr.headset.isDown(hand, 'trigger')
-    hands.colliders[i]:getShapes()[1]:setSensor(not hands.solid[i])
+    hands.colliders[i]:setSensor(not hands.solid[i])
     -- hold/release colliders
     if lovr.headset.isDown(hand, 'grip') and hands.touching[i] and not hands.holding[i] then
       hands.holding[i] = hands.touching[i]
       -- grab object with ball joint to drag it, and slider joint to also match the orientation
-      lovr.physics.newBallJoint(hands.colliders[i], hands.holding[i], vr:mul(0, 0, 0))
-      lovr.physics.newSliderJoint(hands.colliders[i], hands.holding[i], quat(vr):direction())
+      lovr.physics.newBallJoint(hands.colliders[i], hands.holding[i], hands.colliders[i]:getPosition())
+      lovr.physics.newSliderJoint(hands.colliders[i], hands.holding[i], colliderOrientation:direction())
     end
     if lovr.headset.wasReleased(hand, 'grip') and hands.holding[i] then
       for _,joint in ipairs(hands.colliders[i]:getJoints()) do
@@ -130,14 +134,12 @@ end
 
 
 function drawBoxCollider(pass, collider, is_sensor)
-  -- query current pose (location and orientation)
-  local pose = mat4(collider:getPose())
-  -- query dimensions of box
-  local shape = collider:getShapes()[1]
-  local size = vec3(shape:getDimensions())
   -- draw box
-  pose:scale(size)
-  pass:box(pose, is_sensor and 'line' or 'fill')
+  pass:push()
+  pass:transform(collider:getPose())
+  pass:scale(collider:getShape():getDimensions())
+  pass:box(nil, is_sensor and 'line' or 'fill')
+  pass:pop()
 end
 
 

+ 6 - 6
examples/Physics/Newtons_Cradle/main.lua

@@ -15,18 +15,18 @@ local gap = 0.01
 function lovr.load()
   world = lovr.physics.newWorld({ restitutionThreshold = .05 })
   -- a static geometry from which balls are suspended
-  local size = vec3(1.2, 0.1, 0.3)
-  frame = world:newBoxCollider(vec3(0, 2, -2), size)
+  local size = vector(1.2, 0.1, 0.3)
+  frame = world:newBoxCollider(vector(0, 2, -2), size)
   frame:setKinematic(true)
   framePose = lovr.math.newMat4(frame:getPose()):scale(size)
   -- create balls along the length of frame and attach them with two distance joints to frame
   for x = -0.5, 0.5, 1 / count do
-    local ball = world:newSphereCollider(vec3(x, 1, -2), radius - gap)
+    local ball = world:newSphereCollider(vector(x, 1, -2), radius - gap)
     ball:setRestitution(1.0)
     table.insert(balls, ball)
 
-    lovr.physics.newDistanceJoint(frame, ball, vec3(x, 2, -2 + 0.25), vec3(x, 1, -2))
-    lovr.physics.newDistanceJoint(frame, ball, vec3(x, 2, -2 - 0.25), vec3(x, 1, -2))
+    lovr.physics.newDistanceJoint(frame, ball, vector(x, 2, -2 + 0.25), vector(x, 1, -2))
+    lovr.physics.newDistanceJoint(frame, ball, vector(x, 2, -2 - 0.25), vector(x, 1, -2))
   end
   -- displace the last ball to set the Newton's cradle in motion
   local lastBall = balls[#balls]
@@ -40,7 +40,7 @@ function lovr.draw(pass)
   pass:box(framePose)
   pass:setColor(1, 1, 1)
   for i, ball in ipairs(balls) do
-    local position = vec3(ball:getPosition())
+    local position = vector(ball:getPosition())
     pass:sphere(position, radius)
   end
 end

+ 7 - 7
examples/Physics/Saloon_Door/main.lua

@@ -7,11 +7,11 @@ local passenger
 function lovr.load()
   world = lovr.physics.newWorld(0, -9.8, 0, false)
   -- a static geometry that functions as door frame
-  doorR = world:newBoxCollider(vec3( 0.55, 0.5, -1), vec3(1, 1, 0.2))
-  doorL = world:newBoxCollider(vec3(-0.55, 0.5, -1), vec3(1, 1, 0.2))
+  doorR = world:newBoxCollider(vector( 0.55, 0.5, -1), vector(1, 1, 0.2))
+  doorL = world:newBoxCollider(vector(-0.55, 0.5, -1), vector(1, 1, 0.2))
   -- attach doors with vertical hinges
-  local hingeR = lovr.physics.newHingeJoint(nil, doorR, vec3( 1, 0, -1), vec3(0,1,0))
-  local hingeL = lovr.physics.newHingeJoint(nil, doorL, vec3(-1, 0, -1), vec3(0,1,0))
+  local hingeR = lovr.physics.newHingeJoint(nil, doorR, vector( 1, 0, -1), vector(0,1,0))
+  local hingeL = lovr.physics.newHingeJoint(nil, doorL, vector(-1, 0, -1), vector(0,1,0))
   -- set up motors to return the doors to their initial orientation
   hingeR:setMotorMode('position')
   hingeL:setMotorMode('position')
@@ -21,7 +21,7 @@ function lovr.load()
   passenger = world:newCapsuleCollider(0, 0, 0,  0.4, 1)
   passenger:getShape():setOffset(0, 0.5, 0,  math.pi/2, 1, 0, 0)
   passenger:setKinematic(true)
-  passenger:moveKinematic(vec3(0, 0, -4), nil, 3)
+  passenger:moveKinematic(vector(0, 0, -4), nil, 3)
 end
 
 function lovr.draw(pass)
@@ -31,7 +31,7 @@ function lovr.draw(pass)
     local pose = mat4(collider:getPose()) * mat4(shape:getOffset())
     local shape_type = shape:getType()
     if shape_type == 'box' then
-      local size = vec3(collider:getShape():getDimensions())
+      local size = vector(collider:getShape():getDimensions())
       pass:box(pose:scale(size))
     elseif shape_type == 'capsule' then
       local l, r = shape:getLength(), shape:getRadius()
@@ -48,7 +48,7 @@ function lovr.update(dt)
     local _, _, z = passenger:getPosition()
     z = z > -1 and -4 or 2
     -- moveKinematic is prefered over setPosition as it will correctly set the passenger's velocity
-    passenger:moveKinematic(vec3(0, 0, z), nil, 3)
+    passenger:moveKinematic(vector(0, 0, z), nil, 3)
   end
   world:update(dt)
 end

+ 2 - 1
examples/Physics/Terrain/main.lua

@@ -48,7 +48,8 @@ end
 function lovr.draw(pass)
   pass:setColor(0.925, 0.745, 0.137)
   for _, collider in ipairs(box_colliders) do
-    pass:box(mat4(collider:getPose()))
+    local x, y, z, angle, ax, ay, az = collider:getPose()
+    pass:cube(x, y, z, 1, angle, ax, ay, az)
   end
 
   pass:setColor(0.565, 0.404, 0.463)

+ 15 - 17
examples/Physics/Wrecking_Ball/main.lua

@@ -14,24 +14,24 @@ Some steps that can help solve the issue:
 local world
 
 function lovr.load()
-  world = lovr.physics.newWorld(0, -3, 0, false)
+  world = lovr.physics.newWorld({ stabilization = .5 })
   -- ground plane
-  local box = world:newBoxCollider(vec3(0, -0.05, 0), vec3(20, 0.1, 20))
+  local box = world:newBoxCollider(vector(0, -0.05, 0), vector(20, 0.1, 20))
   box:setKinematic(true)
   -- hanger
-  local hangerPosition = vec3(0, 2, -1)
-  local hanger = world:newBoxCollider(hangerPosition, vec3(0.3, 0.1, 0.3))
+  local hangerPosition = vector(0, 2, -1)
+  local hanger = world:newBoxCollider(hangerPosition, vector(0.3, 0.1, 0.3))
   hanger:setKinematic(true)
   -- ball
-  local ballPosition = vec3(-1, 1, -1)
+  local ballPosition = vector(-1, 1, -1)
   local ball = world:newSphereCollider(ballPosition, 0.2)
   -- rope
   local firstEnd, lastEnd = makeRope(
-    hangerPosition + vec3(0, -0.1, 0),
-    ballPosition   + vec3(0,  0.3, 0),
+    hangerPosition + vector(0, -0.1, 0),
+    ballPosition   + vector(0,  0.3, 0),
     0.02, 10)
-  lovr.physics.newDistanceJoint(hanger, firstEnd, hangerPosition, vec3(firstEnd:getPosition()))
-  lovr.physics.newDistanceJoint(ball, lastEnd, ballPosition, vec3(lastEnd:getPosition()))
+  lovr.physics.newDistanceJoint(hanger, firstEnd, hangerPosition, vector(firstEnd:getPosition()))
+  lovr.physics.newDistanceJoint(ball, lastEnd, ballPosition, vector(lastEnd:getPosition()))
   -- brick wall
   local x = 0.3
   local even = true
@@ -55,7 +55,7 @@ function lovr.draw(pass)
   for i, collider in ipairs(world:getColliders()) do
     local shade = (i - 10) / #world:getColliders()
     pass:setColor(shade, shade, shade)
-    local shape = collider:getShapes()[1]
+    local shape = collider:getShape()
     local shapeType = shape:getType()
     local x,y,z, angle, ax,ay,az = collider:getPose()
     if shapeType == 'box' then
@@ -74,22 +74,20 @@ function makeRope(origin, destination, thickness, elements)
   thickness = thickness or length / 100
   elements = elements or 30
   elementSize = length / elements
-  local orientation = vec3(destination - origin):normalize()
+  local orientation = quaternion.lookdir(destination - origin)
   local first, last, prev
   for i = 1, elements do
-    local position = vec3(origin):lerp(destination, (i - 0.5) / elements)
-    local anchor   = vec3(origin):lerp(destination, (i - 1.0) / elements)
-    element = world:newBoxCollider(position, vec3(thickness, thickness, elementSize * 0.95))
+    local position = origin:lerp(destination, (i - 0.5) / elements)
+    local anchor   = origin:lerp(destination, (i - 1.0) / elements)
+    element = world:newBoxCollider(position, vector(thickness, thickness, elementSize * 0.95))
     element:setRestitution(0.1)
     element:setGravityIgnored(true)
-    element:setOrientation(quat(orientation))
+    element:setOrientation(orientation)
     element:setLinearDamping(0.01)
     element:setAngularDamping(0.01)
     element:setMass(0.001)
     if prev then
       local joint = lovr.physics.newBallJoint(prev, element, anchor)
-      joint:setResponseTime(10)
-      joint:setTightness(1)
     else
       first = element
     end

+ 5 - 6
examples/Physics/Zip_Line/main.lua

@@ -8,21 +8,20 @@ local world
 
 function lovr.load()
   world = lovr.physics.newWorld(0, -3, 0, false)
-  local hanger = world:newBoxCollider(vec3(1, 1.9, -1), vec3(0.1, 0.1, 0.3))
+  local hanger = world:newBoxCollider(vector(1, 1.9, -1), vector(0.1, 0.1, 0.3))
   hanger:setKinematic(true)
-  local trolley = world:newBoxCollider(vec3(-1, 2, -1), vec3(0.2, 0.2, 0.5))
+  local trolley = world:newBoxCollider(vector(-1, 2, -1), vector(0.2, 0.2, 0.5))
   trolley:setRestitution(0.7)
   -- calculate axis that passes through centers of hanger and trolley
-  local sliderAxis = vec3(hanger:getPosition()) - vec3(trolley:getPosition())
+  local sliderAxis = vector(hanger:getPosition()) - vector(trolley:getPosition())
   -- constraint the trolley so that it can only slide along specified axis without any rotation
   joint = lovr.physics.newSliderJoint(hanger, trolley, sliderAxis)
   -- hang a weight from trolley
-  local weight = world:newCapsuleCollider(vec3(-1, 1.5, -1), 0.1, 0.4)
+  local weight = world:newCapsuleCollider(vector(-1, 1.5, -1), 0.1, 0.4)
   weight:setOrientation(math.pi/2, 1,0,0)
   weight:setLinearDamping(0.005)
   weight:setAngularDamping(0.01)
-  local joint = lovr.physics.newDistanceJoint(trolley, weight, vec3(trolley:getPosition()), vec3(weight:getPosition()) + vec3(0, 0.3, 0))
-  joint:setResponseTime(10) -- make the hanging rope streachable
+  local joint = lovr.physics.newDistanceJoint(trolley, weight, vector(trolley:getPosition()), vector(weight:getPosition()) + vector(0, 0.3, 0))
 
   lovr.graphics.setBackgroundColor(0.1, 0.1, 0.1)
 end