Files
GTA4RP/scripts/client/mouse-camera.js
2021-03-15 12:30:35 -05:00

725 lines
18 KiB
JavaScript

// ===========================================================================
// Asshat-Gaming Roleplay
// https://github.com/VortrexFTW/gtac_asshat_rp
// Copyright (c) 2021 Asshat-Gaming (https://asshatgaming.com)
// ---------------------------------------------------------------------------
// FILE: mouse-camera.js
// DESC: Provides a freelook camera similar to SA for III and VC
// TYPE: Client (JavaScript)
// ===========================================================================
// CREDITS TO LUCASC190 FOR MAKING THE MOUSE CAMERA
// WALKING CODE ADDED BY VORTREX
function SetStandardControlsEnabled(bEnabled)
{
if (gta.standardControls === undefined)
{
console.warn("gta.standardControls not implemented");
return;
}
gta.standardControls = bEnabled;
}
function GetCurrentPlayerIndex()
{
return 0;
}
function GetPlayerPed(uiIndex)
{
if (uiIndex >= 1)
throw new Error("player index out of range");
return localPlayer;
}
function GetPedVehicle(pPed)
{
return pPed.vehicle;
}
let ENTITYTYPE_BUILDING = 1;
let ENTITYTYPE_VEHICLE = 2;
let ENTITYTYPE_PED = 3;
let ENTITYTYPE_OBJECT = 4;
let ENTITYTYPE_DUMMY = 5;
function GetEntityType(Entity)
{
if (Entity.isType(ELEMENT_BUILDING))
return ENTITYTYPE_BUILDING;
if (Entity.isType(ELEMENT_VEHICLE))
return ENTITYTYPE_VEHICLE;
if (Entity.isType(ELEMENT_PED))
return ENTITYTYPE_PED;
if (Entity.isType(ELEMENT_OBJECT))
return ENTITYTYPE_OBJECT;
//if (Entity.isType(ELEMENT_DUMMY))
// return ENTITYTYPE_DUMMY;
return undefined;
}
function GetPlaceableMatrix(pPlaceable)
{
if (pPlaceable == GetCamera())
return gta.cameraMatrix;
return pPlaceable.matrix;
}
function GetEntityModel(pEntity)
{
return pEntity;
}
function GetModelBoundingSphere(usModel)
{
return [usModel.boundingRadius, usModel.boundingCentre.x, usModel.boundingCentre.y, usModel.boundingCentre.z];
}
function GetMouseSpeed()
{
if (gui.cursorEnabled)
return [0,0];
let MouseSpeed = gta.getMouseSpeed();
return [MouseSpeed.x,-MouseSpeed.y];
}
function GetMouseSensitivity()
{
if (gta.getMouseSensitivity === undefined)
{
//console.error("gta.getMouseSensitivity not implemented");
return [0.0025,0.003];
}
let MouseSensitivity = gta.getMouseSensitivity();
return [MouseSensitivity.x,MouseSensitivity.y];
}
let GetCamera;
{
const Camera = Symbol();
GetCamera = function()
{
return Camera;
}
}
function AreEntityCollisionsEnabled(pEntity)
{
return pEntity.collisionsEnabled;
}
function SetEntityCollisionsEnabled(pEntity, bCollisionsEnabled)
{
pEntity.collisionsEnabled = bCollisionsEnabled;
}
function ProcessLineOfSight(vecStartX, vecStartY, vecStartZ, vecEndX, vecEndY, vecEndZ, bCheckBuildings, bCheckVehicles, bCheckPeds, bCheckObjects, bCheckDummies, bCheckSeeThroughStuff, bIgnoreSomeObjectsForCamera)
{
if (gta.processLineOfSight === undefined)
{
console.warn("gta.processLineOfSight not implemented");
return [null];
}
let Result = gta.processLineOfSight([vecStartX, vecStartY, vecStartZ], [vecEndX, vecEndY, vecEndZ], bCheckBuildings, bCheckVehicles, bCheckPeds, bCheckObjects, bCheckDummies, bCheckSeeThroughStuff, bIgnoreSomeObjectsForCamera);
if (Result == null)
return [null];
return [Result.position.x, Result.position.y ,Result.position.z, Result.normal.x, Result.normal.y ,Result.normal.z, Result.entity];
}
function SetPlaceableMatrix(pPlaceable, mat)
{
if (pPlaceable == GetCamera())
{
gta.setCameraMatrix(mat);
return;
}
pPlaceable.matrix = mat;
}
const UpdateCamera = gta.updateCamera;
let GetTickCount;
{
let FrameCount = 0;
setInterval(() =>
{
++FrameCount;
}, 0);
let GTAFrameCount = 0;
addEventHandler("OnProcess", (event, deltaTime) =>
{
++GTAFrameCount;
});
GetTickCount = function(bGTA, bFrames)
{
if (bFrames)
return bGTA ? GTAFrameCount : FrameCount;
else
return bGTA ? gta.tickCount : sdl.ticks;
}
}
function easingSinusoidalInOut(t,b,c,d)//TODO: Move this to MathUtil.js
{
return -c/2 * (Math.cos((Math.PI)*t/d) - 1) + b;
}
//TODO: extract
function applyMultiplierTimeStep(m,t)//TODO: Move this to MathUtil.js
{
return Math.max(Math.min(1.0-(1.0-m)*(t),1),0);
}
//TODO: getOffset
//TODO: round
//TODO: getNumberBetween
//TODO: split
//TODO: isWhiteSpaceCharacter
//TODO: isControlCharacter
//TODO: alert
//TODO: confirm
const identityMatrix = new Matrix4x4();
if (identityMatrix.setIdentity === undefined)
{
identityMatrix.m11 = 1;
identityMatrix.m12 = 0;
identityMatrix.m13 = 0;
identityMatrix.m14 = 0;
identityMatrix.m21 = 0;
identityMatrix.m22 = 1;
identityMatrix.m23 = 0;
identityMatrix.m24 = 0;
identityMatrix.m31 = 0;
identityMatrix.m32 = 0;
identityMatrix.m33 = 1;
identityMatrix.m34 = 0;
identityMatrix.m41 = 0;
identityMatrix.m42 = 0;
identityMatrix.m43 = 0;
identityMatrix.m44 = 1;
}
const cameraIdentityMatrix = new Matrix4x4();
cameraIdentityMatrix.m11 = -1;
cameraIdentityMatrix.m12 = 0;
cameraIdentityMatrix.m13 = 0;
cameraIdentityMatrix.m14 = 0;
cameraIdentityMatrix.m21 = 0;
cameraIdentityMatrix.m22 = 1;
cameraIdentityMatrix.m23 = 0;
cameraIdentityMatrix.m24 = 0;
cameraIdentityMatrix.m31 = 0;
cameraIdentityMatrix.m32 = 0;
cameraIdentityMatrix.m33 = 1;
cameraIdentityMatrix.m34 = 0;
cameraIdentityMatrix.m41 = 0;
cameraIdentityMatrix.m42 = 0;
cameraIdentityMatrix.m43 = 0;
cameraIdentityMatrix.m44 = 1;
function createMultipliedMatrix()
{
let matrix = new Matrix4x4();
matrix.setMultiply.apply(matrix, arguments);
return matrix;
}
function createXRotationMatrix(x)
{
let matrix = new Matrix4x4();
matrix.setRotateX(x);
return matrix;
}
function createYRotationMatrix(x)
{
let matrix = new Matrix4x4();
matrix.setRotateY(x);
return matrix;
}
function createZRotationMatrix(z)
{
let matrix = new Matrix4x4();
matrix.setRotateZ(z);
return matrix;
}
function createTranslationMatrix(x,y,z)
{
let matrix = new Matrix4x4();
matrix.setTranslate([x,y,z]);
return matrix;
}
//TODO: createScaleMatrix
function getDotProduct(x,y,z,x2,y2,z2)
{
return x*x2 + y*y2 + z*z2;
}
function getCrossProduct(x,y,z,x2,y2,z2)
{
return [y*z2-z*y2, z*x2-x*z2, x*y2-y*x2];
}
function getLength(x,y,z)
{
return Math.sqrt(getDotProduct(x,y,z,x,y,z));
}
function normalise(x,y,z)
{
let length = getLength(x,y,z);
if (length == 0)
throw new Error("an attempt was made to normalise a three dimensional vector with a length of zero");
return [x/length, y/length, z/length];
}
function createLookAtLHMatrix(eyeX, eyeY, eyeZ, atX, atY, atZ, upX,upY,upZ)
{
let matrix = new Matrix4x4();
let [lookX, lookY, lookZ] = normalise(atX-eyeX,atY-eyeY,atZ-eyeZ);
let [rightX, rightY, rightZ] = normalise.apply(null,getCrossProduct(upX,upY,upZ,lookX, lookY, lookZ));
[upX,upY,upZ] = getCrossProduct(lookX, lookY, lookZ,rightX, rightY, rightZ);
matrix.m11 = rightX;
matrix.m12 = rightY;
matrix.m13 = rightZ;
matrix.m14 = 0;
matrix.m21 = lookX;
matrix.m22 = lookY;
matrix.m23 = lookZ;
matrix.m24 = 0;
matrix.m31 = upX;
matrix.m32 = upY;
matrix.m33 = upZ;
matrix.m34 = 0;
matrix.m41 = eyeX;
matrix.m42 = eyeY;
matrix.m43 = eyeZ;
matrix.m44 = 1;
matrix.m41 = eyeX;
matrix.m42 = eyeY;
matrix.m43 = eyeZ;
matrix.m44 = 1;
return matrix;
}
function getDifferenceBetweenAngles(current,target)
{
let f = (((target-current)+Math.PI)/(Math.PI*2));
return ((f-Math.floor(f))*(Math.PI*2))-Math.PI;
}
let easeCamera = false;
let easeStartTicks;
let easeDuration;
let easeStartPosX, easeStartPosY, easeStartPosZ;
let easeStartLookX, easeStartLookY, easeStartLookZ;
let easeStartUpX, easeStartUpY, easeStartUpZ;
function getCameraPositionInfo(matrix)
{
return [matrix.m41, matrix.m42, matrix.m43, matrix.m21, matrix.m22, matrix.m23, matrix.m31, matrix.m32, matrix.m33];
}
function startCameraEase()
{
easeCamera = true;
easeStartTicks = GetTickCount(true,false);
easeDuration = 1000;
let matrix = GetPlaceableMatrix(GetCamera());
[easeStartPosX, easeStartPosY, easeStartPosZ, easeStartLookX, easeStartLookY, easeStartLookZ, easeStartUpX, easeStartUpY, easeStartUpZ] = getCameraPositionInfo(matrix);
}
function applyCameraEase(matrix)
{
if (!easeCamera)
return matrix;
let ease = (GetTickCount(true,false)-easeStartTicks)/easeDuration;
if (ease < 1)
{
ease = easingSinusoidalInOut(ease,0,1,1);
let [newPosX, newPosY, newPosZ, newLookX, newLookY, newLookZ, newUpX, newUpY, newUpZ] = getCameraPositionInfo(matrix);
let easePosX = easeStartPosX+(newPosX-easeStartPosX)*ease;
let easePosY = easeStartPosY+(newPosY-easeStartPosY)*ease;
let easePosZ = easeStartPosZ+(newPosZ-easeStartPosZ)*ease;
let easeLookX = easeStartLookX+(newLookX-easeStartLookX)*ease;
let easeLookY = easeStartLookY+(newLookY-easeStartLookY)*ease;
let easeLookZ = easeStartLookZ+(newLookZ-easeStartLookZ)*ease;
let easeUpX = easeStartUpX+(newUpX-easeStartUpX)*ease;
let easeUpY = easeStartUpY+(newUpY-easeStartUpY)*ease;
let easeUpZ = easeStartUpZ+(newUpZ-easeStartUpZ)*ease;
return createLookAtLHMatrix(easePosX,easePosY,easePosZ,easePosX+easeLookX,easePosY+easeLookY,easePosZ+easeLookZ,easeUpX,easeUpY,easeUpZ);
}
return matrix;
}
function isCameraEasing()
{
return easeCamera && GetTickCount(true,false) < (easeStartTicks+easeDuration);
}
let oldCameraTarget = null;
let OldPosition = null;//2019 Lucas was here!
let cameraRotZ;
let cameraRotY;
function getCameraTarget()
{
let playerPed = GetPlayerPed(GetCurrentPlayerIndex());
let vehicle = GetPedVehicle(playerPed);
if (vehicle != null)
return vehicle;
if (playerPed != null)
{
//if (playerPed.health <= 1)//Breaks because of fade//2019 Lucas was here!
// return null;
return playerPed;
}
return null;
}
function isRelativeToTarget(target)
{
if (GetEntityType(target) == ENTITYTYPE_PED)
return false;
return false
}
function isClipped(target)
{
if (GetEntityType(target) == ENTITYTYPE_PED)
return true;
return true;
}
//2019 Lucas was here!
function ShouldReturnToRestRotation(Target)
{
if (GetEntityType(Target) == ENTITYTYPE_PED)
return false;
return true;
}
function getCameraRestRotation(target)
{
let targetMatrix = GetPlaceableMatrix(target);
let rotZ;
if (isRelativeToTarget(target))
rotZ = 0;
else
rotZ = -Math.atan2(targetMatrix.m21,targetMatrix.m22);
let rotY = -0.2;
return [rotZ, rotY];
}
function resetCameraRotation()
{
[cameraRotZ, cameraRotY] = getCameraRestRotation(getCameraTarget());
}
//2019 Lucas was here!
let DeltaTime = 0;
addEventHandler("OnProcess", (event, deltaTime) =>
{
DeltaTime = deltaTime;
if(!localPlayer) {
return false;
}
if(gta.game >= GAME_GTA_SA) {
// We don't need this for GTA SA+
return false;
}
/*
if(localPlayer.vehicle == null) {
let newAngle = 0.0;
let moveKeyHeld = false;
if(isKeyDown(SDLK_w)) {
newAngle = Math.PI;
moveKeyHeld = true;
if(isKeyDown(SDLK_a)) {
newAngle = newAngle+Math.PI/4;
} else if(isKeyDown(SDLK_d)) {
newAngle = newAngle-Math.PI/4;
}
} else if(isKeyDown(SDLK_s)) {
newAngle = Math.PI*2;
moveKeyHeld = true;
if(isKeyDown(SDLK_a)) {
newAngle = newAngle-Math.PI/4;
} else if(isKeyDown(SDLK_d)) {
newAngle = newAngle+Math.PI/4;
}
} else if(isKeyDown(SDLK_a)) {
newAngle = -Math.PI/2;
moveKeyHeld = true;
} else if(isKeyDown(SDLK_d)) {
newAngle = Math.PI/2;
moveKeyHeld = true;
}
if(moveKeyHeld && !gui.cursorEnabled) {
localPlayer.heading = cameraAngle + newAngle;
}
if(sdl.getModState()&KMOD_ALT && !gui.cursorEnabled) {
if(moveKeyHeld) {
let position = getPosInFrontOfPos(localPlayer.position, localPlayer.heading, 1.0);
localPlayer.walkTo(vec3ToVec2(position));
isWalking = true;
triggerNetworkEvent("ag.walk", true);
} else {
isWalking = false;
triggerNetworkEvent("ag.walk", false);
}
} else {
isWalking = false;
triggerNetworkEvent("ag.walk", false);
}
}
*/
});
let IdleTime = 0;//2019 Lucas was here!
function processReturnToRestRotation()
{
//resetCameraRotation();//2019 Lucas was here!
//2019 Lucas was here!
let Target = getCameraTarget();
if (!ShouldReturnToRestRotation(Target))
return;
IdleTime += DeltaTime;
if (IdleTime > 1.5)
{
let Velocity = Target.velocity;
let Matrix = Target.matrix;
let Speed = getDotProduct(Velocity.x,Velocity.y,Velocity.z,Matrix.getElement(1*4+0),Matrix.getElement(1*4+1),Matrix.getElement(1*4+2));
let AbsSpeed = Math.abs(Speed);
let Multiplier = Math.min(AbsSpeed/0.75, 1);
if (Multiplier != 0)
{
let [TargetCameraRotZ2, TargetCameraRotY2] = getCameraRestRotation(Target);
if (Speed < 0)
TargetCameraRotZ2 += Math.PI;
let TimeStep = gta.timeStep/50*60;
cameraRotZ += getDifferenceBetweenAngles(cameraRotZ,TargetCameraRotZ2)*applyMultiplierTimeStep(1/20,TimeStep)*Multiplier;
cameraRotY += getDifferenceBetweenAngles(cameraRotY,TargetCameraRotY2)*applyMultiplierTimeStep(1/20,TimeStep)*Multiplier;
}
}
}
function cancelReturnToRestRotation()
{
IdleTime = 0;//2019 Lucas was here!
}
let distance;
let zIncrease;
function getCameraOffsetInfo(target)
{
if (GetEntityType(target) == ENTITYTYPE_PED)
{
let distance = 4;
let zIncrease = 0.8;
let offsetX = 0;
let offsetY = 0;
let offsetZ = 0;
return [distance, zIncrease, offsetX, offsetY, offsetZ];
}
let model = GetEntityModel(target);
let [radius] = GetModelBoundingSphere(model);
let minDistance;
let maxDistance;
let minZIncrease;
let maxZIncrease;
let minRadius;
let maxRadius;
let offsetX;
let offsetY;
let offsetZ;
if (radius <= 3.0535011291504)
{
minDistance = 4;
maxDistance = 8;
minZIncrease = 0.5;
maxZIncrease = 1;
minRadius = 2;
maxRadius = 3.0535011291504;
}
else
{
minDistance = 8;
maxDistance = 16;
minZIncrease = 1;
maxZIncrease = 2;
minRadius = 3.05350112915042;
maxRadius = 6.3955960273743;
}
offsetX = 0;
offsetY = 0;
offsetZ = 0;
distance = minDistance+(radius-minRadius)/(maxRadius-minRadius)*(maxDistance-minDistance);
zIncrease = minZIncrease+(radius-minRadius)/(maxRadius-minRadius)*(maxZIncrease-minZIncrease);
return [distance, zIncrease, offsetX, offsetY, offsetZ];
}
function update()
{
let target = getCameraTarget();
if (target != null)
{
if (oldCameraTarget != target)
{
//if (oldCameraTarget != null)//2019 Lucas was here!
let Position = target.position;
if (OldPosition == null || getLength(Position.x-OldPosition.x,Position.y-OldPosition.y,Position.z-OldPosition.z) < 10)
startCameraEase()
resetCameraRotation()
}
let [mouseSpeedX, mouseSpeedY] = GetMouseSpeed();
let [mouseSensitivityX, mouseSensitivityY] = GetMouseSensitivity();
mouseSpeedX = mouseSpeedX*mouseSensitivityX*2;
mouseSpeedY = mouseSpeedY*mouseSensitivityY*2;
if (mouseSpeedX == 0 && mouseSpeedY == 0)
{
processReturnToRestRotation();
}
else
{
cameraRotZ = cameraRotZ-mouseSpeedX;
cameraRotY = cameraRotY-mouseSpeedY;
cancelReturnToRestRotation();
}
cameraRotY = Math.max(cameraRotY,-Math.PI/2+0.01);
if (GetEntityType(target) != ENTITYTYPE_PED)
cameraRotY = Math.min(cameraRotY,Math.PI/8.5);//2019 Lucas was here!
else
cameraRotY = Math.min(cameraRotY,Math.PI/4);
let camera = GetCamera();
let targetMatrix = GetPlaceableMatrix(target);
let [distance, zIncrease, offsetX, offsetY, offsetZ] = getCameraOffsetInfo(target);
let offsetTranslationMatrix = createTranslationMatrix(offsetX, offsetY, offsetZ);
targetMatrix = createMultipliedMatrix(offsetTranslationMatrix,targetMatrix);
let targetPosX, targetPosY, targetPosZ;
if (isRelativeToTarget(target))
[targetPosX, targetPosY, targetPosZ] = [0,0,0];
else
[targetPosX, targetPosY, targetPosZ] = [targetMatrix.m41,targetMatrix.m42,targetMatrix.m43];
let distanceTranslationMatrix = createTranslationMatrix(0,-distance,0);
targetPosZ = targetPosZ+zIncrease;
let targetTranslationMatrix = createTranslationMatrix(targetPosX, targetPosY, targetPosZ);
let offsetRotationX = createXRotationMatrix(cameraRotY);
let offsetRotationZ = createZRotationMatrix(cameraRotZ);
let cameraMatrix = createMultipliedMatrix(cameraIdentityMatrix,distanceTranslationMatrix,offsetRotationX,offsetRotationZ,targetTranslationMatrix);
if (isRelativeToTarget(target))
{
cameraMatrix = createMultipliedMatrix(cameraMatrix,targetMatrix);
targetTranslationMatrix = createMultipliedMatrix(targetTranslationMatrix,targetMatrix);
}
if (isClipped(target))
{
let startX = targetTranslationMatrix.m41;
let startY = targetTranslationMatrix.m42;
let startZ = targetTranslationMatrix.m43;
let endX = cameraMatrix.m41;
let endY = cameraMatrix.m42;
let endZ = cameraMatrix.m43;
let checkBuildings = true;
let checkVehicles = true;
let checkPeds = true;
let checkObjects = true;
let checkDummies = false;
let checkSeeThroughStuff = false;
let ignoreSomeObjectsForCamera = true;
let collisionsEnabled = AreEntityCollisionsEnabled(target);
if (collisionsEnabled)
SetEntityCollisionsEnabled(target,false);
let [positionX,positionY,positionZ,normalX,normalY,normalZ,targetEntity] = ProcessLineOfSight(startX,startY,startZ,endX,endY,endZ,checkBuildings,checkVehicles,checkPeds,checkObjects,checkDummies,checkSeeThroughStuff,ignoreSomeObjectsForCamera);
if (collisionsEnabled)
SetEntityCollisionsEnabled(target,true);
if (positionX != null)
{
//2019 Lucas was here!
let Distance = 0.3;
positionX += normalX*Distance;
positionY += normalY*Distance;
positionZ += normalZ*Distance;
cameraMatrix.m41 = positionX;
cameraMatrix.m42 = positionY;
cameraMatrix.m43 = positionZ;
}
}
if (isCameraEasing())
cameraMatrix = applyCameraEase(cameraMatrix);
SetPlaceableMatrix(camera,cameraMatrix);
UpdateCamera(camera);
}
oldCameraTarget = target;
OldPosition = (target != null) ? target.position : null;//2019 Lucas was here!
return target != null;
}
addEventHandler("OnCameraProcess", (event) =>
{
if(mouseCameraEnabled) {
update();
event.preventDefault();
}
});
function getPosInFrontOfPos(pos, angle, distance) {
let x = (pos.x+((Math.cos(angle+(Math.PI/2)))*distance));
let y = (pos.y+((Math.sin(angle+(Math.PI/2)))*distance));
let z = pos.z;
return new Vec3(x,y,z);
}
function vec3ToVec2(pos) {
return new Vec2(pos[0], pos[1]);
}
/*
addEventHandler("OnEntityProcess", function(event, entity) {
if(entity.type == ELEMENT_PLAYER) {
if(entity != localPlayer) {
let isPlayerWalking = entity.getData("ag.walk");
if(isPlayerWalking == true) {
let position = getPosInFrontOfPos(entity.position, entity.heading, 1.0);
entity.walkTo(position);
}
}
}
});
function getHeadingFromPosToPos(pos1, pos2) {
let x = pos2.x-pos1.x;
let y = pos2.y-pos1.y;
let rad = Math.atan2(y, x);
let deg = radToDeg(rad);
deg -= 90;
deg = deg % 360;
return degToRad(deg);
}
*/