File size: 37,504 Bytes
b6a38d7 |
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 1001 1002 1003 1004 1005 1006 |
---
--- Calculates the power of a camera shake effect based on the position of the camera and the position of the shake.
--- @param pos point The position of the shake.
--- @param radius_insight number The radius within which the shake is considered to be in sight of the camera.
--- @param radius_outofsight number The radius beyond which the shake is considered to be out of sight of the camera.
--- @return number The power of the camera shake effect, as a percentage.
---
function CameraShake_GetEffectPower(pos, radius_insight, radius_outofsight)
local cam_pos, cam_look = GetCamera()
local camera_orientation = CalcOrientation(cam_pos, cam_look)
local shake_orientation = CalcOrientation(cam_pos, pos)
local dist = DistSegmentToPt(cam_pos, cam_look, pos)
if dist < 0 then
assert(false)
return 0
end
local radius
if abs(AngleDiff(shake_orientation, camera_orientation)) < const.CameraShakeFOV/2 then
radius = radius_insight or const.ShakeRadiusInSight
else
radius = radius_outofsight or const.ShakeRadiusOutOfSight
end
return dist < radius and 100 * (radius - dist) / radius or 0
end
--- Starts a camera shake effect with the specified position and power.
-- @cstyle void CameraShake(point pos, int power).
-- @param pos point.
-- @param power int.
-- @return void.
function CameraShake(pos, power)
power = power * CameraShake_GetEffectPower(pos) / 100
if power == 0 then return end
local total_duration = const.MinShakeDuration + power*(const.MaxShakeDuration-const.MinShakeDuration)/const.MaxShakePower
local shake_offset = power*const.MaxShakeOffset/const.MaxShakePower
local shake_roll = power*const.MaxShakeRoll/const.MaxShakePower
camera.Shake(total_duration, const.ShakeTick, shake_offset, shake_roll)
end
---
--- Stores the current camera shake thread and the maximum offset for the camera shake effect.
---
--- @field camera_shake_thread thread The current camera shake thread.
--- @field camera_shake_max_offset number The maximum offset for the camera shake effect.
---
MapVar("camera_shake_thread", false)
MapVar("camera_shake_max_offset", 0)
---
--- Performs a camera shake effect with the specified parameters.
---
--- @param total_duration number The total duration of the camera shake effect, in seconds.
--- @param shake_tick number The interval between each shake, in seconds.
--- @param max_offset number The maximum offset of the camera shake, in meters.
--- @param max_roll_offset number The maximum roll offset of the camera shake, in degrees.
---
local function DoShakeCamera(total_duration, shake_tick, max_offset, max_roll_offset)
local time_left = total_duration
while true do
local LookAtOffset = RandPoint(1500, 500, 500)
local EyePtOffset = RandPoint(1500, 500, 500)
local len = Max(1, 2 * time_left * max_offset / total_duration)
local angle = 60 * time_left * max_roll_offset / total_duration
if LookAtOffset:Len2() > 0 then
LookAtOffset = SetLen(LookAtOffset, len)
end
if EyePtOffset:Len2() > 0 then
EyePtOffset = SetLen(EyePtOffset, len)
end
camera.SetLookAtOffset(LookAtOffset, shake_tick)
camera.SetEyeOffset(EyePtOffset, shake_tick)
camera.SetRollOffset(AsyncRand(2 * angle + 1) - angle, shake_tick)
if total_duration > 0 then
time_left = time_left - shake_tick
if time_left <= shake_tick then
Sleep(time_left)
break
end
end
Sleep(shake_tick)
end
camera.ShakeStop(shake_tick)
end
---
--- Performs a camera shake effect with the specified parameters.
---
--- @param total_duration number The total duration of the camera shake effect, in seconds.
--- @param shake_tick number The interval between each shake, in seconds.
--- @param shake_max_offset number The maximum offset of the camera shake, in meters. This value is clamped to the range [0, 10m].
--- @param shake_max_roll number The maximum roll offset of the camera shake, in degrees. This value is clamped to the range [0, 180].
---
function camera.Shake(total_duration, shake_tick, shake_max_offset, shake_max_roll)
local max_offset = Clamp(shake_max_offset, 0, 10 * guim)
assert(max_offset == shake_max_offset, "camera.Shake() max_offset should be [0-10m]!")
local max_roll = Clamp(shake_max_roll, 0, 180)
assert(max_roll == shake_max_roll, "camera.Shake() max_roll should be [0-180]!")
if total_duration == 0 or shake_tick <= 0 then
return
end
if IsValidThread(camera_shake_thread) then
if camera_shake_max_offset > shake_max_offset then
return
end
DeleteThread(camera_shake_thread)
end
camera_shake_max_offset = max_offset
camera_shake_thread = CreateRealTimeThread(DoShakeCamera, total_duration, shake_tick, max_offset, max_roll)
MakeThreadPersistable(camera_shake_thread)
end
---
--- Stops the camera shake effect.
---
--- @param shake_tick number The interval between each shake, in seconds.
---
function camera.ShakeStop(shake_tick)
camera.SetRollOffset(0, 0)
camera.SetLookAtOffset(point30, shake_tick or 0)
camera.SetEyeOffset(point30, shake_tick or 0)
camera_shake_max_offset = 0
if IsValidThread(camera_shake_thread) and CurrentThread() ~= camera_shake_thread then
DeleteThread(camera_shake_thread)
end
camera_shake_thread = false
end
function OnMsg.ChangeMap()
camera.ShakeStop()
end
---
--- Sets the camera to the specified position, look-at point, camera type, zoom, and field of view.
---
--- @param ptCamera vec3 The position of the camera.
--- @param ptCameraLookAt vec3 The point the camera is looking at.
--- @param camType string The type of camera to use. Can be "3p", "RTS", "Max", or "Tac".
--- @param zoom number The zoom level of the camera.
--- @param properties table A table of camera properties to set.
--- @param fovX number The field of view of the camera in degrees.
--- @param time number The duration of the camera transition in seconds.
---
--- @return nil
---
function SetCamera(ptCamera, ptCameraLookAt, camType, zoom, properties, fovX, time)
if type(ptCamera) == "table" then
return SetCamera(unpack_params(ptCamera))
end
time = time or 0
if camType then
if camType == "Max" or camType == "3p" or camType == "RTS" or camType == "Tac" then
camType = "camera" .. camType
end
_G[camType].Activate(1)
end
if not ptCamera then
return
end
if camera3p.IsActive() then
camera3p.SetEye(ptCamera, time)
camera3p.SetLookAt(ptCameraLookAt, time)
elseif cameraRTS.IsActive() then
if properties then
cameraRTS.SetProperties(1, properties)
end
cameraRTS.SetCamera(ptCamera, ptCameraLookAt, time)
if zoom then
cameraRTS.SetZoom(zoom)
end
elseif cameraMax.IsActive() then
-- cameraMax can't look straight down
local diff = ptCameraLookAt - ptCamera
if diff:x() == 0 and diff:y() == 0 then
ptCamera = ptCamera:SetX(ptCamera:x()-5)
end
cameraMax.SetCamera(ptCamera, ptCameraLookAt, time)
elseif cameraTac.IsActive() then
cameraTac.SetCamera(ptCamera, ptCameraLookAt, time)
if properties then
local floor = properties.floor
local overview = properties.overview
if floor then
cameraTac.SetFloor(floor)
end
if overview ~= nil then
cameraTac.SetOverview(overview, true)
end
end
if zoom then
cameraTac.SetZoom(zoom)
end
end
SetCameraFov(fovX)
end
---
--- Sets the camera field of view (FOV) to the specified value.
---
--- @param fovX number The horizontal field of view in degrees. If not provided, defaults to 70 degrees.
---
function SetCameraFov(fovX)
camera.SetFovX(fovX or 70 * 60)
end
---
--- Sets the camera field of view (FOV) to the specified value, with optional easing.
---
--- @param properties table A table containing the following properties:
--- - FovX: number The horizontal field of view in degrees.
--- - FovXNarrow: number The horizontal field of view in degrees for 3:4 screens.
--- - FovXWide: number The horizontal field of view in degrees for 21:9 screens.
--- @param duration number (optional) The duration of the FOV change in seconds.
--- @param easing string (optional) The easing function to use for the FOV change.
---
function SetRTSCameraFov(properties, duration, easing)
local FovX = properties.FovX
local minFovX = properties.FovXNarrow -- FovX for 3:4 screens
local minX, minY = 4, 3
local maxFovX = properties.FovXWide -- FovX for 21:9 screens
local maxX, maxY = 21, 9
if FovX and minFovX and maxFovX then
-- when both FovXNarrow and FovXWide are supplied,
-- FovX at 16:9 is computed and equals FovXNarrow * 5 / 9 + FovXWide * 4 / 9
assert(abs(minFovX * 5 / 9 + maxFovX * 4 / 9 - FovX) < 60)
end
FovX = FovX or 90 * 60
if not minFovX then
minFovX = FovX
minX, minY = 16, 9
end
if not maxFovX then
maxFovX = FovX
maxX, maxY = 16, 9
end
hr.CameraFovEasing = easing or "Linear"
camera.SetAutoFovX(1, duration or 0, minFovX, minX, minY, maxFovX, maxX, maxY)
end
-- init from last camera with reasonable settings ( at editor exit, ... )
-- or set to map center if first run
---
--- Sets the default camera to the RTS (Real-Time Strategy) camera type and configures its properties.
---
--- If the Libs.Sim module is available, it retrieves the RTS camera properties from the account storage and applies them.
--- Otherwise, it uses the default RTS camera properties defined in const.DefaultCameraRTS.
---
--- The function also sets the camera's field of view using the SetRTSCameraFov function, and positions the camera to the center of the map if the current look-at position is at (0, 0).
---
--- Finally, it calls the ViewObjectRTS function to set the camera's position and look-at to the center of the map.
---
function SetDefaultCameraRTS()
cameraRTS.Activate(1)
cameraRTS.SetProperties(1, const.DefaultCameraRTS)
if Libs.Sim then
cameraRTS.SetProperties(1, GetRTSCamPropsFromAccountStorage())
end
SetRTSCameraFov(const.DefaultCameraRTS)
local lookat = cameraRTS.GetLookAt()
if lookat:x() == 0 and lookat:y() == 0 then
lookat = point(terrain.GetMapSize()) / 2
end
ViewObjectRTS(lookat, 0)
end
---
--- Returns a table of available camera types.
---
--- @return table Camera types
---
function GetCameraTypesItems()
return {"3p", "RTS", "Max", "Tac"}
end
---
--- Returns the current camera position, look-at position, camera type, zoom level, and camera properties.
---
--- @return point, point, string, number, table, number Camera position, look-at position, camera type, zoom level, camera properties, and field of view angle
---
function GetCamera()
local ptCamera, ptCameraLookAt, camType, zoom, properties, fovX
if camera3p.IsActive() then
ptCamera, ptCameraLookAt = camera.GetEye(), camera3p.GetLookAt()
camType = "3p"
elseif cameraRTS.IsActive() then
ptCamera, ptCameraLookAt = cameraRTS.GetPosLookAt()
camType = "RTS"
zoom = cameraRTS.GetZoom()
properties = cameraRTS.GetProperties(1)
elseif cameraMax.IsActive() then
ptCamera, ptCameraLookAt = cameraMax.GetPosLookAt()
camType = "Max"
elseif cameraTac.IsActive() then
ptCamera, ptCameraLookAt = cameraTac.GetPosLookAt()
camType = "Tac"
zoom = cameraTac.GetZoom()
properties = {floor=cameraTac.GetFloor(), overview=cameraTac.GetIsInOverview()}
else
ptCamera, ptCameraLookAt = camera.GetEye(), camera.GetEye() + SetLen(camera.GetDirection(), 3 * guim)
end
fovX = camera.GetFovX()
return ptCamera, ptCameraLookAt, camType, zoom, properties, fovX
end
if FirstLoad then
ptLastCameraPos = false
ptLastCameraLookAt = false
cameraMax3DView = {
toggle = false,
old_pos = false,
old_lookat = false,
}
end
---
--- Cleans up the state of the cameraMax3DView object.
--- Resets the toggle, old_pos, and old_lookat properties to their default values.
---
function cameraMax3DView:Clean()
self.toggle = false
self.old_pos = false
self.old_lookat = false
end
-- returns the new camera pos and the look pos of the selection
---
--- Rotates the camera in the 3D Max view to the specified view direction.
---
--- @param view_direction point The new view direction for the camera.
---
local function cameraMax3DView_Rotate(view_direction)
local sel = editor.GetSel()
local cnt = #sel
if cnt == 0 then
print("You need to select object(s) for this operation")
return
end
local center = point30
for i = 1, cnt do
local bsc = sel[i]:GetBSphere()
center = center + bsc
end
if cnt > 0 then
-- find center of the selection
center = point(center:x() / cnt, center:y() / cnt, center:z() / cnt)
-- find the radius of the bounding sphere of the selection
local selSize = 0
for i = 1, cnt do
local bsc, bsr = sel[i]:GetBSphere()
local dist = bsc:Dist(center) + bsr
if selSize < dist then
selSize = dist
end
end
selSize = 2 * selSize -- get the diameter of the selection
-- move the camera position to look in the center of the selection
local half_fovY = MulDivRound(camera.GetFovY(), 1, 2)
local fov_sin, fov_cos = sin(half_fovY), cos(half_fovY)
local dist_from_camera = (fov_sin > 0) and MulDivRound((selSize / 2), fov_cos, fov_sin) or (selSize / 2)
view_direction = SetLen(view_direction, dist_from_camera * 130 / 100)
local pos = center + view_direction
cameraMax.SetCamera(pos, center, 0)
end
end
---
--- Rotates the camera in the 3D Max view to the up direction.
---
function cameraMax3DView:SetViewUp()
cameraMax3DView_Rotate(point(0, 0, 1))
end
---
--- Rotates the camera in the 3D Max view to the down direction.
---
function cameraMax3DView:SetViewDown()
cameraMax3DView_Rotate(point(0, 0, -1))
end
---
--- Sets the camera to the old position and look-at point.
---
function cameraMax3DView:SetViewOld()
cameraMax.SetCamera(cameraMax3DView.old_pos, cameraMax3DView.old_lookat, 0)
end
---
--- Rotates the camera in the 3D Max view around the Z axis.
---
--- @param dir string The direction to rotate the camera, either "east" or "west".
---
function cameraMax3DView:RotateZ(dir)
local pos, look_at = cameraMax.GetPosLookAt()
local cam_angle = (camera.GetYaw() / 60) + 180
local cam_quadrant = (cam_angle / 90) % 4 + 1
local correction = 0
local z_axis = point(0, 0, 1)
if cam_angle % 90 ~= 0 then
if cam_angle - 90 * (cam_quadrant - 1) < 90 * cam_quadrant - cam_angle then
correction = -(cam_angle - 90 * (cam_quadrant - 1))
else
correction = 90 * cam_quadrant - cam_angle
end
cam_angle = cam_angle + correction
end
local view_dir = false
if dir == "east" then
view_dir = RotateAxis(pos, z_axis, (cam_angle - 90) * 60)
else
view_dir = RotateAxis(pos, z_axis, (cam_angle + 90) * 60)
end
if view_dir then
cameraMax3DView_Rotate(Normalize(view_dir))
end
end
---
--- Sets the camera position and look-at point.
---
--- @param pos table The position of the camera, represented as a point.
--- @param dist number (optional) The distance from the camera to the look-at point.
--- @param cam_type number (optional) The type of camera to use.
---
--- If `pos` is `InvalidPos()`, the camera will be reset to the last known position and look-at point.
--- If `pos` does not have a valid Z coordinate, it will be set to the terrain Z coordinate.
--- The camera vector is calculated based on the `pos` and the look-at point, and the camera is set to this position.
---
function ViewPos(pos, dist, cam_type)
local ptCamera, ptCameraLookAt = GetCamera()
if not ptCamera then
return
end
if pos == InvalidPos() then
pos = nil
end
if not pos then
if ptLastCameraPos then
SetCamera(ptLastCameraPos, ptLastCameraLookAt, cam_type)
end
return
end
ptLastCameraPos, ptLastCameraLookAt = ptCamera, ptCameraLookAt
if not pos:z() then
pos = pos:SetTerrainZ()
end
local cameraVector = ptCameraLookAt - ptCamera
if dist then
cameraVector = SetLen(cameraVector, dist)
end
ptCamera = pos - cameraVector
ptCameraLookAt = pos
SetCamera(ptCamera, ptCameraLookAt, cam_type)
end
---
--- Sets the camera to view the specified object.
---
--- @param obj MapObject|number The object to view, or its handle.
--- @param dist number (optional) The distance from the camera to the object.
---
--- If `obj` is a number, it is assumed to be the handle of a `MapObject` and looked up in `HandleToObject`.
--- If `pos` is `InvalidPos()`, the camera will be reset to the last known position and look-at point.
--- If `pos` does not have a valid Z coordinate, it will be set to the terrain Z coordinate.
--- The camera vector is calculated based on the `pos` and the look-at point, and the camera is set to this position.
---
ViewObject = function(obj, dist)
if type(obj) == "number" and HandleToObject[obj] then
obj = HandleToObject[obj]
end
local pos = IsValid(obj) and obj:GetPos()
if not pos or pos == InvalidPos() then
return
end
if dist then
ViewPos(pos, dist)
else
local center, radius = obj:GetBSphere()
ViewPos(center, Max(guim, radius * 10))
end
end
---
--- Caches the last object viewed by `ViewNextObject`.
---
--- This cache is used to keep track of the last object that was viewed, so that `ViewNextObject` can cycle through the objects in the order they were viewed.
---
local ViewNextObjectCache
function OnMsg.ChangeMap()
ViewNextObjectCache = nil
end
-- Cycles ViewObject in the array objs, viewing the next object every time it is called for the same set of parameters
---
--- Cycles through the next object in the given list of objects and views it.
---
--- @param name string (optional) The class name of the objects to cycle through. If not provided, the class name of the last selected object is used.
--- @param objs table (optional) The list of objects to cycle through. If not provided, all objects of the given class name are used.
--- @param select_obj boolean (optional) Whether to select the object after viewing it.
---
--- If `name` is not provided and there is no last selected object, this function does nothing.
--- If `objs` is not provided, all objects of the given class name are used.
--- The function keeps track of the last object viewed and cycles to the next one in the list.
--- If the end of the list is reached, it cycles back to the beginning.
--- If `select_obj` is true, the viewed object is also selected.
---
function ViewNextObject(name, objs, select_obj)
name = name or ""
local last
if not objs then
if name == "" then
last = SelectedObj
name = last and last.class
select_obj = true
end
if not IsKindOf(g_Classes[name], "MapObject") then
return
end
objs = MapGet("map", name)
end
ViewNextObjectCache = ViewNextObjectCache or setmetatable({}, weak_values_meta)
last = last or ViewNextObjectCache[name]
local idx = last and table.find(objs, last) or 0
last = objs[idx + 1] or objs[1]
ViewNextObjectCache[name] = last
ViewObject(last)
SelectObj(last)
end
---
--- Cycles through the next object in the given list of objects and views it.
---
--- @param name string (optional) The class name of the objects to cycle through. If not provided, the class name of the last selected object is used.
--- @param objs table (optional) The list of objects to cycle through. If not provided, all objects of the given class name are used.
--- @param select_obj boolean (optional) Whether to select the object after viewing it.
---
--- If `name` is not provided and there is no last selected object, this function does nothing.
--- If `objs` is not provided, all objects of the given class name are used.
--- The function keeps track of the last object viewed and cycles to the next one in the list.
--- If the end of the list is reached, it cycles back to the beginning.
--- If `select_obj` is true, the viewed object is also selected.
---
function ViewObjects(objects)
objects = objects or {}
local dgs = XEditorSelectSingleObjects
XEditorSelectSingleObjects = 1
editor.ChangeSelWithUndoRedo(objects)
XEditorSelectSingleObjects = dgs
if #objects == 0 then
return
end
local bbox = GetObjectsBBox(objects)
local center, radius = bbox:GetBSphere()
local cam_pos = camera.GetEye()
local h = cam_pos:z() - terrain.GetSurfaceHeight(cam_pos)
local eye = center:SetZ(0) + SetLen((cam_pos - center):SetZ(0), h)
eye = eye:SetZ(terrain.GetSurfaceHeight(eye) + h)
local dist = (eye - center):Len()
local new_dist = Clamp(Max(dist, 2*radius), 10*guim, 100*guim)
eye = center + MulDivRound(eye - center, new_dist, dist)
local steps = 18
local angle = 360 * 60 / steps
local max_radius = 2 * guim
local success = true
local objects_map = {}
for i=1,#objects do
objects_map[objects[i]] = true
end
while true do
local objs = IntersectSegmentWithObjects(eye, center, const.efVisible)
if not objs then
break
end
local objects_too_big = false
for i=1,#objs do
local obj = objs[i]
if not objects_map[obj] then
local center, radius = obj:GetBSphere()
if radius > max_radius then
objects_too_big = true
break
end
end
end
if not objects_too_big then
break
end
steps = steps - 1
if steps <= 1 then
success = false
break
end
eye = RotateAroundCenter(center, eye, angle)
eye = eye:SetZ(terrain.GetSurfaceHeight(eye) + h)
end
if success then
SetCamera(eye, center)
end
end
if FirstLoad then
SplitScreenType = false
SplitScreenEnabled = true
SecondViewEnabled = false
SecondViewViewport = false
end
-- call this after every resolution/scene size change to recalc and setup appropriate views for single or split screen
---
--- Sets up the camera views for single or split screen.
---
--- @param size number|nil The size of the screen, if provided.
---
--- This function is responsible for configuring the camera views based on the current split screen settings.
--- If split screen is enabled, it sets up two views - one for each player. The views can be either horizontal or vertical.
--- If split screen is disabled, it sets up a single view that covers the entire screen.
--- The function adjusts the viewport settings of the camera accordingly.
---
function SetupViews(size)
local w, h = 1000000, 1000000
if SecondViewEnabled and SecondViewViewport then
camera.SetViewCount(2)
camera.SetViewport(box(0, 0, w, h), 1)
camera.SetViewport(SecondViewViewport, 2)
elseif SplitScreenEnabled then
if SplitScreenType == "horizontal" then
camera.SetViewCount(2)
camera.SetViewport(box(0, 0, w, h / 16 * 8), 1)
camera.SetViewport(box(0, (h + 15) / 16 * 8, w, h), 2)
elseif SplitScreenType == "vertical" then
camera.SetViewCount(2)
camera.SetViewport(box(0, 0, w / 16 * 8, h), 1)
camera.SetViewport(box((w + 15) / 16 * 8, 0, w, h), 2)
else
camera.SetViewCount(1)
camera.SetViewport(box(0, 0, w, h), 1)
end
else
if not SplitScreenType then
camera.SetViewCount(1)
camera.SetViewport(box(0, 0, w, h), 1)
else
camera.SetViewport(box(0, 0, w, h), 1)
end
end
end
if FirstLoad then
SplitScreenDisableReasons = {}
end
---
--- Enables or disables split screen mode based on the provided reason.
---
--- @param on boolean Whether to enable or disable split screen mode.
--- @param reason string The reason for enabling or disabling split screen mode.
---
--- This function is responsible for managing the state of split screen mode. It updates the `SplitScreenDisableReasons` table to track the reasons for enabling or disabling split screen mode. If there are no more reasons to disable split screen mode, it enables it. Otherwise, it disables it. The function also calls `SetupViews()` to reconfigure the camera views and sends a "SplitScreenChange" message.
---
function SetSplitScreenEnabled(on, reason)
assert(reason)
SplitScreenDisableReasons[reason] = (on == false) or nil
on = not next(SplitScreenDisableReasons)
if SplitScreenEnabled ~= on then
SplitScreenEnabled = on
SetupViews()
Msg("SplitScreenChange", true)
end
end
---
--- Enables the second view for the camera and sets the viewport for it.
---
--- @param viewport table The viewport for the second view.
---
--- This function enables the second view for the camera and sets the viewport for it. It updates the `SecondViewEnabled` and `SecondViewViewport` variables and then calls the `SetupViews()` function to reconfigure the camera views.
---
function EnableSecondView(viewport)
SecondViewEnabled = true
SecondViewViewport = viewport
SetupViews()
end
---
--- Disables the second view for the camera.
---
--- This function disables the second view for the camera by setting the `SecondViewEnabled` variable to `false` and calling the `SetupViews()` function to reconfigure the camera views.
---
function DisableSecondView()
SecondViewEnabled = false
SetupViews()
end
---
--- Sets the split screen type.
---
--- @param type string The type of split screen to use, or an empty string to disable split screen.
---
--- This function sets the `SplitScreenType` variable to the provided `type` parameter. If the `type` is an empty string, split screen is disabled. The function then calls `SetupViews()` to reconfigure the camera views, and sends a "SplitScreenChange" message if the split screen type has changed.
function SetSplitScreenType(type)
if type == "" then
type = false
end
local bChange = SplitScreenType ~= type
SplitScreenType = type
if not CameraControlScene then
SetupViews()
end
if bChange then
Msg("SplitScreenChange")
end
end
---
--- Checks if split screen is enabled.
---
--- @return boolean true if split screen is enabled, false otherwise
---
function IsSplitScreenEnabled()
return SplitScreenEnabled and SplitScreenType and true
end
---
--- Checks if split screen is in horizontal mode.
---
--- @return boolean true if split screen is in horizontal mode, false otherwise
---
function IsSplitScreenHorizontal()
return SplitScreenEnabled and SplitScreenType == "horizontal"
end
---
--- Checks if split screen is in vertical mode.
---
--- @return boolean true if split screen is in vertical mode, false otherwise
---
function IsSplitScreenVertical()
return SplitScreenEnabled and SplitScreenType == "vertical"
end
---
--- Loads a map and camera location from a saved state.
---
--- @param map string The name of the map to load.
--- @param cam_params table A table containing the camera parameters to set.
--- @param editor_mode boolean Whether to activate the editor mode after loading.
--- @param map_rand number The random seed to use for the map.
---
--- This function loads a map and camera location from a saved state. It first checks if the map exists, and if not, prints an error message. It then creates a real-time thread to perform the following steps:
---
--- 1. Deactivate the editor.
--- 2. If the map or random seed is different from the current map, change the map and restore the configuration.
--- 3. If the editor mode is enabled, activate the editor.
--- 4. Set the camera parameters, activating the fly camera if necessary.
--- 5. Close any open menu dialogs.
--- 6. Send a "OnDbgLoadLocation" message.
---
--- This function is typically used for debugging purposes, to quickly load a specific map and camera location.
function DbgLoadLocation(map, cam_params, editor_mode, map_rand)
if not MapData[map] then
print("No such map:", map)
return
end
CreateRealTimeThread(function()
EditorDeactivate()
if map ~= GetMapName() or map_rand and map_rand ~= MapLoadRandom then
if map_rand then
table.change(config, "DbgLoadLocation", {FixedMapLoadRandom=map_rand})
end
ChangeMap(map)
table.restore(config, "DbgLoadLocation", true)
end
if editor_mode then
EditorActivate()
end
if cam_params then
if cam_params[3] == "Fly" then
cam_params[3] = "Max"
SetCamera(table.unpack(cam_params))
cameraFly.Activate()
else
SetCamera(table.unpack(cam_params))
end
end
CloseMenuDialogs()
Msg("OnDbgLoadLocation")
end)
end
---
--- Gets a string representation of the current camera location that can be used to restore the camera state.
---
--- @return string A string that can be passed to `DbgLoadLocation` to restore the camera state.
---
function GetCameraLocationString()
local cam_params
if cameraFly.IsActive() then
-- Fly camera doesn't expose its parameters, but it can be saved as Max and forced to Fly again on load
cameraMax.Activate()
cam_params = {GetCamera()}
cam_params[3] = "Fly"
cameraFly.Activate()
else
cam_params = {GetCamera()}
end
return string.format("DbgLoadLocation( \"%s\", %s, %s, %s)\n", GetMapName(), TableToLuaCode(cam_params, ' '),
IsEditorActive() and "true" or "false", tostring(MapLoadRandom))
end
function OnMsg.BugReportStart(print_func)
print_func(string.format("\nLocation: (paste in the console)\n%s", GetCameraLocationString()))
end
if FirstLoad then
g_ResetSceneCameraViewportThread = false
end
function OnMsg.SystemSize(pt)
--if FullscreenMode() == 0 then
DeleteThread(g_ResetSceneCameraViewportThread)
g_ResetSceneCameraViewportThread = CreateRealTimeThread(function()
WaitNextFrame(1)
SetupViews(pt)
end)
--end
end
---
--- Checks if the given position is a valid camera position.
---
--- @param pos point The position to check.
--- @return boolean True if the position is valid, false otherwise.
---
local function IsValidCameraPos(pos)
return pos and pos ~= point30 and pos ~= InvalidPos()
end
---
--- Checks if the camera can move between two positions without intersecting terrain.
---
--- @param pos0 point The starting position for the camera movement.
--- @param pos1 point The ending position for the camera movement.
--- @return boolean True if the camera can move between the two positions without intersecting terrain, false otherwise.
---
local function CanMoveCamBetween(pos0, pos1)
local max_move_dist = const.MaxMoveCamDist or max_int
if max_move_dist >= max_int or IsCloser(pos0, pos1, max_move_dist) then
return true
end
return not terrain.IntersectSegment(pos0, pos1)
end
---
--- Moves the camera to view the specified object, optionally with a zoom level.
---
--- @param obj table|point The object or position to view
--- @param time number The time in seconds for the camera to move to the new position
--- @param pos point The position to move the camera to
--- @param zoom number The zoom level to set the camera to
---
function ViewObjectRTS(obj, time, pos, zoom)
if not obj then
return
end
local la = IsPoint(obj) and obj or IsValid(obj)
and (obj:HasMember("GetLogicalPos") and obj:GetLogicalPos() or obj:GetVisualPos())
if not la or la == InvalidPos() then
return
end
la = la:SetTerrainZ()
local cur_pos, cur_la = cameraRTS.GetPosLookAt()
if not pos then
local cur_off = cur_pos - cur_la
if not IsValidCameraPos(cur_pos) or cur_pos == cur_la then
local lookatDist = const.DefaultCameraRTS.LookatDistZoomIn
+ (const.DefaultCameraRTS.LookatDistZoomOut - const.DefaultCameraRTS.LookatDistZoomIn)
* cameraRTS.GetZoom()
cur_off = SetLen(point(1, 1, 0), lookatDist * guim) + point(0, 0, cameraRTS.GetHeight() * guim)
zoom = zoom or 0.5
end
pos = la + cur_off
end
pos, la = cameraRTS.Normalize(pos, la)
if not IsValidCameraPos(cur_pos) or not CanMoveCamBetween(cur_pos, pos) then
time = 0
elseif not time then
local min_dist, max_dist = 200 * guim, 1000 * guim
local min_time, max_time = 200, 500
local dist_factor = Clamp(pos:Dist2D(cur_pos) - min_dist, 0, max_dist) * 100 / (max_dist - min_dist)
time = min_time + (max_time - min_time) * dist_factor / 100
end
cameraRTS.SetCamera(pos, la, time or 0, "Sin in/out")
if zoom then
cameraRTS.SetZoom(zoom, time or 0)
end
end
---
--- Defines the available types of camera interpolation.
---
--- @class CameraInterpolationTypes
--- @field linear integer Linear interpolation.
--- @field spherical integer Spherical interpolation.
--- @field polar integer Polar interpolation.
CameraInterpolationTypes = {linear=0, spherical=1, polar=2}
---
--- Defines the available types of camera movement.
---
--- @class CameraMovementTypes
--- @field linear integer Linear movement.
--- @field harmonic integer Harmonic movement.
--- @field accelerated integer Accelerated movement.
--- @field decelerated integer Decelerated movement.
CameraMovementTypes = {linear=0, harmonic=1, accelerated=2, decelerated=3}
---
--- Sets the camera position and lookat point, taking into account the base offset and angle.
---
--- @param pos table The camera position.
--- @param lookat table The camera lookat point.
--- @param base_offset table The base offset to apply to the position and lookat.
--- @param base_angle number The base angle to apply to the position and lookat.
--- @param camera_view integer The camera view to use.
---
function SetCameraPosMaxLookAt(pos, lookat, base_offset, base_angle, camera_view)
cameraMax.SetPositionLookatAndRoll(base_offset + Rotate(pos, base_angle), base_offset + Rotate(lookat, base_angle),
0)
end
---
--- Interpolates the camera position and lookat point between two camera states over a given duration, relative to a reference object.
---
--- @param camera1 table The initial camera state, with `pos` and `lookat` fields.
--- @param camera2 table The final camera state, with `pos` and `lookat` fields.
--- @param duration number The duration of the interpolation in frames.
--- @param relative_to Entity The entity to use as the reference for the camera position and lookat.
--- @param interpolation string The type of interpolation to use, one of "linear", "spherical", or "polar".
--- @param movement string The type of camera movement to use, one of "linear", "harmonic", "accelerated", or "decelerated".
--- @param camera_view integer The camera view to use.
---
function InterpolateCameraMaxWakeup(camera1, camera2, duration, relative_to, interpolation, movement, camera_view)
camera_view = camera_view or 1
local base_offset = IsValid(relative_to) and relative_to:GetVisualPosPrecise(1000) or point30
local base_angle = IsValid(relative_to) and relative_to:GetVisualAngle() or 0
local camera2_pos = Rotate(camera2.pos * 1000 - base_offset, 360 * 60 - base_angle)
local camera2_lookat = Rotate(camera2.lookat * 1000 - base_offset, 360 * 60 - base_angle)
if duration > 1 then
local camera1_pos = Rotate(camera1.pos * 1000 - base_offset, 360 * 60 - base_angle)
local camera1_lookat = Rotate(camera1.lookat * 1000 - base_offset, 360 * 60 - base_angle)
SetCameraPosMaxLookAt(camera1_pos, camera1_lookat, base_offset, base_angle, camera_view)
for t = 1, duration do
if WaitWakeup(1) then
break
end
base_offset = IsValid(relative_to) and relative_to:GetVisualPosPrecise(1000) or point30
base_angle = IsValid(relative_to) and relative_to:GetVisualAngle() or 0
local p, l = CameraLerp(camera1_pos, camera1_lookat, camera2_pos, camera2_lookat, t, duration,
CameraInterpolationTypes[interpolation] or 0, CameraMovementTypes[movement] or 0)
SetCameraPosMaxLookAt(p, l, base_offset, base_angle, camera_view)
end
end
SetCameraPosMaxLookAt(camera2_pos, camera2_lookat, base_offset, base_angle, camera_view)
end
---
--- Toggles the fly camera mode.
---
--- If the fly camera is active, it deactivates the fly camera and applies the camera and controllers.
--- If the fly camera is not active, it activates the fly camera and recalculates the active player control.
--- It also sets the mouse delta mode accordingly.
---
function CheatToggleFlyCamera()
if cameraFly.IsActive() then
SetMouseDeltaMode(false)
if rawget(_G, "GetPlayerControlObj") and GetPlayerControlObj() then
ApplyCameraAndControllers()
else
SetupInitialCamera()
end
else
print("Camera Fly")
cameraFly.Activate(1)
if rawget(_G, "GetPlayerControlObj") and GetPlayerControlObj() then
PlayerControl_RecalcActive(true)
end
SetMouseDeltaMode(true)
end
end
|