kiki/coffee/camera.coffee
2016-10-12 16:27:29 +02:00

249 lines
10 KiB
CoffeeScript

# 0000000 0000000 00 00 00000000 00000000 0000000
# 000 000 000 000 000 000 000 000 000 000
# 000 000000000 000000000 0000000 0000000 000000000
# 000 000 000 000 0 000 000 000 000 000 000
# 0000000 000 000 000 000 00000000 000 000 000 000
{
clamp
} = require './tools/tools'
log = require './tools/log'
Matrix = require './lib/matrix'
Vector = require './lib/vector'
Quaternion = require './lib/quaternion'
class Camera extends Matrix
@INSIDE = 0
@BEHIND = 1
@FOLLOW = 2
constructor: (@player, opt) ->
@fov = opt?.fov ? 90
@near = opt?.near ? 0.01
@eye_distance = @near
@far = opt?.far ? 30
@mode = Camera.BEHIND
@aspect = opt.aspect ? -1
@dist = 10
@border = [0,0,0,0]
super
@setViewport 0.0, 0.0, 1.0, 1.0
@cam = new THREE.PerspectiveCamera @fov, @aspect, @near, @far
@cam.position.z = @dist
step: (step) ->
switch @mode
when Camera.INSIDE then @insideProjection()
when Camera.BEHIND then @behindProjection()
when Camera.FOLLOW then @followProjection()
camPos = @getPosition()
@cam.position.copy camPos
@cam.up.copy @getYVector()
@cam.lookAt camPos.plus @getZVector()
if @light?
pos = @getPosition().plus @light_offset
@light.setDirection -@getZVector()
@light.setPosition new Vector pos[X], pos[Y], pos[Z], 1.0 # positional light source
getLookAtPosition: -> @getZVector().mul(-@eye_distance).plus @getPosition()
setOrientation: (o) ->
@setYVector o.rotate Vector.unitY
@setZVector o.rotate Vector.unitZ
@setXVector o.rotate Vector.minusX
@cam.up.copy @getYVector()
@cam.lookAt @getPosition().plus @getZVector()
updateViewport: ->
# ss = world.screenSize
# vp = []
# vp[0] = @viewport[0] * ss.w + @border[0]
# vp[1] = @viewport[1] * ss.h + @border[1]
# vp[2] = @viewport[2] * ss.w - @border[0] - @border[2]
# vp[3] = @viewport[3] * ss.h - @border[1] - @border[3]
setViewportBorder: (l, b, r, t) ->
# @border = [l,b,r,t]
# @updateViewport()
setViewport: (l, b, w, h) ->
# @viewport = [l,b,w,h]
# @updateViewport()
#
setFov: (fov) -> @fov = Math.max(2.0, Math.min fov, 175.0)
# 00000000 00000000 0000000 000 00000000 0000000 000000000 000 0000000 000 000
# 000 000 000 000 000 000 000 000 000 000 000 000 000 0000 000
# 00000000 0000000 000 000 000 0000000 000 000 000 000 000 000 0 000
# 000 000 000 000 000 000 000 000 000 000 000 000 000 000 0000
# 000 000 000 0000000 0000000 00000000 0000000 000 000 0000000 000 000
insideProjection: () ->
playerPos = @player.currentPos()
playerDir = @player.currentDir()
playerUp = @player.currentUp()
lookAngle = @player.look_angle
posDelta = world.getSpeed() / 10.0 # smooth camera movement a little bit
camPos = playerPos
if lookAngle < 0
camPos.add playerUp.mul -2*lookAngle/90
@setPosition @getPosition().mul(1.0-posDelta).plus camPos.mul posDelta
if lookAngle # player is looking up or down
@setXVector playerDir.cross(playerUp).normal()
rot = Quaternion.rotationAroundVector lookAngle, @getXVector()
@setYVector rot.rotate playerUp
@setZVector rot.rotate playerDir #.neg()
else
# smooth camera rotation a little bit
lookDelta = (2.0 - @getZVector().dot playerDir) * world.getSpeed() / 50.0
newLookVector = @getZVector().mul(1.0 - lookDelta).plus playerDir.mul lookDelta
newLookVector.normalize()
@setXVector playerUp.cross(newLookVector).normal()
@setYVector playerUp
@setZVector newLookVector
@projection
# 0000000 00000000 000 000 000 000 000 0000000
# 000 000 000 000 000 000 0000 000 000 000
# 0000000 0000000 000000000 000 000 0 000 000 000
# 000 000 000 000 000 000 000 0000 000 000
# 0000000 00000000 000 000 000 000 000 0000000
behindProjection: () ->
playerPos = @player.currentPos()
playerDir = @player.currentDir()
playerUp = @player.currentUp()
lookAngle = @player.look_angle
# find a valid camera position
botToCamera = playerUp.minus playerDir.mul 2
min_f = botToCamera.length()
botToCamera.normalize()
min_f = Math.min world.getWallDistanceForRay(playerPos, botToCamera), min_f
camPos = playerPos.plus botToCamera.mul Math.max(min_f, 0.72) * (1-Math.abs(lookAngle)/90)
if lookAngle < 0
camPos.add playerUp.mul -2*lookAngle/90
camPos = world.getInsideWallPosWithDelta camPos, 0.2
# smooth camera movement a little bit
posDelta = 0.2
@setPosition @getPosition().mul(1.0 - posDelta).plus camPos.mul posDelta
if lookAngle
# log "look_angle #{lookAngle}"
@setXVector playerDir.cross(playerUp).normal()
rot = Quaternion.rotationAroundVector lookAngle, @getXVector()
@setYVector rot.rotate playerUp
@setZVector rot.rotate playerDir #.neg()
else
# smooth camera rotation a little bit
lookDelta = 0.3
newLookVector = @getZVector().mul(1.0 - lookDelta).plus (playerDir.minus(playerUp.mul(0.2)).normal()).mul lookDelta
newLookVector.normalize()
@setZVector newLookVector
@setXVector playerUp.cross(newLookVector).normal()
@setYVector newLookVector.cross(@getXVector()).normal()
# 00000000 0000000 000 000 0000000 000 000
# 000 000 000 000 000 000 000 000 0 000
# 000000 000 000 000 000 000 000 000000000
# 000 000 000 000 000 000 000 000 000
# 000 0000000 0000000 0000000 0000000 00 00
followProjection: () ->
camPos = @getPosition()
desiredDistance = 2.0 # desired distance from camera to bot
playerPos = @player.currentPos()
playerDir = @player.currentDir()
playerUp = @player.currentUp()
playerLeft = @player.currentLeft()
# first, adjust distance from camera to bot
botToCamera = camPos.minus playerPos # vector from bot to current pos
cameraBotDistance = botToCamera.length() # distance from camera to bot
if cameraBotDistance >= desiredDistance
difference = cameraBotDistance - desiredDistance
delta = difference*difference/400.0 # weight for following speed
camPos = camPos.mul(1.0-delta).plus playerPos.mul delta
else
difference = desiredDistance - cameraBotDistance
delta = difference/20.0 # weight for negative following speed
camPos = camPos.mul(1.0-delta).plus (playerPos.plus botToCamera.normal().mul desiredDistance).mul delta
# second, rotate around bot
botToCamera = camPos.minus playerPos
botToCameraNormal = botToCamera.normal()
# rotate camera vertically
verticalAngle = Vector.RAD2DEG Math.acos(clamp(-1.0, 1.0, botToCameraNormal.dot playerUp))
if verticalAngle > 45
# log "verticalAngle #{verticalAngle}"
rotQuat = Quaternion.rotationAroundVector(verticalAngle/400.0, botToCameraNormal.cross(playerUp))
botToCamera = rotQuat.rotate botToCamera
botToCameraNormal = botToCamera.normal()
camPos = playerPos.plus botToCamera
rotFactor = 1.0
wall_distance = world.getWallDistanceForPos camPos
if wall_distance < 0.5 # try avoid piercing walls
if wall_distance < 0.2
camPos = world.getInsideWallPosWithDelta camPos, 0.2
botToCamera = camPos.minus playerPos
botToCameraNormal = botToCamera.normal()
rotFactor = 0.5 / (wall_distance-0.2)
# try view bot from behind
# calculate horizontal angle between bot orientation and vector to camera
mappedToXZ = (botToCamera.minus playerUp.mul(botToCamera.dot playerUp)).normal()
horizontalAngle = Vector.RAD2DEG Math.acos(clamp(-1.0, 1.0, -playerDir.dot mappedToXZ))
if botToCameraNormal.dot(playerLeft) < 0
horizontalAngle = -horizontalAngle
rotQuat = Quaternion.rotationAroundVector horizontalAngle/(rotFactor*400.0), playerUp
camPos = playerPos.plus rotQuat.rotate botToCamera
botToCamera = camPos.minus playerPos
botToCameraNormal = botToCamera.normal()
@setPosition camPos # finally, set the position
# slowly adjust look direction by interpolating current and desired directions
lookDelta = @getZVector().dot botToCameraNormal
lookDelta *= lookDelta / 30.0
newLookVector = @getZVector().mul(1.0-lookDelta).plus botToCameraNormal.neg().mul(lookDelta)
newLookVector.normalize()
# slowly adjust up vector by interpolating current and desired up vectors
upDelta = 1.5-@getYVector().dot playerUp
upDelta *= upDelta / 100.0
newUpVector = @getYVector().mul(1.0-upDelta).plus playerUp.mul(upDelta)
newUpVector.normalize()
newLeftVector = newUpVector.cross newLookVector
# finished interpolations, update camera matrix
@setXVector newLeftVector
@setYVector newUpVector
@setZVector newLookVector
@projection
module.exports = Camera