Files
OrbitalCamera/camera.nut
2026-01-05 23:43:45 +05:00

242 lines
7.0 KiB
Plaintext

local DEG_TO_RAD = PI / 180.0
local distance = 450.0
local minDistance = 100.0
local maxDistance = 800.0
local yaw = 0.0
local pitch = 1.0
local targetVob = null
local targetNode = ""
local targetVobMatrix = null
local currentMatrix = null
local currentVisualAlpha = 1.0
local maxVisualAlpha = 1.0
local yawSpeed = 0.1
local pitchSpeed = 0.1
local zoomSpeed = 25.0
local isPitchInverted = false
local isContolsDisabled = false
// Additional hard coded settings
local lerpRatio = 0.3 // Camera rotation smoothness
local movementLerpRatio = 0.02 // Camera movement smoothness
local collisionOffset = 50.0 // Distance offset from collision objects
local distanceThreshold = 100.0 // Distance below that value will apply vob transparency
local pitchThreshold = 150.0 // Horizontal rotation below this angle will apply vob transparency
// Utility
local function mat4Lerp(mat1, mat2, lerpRatio) {
local transposedMat1 = mat1.transpose()
local transposedMat2 = mat2.transpose()
local out = []
for (local i = 0; i < 4; i++)
out.append(Vec4.lerp(
lerpRatio,
transposedMat1[i],
transposedMat2[i]
))
local outMat = Mat4(out[0], out[1], out[2], out[3]).transpose()
outMat.makeOrthonormal()
return outMat
}
local function interpolateArc(pos1, pos2, center, t) {
local vec1 = pos1 - center
local vec2 = pos2 - center
local len1 = vec1.len()
local len2 = vec2.len()
local len = lerp(len1, len2, t)
vec1 = vec1.normalizeSafe()
vec2 = vec2.normalizeSafe()
local dot = Vec3.dot(vec1, vec2)
local angle = acos(clamp(dot, -1.0, 1.0))
if (angle < 0.001) {
local resultDir = Vec3.lerp(t, vec1, vec2)
return center + resultDir * len
}
local t1 = sin((1.0 - t) * angle)
local t2 = sin(t * angle)
local resultDir = vec1 * t1 + vec2 * t2
resultDir = resultDir.normalizeSafe()
return center + resultDir * len
}
// Private API
local function interpolateCameraMatrix(targetMatrix, center) {
local currentPosition = currentMatrix.getTranslation()
local targetPosition = targetMatrix.getTranslation()
local currentRotation = Quat()
local targetRotation = Quat()
currentRotation.fromMat4(currentMatrix)
targetRotation.fromMat4(targetMatrix)
local position = interpolateArc(
currentPosition,
targetPosition,
center,
lerpRatio
)
local rotation = Quat.slerp(
lerpRatio,
currentRotation,
targetRotation
)
local forward = (center - position).normalize()
local rotationMat = rotation.toMat4()
local up = rotationMat.getUpVector()
return Mat4.lookAt(position, center, up)
}
local function getSafeDistance(vobPosition, cameraPosition) {
local direction = cameraPosition - vobPosition
local report = GameWorld.traceRayNearestHit(
vobPosition,
direction,
TRACERAY_POLY_NORMAL | TRACERAY_VOB_IGNORE_CHARACTER | TRACERAY_POLY_IGNORE_TRANSP
)
if (report == null) return distance
return vobPosition.distance(report.intersect) - collisionOffset
}
local function updateCameraMatrix() {
local currentVobMatrix = targetVob.getTrafoModelNodeToWorld(targetNode)
targetVobMatrix = mat4Lerp(targetVobMatrix, currentVobMatrix, movementLerpRatio)
local targetVobPosition = targetVobMatrix.getTranslation()
local yawRad = yaw * DEG_TO_RAD
local pitchRad = pitch * DEG_TO_RAD
local verticalFactor = cos(pitchRad)
local horizontalFactor = sin(pitchRad)
local targetCameraPosition = Vec3(
targetVobPosition.x + sin(yawRad) * distance * horizontalFactor,
targetVobPosition.y + distance * verticalFactor,
targetVobPosition.z + cos(yawRad) * distance * horizontalFactor
)
local targetMatrix = Mat4.lookAt(
targetCameraPosition,
targetVobPosition,
Vec3(0, 1, 0)
)
local safeDistance = getSafeDistance(targetVobPosition, targetCameraPosition)
if (safeDistance != distance) {
local minSafeDistance = 10.0
safeDistance = clamp(safeDistance, minSafeDistance, maxDistance)
targetCameraPosition = Vec3(
targetVobPosition.x + sin(yawRad) * safeDistance * horizontalFactor,
targetVobPosition.y + safeDistance * verticalFactor,
targetVobPosition.z + cos(yawRad) * safeDistance * horizontalFactor
)
targetMatrix.setTranslation(targetCameraPosition)
}
currentMatrix = interpolateCameraMatrix(targetMatrix, targetVobPosition)
Camera.targetVob.matrix = currentMatrix
}
local function updateAlpha() {
local targetRatio = maxVisualAlpha
local cameraPosition = currentMatrix.getTranslation()
local targetPosition = targetVobMatrix.getTranslation()
local currentDistance = cameraPosition.distance(targetPosition)
if (currentDistance < distanceThreshold)
targetRatio = (currentDistance / distanceThreshold) * (maxVisualAlpha / 2.0)
else if (pitch > pitchThreshold)
targetRatio = ((179.0 - pitch) / 29.0) * (maxVisualAlpha / 2.0)
targetRatio = clamp(targetRatio, 0.0, 1.0)
currentVisualAlpha = lerp(currentVisualAlpha, targetRatio, 0.1)
targetVob.visualAlpha = currentVisualAlpha
}
addEventHandler("onMouseMove", function(x, y) {
if (isContolsDisabled) return
local pitchMultiplier = isPitchInverted ? -1 : 1
yaw += x * yawSpeed
pitch += y * pitchSpeed * pitchMultiplier
yaw = (yaw % 360 + 360) % 360
pitch = clamp(pitch, 1.0, 179.0)
})
addEventHandler("onMouseWheel", function(direction) {
if (isContolsDisabled) return
distance -= direction * zoomSpeed
distance = clamp(distance, minDistance, maxDistance)
})
addEventHandler("onRender", function() {
if (targetVob == null) return
updateCameraMatrix()
updateAlpha()
})
// Public API
function isContolsDisabled() { return isContolsDisabled }
function getTargetVob() { return targetVob }
function getTargetNode() { return targetNode }
function getXAxisSensitivity() { return yawSpeed }
function getYAxisSensitivity() { return pitchSpeed }
function getZoomSensitivity() { return zoomSpeed }
function isYAxisInverted() { return isPitchInverted }
function getMinDistance() { return minDistance }
function getMaxDistance() { return maxDistance }
function getCurrentDistance() { return distance }
function setTargetVob(vob, node = "") {
targetVob = vob
targetNode = node
targetVobMatrix = targetVob.getTrafoModelNodeToWorld(targetNode)
currentMatrix = Camera.targetVob.matrix
currentVisualAlpha = targetVob.visualAlpha
maxVisualAlpha = currentVisualAlpha
}
function setControlsDisabled(value) { isControlsDisabled = value }
function setXAxisSensitivity(value) { yawSpeed = value }
function setYAxisSensitivity(value) { pitchSpeed = value }
function setZoomSensitivity(value) { zoomSpeed = value }
function setYAxisInverted(value) { isPitchInverted = value }
function setMinDistance(value) { minDistance = clamp(value, value, maxDistance) }
function setMaxDistance(value) { maxDistance = clamp(value, minDistance, value) }