-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathVehicleCamera.lua
More file actions
1327 lines (985 loc) · 50.5 KB
/
VehicleCamera.lua
File metadata and controls
1327 lines (985 loc) · 50.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
---Camera for vehicles
local VehicleCamera_mt = Class(VehicleCamera)
---Creating vehicle camera
-- @param Vehicle vehicle The vehicle that this camera belongs to.
-- @param table? customMt Custom metatable.
-- @return table self The created instance.
function VehicleCamera.new(vehicle, customMt)
local self = setmetatable({}, customMt or VehicleCamera_mt)
self.vehicle = vehicle
self.isActivated = false
self.limitRotXDelta = 0
self.cameraNode = nil
self.raycastDistance = 0
self.normalX = 0
self.normalY = 0
self.normalZ = 0
self.raycastNodes = {}
self.disableCollisionTime = -1
self.lookAtPosition = {0,0,0}
self.lookAtLastTargetPosition = {0,0,0}
self.position = {0,0,0}
self.lastTargetPosition = {0,0,0}
self.upVector = {0,0,0}
self.lastUpVector = {0,0,0}
self.lastInputValues = {}
self.lastInputValues.upDown = 0
self.lastInputValues.leftRight = 0
self.isCollisionEnabled = true
if g_modIsLoaded["FS22_disableVehicleCameraCollision"] or g_isDevelopmentVersion then
self.isCollisionEnabled = g_gameSettings:getValue(GameSettings.SETTING.CAMERA_CHECK_COLLISION)
g_messageCenter:subscribe(MessageType.SETTING_CHANGED[GameSettings.SETTING.CAMERA_CHECK_COLLISION], self.onCameraCollisionDetectionSettingChanged, self)
end
g_messageCenter:subscribe(MessageType.SETTING_CHANGED[GameSettings.SETTING.ACTIVE_SUSPENSION_CAMERA], self.onActiveCameraSuspensionSettingChanged, self)
g_messageCenter:subscribe(MessageType.SETTING_CHANGED[GameSettings.SETTING.FOV_Y], self.onFovySettingChanged, self)
return self
end
---Load vehicle camera from xml file
-- @param integer xmlFile id of xml object
-- @param string key key
-- @return boolean success success
function VehicleCamera:loadFromXML(xmlFile, key, savegame, cameraIndex)
XMLUtil.checkDeprecatedXMLElements(xmlFile, self.vehicle.configFileName, key .. "#index", "#node") -- FS17 to FS19
self.cameraNode = xmlFile:getValue(key .. "#node", nil, self.vehicle.components, self.vehicle.i3dMappings)
if self.cameraNode == nil or not getHasClassId(self.cameraNode, ClassIds.CAMERA) then
Logging.xmlWarning(xmlFile, "Invalid camera node for camera '%s'. Must be a camera type!", key)
return false
end
self.shadowFocusBoxNode = xmlFile:getValue(key .. "#shadowFocusBox", nil, self.vehicle.components, self.vehicle.i3dMappings)
if self.shadowFocusBoxNode ~= nil and (not getHasClassId(self.shadowFocusBoxNode, ClassIds.SHAPE) or not getShapeIsCPUMesh(self.shadowFocusBoxNode)) then
Logging.xmlWarning(xmlFile, "Invalid camera shadow focus box '%s'. Must be a shape and cpu mesh", getName(self.shadowFocusBoxNode))
self.shadowFocusBoxNode = nil
end
if Platform.gameplay.hasShadowFocusBox then
if self.isInside and self.shadowFocusBoxNode == nil then
Logging.xmlDevWarning(xmlFile, "Missing shadow focus box for indoor camera '%s'", key)
end
else
if self.shadowFocusBoxNode ~= nil then
Logging.xmlDevWarning(xmlFile, "Shadow focus box for camera '%s' not allowed on this platform", key)
self.shadowFocusBoxNode = nil
end
end
self.isInside = xmlFile:getValue(key .. "#isInside", false)
self.allowHeadTracking = xmlFile:getValue(key .. "#allowHeadTracking", self.isInside)
self.useOutdoorSounds = xmlFile:getValue(key .. "#useOutdoorSounds", not self.isInside)
local lowResColHandlerHighPrio = self.isInside -- update precipitation occlusion cells directly next to the camera each frame to avoid visible particles inside the cabin
local dofInfo
if not self.isInside then
dofInfo = g_depthOfFieldManager:createInfo(0.5, 1, 0.3, 400, 1400, false) --DOF ON
-- dofInfo = g_depthOfFieldManager:createInfo(nil, nil, nil, nil, nil, nil) --DOF OFF
end
g_cameraManager:addCamera(self.cameraNode, self.shadowFocusBoxNode, false, lowResColHandlerHighPrio, dofInfo)
-- collect vehicle collision nodes to toggle PRECIPITATION_BLOCKING col bit when indoor camera is active
if self.isInside then
self.collisionNodes = {}
I3DUtil.iterateRecursively(self.vehicle.rootNode, function(node)
if getHasClassId(node, ClassIds.SHAPE) and (getRigidBodyType(node) ~= RigidBodyType.NONE or getIsCompoundChild(node)) and bit32.btest(getCollisionFilterGroup(node), CollisionFlag.VEHICLE) then
table.insert(self.collisionNodes, node)
end
end, true)
end
self.defaultFovY = getFovY(self.cameraNode)
self.fovY = calculateFovY(self.defaultFovY)
setFovY(self.cameraNode, self.fovY)
self.isRotatable = xmlFile:getValue(key .. "#rotatable", false)
self.limit = xmlFile:getValue(key .. "#limit", false)
if self.limit then
self.rotMinX = xmlFile:getValue(key .. "#rotMinX")
self.rotMaxX = xmlFile:getValue(key .. "#rotMaxX")
self.transMin = xmlFile:getValue(key .. "#transMin")
self.transMax = xmlFile:getValue(key .. "#transMax")
if self.transMax ~= nil then
self.transMax = math.max(self.transMin, self.transMax * Platform.gameplay.maxCameraZoomFactor)
end
if self.rotMinX == nil or self.rotMaxX == nil or self.transMin == nil or self.transMax == nil then
Logging.xmlWarning(xmlFile, "Missing 'rotMinX', 'rotMaxX', 'transMin' or 'transMax' for camera '%s'", key)
return false
end
end
if self.isRotatable then
self.rotateNode = xmlFile:getValue(key .. "#rotateNode", nil, self.vehicle.components, self.vehicle.i3dMappings)
self.hasExtraRotationNode = self.rotateNode ~= nil
end
local rotation = xmlFile:getValue(key.."#rotation", nil, true)
if rotation ~= nil then
local rotationNode = self.cameraNode
if self.rotateNode ~= nil then
rotationNode = self.rotateNode
end
setRotation(rotationNode, unpack(rotation))
end
local translation = xmlFile:getValue(key.."#translation", nil, true)
if translation ~= nil then
setTranslation(self.cameraNode, unpack(translation))
end
self.allowTranslation = (self.rotateNode ~= nil and self.rotateNode ~= self.cameraNode)
self.useMirror = xmlFile:getValue(key .. "#useMirror", false)
self.useWorldXZRotation = xmlFile:getValue(key .. "#useWorldXZRotation") -- overrides the ingame setting
self.resetCameraOnVehicleSwitch = xmlFile:getValue(key .. "#resetCameraOnVehicleSwitch") -- overrides the ingame setting
self.suspensionNodeIndex = xmlFile:getValue(key .. "#suspensionNodeIndex")
if (not Platform.gameplay.useWorldCameraInside and self.isInside) or
(not Platform.gameplay.useWorldCameraOutside and not self.isInside) then
self.useWorldXZRotation = false
end
self.positionSmoothingParameter = 0
self.lookAtSmoothingParameter = 0
local useDefaultPositionSmoothing = xmlFile:getValue(key .. "#useDefaultPositionSmoothing", true)
if useDefaultPositionSmoothing then
if self.isInside then
self.positionSmoothingParameter = 0.128 -- 0.095
self.lookAtSmoothingParameter = 0.176 -- 0.12
else
self.positionSmoothingParameter = 0.016
self.lookAtSmoothingParameter = 0.022
end
end
self.positionSmoothingParameter = xmlFile:getValue(key .. "#positionSmoothingParameter", self.positionSmoothingParameter)
self.lookAtSmoothingParameter = xmlFile:getValue(key .. "#lookAtSmoothingParameter", self.lookAtSmoothingParameter)
local useHeadTracking = g_gameSettings:getValue(GameSettings.SETTING.IS_HEAD_TRACKING_ENABLED) and isHeadTrackingAvailable() and self.allowHeadTracking
if useHeadTracking then
self.positionSmoothingParameter = 0
self.lookAtSmoothingParameter = 0
end
self.cameraPositionNode = self.cameraNode
if self.positionSmoothingParameter > 0 then
-- create a node which indicates the target position of the camera
self.cameraPositionNode = createTransformGroup("cameraPositionNode")
local camIndex = getChildIndex(self.cameraNode)
link(getParent(self.cameraNode), self.cameraPositionNode, camIndex)
local x,y,z = getTranslation(self.cameraNode)
local rx,ry,rz = getRotation(self.cameraNode)
setTranslation(self.cameraPositionNode, x, y, z)
setRotation(self.cameraPositionNode, rx, ry, rz)
-- parent node of the camera in world space that is already aligned with the Y rotation to the target position
-- this avoids gimbal lock issues when at 0 or 180 degree (#57918)
self.cameraWorldParent = createTransformGroup("cameraWorldParent")
link(self.cameraWorldParent, self.cameraNode)
end
self.rotYSteeringRotSpeed = xmlFile:getValue(key .. "#rotYSteeringRotSpeed", 0)
if self.rotateNode == nil or self.rotateNode == self.cameraNode then
self.rotateNode = self.cameraPositionNode
end
if useHeadTracking then
local dx,_,dz = localDirectionToLocal(self.cameraPositionNode, getParent(self.cameraPositionNode), 0, 0, 1)
local tx,ty,tz = localToLocal(self.cameraPositionNode, getParent(self.cameraPositionNode), 0, 0, 0)
self.headTrackingNode = createTransformGroup("headTrackingNode")
link(getParent(self.cameraPositionNode), self.headTrackingNode)
setTranslation(self.headTrackingNode, tx, ty, tz)
if math.abs(dx)+math.abs(dz) > 0.0001 then
setDirection(self.headTrackingNode, dx, 0, dz, 0, 1, 0)
else
setRotation(self.headTrackingNode, 0, 0, 0)
end
end
self.origRotX, self.origRotY, self.origRotZ = getRotation(self.rotateNode)
self.rotX = self.origRotX
self.rotY = self.origRotY
self.rotZ = self.origRotZ
self.origTransX, self.origTransY, self.origTransZ = getTranslation(self.cameraPositionNode)
self.transX = self.origTransX
self.transY = self.origTransY
self.transZ = self.origTransZ
local transLength = MathUtil.vector3Length(self.origTransX, self.origTransY, self.origTransZ) + 0.00001 -- prevent devision by zero
self.zoom = transLength
self.zoomTarget = transLength
self.zoomDefault = transLength
self.zoomLimitedTarget = -1
local trans1OverLength = 1.0/transLength
self.transDirX = trans1OverLength*self.origTransX
self.transDirY = trans1OverLength*self.origTransY
self.transDirZ = trans1OverLength*self.origTransZ
if self.allowTranslation then
if transLength <= 0.01 then
Logging.xmlWarning(xmlFile, "Invalid camera translation for camera '%s'. Distance needs to be bigger than 0.01", key)
end
end
table.insert(self.raycastNodes, self.rotateNode)
for _, raycastKey in xmlFile:iterator(key ..".raycastNode") do
XMLUtil.checkDeprecatedXMLElements(xmlFile, self.vehicle.configFileName, raycastKey .. "#index", raycastKey .. "#node") --FS17 to FS19
local node = xmlFile:getValue(raycastKey .. "#node", nil, self.vehicle.components, self.vehicle.i3dMappings)
if node ~= nil then
table.insert(self.raycastNodes, node)
end
end
local sx, sy, sz = getScale(self.cameraNode)
if sx ~= 1 or sy ~= 1 or sz ~= 1 then
Logging.xmlWarning(xmlFile, "Vehicle camera with scale found for camera '%s'. Resetting to scale 1", key)
setScale(self.cameraNode, 1, 1, 1)
end
self.changeObjects = {}
ObjectChangeUtil.loadObjectChangeFromXML(xmlFile, key, self.changeObjects, self.vehicle.components, self.vehicle)
ObjectChangeUtil.setObjectChanges(self.changeObjects, false, self.vehicle, self.vehicle.setMovingToolDirty)
if not g_gameSettings:getValue(GameSettings.SETTING.RESET_CAMERA) or g_currentMission.vehicleSystem.isReloadRunning then
if savegame ~= nil and not savegame.resetVehicles then
local cameraKey = string.format(savegame.key..".enterable.camera(%d)", cameraIndex)
if savegame.xmlFile:hasProperty(cameraKey) then
local rotX, rotY, rotZ = savegame.xmlFile:getValue(cameraKey.."#rotation", {self.rotX, self.rotY, self.rotZ})
if not (MathUtil.isNan(rotX) or MathUtil.isNan(rotY) or MathUtil.isNan(rotZ)) then
self.rotX, self.rotY, self.rotZ = rotX, rotY, rotZ
if self.allowTranslation then
self.transX, self.transY, self.transZ = savegame.xmlFile:getValue(cameraKey.."#translation", {self.transX, self.transY, self.transZ})
self.zoom = savegame.xmlFile:getValue(cameraKey.."#zoom", self.zoom)
self.zoomTarget = self.zoom
end
setTranslation(self.cameraPositionNode, self.transX, self.transY, self.transZ)
setRotation(self.rotateNode, self.rotX, self.rotY, self.rotZ)
if g_currentMission.vehicleSystem.isReloadRunning then
local fovY = savegame.xmlFile:getValue(cameraKey.."#fovY")
if fovY ~= nil then
setFovY(self.cameraNode, fovY)
end
end
self.lodDebugModeLoaded = savegame.xmlFile:getValue(cameraKey.."#lodDebugActive", false)
if self.lodDebugModeLoaded then
self.loadDebugZoom = savegame.xmlFile:getValue(cameraKey.."#lodDebugZoom", self.zoom)
end
--#debug self.cameraYDebugModeLoaded = savegame.xmlFile:getValue(cameraKey.."#cameraYDebugActive", false)
--#debug if self.cameraYDebugModeLoaded then
--#debug self.cameraYDebugHeight = savegame.xmlFile:getValue(cameraKey.."#cameraYDebugHeight")
--#debug end
end
end
end
end
return true
end
---Called after loading
-- @param table savegame savegame data
function VehicleCamera:onPostLoad(savegame)
self.suspensionNode = nil
if self.suspensionNodeIndex ~= nil and self.vehicle.getSuspensionNodeFromIndex ~= nil then
self.suspensionNode = self.vehicle:getSuspensionNodeFromIndex(self.suspensionNodeIndex)
if self.suspensionNode == nil then
Logging.warning("Vehicle Camera '%s' with invalid suspensionIndex '%s' found.", getName(self.cameraNode), self.suspensionNodeIndex)
end
end
if self.suspensionNode ~= nil then
if self.suspensionNode.node ~= nil then
self.cameraSuspensionParentNode = createTransformGroup("cameraSuspensionParentNode")
link(self.suspensionNode.node, self.cameraSuspensionParentNode)
setWorldTranslation(self.cameraSuspensionParentNode, getWorldTranslation(getParent(self.cameraPositionNode)))
setWorldQuaternion(self.cameraSuspensionParentNode, getWorldQuaternion(getParent(self.cameraPositionNode)))
self.cameraBaseParentNode = getParent(self.cameraPositionNode)
self.lastActiveCameraSuspensionSetting = false
else
Logging.warning("Vehicle Camera '%s' with invalid suspensionIndex '%s' found. CharacterTorso suspensions are not allowed.", getName(self.cameraNode), self.suspensionNodeIndex)
self.suspensionNode = nil
end
end
end
---
function VehicleCamera:saveToXMLFile(xmlFile, key, usedModNames)
xmlFile:setValue(key .. "#rotation", self.rotX, self.rotY, self.rotZ)
xmlFile:setValue(key .. "#translation", self.transX, self.transY, self.transZ)
xmlFile:setValue(key .. "#zoom", self.zoom)
xmlFile:setValue(key .. "#fovY", getFovY(self.cameraNode))
if self.lodDebugMode then
xmlFile:setValue(key .. "#lodDebugActive", true)
xmlFile:setValue(key .. "#lodDebugZoom", self.loadDebugZoom)
end
--#debug if self.cameraYDebugMode then
--#debug xmlFile:setValue(key .. "#cameraYDebugActive", true)
--#debug xmlFile:setValue(key .. "#cameraYDebugHeight", getOrthographicHeight(self.cameraNode))
--#debug end
end
---Deleting vehicle camera
function VehicleCamera:delete()
g_cameraManager:removeCamera(self.cameraNode)
self:onDeactivate()
if self.cameraNode ~= nil and self.positionSmoothingParameter > 0 then
delete(self.cameraNode)
self.cameraNode = nil
end
if self.cameraWorldParent ~= nil then
delete(self.cameraWorldParent)
self.cameraWorldParent = nil
end
g_messageCenter:unsubscribe(MessageType.SETTING_CHANGED[GameSettings.SETTING.ACTIVE_SUSPENSION_CAMERA], self)
g_messageCenter:unsubscribe(MessageType.SETTING_CHANGED[GameSettings.SETTING.FOV_Y], self)
g_messageCenter:unsubscribe(MessageType.SETTING_CHANGED[GameSettings.SETTING.CAMERA_CHECK_COLLISION], self)
end
---Zoom camera smoothly
-- @param float offset offset
function VehicleCamera:zoomSmoothly(offset)
local transMin, transMax = self.transMin, self.transMax
--#debug if Input.isKeyPressed(Input.KEY_lalt) then
--#debug offset = offset * 0.1
--#debug transMin, transMax = 0, 100
--#debug end
if self.lodDebugMode then
offset = offset * 10
end
local zoomTarget = self.zoomTarget
if transMin ~= nil and transMax ~= nil and transMin ~= transMax then
zoomTarget = math.min(transMax, math.max(transMin, self.zoomTarget + offset))
end
self.zoomTarget = zoomTarget
--#debug if self.cameraYDebugMode then
--#debug setOrthographicHeight(self.cameraNode, getOrthographicHeight(self.cameraNode) + offset * 0.1)
--#debug end
end
---Raycast callback
-- @param integer transformId id raycasted object
-- @param float x x raycast position
-- @param float y y raycast position
-- @param float z z raycast position
-- @param float distance distance to raycast position
-- @param float nx normal x
-- @param float ny normal y
-- @param float nz normal z
function VehicleCamera:raycastCallback(transformId, x, y, z, distance, nx, ny, nz)
self.raycastDistance = distance
self.normalX = nx
self.normalY = ny
self.normalZ = nz
self.raycastTransformId = transformId
end
---Update
-- @param float dt time since last call in ms
function VehicleCamera:update(dt)
--#profile RemoteProfiler.zoneBeginN("VehicleCamera:update")
local target = self.zoomTarget
if self.zoomLimitedTarget >= 0 then
target = math.min(self.zoomLimitedTarget, self.zoomTarget)
end
self.zoom = target + ( math.pow(0.99579, dt) * (self.zoom - target) )
--#debug if Input.isKeyPressed(Input.KEY_lalt) then
--#debug if self.origLimit == nil then
--#debug self.origLimit = self.limit
--#debug end
--#debug self.limit = false
--#debug else
--#debug if self.origLimit ~= nil then
--#debug self.limit = self.origLimit
--#debug end
--#debug end
if self.lastInputValues.upDown ~= 0 then
local value = self.lastInputValues.upDown * g_gameSettings:getValue(GameSettings.SETTING.CAMERA_SENSITIVITY)
self.lastInputValues.upDown = 0
value = g_gameSettings:getValue(GameSettings.SETTING.INVERT_Y_LOOK) and -value or value
if self.isRotatable then
if self.isActivated and not g_gui:getIsGuiVisible() then
if self.limitRotXDelta > 0.001 then
self.rotX = math.min(self.rotX - value, self.rotX)
elseif self.limitRotXDelta < -0.001 then
self.rotX = math.max(self.rotX - value, self.rotX)
else
self.rotX = self.rotX - value
end
if self.limit then
self.rotX = math.min(self.rotMaxX, math.max(self.rotMinX, self.rotX))
end
end
end
end
if self.lastInputValues.leftRight ~= 0 or self.autoRotateOverride then
local value = self.autoRotateOverride or (self.lastInputValues.leftRight * g_gameSettings:getValue(GameSettings.SETTING.CAMERA_SENSITIVITY))
self.lastInputValues.leftRight = 0
if self.isRotatable then
if self.isActivated and not g_gui:getIsGuiVisible() then
self.rotY = self.rotY - value
end
end
end
--
if g_gameSettings:getValue(GameSettings.SETTING.IS_HEAD_TRACKING_ENABLED) and isHeadTrackingAvailable() and self.allowHeadTracking and self.headTrackingNode ~= nil then
local tx,ty,tz = getHeadTrackingTranslation()
local pitch,yaw,roll = getHeadTrackingRotation()
if pitch ~= nil then
local camParent = getParent(self.cameraNode)
local ctx,cty,ctz
local crx,cry,crz
if camParent ~= 0 then
ctx, cty, ctz = localToLocal(self.headTrackingNode, camParent, tx, ty, tz)
crx, cry, crz = localRotationToLocal(self.headTrackingNode, camParent, pitch,yaw,roll)
else
ctx, cty, ctz = localToWorld(self.headTrackingNode, tx, ty, tz)
crx, cry, crz = localRotationToWorld(self.headTrackingNode, pitch,yaw,roll)
end
setRotation(self.cameraNode, crx, cry, crz)
setTranslation(self.cameraNode, ctx, cty, ctz)
end
else
self:updateRotateNodeRotation()
if self.limit then
-- adjust rotation to avoid clipping with terrain
if self.isRotatable and ((self.useWorldXZRotation == nil and g_gameSettings:getValue(GameSettings.SETTING.USE_WORLD_CAMERA)) or self.useWorldXZRotation) then
local numIterations = 4
for _=1, numIterations do
local transX, transY, transZ = self.transDirX*self.zoom, self.transDirY*self.zoom, self.transDirZ*self.zoom
local x,y,z = localToWorld(getParent(self.cameraPositionNode), transX, transY, transZ)
local terrainHeight = DensityMapHeightUtil.getHeightAtWorldPos(x,0,z)
local minHeight = terrainHeight + 0.9
if y < minHeight then
local h = math.sin(self.rotX)*self.zoom
local h2 = h-(minHeight-y)
self.rotX = math.asin(math.clamp(h2/self.zoom, -1, 1))
self:updateRotateNodeRotation()
else
break
end
end
end
-- adjust zoom to avoid collision with objects
if self.allowTranslation then
self.limitRotXDelta = 0
local hasCollision, collisionDistance, nx,ny,nz, normalDotDir = self:getCollisionDistance()
if hasCollision then
local distOffset = 0.1
if normalDotDir ~= nil then
local absNormalDotDir = math.abs(normalDotDir)
distOffset = MathUtil.lerp(1.2, 0.1, absNormalDotDir*absNormalDotDir*(3-2*absNormalDotDir))
end
collisionDistance = math.max(collisionDistance-distOffset, 0.01)
self.disableCollisionTime = g_currentMission.time+400
self.zoomLimitedTarget = collisionDistance
if collisionDistance < self.zoom then
self.zoom = collisionDistance
end
if self.isRotatable and nx ~= nil and collisionDistance < self.transMin then
local _,lny,_ = worldDirectionToLocal(self.rotateNode, nx,ny,nz)
if lny > 0.5 then
self.limitRotXDelta = 1
elseif lny < -0.5 then
self.limitRotXDelta = -1
end
end
else
if self.disableCollisionTime <= g_currentMission.time then
self.zoomLimitedTarget = -1
end
end
end
end
self.transX, self.transY, self.transZ = self.transDirX*self.zoom, self.transDirY*self.zoom, self.transDirZ*self.zoom
setTranslation(self.cameraPositionNode, self.transX, self.transY, self.transZ)
if self.positionSmoothingParameter > 0 then
local interpDt = g_physicsDt
if self.vehicle.spec_rideable ~= nil then
interpDt = self.vehicle.spec_rideable.interpolationDt
end
if g_server == nil then
-- on clients, we interpolate the vehicles with dt, thus we need to use the same for camera interpolation
interpDt = dt
end
if interpDt > 0 then
local xlook,ylook,zlook = getWorldTranslation(self.rotateNode)
local lookAtPos = self.lookAtPosition
local lookAtLastPos = self.lookAtLastTargetPosition
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)
lookAtLastPos[1],lookAtLastPos[2],lookAtLastPos[3] = xlook,ylook,zlook
local x,y,z = getWorldTranslation(self.cameraPositionNode)
local pos = self.position
local lastPos = self.lastTargetPosition
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)
lastPos[1],lastPos[2],lastPos[3] = x,y,z
local upx, upy, upz = localDirectionToWorld(self.rotateNode, self:getTiltDirectionOffset(), 1, 0)
local up = self.upVector
local lastUp = self.lastUpVector
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)
lastUp[1],lastUp[2],lastUp[3] = upx, upy, upz
self:setSeparateCameraPose()
end
end
end
if MathUtil.isNan(self.rotX) or MathUtil.isNan(self.rotY) or MathUtil.isNan(self.rotZ) then
self:resetCamera()
end
--#debug if self.cameraYDebugMode then
--#debug setTextAlignment(RenderText.ALIGN_CENTER)
--#debug setTextBold(true)
--#debug
--#debug local x, y, z = getTranslation(self.rotateNode)
--#debug local text = string.format("Camera Position: %.2f %.2f %.2f", x, y, z)
--#debug
--#debug setTextColor(0, 0, 0, 1)
--#debug renderText(0.5, 0.01, 0.025, text)
--#debug
--#debug setTextColor(1, 1, 1, 1)
--#debug renderText(0.499, 0.012, 0.025, text)
--#debug
--#debug setTextAlignment(RenderText.ALIGN_LEFT)
--#debug setTextBold(false)
--#debug end
--#profile RemoteProfiler.zoneEnd()
end
---Called on activate
function VehicleCamera:onActivate()
if self.cameraNode == nil then
return
end
if g_addCheatCommands then
addConsoleCommand("gsCameraAutoRotate", "Auto rotate vehicle outdoor camera", "consoleCommandSetAutoRotate", self, "speed")
addConsoleCommand("gsCameraOffset", "Offset vehicle outdoor camera target", "consoleCommandSetOffset", self, "x; y; z")
addConsoleCommand("gsCameraRotationSaveLoad", "Save and load current camera rotation + zoom", "consoleCommandRotationSaveLoad", self)
end
self:onActiveCameraSuspensionSettingChanged(g_gameSettings:getValue(GameSettings.SETTING.ACTIVE_SUSPENSION_CAMERA))
self.isActivated = true
if not g_currentMission.vehicleSystem.isReloadRunning then
if (self.resetCameraOnVehicleSwitch == nil and g_gameSettings:getValue(GameSettings.SETTING.RESET_CAMERA)) or self.resetCameraOnVehicleSwitch then
self:resetCamera()
end
end
if g_cameraManager:getActiveCamera() ~= self.cameraNode then
g_cameraManager:setActiveCamera(self.cameraNode)
end
local rx,ry,rz = getWorldRotation(self.rotateNode)
if MathUtil.isNan(rx) or MathUtil.isNan(ry) or MathUtil.isNan(rz) then
self:resetCamera()
end
if self.positionSmoothingParameter > 0 then
local xlook,ylook,zlook = getWorldTranslation(self.rotateNode)
-- inject offset from console command
xlook = xlook + (self.offsetX or 0)
ylook = ylook + (self.offsetY or 0)
zlook = zlook + (self.offsetZ or 0)
self.lookAtPosition[1] = xlook
self.lookAtPosition[2] = ylook
self.lookAtPosition[3] = zlook
self.lookAtLastTargetPosition[1] = xlook
self.lookAtLastTargetPosition[2] = ylook
self.lookAtLastTargetPosition[3] = zlook
local x,y,z = getWorldTranslation(self.cameraPositionNode)
self.position[1] = x
self.position[2] = y
self.position[3] = z
self.lastTargetPosition[1] = x
self.lastTargetPosition[2] = y
self.lastTargetPosition[3] = z
local upx, upy, upz = localDirectionToWorld(self.rotateNode, self:getTiltDirectionOffset(), 1, 0)
self.upVector[1] = upx
self.upVector[2] = upy
self.upVector[3] = upz
self.lastUpVector[1] = upx
self.lastUpVector[2] = upy
self.lastUpVector[3] = upz
setWorldRotation(self.cameraNode, rx,ry,rz)
setWorldTranslation(self.cameraNode, x,y,z)
end
self.lastInputValues = {}
self.lastInputValues.upDown = 0
self.lastInputValues.leftRight = 0
-- activate action event callbacks
local _, actionEventId1 = g_inputBinding:registerActionEvent(InputAction.AXIS_LOOK_UPDOWN_VEHICLE, self, VehicleCamera.actionEventLookUpDown, false, false, true, true, nil)
local _, actionEventId2 = g_inputBinding:registerActionEvent(InputAction.AXIS_LOOK_LEFTRIGHT_VEHICLE, self, VehicleCamera.actionEventLookLeftRight, false, false, true, true, nil)
g_inputBinding:setActionEventTextVisibility(actionEventId1, false)
g_inputBinding:setActionEventTextVisibility(actionEventId2, false)
ObjectChangeUtil.setObjectChanges(self.changeObjects, true, self.vehicle, self.vehicle.setMovingToolDirty)
-- add PRECIPITATION_BLOCKING bit to cols
self:updatePrecipitationCollisions(true)
if g_touchHandler ~= nil then
self.touchListenerPinch = g_touchHandler:registerGestureListener(TouchHandler.GESTURE_PINCH, VehicleCamera.touchEventZoomInOut, self)
self.touchListenerY = g_touchHandler:registerGestureListener(TouchHandler.GESTURE_AXIS_Y, VehicleCamera.touchEventLookUpDown, self)
self.touchListenerX = g_touchHandler:registerGestureListener(TouchHandler.GESTURE_AXIS_X, VehicleCamera.touchEventLookLeftRight, self)
end
g_activeVehicleCamera = self
if self.lodDebugModeLoaded then
self:setLODDebugState(self.lodDebugModeLoaded, self.loadDebugZoom)
self.lodDebugModeLoaded = nil
end
--#debug if self.cameraYDebugModeLoaded then
--#debug self:setCameraYDebugState(self.cameraYDebugModeLoaded, self.cameraYDebugHeight)
--#debug self.cameraYDebugModeLoaded = nil
--#debug end
end
---
function VehicleCamera:setLODDebugState(state, zoom)
if state ~= self.lodDebugMode then
self.lodDebugMode = state
if self.lodDebugMode then
self.transMaxOrig = self.transMax
self.transMax = 350
self.loadDebugZoom = zoom or self.zoom
setViewDistanceCoeff(1)
setLODDistanceCoeff(1)
setTerrainLODDistanceCoeff(1)
else
self.transMax = self.transMaxOrig
self.zoomTarget = self.zoomDefault
self.zoom = self.zoomDefault
setFovY(self.cameraNode, self.fovY)
setViewDistanceCoeff(g_settingsModel.percentValues[g_settingsModel:getValue(SettingsModel.SETTING.OBJECT_DRAW_DISTANCE)])
setLODDistanceCoeff(g_settingsModel.percentValues[g_settingsModel:getValue(SettingsModel.SETTING.LOD_DISTANCE)])
setTerrainLODDistanceCoeff(g_settingsModel.percentValues[g_settingsModel:getValue(SettingsModel.SETTING.TERRAIN_LOD_DISTANCE)])
end
end
end
---
function VehicleCamera:setCameraYDebugState(state, height)
if state ~= self.cameraYDebugMode then
self.cameraYDebugMode = state
if self.cameraYDebugMode then
self.cameraYDebugHeight = tonumber(height) or 5
self.cameraYDebugZoom = self.zoom
self.rotX, self.rotY, self.rotZ = 0, math.pi * 0.5, 0
setRotation(self.rotateNode, self.rotX, self.rotY, self.rotZ)
setIsOrthographic(self.cameraNode, true)
setOrthographicHeight(self.cameraNode, tonumber(height) or 5)
self.isRotatable = false
g_currentMission.hud:setIsVisible(false)
else
self.isRotatable = true
setIsOrthographic(self.cameraNode, false)
if self == g_activeVehicleCamera then
g_currentMission.hud:setIsVisible(true)
end
end
end
end
---Called on deactivate
function VehicleCamera:onDeactivate()
self.isActivated = false
removeConsoleCommand("gsCameraAutoRotate")
removeConsoleCommand("gsCameraOffset")
removeConsoleCommand("gsCameraRotationSaveLoad")
-- remove action event callbacks
g_inputBinding:removeActionEventsByTarget(self)
ObjectChangeUtil.setObjectChanges(self.changeObjects, false, self.vehicle, self.vehicle.setMovingToolDirty)
-- remove PRECIPITATION_BLOCKING bit from cols
self:updatePrecipitationCollisions(false)
if g_touchHandler ~= nil then
g_touchHandler:removeGestureListener(self.touchListenerPinch)
g_touchHandler:removeGestureListener(self.touchListenerY)
g_touchHandler:removeGestureListener(self.touchListenerX)
end
if self.lodDebugMode then
self:setLODDebugState(false)
end
--#debug if self.cameraYDebugMode then
--#debug self:setCameraYDebugState(false)
--#debug end
if g_activeVehicleCamera == self then
g_activeVehicleCamera = nil
end
end
---
function VehicleCamera:actionEventLookUpDown(actionName, inputValue, callbackState, isAnalog, isMouse)
if isMouse then
inputValue = inputValue * 0.001 * 16.666
else
inputValue = inputValue * 0.001 * g_currentDt
end
self.lastInputValues.upDown = self.lastInputValues.upDown + inputValue
--#debug if self.cameraYDebugMode then
--#debug local mouseButtonLast, _ = g_inputBinding:getMouseButtonState()
--#debug if mouseButtonLast == Input.MOUSE_BUTTON_MIDDLE then
--#debug local x, y, z = getTranslation(self.rotateNode)
--#debug setTranslation(self.rotateNode, x, y + inputValue, z)
--#debug end
--#debug end
end
---
function VehicleCamera:touchEventLookUpDown(value)
if self.isActivated then
local factor = (g_screenHeight * g_pixelSizeX) * -75
VehicleCamera.actionEventLookUpDown(self, nil, value * factor, nil, nil, false)
end
end
---
function VehicleCamera:touchEventZoomInOut(value)
if self.isActivated then
self:zoomSmoothly(value * 15)
end
end
---
function VehicleCamera:touchEventLookLeftRight(value)
if self.isActivated then
local factor = (g_screenAspectRatio) * 75
VehicleCamera.actionEventLookLeftRight(self, nil, value * factor, nil, nil, false)
end
end
---
function VehicleCamera:actionEventLookLeftRight(actionName, inputValue, callbackState, isAnalog, isMouse)
if isMouse then
inputValue = inputValue * 0.001 * 16.666
else
inputValue = inputValue * 0.001 * g_currentDt
end
self.lastInputValues.leftRight = self.lastInputValues.leftRight + inputValue
--#debug if self.cameraYDebugMode then
--#debug local mouseButtonLast, _ = g_inputBinding:getMouseButtonState()
--#debug if mouseButtonLast == Input.MOUSE_BUTTON_MIDDLE then
--#debug local x, y, z = getTranslation(self.rotateNode)
--#debug setTranslation(self.rotateNode, x, y, z + inputValue)
--#debug end
--#debug end
end
---Reset camera to original pose
function VehicleCamera:resetCamera()
self.rotX = self.origRotX
self.rotY = self.origRotY
self.rotZ = self.origRotZ
self.transX = self.origTransX
self.transY = self.origTransY
self.transZ = self.origTransZ
local transLength = MathUtil.vector3Length(self.origTransX, self.origTransY, self.origTransZ)
self.zoom = transLength
self.zoomTarget = transLength
self.zoomLimitedTarget = -1
self:updateRotateNodeRotation()
setTranslation(self.cameraPositionNode, self.transX, self.transY, self.transZ)
if self.positionSmoothingParameter > 0 then
local xlook,ylook,zlook = getWorldTranslation(self.rotateNode)
self.lookAtPosition[1] = xlook + (self.offsetX or 0)
self.lookAtPosition[2] = ylook + (self.offsetY or 0)
self.lookAtPosition[3] = zlook + (self.offsetZ or 0)
local x,y,z = getWorldTranslation(self.cameraPositionNode)
self.position[1] = x
self.position[2] = y
self.position[3] = z
self:setSeparateCameraPose()
end
end
---Update rotation node rotation
function VehicleCamera:updateRotateNodeRotation()
local rotY = self.rotY
if self.rotYSteeringRotSpeed ~= nil and self.rotYSteeringRotSpeed ~= 0 and self.vehicle.spec_articulatedAxis ~= nil and self.vehicle.spec_articulatedAxis.interpolatedRotatedTime ~= nil then
rotY = rotY + self.vehicle.spec_articulatedAxis.interpolatedRotatedTime*self.rotYSteeringRotSpeed
end
if (self.useWorldXZRotation == nil and g_gameSettings:getValue(GameSettings.SETTING.USE_WORLD_CAMERA)) or self.useWorldXZRotation then
local vehicleDirectionX, _, vehicleDirectionZ = localDirectionToWorld(getParent(self.rotateNode), 0,0,1)
vehicleDirectionX, vehicleDirectionZ = MathUtil.vector2Normalize(vehicleDirectionX, vehicleDirectionZ)
local newDx = math.cos(self.rotX) * (math.cos(rotY)*vehicleDirectionX + math.sin(rotY)*vehicleDirectionZ)
local newDy = -math.sin(self.rotX)
local newDz = math.cos(self.rotX) * (-math.sin(rotY)*vehicleDirectionX + math.cos(rotY)*vehicleDirectionZ)
newDx,newDy,newDz = worldDirectionToLocal(getParent(self.rotateNode), newDx,newDy,newDz)
local upx,upy,upz = worldDirectionToLocal(getParent(self.rotateNode), 0,1,0)
-- worst case check
if math.abs(MathUtil.dotProduct(newDx,newDy,newDz, upx,upy,upz)) > ( 0.99 * MathUtil.vector3Length(newDx,newDy,newDz) * MathUtil.vector3Length(upx,upy,upz) ) then
setRotation(self.rotateNode, self.rotX, rotY, self.rotZ)
else
setDirection(self.rotateNode, newDx,newDy,newDz, upx,upy,upz)
end
else
setRotation(self.rotateNode, self.rotX, rotY, self.rotZ)
end