876 | function VehicleCamera:getCollisionDistance() |
877 | if not self.isCollisionEnabled then |
878 | return false, nil, nil, nil, nil, nil |
879 | end |
880 | |
881 | local raycastMask = VehicleCamera.raycastMask |
882 | |
883 | local targetCamX, targetCamY, targetCamZ = localToWorld(self.rotateNode, self.transDirX*self.zoomTarget, self.transDirY*self.zoomTarget, self.transDirZ*self.zoomTarget) |
884 | |
885 | local hasCollision = false |
886 | local collisionDistance = -1 |
887 | local normalX,normalY,normalZ |
888 | local normalDotDir |
889 | for _, raycastNode in ipairs(self.raycastNodes) do |
890 | |
891 | hasCollision = false |
892 | |
893 | local nodeX, nodeY, nodeZ = getWorldTranslation(raycastNode) |
894 | local dirX, dirY, dirZ = targetCamX-nodeX, targetCamY-nodeY, targetCamZ-nodeZ |
895 | local dirLength = MathUtil.vector3Length(dirX, dirY, dirZ) |
896 | dirX = dirX / dirLength |
897 | dirY = dirY / dirLength |
898 | dirZ = dirZ / dirLength |
899 | |
900 | local startX = nodeX |
901 | local startY = nodeY |
902 | local startZ = nodeZ |
903 | local currentDistance = 0 |
904 | local minDistance = self.transMin |
905 | |
906 | while true do |
907 | if (dirLength-currentDistance) <= 0 then |
908 | break |
909 | end |
910 | self.raycastDistance = 0 |
911 | raycastClosest(startX, startY, startZ, dirX, dirY, dirZ, "raycastCallback", dirLength-currentDistance, self, raycastMask, true) |
912 | |
913 | if self.raycastDistance ~= 0 then |
914 | currentDistance = currentDistance + self.raycastDistance+0.001 |
915 | local ndotd = MathUtil.dotProduct(self.normalX, self.normalY, self.normalZ, dirX, dirY, dirZ) |
916 | |
917 | local isAttachedVehicle = false |
918 | local ignoreObject = false |
919 | local object = g_currentMission:getNodeObject(self.raycastTransformId) |
920 | if object ~= nil then |
921 | local vehicles = self.vehicle:getChildVehicles() |
922 | for i=1, #vehicles do |
923 | local vehicle = vehicles[i] |
924 | |
925 | if object ~= vehicle then |
926 | local attached1 = object.getIsAttachedTo ~= nil and object:getIsAttachedTo(vehicle) |
927 | local attached2 = vehicle.getIsAttachedTo ~= nil and vehicle:getIsAttachedTo(object) |
928 | isAttachedVehicle = attached1 or attached2 |
929 | |
930 | local mountObject = object.dynamicMountObject or object.tensionMountObject or object.mountObject |
931 | if mountObject ~= nil and (mountObject == vehicle or mountObject.rootVehicle == vehicle) then |
932 | isAttachedVehicle = true |
933 | end |
934 | end |
935 | |
936 | if isAttachedVehicle then |
937 | break |
938 | end |
939 | end |
940 | end |
941 | |
942 | -- ignore cut trees that are loaded to a vehicle |
943 | if getHasClassId(self.raycastTransformId, ClassIds.SHAPE) and getSplitType(self.raycastTransformId) ~= 0 then |
944 | ignoreObject = true |
945 | end |
946 | |
947 | if getHasTrigger(self.raycastTransformId) then |
948 | ignoreObject = true |
949 | end |
950 | |
951 | if isAttachedVehicle or object == self.vehicle or ignoreObject then --isAttachedNode or isDynamicallyMounted then |
952 | if ndotd > 0 then |
953 | minDistance = math.max(minDistance, currentDistance) |
954 | end |
955 | else |
956 | hasCollision = true |
957 | -- we take the distance from the rotate node |
958 | if raycastNode == self.rotateNode then |
959 | normalX,normalY,normalZ = self.normalX, self.normalY, self.normalZ |
960 | |
961 | -- for static buildings we allow less than min. distance |
962 | -- for all other objects we limit by min. camera translation (e.g. if you load a dynamic object onto a pickup truck) |
963 | if getRigidBodyType(self.raycastTransformId) == RigidBodyType.STATIC then |
964 | collisionDistance = currentDistance |
965 | else |
966 | collisionDistance = math.max(self.transMin, currentDistance) |
967 | end |
968 | |
969 | normalDotDir = ndotd |
970 | end |
971 | break |
972 | end |
973 | startX = nodeX+dirX*currentDistance |
974 | startY = nodeY+dirY*currentDistance |
975 | startZ = nodeZ+dirZ*currentDistance |
976 | else |
977 | break |
978 | end |
979 | end |
980 | if not hasCollision then |
981 | break |
982 | end |
983 | end |
984 | |
985 | return hasCollision, collisionDistance, normalX,normalY,normalZ, normalDotDir |
986 | end |
66 | function VehicleCamera:loadFromXML(xmlFile, key, savegame, cameraIndex) |
67 | XMLUtil.checkDeprecatedXMLElements(xmlFile, self.vehicle.configFileName, key .. "#index", "#node") -- FS17 to FS19 |
68 | |
69 | self.cameraNode = xmlFile:getValue(key .. "#node", nil, self.vehicle.components, self.vehicle.i3dMappings) |
70 | if self.cameraNode == nil or not getHasClassId(self.cameraNode, ClassIds.CAMERA) then |
71 | Logging.xmlWarning(xmlFile, "Invalid camera node for camera '%s'. Must be a camera type!", key) |
72 | return false |
73 | end |
74 | |
75 | self.fovY = calculateFovY(self.cameraNode) |
76 | setFovY(self.cameraNode, self.fovY) |
77 | |
78 | self.isRotatable = xmlFile:getValue(key .. "#rotatable", false) |
79 | self.limit = xmlFile:getValue(key .. "#limit", false) |
80 | if self.limit then |
81 | self.rotMinX = xmlFile:getValue(key .. "#rotMinX") |
82 | self.rotMaxX = xmlFile:getValue(key .. "#rotMaxX") |
83 | |
84 | self.transMin = xmlFile:getValue(key .. "#transMin") |
85 | self.transMax = xmlFile:getValue(key .. "#transMax") |
86 | |
87 | if self.transMax ~= nil then |
88 | self.transMax = math.max(self.transMin, self.transMax * Platform.gameplay.maxCameraZoomFactor) |
89 | end |
90 | |
91 | if self.rotMinX == nil or self.rotMaxX == nil or self.transMin == nil or self.transMax == nil then |
92 | Logging.xmlWarning(xmlFile, "Missing 'rotMinX', 'rotMaxX', 'transMin' or 'transMax' for camera '%s'", key) |
93 | return false |
94 | end |
95 | end |
96 | |
97 | self.isInside = xmlFile:getValue(key .. "#isInside", false) |
98 | self.allowHeadTracking = xmlFile:getValue(key .. "#allowHeadTracking", self.isInside) |
99 | |
100 | self.shadowFocusBoxNode = xmlFile:getValue(key .. "#shadowFocusBox", nil, self.vehicle.components, self.vehicle.i3dMappings) |
101 | if self.shadowFocusBoxNode ~= nil and not getHasClassId(self.shadowFocusBoxNode, ClassIds.SHAPE) then |
102 | Logging.xmlWarning(xmlFile, "Invalid camera shadow focus box '%s'. Must be a shape and cpu mesh", getName(self.shadowFocusBoxNode)) |
103 | self.shadowFocusBoxNode = nil |
104 | end |
105 | |
106 | if self.isInside and self.shadowFocusBoxNode == nil then |
107 | Logging.xmlDevWarning(xmlFile, "Missing shadow focus box for indoor camera '%s'", key) |
108 | end |
109 | |
110 | self.useOutdoorSounds = xmlFile:getValue(key .. "#useOutdoorSounds", not self.isInside) |
111 | |
112 | if self.isRotatable then |
113 | self.rotateNode = xmlFile:getValue(key .. "#rotateNode", nil, self.vehicle.components, self.vehicle.i3dMappings) |
114 | self.hasExtraRotationNode = self.rotateNode ~= nil |
115 | end |
116 | |
117 | local rotation = xmlFile:getValue(key.."#rotation", nil, true) |
118 | if rotation ~= nil then |
119 | local rotationNode = self.cameraNode |
120 | if self.rotateNode ~= nil then |
121 | rotationNode = self.rotateNode |
122 | end |
123 | setRotation(rotationNode, unpack(rotation)) |
124 | end |
125 | local translation = xmlFile:getValue(key.."#translation", nil, true) |
126 | if translation ~= nil then |
127 | setTranslation(self.cameraNode, unpack(translation)) |
128 | end |
129 | |
130 | self.allowTranslation = (self.rotateNode ~= nil and self.rotateNode ~= self.cameraNode) |
131 | |
132 | self.useMirror = xmlFile:getValue(key .. "#useMirror", false) |
133 | self.useWorldXZRotation = xmlFile:getValue(key .. "#useWorldXZRotation") -- overrides the ingame setting |
134 | self.resetCameraOnVehicleSwitch = xmlFile:getValue(key .. "#resetCameraOnVehicleSwitch") -- overrides the ingame setting |
135 | self.suspensionNodeIndex = xmlFile:getValue(key .. "#suspensionNodeIndex") |
136 | |
137 | if (not Platform.gameplay.useWorldCameraInside and self.isInside) or |
138 | (not Platform.gameplay.useWorldCameraOutside and not self.isInside) then |
139 | self.useWorldXZRotation = false |
140 | end |
141 | |
142 | self.positionSmoothingParameter = 0 |
143 | self.lookAtSmoothingParameter = 0 |
144 | local useDefaultPositionSmoothing = xmlFile:getValue(key .. "#useDefaultPositionSmoothing", true) |
145 | if useDefaultPositionSmoothing then |
146 | if self.isInside then |
147 | self.positionSmoothingParameter = 0.128 -- 0.095 |
148 | self.lookAtSmoothingParameter = 0.176 -- 0.12 |
149 | else |
150 | self.positionSmoothingParameter = 0.016 |
151 | self.lookAtSmoothingParameter = 0.022 |
152 | end |
153 | end |
154 | self.positionSmoothingParameter = xmlFile:getValue(key .. "#positionSmoothingParameter", self.positionSmoothingParameter) |
155 | self.lookAtSmoothingParameter = xmlFile:getValue(key .. "#lookAtSmoothingParameter", self.lookAtSmoothingParameter) |
156 | |
157 | local useHeadTracking = g_gameSettings:getValue("isHeadTrackingEnabled") and isHeadTrackingAvailable() and self.allowHeadTracking |
158 | if useHeadTracking then |
159 | self.positionSmoothingParameter = 0 |
160 | self.lookAtSmoothingParameter = 0 |
161 | end |
162 | |
163 | self.cameraPositionNode = self.cameraNode |
164 | if self.positionSmoothingParameter > 0 then |
165 | -- create a node which indicates the target position of the camera |
166 | self.cameraPositionNode = createTransformGroup("cameraPositionNode") |
167 | local camIndex = getChildIndex(self.cameraNode) |
168 | link(getParent(self.cameraNode), self.cameraPositionNode, camIndex) |
169 | local x,y,z = getTranslation(self.cameraNode) |
170 | local rx,ry,rz = getRotation(self.cameraNode) |
171 | setTranslation(self.cameraPositionNode, x, y, z) |
172 | setRotation(self.cameraPositionNode, rx, ry, rz) |
173 | |
174 | unlink(self.cameraNode) |
175 | end |
176 | self.rotYSteeringRotSpeed = xmlFile:getValue(key .. "#rotYSteeringRotSpeed", 0) |
177 | |
178 | if self.rotateNode == nil or self.rotateNode == self.cameraNode then |
179 | self.rotateNode = self.cameraPositionNode |
180 | end |
181 | |
182 | if useHeadTracking then |
183 | local dx,_,dz = localDirectionToLocal(self.cameraPositionNode, getParent(self.cameraPositionNode), 0, 0, 1) |
184 | local tx,ty,tz = localToLocal(self.cameraPositionNode, getParent(self.cameraPositionNode), 0, 0, 0) |
185 | self.headTrackingNode = createTransformGroup("headTrackingNode") |
186 | link(getParent(self.cameraPositionNode), self.headTrackingNode) |
187 | setTranslation(self.headTrackingNode, tx, ty, tz) |
188 | if math.abs(dx)+math.abs(dz) > 0.0001 then |
189 | setDirection(self.headTrackingNode, dx, 0, dz, 0, 1, 0) |
190 | else |
191 | setRotation(self.headTrackingNode, 0, 0, 0) |
192 | end |
193 | end |
194 | |
195 | self.origRotX, self.origRotY, self.origRotZ = getRotation(self.rotateNode) |
196 | self.rotX = self.origRotX |
197 | self.rotY = self.origRotY |
198 | self.rotZ = self.origRotZ |
199 | |
200 | self.origTransX, self.origTransY, self.origTransZ = getTranslation(self.cameraPositionNode) |
201 | self.transX = self.origTransX |
202 | self.transY = self.origTransY |
203 | self.transZ = self.origTransZ |
204 | |
205 | local transLength = MathUtil.vector3Length(self.origTransX, self.origTransY, self.origTransZ) + 0.00001 -- prevent devision by zero |
206 | self.zoom = transLength |
207 | self.zoomTarget = transLength |
208 | self.zoomDefault = transLength |
209 | self.zoomLimitedTarget = -1 |
210 | |
211 | local trans1OverLength = 1.0/transLength |
212 | self.transDirX = trans1OverLength*self.origTransX |
213 | self.transDirY = trans1OverLength*self.origTransY |
214 | self.transDirZ = trans1OverLength*self.origTransZ |
215 | if self.allowTranslation then |
216 | if transLength <= 0.01 then |
217 | Logging.xmlWarning(xmlFile, "Invalid camera translation for camera '%s'. Distance needs to be bigger than 0.01", key) |
218 | end |
219 | end |
220 | |
221 | table.insert(self.raycastNodes, self.rotateNode) |
222 | local i=0 |
223 | while true do |
224 | local raycastKey = key..string.format(".raycastNode(%d)", i) |
225 | if not xmlFile:hasProperty(raycastKey) then |
226 | break |
227 | end |
228 | |
229 | XMLUtil.checkDeprecatedXMLElements(xmlFile, self.vehicle.configFileName, raycastKey .. "#index", raycastKey .. "#node") --FS17 to FS19 |
230 | |
231 | local node = xmlFile:getValue(raycastKey .. "#node", nil, self.vehicle.components, self.vehicle.i3dMappings) |
232 | if node ~= nil then |
233 | table.insert(self.raycastNodes, node) |
234 | end |
235 | |
236 | i = i + 1 |
237 | end |
238 | |
239 | local sx, sy, sz = getScale(self.cameraNode) |
240 | if sx ~= 1 or sy ~= 1 or sz ~= 1 then |
241 | Logging.xmlWarning(xmlFile, "Vehicle camera with scale found for camera '%s'. Resetting to scale 1", key) |
242 | setScale(self.cameraNode, 1, 1, 1) |
243 | end |
244 | |
245 | self.headTrackingPositionOffset = {0, 0, 0} |
246 | self.headTrackingRotationOffset = {0, 0, 0} |
247 | |
248 | self.changeObjects = {} |
249 | ObjectChangeUtil.loadObjectChangeFromXML(xmlFile, key, self.changeObjects, self.vehicle.components, self.vehicle) |
250 | ObjectChangeUtil.setObjectChanges(self.changeObjects, false, self.vehicle, self.vehicle.setMovingToolDirty) |
251 | |
252 | |
253 | if not g_gameSettings:getValue("resetCamera") then |
254 | if savegame ~= nil and not savegame.resetVehicles then |
255 | local cameraKey = string.format(savegame.key..".enterable.camera(%d)", cameraIndex) |
256 | if savegame.xmlFile:hasProperty(cameraKey) then |
257 | self.rotX, self.rotY, self.rotZ = savegame.xmlFile:getValue(cameraKey.."#rotation", {self.rotX, self.rotY, self.rotZ}) |
258 | if self.allowTranslation then |
259 | self.transX, self.transY, self.transZ = savegame.xmlFile:getValue(cameraKey.."#translation", {self.transX, self.transY, self.transZ}) |
260 | |
261 | self.zoom = savegame.xmlFile:getValue(cameraKey.."#zoom", self.zoom) |
262 | self.zoomTarget = self.zoom |
263 | end |
264 | |
265 | setTranslation(self.cameraPositionNode, self.transX, self.transY, self.transZ) |
266 | setRotation(self.rotateNode, self.rotX, self.rotY, self.rotZ) |
267 | |
268 | if g_currentMission.isReloadingVehicles then |
269 | local fovY = savegame.xmlFile:getValue(cameraKey.."#fovY") |
270 | if fovY ~= nil then |
271 | setFovY(self.cameraNode, fovY) |
272 | end |
273 | end |
274 | |
275 | --#debug local lodDebugActive = savegame.xmlFile:getValue(cameraKey.."#lodDebugActive", false) |
276 | --#debug if lodDebugActive then |
277 | --#debug self:consoleCommandLODDebug() |
278 | --#debug self.loadDebugZoom = savegame.xmlFile:getValue(cameraKey.."#lodDebugZoom", self.zoom) |
279 | --#debug end |
280 | end |
281 | end |
282 | end |
283 | |
284 | return true |
285 | end |
591 | function VehicleCamera:onActivate() |
592 | if self.cameraNode == nil then |
593 | return |
594 | end |
595 | |
596 | self:onActiveCameraSuspensionSettingChanged(g_gameSettings:getValue("activeSuspensionCamera")) |
597 | |
598 | self.isActivated = true |
599 | if (self.resetCameraOnVehicleSwitch == nil and g_gameSettings:getValue("resetCamera")) or self.resetCameraOnVehicleSwitch then |
600 | self:resetCamera() |
601 | end |
602 | setCamera(self.cameraNode) |
603 | if self.shadowFocusBoxNode then |
604 | setShadowFocusBox(self.shadowFocusBoxNode) |
605 | end |
606 | |
607 | if self.positionSmoothingParameter > 0 then |
608 | local xlook,ylook,zlook = getWorldTranslation(self.rotateNode) |
609 | self.lookAtPosition[1] = xlook |
610 | self.lookAtPosition[2] = ylook |
611 | self.lookAtPosition[3] = zlook |
612 | self.lookAtLastTargetPosition[1] = xlook |
613 | self.lookAtLastTargetPosition[2] = ylook |
614 | self.lookAtLastTargetPosition[3] = zlook |
615 | local x,y,z = getWorldTranslation(self.cameraPositionNode) |
616 | self.position[1] = x |
617 | self.position[2] = y |
618 | self.position[3] = z |
619 | self.lastTargetPosition[1] = x |
620 | self.lastTargetPosition[2] = y |
621 | self.lastTargetPosition[3] = z |
622 | local upx, upy, upz = localDirectionToWorld(self.rotateNode, self:getTiltDirectionOffset(), 1, 0) |
623 | self.upVector[1] = upx |
624 | self.upVector[2] = upy |
625 | self.upVector[3] = upz |
626 | self.lastUpVector[1] = upx |
627 | self.lastUpVector[2] = upy |
628 | self.lastUpVector[3] = upz |
629 | |
630 | local rx,ry,rz = getWorldRotation(self.rotateNode) |
631 | |
632 | setRotation(self.cameraNode, rx,ry,rz) |
633 | setTranslation(self.cameraNode, x,y,z) |
634 | end |
635 | |
636 | self.lastInputValues = {} |
637 | self.lastInputValues.upDown = 0 |
638 | self.lastInputValues.leftRight = 0 |
639 | |
640 | -- activate action event callbacks |
641 | local _, actionEventId1 = g_inputBinding:registerActionEvent(InputAction.AXIS_LOOK_UPDOWN_VEHICLE, self, VehicleCamera.actionEventLookUpDown, false, false, true, true, nil) |
642 | local _, actionEventId2 = g_inputBinding:registerActionEvent(InputAction.AXIS_LOOK_LEFTRIGHT_VEHICLE, self, VehicleCamera.actionEventLookLeftRight, false, false, true, true, nil) |
643 | g_inputBinding:setActionEventTextVisibility(actionEventId1, false) |
644 | g_inputBinding:setActionEventTextVisibility(actionEventId2, false) |
645 | |
646 | ObjectChangeUtil.setObjectChanges(self.changeObjects, true, self.vehicle, self.vehicle.setMovingToolDirty) |
647 | |
648 | if g_touchHandler ~= nil then |
649 | self.touchListenerPinch = g_touchHandler:registerGestureListener(TouchHandler.GESTURE_PINCH, VehicleCamera.touchEventZoomInOut, self) |
650 | self.touchListenerY = g_touchHandler:registerGestureListener(TouchHandler.GESTURE_AXIS_Y, VehicleCamera.touchEventLookUpDown, self) |
651 | self.touchListenerX = g_touchHandler:registerGestureListener(TouchHandler.GESTURE_AXIS_X, VehicleCamera.touchEventLookLeftRight, self) |
652 | end |
653 | |
654 | --#debug addConsoleCommand("gsVehicleDebugLOD", "Enables vehicle LOD debug", "consoleCommandLODDebug", self) |
655 | end |
1014 | function VehicleCamera.registerCameraXMLPaths(schema, basePath) |
1015 | schema:register(XMLValueType.NODE_INDEX, basePath .. "#node", "Camera node") |
1016 | schema:register(XMLValueType.BOOL, basePath .. "#rotatable", "Camera is rotatable", false) |
1017 | schema:register(XMLValueType.BOOL, basePath .. "#limit", "Has limits", false) |
1018 | schema:register(XMLValueType.FLOAT, basePath .. "#rotMinX", "Min. X rotation") |
1019 | schema:register(XMLValueType.FLOAT, basePath .. "#rotMaxX", "Max. X rotation") |
1020 | schema:register(XMLValueType.FLOAT, basePath .. "#transMin", "Min. Z translation") |
1021 | schema:register(XMLValueType.FLOAT, basePath .. "#transMax", "Max. Z translation") |
1022 | |
1023 | schema:register(XMLValueType.BOOL, basePath .. "#isInside", "Is camera inside. Used for camera smoothing and fallback/default value for 'useOutdoorSounds'", false) |
1024 | schema:register(XMLValueType.BOOL, basePath .. "#allowHeadTracking", "Allow head tracking", "isInside value") |
1025 | schema:register(XMLValueType.NODE_INDEX, basePath .. "#shadowFocusBox", "Shadow focus box") |
1026 | |
1027 | schema:register(XMLValueType.BOOL, basePath .. "#useOutdoorSounds", "Use outdoor sounds", "false for 'isInside' cameras, otherwise true") |
1028 | schema:register(XMLValueType.NODE_INDEX, basePath .. "#rotateNode", "Rotate node") |
1029 | schema:register(XMLValueType.VECTOR_ROT, basePath .. "#rotation", "Camera rotation") |
1030 | schema:register(XMLValueType.VECTOR_TRANS, basePath .. "#translation", "Camera translation") |
1031 | |
1032 | schema:register(XMLValueType.BOOL, basePath .. "#useMirror", "Use mirrors", false) |
1033 | schema:register(XMLValueType.BOOL, basePath .. "#useWorldXZRotation", "Use world XZ rotation") |
1034 | schema:register(XMLValueType.BOOL, basePath .. "#resetCameraOnVehicleSwitch", "Reset camera on vehicle switch") |
1035 | schema:register(XMLValueType.INT, basePath .. "#suspensionNodeIndex", "Index of seat suspension node") |
1036 | schema:register(XMLValueType.BOOL, basePath .. "#useDefaultPositionSmoothing", "Use default position smoothing parameters", true) |
1037 | |
1038 | schema:register(XMLValueType.FLOAT, basePath .. "#positionSmoothingParameter", "Position smoothing parameter", "0.128 for indoor / 0.016 for outside") |
1039 | schema:register(XMLValueType.FLOAT, basePath .. "#lookAtSmoothingParameter", "Look at smoothing parameter", "0.176 for indoor / 0.022 for outside") |
1040 | |
1041 | schema:register(XMLValueType.ANGLE, basePath .. "#rotYSteeringRotSpeed", "Rot Y steering rotation speed", 0) |
1042 | |
1043 | schema:register(XMLValueType.NODE_INDEX, basePath .. ".raycastNode(?)#node", "Raycast node") |
1044 | |
1045 | ObjectChangeUtil.registerObjectChangeXMLPaths(schema, basePath) |
1046 | end |
818 | function VehicleCamera:setSeparateCameraPose() |
819 | if self.rotateNode ~= self.cameraPositionNode then |
820 | local dx = self.position[1] - self.lookAtPosition[1] |
821 | local dy = self.position[2] - self.lookAtPosition[2] |
822 | local dz = self.position[3] - self.lookAtPosition[3] |
823 | |
824 | local upx, upy, upz = unpack(self.upVector) |
825 | if upx == 0 and upy == 0 and upz == 0 then |
826 | upy = 1 |
827 | end |
828 | |
829 | if math.abs(dx) < 0.001 and math.abs(dz) < 0.001 then |
830 | upx = 0.1 |
831 | end |
832 | |
833 | setDirection(self.cameraNode, dx,dy,dz, upx,upy,upz) |
834 | else |
835 | local dx, dy, dz = localDirectionToWorld(self.rotateNode, 0, 0, 1) |
836 | local upx, upy, upz = localDirectionToWorld(self.rotateNode, self:getTiltDirectionOffset(), 1, 0) |
837 | setDirection(self.cameraNode, dx, dy, dz, upx, upy, upz) |
838 | end |
839 | setTranslation(self.cameraNode, self.position[1],self.position[2],self.position[3]) |
840 | |
841 | --#debug if self.lodDebugMode then |
842 | --#debug local _, _ , curZoom = localToLocal(self.cameraNode, self.rotateNode, 0, 0, 0) |
843 | --#debug local l = math.atan(self.fovY) * self.loadDebugZoom |
844 | --#debug local mouseButtonLast, mouseButtonStateLast = g_inputBinding:getMouseButtonState() |
845 | --#debug if mouseButtonStateLast and mouseButtonLast == Input.MOUSE_BUTTON_MIDDLE then |
846 | --#debug setFovY(self.cameraNode, self.fovY) |
847 | --#debug else |
848 | --#debug setFovY(self.cameraNode, math.tan(l / math.max(curZoom, l))) |
849 | --#debug end |
850 | --#debug setTextAlignment(RenderText.ALIGN_CENTER) |
851 | --#debug renderText(0.5, 0.1, 0.04, string.format("Distance: %d", self.zoom)) |
852 | --#debug setTextAlignment(RenderText.ALIGN_LEFT) |
853 | --#debug end |
854 | end |
383 | function VehicleCamera:update(dt) |
384 | local target = self.zoomTarget |
385 | if self.zoomLimitedTarget >= 0 then |
386 | target = math.min(self.zoomLimitedTarget, self.zoomTarget) |
387 | end |
388 | self.zoom = target + ( math.pow(0.99579, dt) * (self.zoom - target) ) |
389 | |
390 | -- |
391 | if self.lastInputValues.upDown ~= 0 then |
392 | local value = self.lastInputValues.upDown * g_gameSettings:getValue(GameSettings.SETTING.CAMERA_SENSITIVITY) |
393 | self.lastInputValues.upDown = 0 |
394 | value = g_gameSettings:getValue("invertYLook") and -value or value |
395 | |
396 | if self.isRotatable then |
397 | if self.isActivated and not g_gui:getIsGuiVisible() then |
398 | if self.limitRotXDelta > 0.001 then |
399 | self.rotX = math.min(self.rotX - value, self.rotX) |
400 | elseif self.limitRotXDelta < -0.001 then |
401 | self.rotX = math.max(self.rotX - value, self.rotX) |
402 | else |
403 | self.rotX = self.rotX - value |
404 | end |
405 | |
406 | if self.limit then |
407 | self.rotX = math.min(self.rotMaxX, math.max(self.rotMinX, self.rotX)) |
408 | end |
409 | end |
410 | end |
411 | end |
412 | |
413 | if self.lastInputValues.leftRight ~= 0 then |
414 | local value = self.lastInputValues.leftRight * g_gameSettings:getValue(GameSettings.SETTING.CAMERA_SENSITIVITY) |
415 | self.lastInputValues.leftRight = 0 |
416 | |
417 | if self.isRotatable then |
418 | if self.isActivated and not g_gui:getIsGuiVisible() then |
419 | self.rotY = self.rotY - value |
420 | end |
421 | end |
422 | end |
423 | |
424 | -- |
425 | if g_gameSettings:getValue("isHeadTrackingEnabled") and isHeadTrackingAvailable() and self.allowHeadTracking and self.headTrackingNode ~= nil then |
426 | local tx,ty,tz = getHeadTrackingTranslation() |
427 | local pitch,yaw,roll = getHeadTrackingRotation() |
428 | if pitch ~= nil then |
429 | local camParent = getParent(self.cameraNode) |
430 | local ctx,cty,ctz |
431 | local crx,cry,crz |
432 | if camParent ~= 0 then |
433 | ctx, cty, ctz = localToLocal(self.headTrackingNode, camParent, tx, ty, tz) |
434 | crx, cry, crz = localRotationToLocal(self.headTrackingNode, camParent, pitch,yaw,roll) |
435 | else |
436 | ctx, cty, ctz = localToWorld(self.headTrackingNode, tx, ty, tz) |
437 | crx, cry, crz = localRotationToWorld(self.headTrackingNode, pitch,yaw,roll) |
438 | end |
439 | |
440 | setRotation(self.cameraNode, crx, cry, crz) |
441 | setTranslation(self.cameraNode, ctx, cty, ctz) |
442 | end |
443 | else |
444 | self:updateRotateNodeRotation() |
445 | |
446 | if self.limit then |
447 | -- adjust rotation to avoid clipping with terrain |
448 | if self.isRotatable and ((self.useWorldXZRotation == nil and g_gameSettings:getValue("useWorldCamera")) or self.useWorldXZRotation) then |
449 | local numIterations = 4 |
450 | for _=1, numIterations do |
451 | local transX, transY, transZ = self.transDirX*self.zoom, self.transDirY*self.zoom, self.transDirZ*self.zoom |
452 | local x,y,z = localToWorld(getParent(self.cameraPositionNode), transX, transY, transZ) |
453 | |
454 | local terrainHeight = DensityMapHeightUtil.getHeightAtWorldPos(x,0,z) |
455 | |
456 | local minHeight = terrainHeight + 0.9 |
457 | if y < minHeight then |
458 | local h = math.sin(self.rotX)*self.zoom |
459 | local h2 = h-(minHeight-y) |
460 | self.rotX = math.asin(MathUtil.clamp(h2/self.zoom, -1, 1)) |
461 | self:updateRotateNodeRotation() |
462 | else |
463 | break |
464 | end |
465 | end |
466 | end |
467 | |
468 | -- adjust zoom to avoid collision with objects |
469 | if self.allowTranslation then |
470 | |
471 | self.limitRotXDelta = 0 |
472 | local hasCollision, collisionDistance, nx,ny,nz, normalDotDir = self:getCollisionDistance() |
473 | if hasCollision then |
474 | local distOffset = 0.1 |
475 | if normalDotDir ~= nil then |
476 | local absNormalDotDir = math.abs(normalDotDir) |
477 | distOffset = MathUtil.lerp(1.2, 0.1, absNormalDotDir*absNormalDotDir*(3-2*absNormalDotDir)) |
478 | end |
479 | collisionDistance = math.max(collisionDistance-distOffset, 0.01) |
480 | self.disableCollisionTime = g_currentMission.time+400 |
481 | self.zoomLimitedTarget = collisionDistance |
482 | if collisionDistance < self.zoom then |
483 | self.zoom = collisionDistance |
484 | end |
485 | if self.isRotatable and nx ~= nil and collisionDistance < self.transMin then |
486 | local _,lny,_ = worldDirectionToLocal(self.rotateNode, nx,ny,nz) |
487 | if lny > 0.5 then |
488 | self.limitRotXDelta = 1 |
489 | elseif lny < -0.5 then |
490 | self.limitRotXDelta = -1 |
491 | end |
492 | end |
493 | else |
494 | if self.disableCollisionTime <= g_currentMission.time then |
495 | self.zoomLimitedTarget = -1 |
496 | end |
497 | end |
498 | end |
499 | |
500 | end |
501 | self.transX, self.transY, self.transZ = self.transDirX*self.zoom, self.transDirY*self.zoom, self.transDirZ*self.zoom |
502 | setTranslation(self.cameraPositionNode, self.transX, self.transY, self.transZ) |
503 | |
504 | if self.positionSmoothingParameter > 0 then |
505 | |
506 | local interpDt = g_physicsDt |
507 | |
508 | if self.vehicle.spec_rideable ~= nil then |
509 | interpDt = self.vehicle.spec_rideable.interpolationDt |
510 | end |
511 | |
512 | if g_server == nil then |
513 | -- on clients, we interpolate the vehicles with dt, thus we need to use the same for camera interpolation |
514 | interpDt = dt |
515 | end |
516 | if interpDt > 0 then |
517 | local xlook,ylook,zlook = getWorldTranslation(self.rotateNode) |
518 | local lookAtPos = self.lookAtPosition |
519 | local lookAtLastPos = self.lookAtLastTargetPosition |
520 | lookAtPos[1],lookAtPos[2],lookAtPos[3] = self:getSmoothed(self.lookAtSmoothingParameter, lookAtPos[1],lookAtPos[2],lookAtPos[3], xlook,ylook,zlook, lookAtLastPos[1],lookAtLastPos[2],lookAtLastPos[3], interpDt) |
521 | lookAtLastPos[1],lookAtLastPos[2],lookAtLastPos[3] = xlook,ylook,zlook |
522 | |
523 | local x,y,z = getWorldTranslation(self.cameraPositionNode) |
524 | local pos = self.position |
525 | local lastPos = self.lastTargetPosition |
526 | pos[1],pos[2],pos[3] = self:getSmoothed(self.positionSmoothingParameter, pos[1],pos[2],pos[3], x,y,z, lastPos[1],lastPos[2],lastPos[3], interpDt) |
527 | lastPos[1],lastPos[2],lastPos[3] = x,y,z |
528 | |
529 | local upx, upy, upz = localDirectionToWorld(self.rotateNode, self:getTiltDirectionOffset(), 1, 0) |
530 | local up = self.upVector |
531 | local lastUp = self.lastUpVector |
532 | up[1],up[2],up[3] = self:getSmoothed(self.positionSmoothingParameter, up[1],up[2],up[3], upx, upy, upz, lastUp[1],lastUp[2],lastUp[3], interpDt) |
533 | lastUp[1],lastUp[2],lastUp[3] = upx, upy, upz |
534 | |
535 | self:setSeparateCameraPose() |
536 | end |
537 | end |
538 | |
539 | end |
540 | |
541 | end |