249 lines
10 KiB
CoffeeScript
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 |