Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
An adaptation of Codea craft's Cameras project in which the camera crashes the project
--# Main
-- Cameras
-----------------------------------------
-- Learn Craft
-- Written by John Millard
-- Special thanks to Ignatz for the MultiStep project template
-----------------------------------------
-- Description:
-----------------------------------------
supportedOrientations(ANY)
displayMode(STANDARD)
-- Use this function to perform your initial setup
function setup()
print("Hello Cameras!")
startTab("FirstPersonViewer 👁")
end
function drawStepName(name)
fill(255, 255, 255, 255)
font("Inconsolata")
fontSize(40)
textAlign(LEFT)
textMode(CORNER)
text(name, 30, HEIGHT - 60)
end
function setGlobalButtons(tabNames)
for i, tabName in ipairs(tabNames) do
parameter.action(tabName, function () startTab(tabName) end)
parameter.number("cameraX", -100,100,0)
parameter.number("cameraY", 0,5,4)
parameter.number("cameraZ", -100,100,0)
parameter.number("fov", 0,100,82)
parameter.number("np", 0,10,2)
parameter.number("rx", 0,100,0)
parameter.number("ry", 0,100,180)
parameter.number("gx", -80,80,-6.5)
parameter.number("gy", -10,10,-0.3)
parameter.number("gz", -80,80,5)
end
end
function startTab(tabName)
currentTabGlobal = tabName
if cleanup then cleanup() end
parameter.clear()
loadableName = string.gsub(tabName, "([A-Z])", " %1")
_, _, nameWithoutEmoji, _ = string.find(tabName, "(%a+)%s*")
loadstring(readProjectTab(nameWithoutEmoji))()
setUpViewer(nameWithoutEmoji)
--PrintExplanation(tabName)
setGlobalButtons({"FirstPersonViewer 👁", "OrbitViewer 💫"})
end
function setUpViewer(name)
scene = craft.scene()
local m = craft.model("CastleKit:knightBlue")
model = scene:entity()
model:add(craft.renderer, m)
ground = scene:entity()
ground.scale = vec3(25,2,25)
local k = craft.model("Nature:naturePack_011")
ground:add(craft.renderer, k)
currentCameraGlobal = nil
touches.handlers = {}
if name == "FirstPersonViewer" then
currentCameraGlobal = FirstPersonViewer
camerasettings = scene.camera:get(craft.camera)
camerasettings.fieldOfView = 60
print(camerasettings)
elseif name == "OrbitViewer" then
currentCameraGlobal = OrbitViewer
end
scene.camera:add(currentCameraGlobal, vec3(0,5,0), 10, 5, 40)
end
function PrintExplanation(tabName)
if tabName == "FirstPersonViewer" then
elseif tabName == "OrbitViewer" then
end
end
function update(dt)
scene.camera.x = cameraX
scene.camera.y = cameraY
scene.camera.z = cameraZ
scene.camera.eulerAngles = vec3(rx, ry, 0)
currentCameraGlobal.rx = rx
camerasettings.fieldOfView = fov
camerasettings.nearPlane = np
ground.position = vec3(gx,gy,gz)
scene:update(dt)
end
-- This function gets called once every frame
function draw()
update(DeltaTime)
scene:draw()
drawStepName(currentTabGlobal)
end
--# FirstPersonViewer
-----------------------------------------
-- FirstPersonViewer
-- Written by John Millard
-----------------------------------------
-- Description:
-- A basic viewer for first person cameras.
-- Attach to a camera's entity for basic first person controls:
-- i.e. scene.camera:add(FirstPersonViewer)
-----------------------------------------
FirstPersonViewer = class()
local IDLE = 1
local ROTATE = 2
function FirstPersonViewer:init(camera, tapHandler)
self.camera = camera
self.rx = 0
self.ry = 0
self.state = IDLE
self.enabled = true
self.tapHandler = tapHandler
self.sensitivity = 0.25
touches.addHandler(self, 0, false)
end
function FirstPersonViewer:isActive()
return self.state ~= IDLE
end
function FirstPersonViewer:update()
if self.enabled then
-- clamp vertical rotation between -90 and 90 degrees (no upside down view)
self.rx = math.min(math.max(self.rx, -90), 90)
local rotation = quat.eulerAngles(self.rx, self.ry, 0)
self.camera.rotation = rotation
end
end
function FirstPersonViewer:touched(touch)
if self.state == IDLE then
if touch.state == BEGAN then
self.start = vec2(touch.x, touch.y)
elseif touch.state == MOVING then
local length = (vec2(touch.x, touch.y) - self.start):len()
if length >= 5 then
self.state = ROTATE
end
end
elseif self.state == ROTATE then
if touch.state == MOVING then
self.rx = self.rx - touch.deltaY * self.sensitivity
self.ry = self.ry - touch.deltaX * self.sensitivity
elseif touch.state == ENDED then
self.state = IDLE
end
end
return true
end
--# OrbitViewer
-----------------------------------------
-- OrbitViewer
-- Written by John Millard
-----------------------------------------
-- Description:
-- A basic viewer that orbits a target via rotating, panning and zooming.
-- A particular point in space is used as the target.
-- Single touch rotates while pinching is used for zooming in and out.
-- Two finger drag is used for panning.
-- Attach to a camera's entity for basic first person controls:
-- i.e. scene.camera:add(OrbitViewer)
-----------------------------------------
OrbitViewer = class()
function OrbitViewer:init(entity, target, zoom, minZoom, maxZoom)
self.entity = entity
self.camera = entity:get(craft.camera)
-- The camera's current target
self.target = target or vec3(0,0,0)
self.origin = self.target
self.zoom = zoom or 5
self.minZoom = minZoom or 1
self.maxZoom = maxZoom or 20
self.touches = {}
self.prev = {}
-- Camera rotation
self.rx = 0
self.ry = 0
-- Angular momentum
self.mx = 0
self.my = 0
self.sensitivity = 0.25
touches.addHandler(self, 0, true)
end
-- Project a 2D point z units from the camera
function OrbitViewer:project(p,z)
local origin, dir = self.camera:screenToRay(p)
return origin + dir * z
end
-- Calculate overscroll curve for zooming
function scroll(x,s)
return s * math.log(x + s) - s * math.log(s)
end
function OrbitViewer:update()
if #self.touches == 0 then
-- Apply momentum from previous swipe
self.rx = self.rx + self.mx * DeltaTime
self.ry = self.ry + self.my * DeltaTime
self.mx = self.mx * 0.9
self.my = self.my * 0.9
-- If zooming past min or max interpolate back to limits
if self.zoom > self.maxZoom then
local overshoot = self.zoom - self.maxZoom
overshoot = overshoot * 0.9
self.zoom = self.maxZoom + overshoot
elseif self.zoom < self.minZoom then
local overshoot = self.minZoom - self.zoom
overshoot = overshoot * 0.9
self.zoom = self.minZoom - overshoot
end
elseif #self.touches == 2 then
self.entity.position = self.prev.target - self.entity.forward * self.zoom
local mid = self:pinchMid()
local dist = self:pinchDist()
local p1 = self:project(self.prev.mid, self.zoom)
local p2 = self:project(mid,self.zoom)
self.target = self.prev.target + (p1-p2)
self.zoom = self.prev.zoom * (self.prev.dist / dist)
if self.zoom > self.maxZoom then
local overshoot = self.zoom - self.maxZoom
overshoot = scroll(overshoot, 10.0)
self.zoom = self.maxZoom + overshoot
elseif self.zoom < self.minZoom then
local overshoot = self.minZoom - self.zoom
overshoot = scroll(overshoot, 10.0)
self.zoom = self.minZoom - overshoot
end
end
-- Clamp vertical rotation between -90 and 90 degrees (no upside down view)
self.rx = math.min(math.max(self.rx, -90), 90)
-- Calculate the camera's position and rotation
local rotation = quat.eulerAngles(self.rx, self.ry, 0)
self.entity.rotation = rotation
local t = vec3(self.target.x, self.target.y, self.target.z)
self.entity.position = t + self.entity.forward * -self.zoom
end
-- Calculate the distance between the current two touches
function OrbitViewer:pinchDist()
local p1 = vec2(self.touches[1].x, self.touches[1].y)
local p2 = vec2(self.touches[2].x, self.touches[2].y)
return p1:dist(p2)
end
-- Calculate the mid point between the current two touches
function OrbitViewer:pinchMid()
local p1 = vec2(self.touches[1].x, self.touches[1].y)
local p2 = vec2(self.touches[2].x, self.touches[2].y)
return (p1 + p2) * 0.5
end
function OrbitViewer:touched(touch)
if touch.tapCount == 2 then
self.target = self.origin
end
-- Allow a maximum of 2 touches
if touch.state == BEGAN and #self.touches < 2 then
table.insert(self.touches, touch)
if #self.touches == 2 then
self.prev.target = vec3(self.target:unpack())
self.prev.mid = self:pinchMid()
self.prev.dist = self:pinchDist()
self.prev.zoom = self.zoom
self.mx = 0
self.my = 0
end
return true
-- Cache updated touches
elseif touch.state == MOVING then
for i = 1,#self.touches do
if self.touches[i].id == touch.id then
self.touches[i] = touch
end
end
-- Remove old touches
elseif touch.state == ENDED or touch.state == CANCELLED then
for i = #self.touches,1,-1 do
if self.touches[i].id == touch.id then
table.remove(self.touches, i)
break
end
end
if #self.touches == 1 then
self.mx = 0
self.my = 0
end
end
-- When all touches are finished apply momentum if moving fast enough
if #self.touches == 0 then
self.mx = -touch.deltaY / DeltaTime * self.sensitivity
self.my = -touch.deltaX / DeltaTime * self.sensitivity
if math.abs(self.mx) < 70 then
self.mx = 0
end
if math.abs(self.my) < 70 then
self.my = 0
end
-- When only one touch is active simply rotate the camera
elseif #self.touches == 1 then
self.rx = self.rx - touch.deltaY * self.sensitivity
self.ry = self.ry - touch.deltaX * self.sensitivity
end
return false
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.