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) }