commit e58c8f1927e9cd14dde756a28c81bd4a30a96a70 Author: AURUMVORAX Date: Mon Jan 5 23:43:45 2026 +0500 Initial diff --git a/README.md b/README.md new file mode 100644 index 0000000..0213342 --- /dev/null +++ b/README.md @@ -0,0 +1,44 @@ +### Usage Example + +``` +local playerVob = Vob(getPlayerPtr(heroId)) + +Camera.modeChangeEnabled = false +Camera.movementEnabled = false + +setTargetVob(playerVob) +// or you can specify node +setTargetVob(playerVob, "BIP01 HEAD") +``` + +### API + +``` +bool isContolsDisabled() + +Vob getTargetVob() +string getTargetNode() + +float getXAxisSensitivity() +float getYAxisSensitivity() +float getZoomSensitivity() + +bool isYAxisInverted() + +float getMinDistance() +float getMaxDistance() +float getCurrentDistance() + +void setTargetVob(Vob vob, string node = "") + +void setControlsDisabled(float value) + +void setXAxisSensitivity(float value) +void setYAxisSensitivity(float value) +void setZoomSensitivity(float value) + +void setYAxisInverted(float value) + +void setMinDistance(float value) +void setMaxDistance(float value) +``` diff --git a/camera.nut b/camera.nut new file mode 100644 index 0000000..00a4538 --- /dev/null +++ b/camera.nut @@ -0,0 +1,242 @@ +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) } \ No newline at end of file