/usr/src/castle-game-engine-5.2.0/x3d/opengl/castleprecalculatedanimation.pas is in castle-game-engine-src 5.2.0-3.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
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 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 1920 1921 1922 1923 1924 1925 1926 1927 1928 1929 1930 1931 1932 1933 1934 1935 1936 1937 1938 1939 1940 1941 1942 1943 1944 1945 1946 1947 1948 1949 1950 1951 1952 1953 1954 1955 1956 1957 1958 1959 1960 1961 1962 1963 1964 1965 1966 1967 1968 1969 1970 1971 1972 1973 1974 1975 1976 1977 1978 1979 1980 1981 1982 1983 1984 1985 1986 1987 1988 1989 1990 1991 1992 1993 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 2025 2026 2027 2028 2029 2030 2031 2032 2033 2034 2035 2036 2037 2038 2039 2040 2041 2042 2043 2044 2045 2046 2047 2048 2049 2050 2051 2052 2053 2054 2055 2056 2057 2058 2059 2060 2061 2062 2063 2064 2065 2066 2067 2068 2069 2070 2071 2072 2073 2074 2075 2076 2077 2078 | {
Copyright 2006-2014 Michalis Kamburelis.
This file is part of "Castle Game Engine".
"Castle Game Engine" is free software; see the file COPYING.txt,
included in this distribution, for details about the copyright.
"Castle Game Engine" is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
----------------------------------------------------------------------------
}
{ A precalculated 3D animation rendered in OpenGL (TCastlePrecalculatedAnimation). }
unit CastlePrecalculatedAnimation;
interface
uses SysUtils, Classes, X3DNodes, CastleRenderer, CastleSceneCore, CastleScene,
CastleUtils, CastleBoxes, CastleClassUtils, CastlePrecalculatedAnimationCore,
CastleKeysMouse, CastleTimeUtils, CastleFrustum, CastleVectors, Castle3D, X3DTriangles,
FGL, CastleTriangles, CastleRectangles;
type
TGetRootNodeWithTime = procedure (const Index: Cardinal;
out RootNode: TX3DRootNode; out Time: Single) of object;
{ A "precalculated" animation done by
interpolating between a number of 3D model states.
After constructing an object of this class, you must actually
load it's animation by calling Load or LoadFromFile or LoadFromEvents
etc.
When loading you must provide one or more X3D models with
their associated times. Animation will show a transition from the first
model to the last. If models are "structurally equal" then the transition
between two successive models will be smooth, otherwise a sudden change
will be shown. "Structurally equal" means
the same nodes hierarchy, the same names of nodes,
the same values of all fields (with the exception of fields
that are floating-point based and so can be interpolated, for example
SFFloat, SFVec3f and equivalent MFXxx fields).
For multi-valued fields (MFXxx) that can be interpolated: note that values
of items may differ, but still the counts of items must be equal.
This creates a list of @link(Scenes) such that
@unorderedList(
@itemSpacing Compact
@item the first scene on the list is exactly the 1st object
@item the last scene on the list is exactly the last object
@item(intermediate scenes are accordingly interpolated between
the two surrounding "predefined" by you scenes)
)
For example, first object may be a small sphere with blue color, the other
object may be a larger sphere with white color, and the simplest
times are 0.0 for the 1st scene and 1.0 for the 2nd scene.
The animation will show the blue sphere growing larger
and fading into the white
color. Of course, any kind of models is allowed --- e.g. it can
be a walking man at various stages, so in effect you get an animation
of walking man.
A special case when you pass only one scene to this class is allowed
(it may be handy in some situations). This will obviously produce
just a still result, i.e. resulting TCastlePrecalculatedAnimation will be just
a wrapper around single TCastleScene instance.
For more information see our engine documentation on
[http://castle-engine.sourceforge.net/engine_doc.php].
Specifically the section
"Non-interactive precalculated animation: TCastlePrecalculatedAnimation",
[http://castle-engine.sourceforge.net/vrml_engine_doc/output/xsl/html/section.animation_precalculated.html]. }
TCastlePrecalculatedAnimation = class(TCastlePrecalculatedAnimationCore)
private
FScenes: TCastleSceneList;
function GetScenes(I: Integer): TCastleScene;
private
Renderer: TGLRenderer;
FCache: TGLRendererContextCache;
FTimeBegin, FTimeEnd: Single;
FTimeLoop: boolean;
FTimeBackwards: boolean;
FOwnsFirstRootNode: boolean;
FLoaded: boolean;
FTimePlaying: boolean;
FTimePlayingSpeed: Single;
FTimeAtLoad: TFloatTime;
FTime: TFloatTime;
FShadowMaps: boolean;
FShadowMapsDefaultSize: Cardinal;
FTryFirstSceneDynamic: boolean;
ValidBoundingBox: boolean;
FBoundingBox: TBox3D;
FCollisionUseLastScene: boolean;
FInitialViewpointIndex: Cardinal;
FInitialViewpointName: string;
procedure SetShadowMaps(const Value: boolean);
procedure SetShadowMapsDefaultSize(const Value: Cardinal);
function InfoBoundingBox: string;
private
{ Helpers for Load implementation. }
Load_RootNodes: TX3DNodeList;
Load_Times: TSingleList;
procedure Load_GetRootNodeWithTime(const Index: Cardinal;
out RootNode: TX3DRootNode; out Time: Single);
private
{ Helpers for LoadFromEvents implementation. }
LoadFromEvents_TimeBegin: Single;
LoadFromEvents_Scene: TCastleSceneCore;
LoadFromEvents_ScenesPerTime: Cardinal;
procedure LoadFromEvents_GetRootNodeWithTime(const Index: Cardinal;
out RootNode: TX3DRootNode; out Time: Single);
procedure LoadFromEvents_GetRootNodeWithTime_Progress(
const Index: Cardinal;
out RootNode: TX3DRootNode; out Time: Single);
procedure SetOwnsFirstRootNode(const Value: boolean);
protected
{ Internal version of @link(Load) routines, feasible to load
from both ready RootNodes array and to automatically generate RootNodes
on the fly.
GetRootNodeWithTime will be called with indexes from 0 to RootNodesCount - 1.
It's guaranteed that it will be called in this order (from 0 upwards to
RootNodesCount - 1) and will be called exactly once for each index.
So it's safe to e.g. create RootNode with some costly operation there.
Note that RootNode passed to GetRootNodeWithTime becomes owned by
this class. Well, you can get control over only the first one,
by AOwnsFirstRootNode, but you cannot free it anyway while this is loaded.
See @link(Load) for more information, including the meaning of
EqualityEpsilon. }
procedure LoadCore(
GetRootNodeWithTime: TGetRootNodeWithTime;
RootNodesCount: Cardinal;
AOwnsFirstRootNode: boolean;
ScenesPerTime: Cardinal;
const EqualityEpsilon: Single);
function HeightCollision(const Position, GravityUp: TVector3Single;
const TrianglesToIgnoreFunc: T3DTriangleIgnoreFunc;
out AboveHeight: Single; out AboveGround: P3DTriangle): boolean; override;
function MoveCollision(
const OldPos, ProposedNewPos: TVector3Single; out NewPos: TVector3Single;
const IsRadius: boolean; const Radius: Single;
const OldBox, NewBox: TBox3D;
const TrianglesToIgnoreFunc: T3DTriangleIgnoreFunc): boolean; override;
function MoveCollision(
const OldPos, NewPos: TVector3Single;
const IsRadius: boolean; const Radius: Single;
const OldBox, NewBox: TBox3D;
const TrianglesToIgnoreFunc: T3DTriangleIgnoreFunc): boolean; override;
function SegmentCollision(const Pos1, Pos2: TVector3Single;
const TrianglesToIgnoreFunc: T3DTriangleIgnoreFunc;
const ALineOfSight: boolean): boolean; override;
function SphereCollision(const Pos: TVector3Single; const Radius: Single;
const TrianglesToIgnoreFunc: T3DTriangleIgnoreFunc): boolean; override;
function BoxCollision(const Box: TBox3D;
const TrianglesToIgnoreFunc: T3DTriangleIgnoreFunc): boolean; override;
function RayCollision(const RayOrigin, RayDirection: TVector3Single;
const TrianglesToIgnoreFunc: T3DTriangleIgnoreFunc): TRayCollision; override;
public
constructor Create(AOwner: TComponent); override;
{ Constructor that allows you to pass your own Cache instance. }
constructor CreateCustomCache(AOwner: TComponent;
ACache: TGLRendererContextCache);
destructor Destroy; override;
{ Load the animation scenes.
Must be called (this or some other loading routine like LoadFromFile)
before you do almost anything with this object.
@link(Loaded) changes to @true after calling this.
@param(RootNodes
Models describing the "predefined" frames of animation.
They must descend from TX3DRootNode.
For all nodes except the first: They are @italic(always)
owned by this class --- that's needed,
because actually we may do some operations on these models when
building animation (including even freeing some RootNodes,
if we will find that they are equivalent to some other RootNodes).
They all must point to different objects.
You must supply at least one item here (you cannot make an animation
from 0 items).)
@param(Times Array specifying the point of time for each "predefined"
frame. Length of this array must equal to length of RootNodes array.)
@param(ScenesPerTime
This says how many scenes will be used for period of time equal to 1.0.
This will determine Scenes.Count.
RootNodes[0] always takes Scenes[0] and RootNodes[High(RootNodes)]
always takes Scenes[Scenes.High].
Note that if we will find that some nodes along the way are
exactly equal, we may drop scenes count between --- because if they are
both equal, we can simply render the same scene for some period
of time. This is an optimization, and you shouldn't notice it at all,
since rendeting will be the same (but less memory-consuming).
Special value ScenesPerTime = 0 means that you want to have only the
RootNodes you explicitly passed in the scene, not more.
No more intermediate scenes will ever be created.
This creates a trivial animation that suddenly jumps from
one RootNode to the next at specified times. It may be useful if you
already have generated a lot of RootNodes, densely distributed
over time, and you don't need TCastlePrecalculatedAnimation to insert any more
scenes.)
@param(EqualityEpsilon
This will be used for comparing fields, to decide if two fields
(and, consequently, nodes) are equal. It will be simply
passed to TX3DField.Equals.
You can pass here 0 to use exact comparison, but it's
advised to use here something > 0. Otherwise we could waste
display list memory (and loading time) for many frames of the
same node that are in fact equal.)
}
procedure Load(
RootNodes: TX3DNodeList;
AOwnsFirstRootNode: boolean;
ATimes: TSingleList;
ScenesPerTime: Cardinal;
const EqualityEpsilon: Single);
{ Load precalculated animation by playing a single VRML/X3D file with
events (interpolators, TimeSensor and such working).
Conceptually, this "records" interactive animation stored in VRML/X3D file
into TCastlePrecalculatedAnimation precalculated animation.
ATimeBegin, ATimeEnd tell what time slice should be recorded.
They will also set @link(TimeBegin) and @link(TimeEnd) properties.
@param(ScenesPerTime
tells with what density should the animation be recorded.
See @link(Load) for ScenesPerTime, EqualityEpsilon precise documentation.
Note that special value ScenesPerTime = 0 is interpreted here as
"record only one, initial frame".)
@param(ProgressTitle When <> '' we will use Progress.Init, Step, Fini
to display nice progress of operation.) }
procedure LoadFromEvents(
RootNode: TX3DRootNode;
AOwnsRootNode: boolean;
const ATimeBegin, ATimeEnd: Single;
ScenesPerTime: Cardinal;
const EqualityEpsilon: Single;
const ProgressTitle: string);
{ Load a dumb animation that consists of only one frame (so actually
there's no animation, everything is static).
This just calls @link(Load) with parameters such that
@orderedList(
@item(RootNodes list contains one specified node)
@item(Times contain only one item 0.0)
@item(ScenesPerTime and EqualityEpsilon have some unimportant
values --- they are not meaningfull when you have only one scene)
)
This is usefull when you know that you have a static scene,
but still you want to treat it as TCastlePrecalculatedAnimation. }
procedure LoadStatic(RootNode: TX3DNode; AOwnsRootNode: boolean);
{ Load animation parameters (models to use, times to use and such)
from given file.
Various file formats are possible, everything that can be handled by
Load3DSequence, in particular simple 3D model files, MD3,
kanim (described on
[http://castle-engine.sourceforge.net/kanim_format.php]).
If you need more control over loading, for example you want to
change some parameters at loading (for example, ScenesPerTime
and EqualityEpsilon of kanim files), you should use
more flexible (and less comfortable to use)
LoadFromFileToVars class procedure (specialized for kanim files)
or Load3DSequence (if you want to handle any files).
@link(Loaded) property changes to @true after calling this.
@param(AllowStdIn If @true, then URL = '-' is understood
as "standard input".)
@param(LoadTime If @true then loading changes
current TimeLoop and TimeBackwards properties.
Sometimes this is sensible (you want to allow control over them
from the file), sometimes not (e.g. you set suitable values for them
by code).
Note that, independent of this, you can always change TimeLoop
and TimeBackwards properties later,
since these properties are writeable at any time.)
@param(Smoothness Scales the number of scenes
created per second. Values > 1 make better quality but also use more memory.
If this parameter isn't given, we use global AnimationSmoothness
(which is by default 1, but may be globally changed).)
@groupBegin }
procedure LoadFromFile(const URL: string;
const AllowStdIn: boolean; const LoadTime: boolean;
const Smoothness: Float);
procedure LoadFromFile(const URL: string;
const AllowStdIn: boolean; const LoadTime: boolean);
{ @groupEnd }
{ This releases all resources allocared by Load (or LoadFromFile).
@link(Loaded) property changes to @false after calling this.
It's safe to call this even if @link(Loaded) is already @false --- then
this will do nothing. }
procedure Close;
property Loaded: boolean read FLoaded;
{ Is the RootNode in first scene owned by this TCastlePrecalculatedAnimation instance?
If yes, it will be freed at closing the animation.
Otherwise, you are responsible for freeing it yourself
(but you cannot do this while animation is loaded, anyway). }
property OwnsFirstRootNode: boolean
read FOwnsFirstRootNode write SetOwnsFirstRootNode;
{ You can read anything from Scenes below. But you cannot set some
things: don't set their scenes Attributes properties.
Use only our @link(Attributes).
The scenes here have TCastleSceneCore.Static set to @true, which means
we assume you will not modify their VRML nodes graph (by TX3DField.Send
and such). Note that this doesn't prevent you from enabling
TCastleSceneCore.ProcessEvents on the first scene (TCastleSceneCore.ProcessEvents
will be property handled regardless of TCastleSceneCore.Static value). }
property Scenes[I: Integer]: TCastleScene read GetScenes;
function ScenesCount: Integer;
{ Just a shortcut for Scenes[0]. }
function FirstScene: TCastleScene;
{ Just a shortcut for Scenes[ScenesCount - 1]. }
function LastScene: TCastleScene;
{ Prepare all scenes for rendering. Basically, this calls
PrepareResources(...) for all Scenes.
There's also a special memory (and prepare time) optimization used
for prManifoldAndBorderEdges: we use the fact that animation scenes are
"structurally equal", and so prepare and share one manifold edges
information for all scenes.
ProgressStep = @true is especially useful with this: we'll call
Progress.Step then after preparing each scene.
For portability, always check PrepareResourcesSteps, but for now this
is just always equal ScenesCount. }
procedure PrepareResources(Options: TPrepareResourcesOptions;
ProgressStep: boolean; BaseLights: TAbstractLightInstancesList); override;
function PrepareResourcesSteps: Cardinal; override;
{ Free resources for all scenes, it's useful if you know
that you will not need some allocated resources anymore and you
want to conserve memory use.
See TCastleSceneCore.FreeResource documentation for a description of what
are possible resources to free. }
procedure FreeResources(Resources: TSceneFreeResources);
{ Close anything associated with current OpenGL context in this class.
This calls GLContextClose on every Scenes[], and additionally may close
some other internal things here. }
procedure GLContextClose; override;
{ Just a shortcut for TimeEnd - TimeBegin. }
function TimeDuration: Single;
{ This is TimeDuration * 2 if TimeBackwards, otherwise it's just
TimeDuration. In other words, this is the time of the one "full"
(forward + backward) animation. }
function TimeDurationWithBack: Single;
{ First and last time that you passed to Load (or that were read
from file by LoadFromFile).
In other words, Times[0] and Times[High(Times)].
@groupBegin }
property TimeBegin: Single read FTimeBegin;
property TimeEnd: Single read FTimeEnd;
{ @groupEnd }
{ Appropriate scene from @link(Scenes) based on given Time.
If Time is between given TimeBegin and TimeEnd,
then this will be appropriate scene in the middle.
For Time outside the range TimeBegin .. TimeEnd
behavior depends on TimeLoop and TimeBackwards properties:
@unorderedList(
@item(When not TimeLoop and not TimeBackwards then:
If Time is < TimeBegin, always the first scene will
be returned. If Time is > TimeEnd, always the last scene will
be returned.
So there will no real animation outside
TimeBegin .. TimeEnd timeline.)
@item(When not TimeLoop and TimeBackwards then:
If Time is < TimeBegin, always the first scene will
be returned. If Time is between TimeEnd and
TimeEnd + TimeDuration, then the animation
will be played backwards. When Time is > TimeEnd + TimeDuration,
again always the first scene will be returned.
So between TimeEnd and TimeEnd + TimeDuration
animation will be played backwards, and
there will no real animation outside
TimeBegin .. TimeEnd + TimeDuration timeline.)
@item(When TimeLoop and not TimeBackwards then:
Outside TimeBegin .. TimeEnd, animation will cycle.
This means that e.g. between TimeEnd and TimeEnd + TimeDuration
animation will be played just like between TimeBegin and TimeEnd.)
@item(When TimeLoop and TimeBackwardsm then:
Outside TimeBegin .. TimeEnd, animation will cycle.
Cycle between TimeEnd and TimeEnd + TimeDuration will
go backwards. Cycle between TimeEnd + TimeDuration
and TimeEnd + TimeDuration * 2 will again go forward.
And so on.)
)
Overloaded version with explicit Loop parameter ignores the TimeLoop
property. This way you can force looping (or force not looping),
regardless of the TimeLoop property, so also regardless
of loop setting in kanim file.
@groupBegin }
function Scene(const Time: Single): TCastleScene;
function Scene(const Time: Single; const Loop: boolean): TCastleScene;
{ @groupEnd }
{ Appropriate scene from @link(Scenes) based on current @link(Time).
This is just a shortcut for Scene(@link(Time)),
useful if you track animation time in our @link(Time) property. }
function CurrentScene: TCastleScene;
{ Attributes controlling rendering.
See TSceneRenderingAttributes and TRenderingAttributes
for documentation of properties.
You can change properties of this
object at any time, but beware that some changes may force
time-consuming regeneration of some things (like OpenGL display lists)
in the nearest Render of the scenes.
So explicitly calling PrepareResources may be useful after
changing these Attributes.
Note that Attributes may be accessed and even changed when the scene
is not loaded (e.g. before calling Load / LoadFromFile).
Also, Attributes are preserved between various animations loaded. }
function Attributes: TSceneRenderingAttributes;
{ The sum of bounding boxes of all animation frames.
Result of this function is cached, which means that it usually returns
very fast. But you have to call ChangedAll when you changed something
inside Scenes[] using some direct Scenes[].RootNode operations,
to force recalculation of this box. }
function BoundingBox: TBox3D; override;
{ Call this before directly freeing some VRML nodes in animation scenes. }
procedure BeforeNodesFree;
{ Call this when you changed something
inside Scenes[] using some direct Scenes[].RootNode operations.
This calls TCastleScene.ChangedAll on all Scenes[]
and invalidates some cached things inside this class. }
procedure ChangedAll;
{ Returns some textual info about this animation.
Similar to TCastleScene.Info. }
function Info(
ATriangleVerticesCounts,
ABoundingBox,
AManifoldAndBorderEdges: boolean): string;
{ Handling key and mouse events.
We pass key and mouse events only if there's exactly one scene
(ScenesCount = 1), as there's no sensible way of activating
VRML/X3D events when TCastlePrecalculatedAnimation contains
more than one scene.
(Precalculated animation of this class, and interactive
animation by TCastleSceneCore.ProcessEvents do not mix sensibly.)
So when ScenesCount = 1, we simply pass key and mouse events to
the only Scene[0]. Be sure to turn on @code(Scene[0].ProcessEvents := true)
if you want to make actual use of it.
@groupBegin }
function Press(const Event: TInputPressRelease): boolean; override;
function Release(const Event: TInputPressRelease): boolean; override;
{ @groupEnd }
procedure Update(const SecondsPassed: Single; var RemoveMe: TRemoveType); override;
{ Initial world time, set by the ResetTimeAtLoad call.
This can be useful for showing user
time like @code("Animation Time: LoadTime + %f") on status bar.
0 means that starting @link(Time) was TimeBegin of the animation
(0.0 in case of normal VRML files, usually 0.0 in case of Kanim).
Note that even when TimeBegin <> 0 (for Kanim), we still set
TimeAtLoad to 0, this is nicer to show to user.
Other value means that we used current real time as time origin,
following VRML/X3D specification.
See also [http://castle-engine.sourceforge.net/x3d_time_origin_considered_uncomfortable.php] }
property TimeAtLoad: TFloatTime read FTimeAtLoad;
{ Current time of the animation. Although you do not have to use it:
you can always acccess any point in time of the animation by @link(Scene).
But sometimes tracking the current time here is most natural
and comfortable.
When we have exactly one scene in Scenes, our methods (ResetTime,
ResetTimeAtLoad and Update) will synchronize Scenes[0].Time
always to the same value as our own @link(Time).
This makes time-dependent nodes (like TimeSensor,
MovieTexture etc.) inside this scene work Ok. }
property Time: TFloatTime read FTime;
{ Set @link(Time) to initial value after loading a world. }
procedure ResetTimeAtLoad(const ForceTimeOrigin: boolean = false);
{ Set @link(Time) to arbitrary value. }
procedure ResetTime(const NewValue: TFloatTime);
procedure Render(const Frustum: TFrustum; const Params: TRenderParams); override;
procedure RenderShadowVolume(
ShadowVolumeRenderer: TBaseShadowVolumeRenderer;
const ParentTransformIsIdentity: boolean;
const ParentTransform: TMatrix4Single); override;
procedure UpdateGeneratedTextures(
const RenderFunc: TRenderFromViewFunction;
const ProjectionNear, ProjectionFar: Single;
const OriginalViewport: TRectangle); override;
procedure VisibleChangeNotification(const Changes: TVisibleChanges); override;
function Dragging: boolean; override;
property Cache: TGLRendererContextCache read FCache;
{ Turn this on to treat specially the case when a single scene (Scenes.Count = 1)
is loaded: we will set this scene's Static = @false.
This allows you to enable VRML/X3D events and dynamically change the scene
in this very special case.
The normal behavior, when we load many scenes (or when this property is @false),
is to set all children scenes Static = @true.
Practically, this is useful only for tools like view3dscene, that want
to have full VRML/X3D events when possible, and at the same time they want
to load everything as TCastlePrecalculatedAnimation, for ease of coding.
To put it simply, just don't use this in normal programs -- it's a hack.
Although Static can be later changed, but changing it (after loading) to @false
is expensive (needs ChangedAll, that also recalculates shape tree, forces
shape octree and other recalculations). That's why this property is needed,
it sets Static correctly before loading the contents. }
property TryFirstSceneDynamic: boolean
read FTryFirstSceneDynamic write FTryFirstSceneDynamic default false;
published
{ Is the animation time playing, and how fast.
For exact meaning of our TimePlaying, TimePlayingSpeed, see
TCastleSceneCore.TimePlaying, TCastleSceneCore.TimePlayingSpeed.
Like in TCastleSceneCore, these are realized by our @link(Update) method,
so Time is automatically increased in @link(Update) which is called
automatically if you added this to some TCastleWindowCustom.Controls or
TCastleControlCustom.Controls.
Note that Scenes[0].TimePlaying, Scenes[0].TimePlayingSpeed do not matter
when you're operating on the TCastlePrecalculatedAnimation level.
They will not affect our @link(Time), or even Scenes[0].Time,
and they will not be synchronized with our values.
@groupBegin }
property TimePlaying: boolean read FTimePlaying write FTimePlaying default true;
property TimePlayingSpeed: Single read FTimePlayingSpeed write FTimePlayingSpeed default 1.0;
{ @groupEnd }
{ See @link(Scene) for precise description what this property does. }
property TimeLoop: boolean read FTimeLoop write FTimeLoop default true;
{ See @link(Scene) for precise description what this property does. }
property TimeBackwards: boolean
read FTimeBackwards write FTimeBackwards default false;
{ Should collision checking check also last animation frame.
Regardless of this value, we always check collision with the
first animation frame (FirstScene), of course only when
FirstScene.OctreeCollisions is initialized, and only if
@link(GetCollides) (which includes @link(GetExists)).
When CollisionUseLastScene is @true, we will also check collision
with the last animation frame's octree, i.e. LastScene.OctreeCollisions.
(Of course, only if it's initialized, e.g. by adding
ssDynamicCollisions to the LastScene.Spatial property.)
So when CollisionUseLastScene, collision checking sees the animation
as a sum of first and last frames geometry.
CollisionUseLastScene
is useful if the object is moving, but the move is very slight,
so that the sum of first and last scenes geometry is good enough
approximation of the whole geometry at any point of the animation.
Although it seems like a totally dumb way to check for collisions,
it's suitable for many purposes (see e.g. uses on "castle hall" level),
it's simple and not memory-consuming, and you don't have to take
any action when animation frame changes (because @link(Time) changes
don't change the colliding geometry, so the animation is static from
the point of view of collision checking routines).
TODO: In the future other collision methods may be available.
First of all, checking with sum of all bounding boxes, or with particular
scene time box, should be available. }
property CollisionUseLastScene: boolean
read FCollisionUseLastScene
write FCollisionUseLastScene default false;
{ At loading, process the animation to support shadow maps.
See TCastleSceneCore.ShadowMaps and related properties for documentation.
@groupBegin }
property ShadowMaps: boolean read FShadowMaps write SetShadowMaps default true;
property ShadowMapsDefaultSize: Cardinal
read FShadowMapsDefaultSize write SetShadowMapsDefaultSize
default TCastleSceneCore.DefaultShadowMapsDefaultSize;
{ @groupEnd }
property InitialViewpointIndex: Cardinal
read FInitialViewpointIndex write FInitialViewpointIndex;
property InitialViewpointName: string
read FInitialViewpointName write FInitialViewpointName;
end;
TCastlePrecalculatedAnimationList = specialize TFPGObjectList<TCastlePrecalculatedAnimation>;
const
DefaultAnimationSmoothness = 1.0;
var
{ Default Smoothness value for TCastlePrecalculatedAnimation.LoadFromFile.
This allows to globally control the precalculated animations quality.
Saved as user preference to Config (if your program will call Config.Load
and Config.Save, see CastleConfig). }
AnimationSmoothness: Single = DefaultAnimationSmoothness;
procedure Register;
implementation
uses Math, X3DFields, CastleProgress, X3DLoad, CastleLog, DateUtils,
CastleShapes, CastleConfig;
procedure Register;
begin
RegisterComponents('Castle', [TCastlePrecalculatedAnimation]);
end;
{ TAnimationScene ------------------------------------------------------ }
type
TAnimationScene = class(TCastleScene)
private
FParentAnimation: TCastlePrecalculatedAnimation;
public
constructor CreateForAnimation(
ARootNode: TX3DRootNode; AOwnsRootNode: boolean;
ACustomRenderer: TGLRenderer;
AParentAnimation: TCastlePrecalculatedAnimation;
AStatic: boolean);
property ParentAnimation: TCastlePrecalculatedAnimation read FParentAnimation;
procedure DoGeometryChanged(const Change: TGeometryChange;
LocalGeometryShape: TShape); override;
procedure VisibleChangeHere(const Changes: TVisibleChanges); override;
procedure CursorChange; override;
function Shared: TCastleScene; override;
end;
constructor TAnimationScene.CreateForAnimation(
ARootNode: TX3DRootNode; AOwnsRootNode: boolean;
ACustomRenderer: TGLRenderer;
AParentAnimation: TCastlePrecalculatedAnimation;
AStatic: boolean);
begin
{ ParentAnimation is used by DoGeometryChanged, which is virtual and
*may* be called by ChangedAll, which *may* called by inherited constructor.
So ParentAnimation must be set even before inherited constructor. }
FParentAnimation := AParentAnimation;
inherited CreateCustomRenderer(nil, ACustomRenderer);
ShadowMaps := FParentAnimation.ShadowMaps;
ShadowMapsDefaultSize := FParentAnimation.ShadowMapsDefaultSize;
InitialViewpointIndex := FParentAnimation.InitialViewpointIndex;
InitialViewpointName := FParentAnimation.InitialViewpointName;
Static := AStatic;
Load(ARootNode, AOwnsRootNode);
end;
function TAnimationScene.Shared: TCastleScene;
begin
Result := FParentAnimation.FirstScene;
end;
procedure TAnimationScene.DoGeometryChanged(const Change: TGeometryChange;
LocalGeometryShape: TShape);
begin
inherited;
ParentAnimation.ValidBoundingBox := false;
end;
procedure TAnimationScene.VisibleChangeHere(const Changes: TVisibleChanges);
begin
inherited;
ParentAnimation.VisibleChangeHere(Changes);
end;
procedure TAnimationScene.CursorChange;
begin
inherited;
{ Maybe in the future we will update here our own cursor, for now: no need.
See T3DList.ListCursorChange implementation comments. }
ParentAnimation.CursorChange;
end;
{ EModelsStructureDifferent -------------------------------------------------- }
type
EModelsStructureDifferent = class(Exception)
constructor CreateFmt(const S: string; const Args: array of const);
end;
constructor EModelsStructureDifferent.CreateFmt(const S: string;
const Args: array of const);
begin
inherited CreateFmt('Models are structurally different: ' + S, Args);
end;
{ TCastlePrecalculatedAnimation ------------------------------------------------------------ }
{ About Create and CreateCustomCache relationship:
Note that Create cannot call CreateCustomCache and depend that
CreateCustomCache calls "inherited Create". This wouldn't be nice
for descendants: If some TCastlePrecalculatedAnimation descendant would override "Create"
to do his initialization, and we would create this descendant by
CreateCustomCache --- we would miss executing descendant's constructor code.
So only our Create may call "inherited Create".
CreateCustomCache should always call just "Create" (virtual). }
constructor TCastlePrecalculatedAnimation.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
if Cache = nil then
FCache := GLContextCache;
Renderer := TGLRenderer.Create(TSceneRenderingAttributes, Cache);
FTimeLoop := true;
FTimeBackwards := false;
FTimePlaying := true;
FTimePlayingSpeed := 1.0;
FShadowMaps := true;
FShadowMapsDefaultSize := TCastleSceneCore.DefaultShadowMapsDefaultSize;
end;
constructor TCastlePrecalculatedAnimation.CreateCustomCache(AOwner: TComponent;
ACache: TGLRendererContextCache);
begin
FCache := ACache;
Create(AOwner);
end;
destructor TCastlePrecalculatedAnimation.Destroy;
begin
Close;
FreeAndNil(Renderer);
FCache := nil; // just to be safe
inherited;
end;
procedure TCastlePrecalculatedAnimation.LoadCore(
GetRootNodeWithTime: TGetRootNodeWithTime;
RootNodesCount: Cardinal;
AOwnsFirstRootNode: boolean;
ScenesPerTime: Cardinal;
const EqualityEpsilon: Single);
{ This will check that Model1 and Model2 are exactly equal,
or that at least interpolating (see VRMLModelLerp) is possible.
If models are structurally different (which means that even
interpolating between Model1 and Model2 is not possible),
it will raise EModelsStructureDifferent. }
procedure CheckVRMLModelsStructurallyEqual(Model1, Model2: TX3DNode);
procedure CheckSFNodesStructurallyEqual(Field1, Field2: TSFNode);
begin
if (Field1.Value <> nil) and (Field2.Value <> nil) then
begin
CheckVRMLModelsStructurallyEqual(Field1.Value, Field2.Value);
end else
if not ((Field1.Value = nil) and (Field2.Value = nil)) then
raise EModelsStructureDifferent.CreateFmt('Field "%s" of type SFNode ' +
'is once NULL and once not-NULL', [Field1.Name]);
end;
procedure CheckMFNodesStructurallyEqual(Field1, Field2: TMFNode);
var
I: Integer;
begin
if Field1.Items.Count <> Field2.Items.Count then
raise EModelsStructureDifferent.CreateFmt(
'Different number of children in MFNode fields: "%d" and "%d"',
[Model1.VRML1ChildrenCount, Model2.VRML1ChildrenCount]);
for I := 0 to Field1.Items.Count - 1 do
CheckVRMLModelsStructurallyEqual(Field1[I], Field2[I]);
end;
var
I: Integer;
begin
{ Yes, Model1 and Model2 must have *exactly* the same classes. }
if Model1.ClassType <> Model2.ClassType then
raise EModelsStructureDifferent.CreateFmt(
'Different nodes classes: "%s" and "%s"',
[Model1.ClassName, Model2.ClassName]);
{ Make sure that *Inline content is loaded now. }
if Model1 is TInlineNode then
begin
TInlineNode(Model1).LoadInlined(false);
TInlineNode(Model2).LoadInlined(false);
end;
if Model1.NodeName <> Model2.NodeName then
raise EModelsStructureDifferent.CreateFmt(
'Different names of nodes: "%s" and "%s"',
[Model1.NodeName, Model2.NodeName]);
{ We are interested whether Model1.BaseUrl and Model2.BaseUrl will
give different results when using them to resolve relative URLs.
Simply comparing them is not good --- they may contain filenames
at the end. Stripping these filenames with ExtractURIPath
is dirty. So we just test CombineURI with a test name. }
if Model1.PathFromBaseUrl('test') <> Model2.PathFromBaseUrl('test') then
raise EModelsStructureDifferent.CreateFmt(
'BaseUrl of nodes different (will resolve relative URLs to different things): "%s" and "%s"',
[Model1.BaseUrl, Model2.BaseUrl]);
if Model1.VRML1ChildrenCount <> Model2.VRML1ChildrenCount then
raise EModelsStructureDifferent.CreateFmt(
'Different number of children in nodes: "%d" and "%d"',
[Model1.VRML1ChildrenCount, Model2.VRML1ChildrenCount]);
for I := 0 to Model1.VRML1ChildrenCount - 1 do
CheckVRMLModelsStructurallyEqual(Model1.VRML1Children[I], Model2.VRML1Children[I]);
{ Yes, the situation below can happen. *Usually* when we know
that Model1 and Model2 are equal classes then we know that
they have the same number of fields of the same type.
However, for TX3DUnknownNode, it's not that easy. Two different instances
of TX3DUnknownNode class may have completely different fields,
so we must safeguard against this. }
if Model1.Fields.Count <> Model2.Fields.Count then
raise EModelsStructureDifferent.CreateFmt(
'Different number of fields in nodes: "%d" and "%d"',
[Model1.Fields.Count, Model2.Fields.Count]);
for I := 0 to Model1.Fields.Count - 1 do
begin
if Model1.Fields[I].ClassType <> Model2.Fields[I].ClassType then
raise EModelsStructureDifferent.CreateFmt(
'Different type of field number %d in nodes: "%s" and "%s"',
[I, Model1.Fields[I].ClassName, Model2.Fields[I].ClassName]);
if Model1.Fields[I] is TSFNode then
CheckSFNodesStructurallyEqual(
TSFNode(Model1.Fields[I]), TSFNode(Model2.Fields[I])) else
if Model1.Fields[I] is TMFNode then
CheckMFNodesStructurallyEqual(
TMFNode(Model1.Fields[I]), TMFNode(Model2.Fields[I])) else
if Model1.Fields[I].CanAssignLerp then
begin
if Model1.Fields[I] is TX3DMultField then
begin
try
(Model1.Fields[I] as TX3DMultField).CheckCountEqual
(Model2.Fields[I] as TX3DMultField);
except
(* Translate EX3DMultFieldDifferentCount exception
(may be raised by TX3DMultField.CheckCountEqual above)
to EModelsStructureDifferent. *)
on E: EX3DMultFieldDifferentCount do
raise EModelsStructureDifferent.CreateFmt('%s', [E.Message]);
end;
end;
{ Else we have single-value field that can lerp.
No need to check anything in this case,
it's ready to go (that is, to lerp). }
end else
begin
{ Check fields for equality.
Some special fields like TInlineNode.FdUrl do not
have to be equal, as they don't have any role for the
"real" meaning of the model. I mean, if TInlineNode.Inlined
contents (loaded from pointed file) have the same structure,
then we're happy. And it's handy to allow this --- see e.g.
examples/models/gus_1_final.wrl and
examples/models/gus_2_final.wrl trick. }
if not (
( (Model1 is TInlineNode) and (Model1.Fields[I].Name = 'url') ) or
Model1.Fields[I].Equals(Model2.Fields[I], EqualityEpsilon)
) then
raise EModelsStructureDifferent.CreateFmt(
'Fields "%s" (class "%s") are not equal',
[Model1.Fields[I].Name, Model1.Fields[I].ClassName]);
end;
end;
end;
{ This will merge equal children of Model1 and Model2,
and check that Model1 and Model2 are exactly equal.
It assumes that models are structurally equal, i.e. that you
already did run CheckVRMLModelsStructurallyEqual over them.
It works recursively: first it checks for every children
are they equal. For each pair that is equal, it replaces
given children in Model2 with appropriate children of Model1.
At the end, if every children pair was equal and additionally
if all fields are equal, then it returns true.
Such copying of references is useful, because then we simply copy given
node's reference instead of duplicating this object.
This way Model1 and Model2 and all models interpolated along the way
can share the same object reference. This is very good, because:
1. If nodes are equal then creating new object each
time would mean that I create a lot of objects with exactly the
same contents. So memory is wasted, without any good reason.
2. For nodes like ImageTexture, this is good because then the image
is loaded from the file only once. This means that memory is saved,
once again. This also means that in case when texture file doesn't
exist, user gets only 1 warning/error message (instead of getting
warning/error message for each duplicated TImageTextureNode instance).
3. Also for nodes like ImageTexture, this means that if we use the same
GLRenderer to render every model of the animation,
then GLRenderer will recognize this and given texture
will be loaded only once for OpenGL. So loading time and
memory are saved *once again* (otherwise OpenGL would allocate
internal copy of texture for each duplicated node, once again
wasting a lot of memory).
Although 2. and 3. are actually somewhat void right now,
as we have a more general cache that caches texture resources
even across different nodes right now (the only need is to have
equal URLs).
4. And later the Shape cache of TGLRenderer can speed
up loading time and conserve memory use, if it sees the same
reference to given GeometryNode twice. }
function VRMLModelsMerge(Model1, Model2: TX3DNode): boolean;
function SFNodesMerge(Field1, Field2: TSFNode): boolean;
begin
Result := true;
{ Equality was already checked by CheckVRMLModelsStructurallyEqual,
so now if one SFNode value is not nil, we know that the other
one is not nil too. }
if Field1.Value <> nil then
begin
if VRMLModelsMerge(Field1.Value, Field2.Value) then
Field1.Value := Field2.Value else
Result := false;
end;
end;
function MFNodesMerge(Field1, Field2: TMFNode): boolean;
var
I: Integer;
begin
Result := true;
{ Note that we already know that Counts are equals,
checked already by CheckVRMLModelsStructurallyEqual. }
Assert(Field1.Items.Count = Field2.Items.Count);
for I := 0 to Field1.Items.Count - 1 do
begin
if VRMLModelsMerge(Field1[I], Field2[I]) then
begin
{ Think of this as
Field1[I] := Field2[I]
but I can't call this directly, I must use Field1.Replace
to not mess reference counts. }
Field1.Replace(I, Field2[I]);
end else
Result := false;
end;
end;
var
I: Integer;
begin
Result := true;
{ Note that this loop will iterate over every Children,
even if somewhere along the way we will already set Result to false.
Even if we already know that Result is false, we stil want to
merge Model1 and Model2 children as much as we can. }
for I := 0 to Model1.VRML1ChildrenCount - 1 do
begin
if VRMLModelsMerge(Model1.VRML1Children[I], Model2.VRML1Children[I]) then
begin
{ Tests: Writeln('merged child ', I, ' of class ',
Model1.VRML1Children[I].NodeTypeName); }
Model1.VRML1Children[I] := Model2.VRML1Children[I];
end else
Result := false;
end;
if not Result then Exit;
for I := 0 to Model1.Fields.Count - 1 do
begin
if Model1.Fields[I] is TSFNode then
begin
if not SFNodesMerge(TSFNode(Model1.Fields[I]),
TSFNode(Model2.Fields[I])) then
Result := false;
end else
if Model1.Fields[I] is TMFNode then
begin
if not MFNodesMerge(TMFNode(Model1.Fields[I]),
TMFNode(Model2.Fields[I])) then
Result := false;
end else
if Model1.Fields[I].CanAssignLerp then
begin
if not Model1.Fields[I].Equals(Model2.Fields[I], EqualityEpsilon) then
Result := false;
end;
{ Other fields were already checked by CheckVRMLModelsStructurallyEqual }
end;
end;
{ Linear interpolation between Model1 and Model2.
A = 0 means Model1, A = 1 means Model2, A between 0 and 1 is lerp
between Model1 and Model2.
If Model1 and Model2 are the same object (the same references),
then this will return just Model1. This way it keeps memory optimization
described by VRMLModelsMerge. This is also true if both Model1 and Model2
are nil: then you can safely call this and it will return also nil. }
function VRMLModelLerp(const A: Single; Model1, Model2: TX3DNode): TX3DNode;
procedure SFNodeLerp(Target, Field1, Field2: TSFNode);
begin
Target.Value := VRMLModelLerp(A, Field1.Value, Field2.Value);
end;
procedure MFNodeLerp(Target, Field1, Field2: TMFNode);
var
I: Integer;
begin
for I := 0 to Field1.Items.Count - 1 do
Target.Add(VRMLModelLerp(A, Field1[I], Field2[I]));
end;
var
I: Integer;
begin
if Model1 = Model2 then
Exit(Model1);
Result := TX3DNodeClass(Model1.ClassType).Create(Model1.NodeName,
Model1.BaseUrl);
try
{ We already loaded all inlines (in CheckVRMLModelsStructurallyEqual).
We have to mark it now, by setting Loaded := true field as necessary
inside inline nodes --- otherwise, they could be loaded again
(adding content to already existing nodes, making content loaded
more than once). }
if Result is TInlineNode then
begin
TInlineNode(Result).LoadedInlineDirectly;
end;
{ TODO: the code below doesn't deal efficiently with the situation when single
TX3DNode is used as a child many times in one of the nodes.
(through VRML "USE" keyword). Code below will then unnecessarily
create copies of such things (wasting construction time and memory),
instead of reusing the same object reference. }
for I := 0 to Model1.VRML1ChildrenCount - 1 do
Result.VRML1ChildAdd(VRMLModelLerp(A, Model1.VRML1Children[I], Model2.VRML1Children[I]));
{ TODO: for TX3DUnknownNode, we should fill here Result.Fields.
Also for TX3DPrototypeNode. }
for I := 0 to Model1.Fields.Count - 1 do
begin
if Model1.Fields[I] is TSFNode then
begin
SFNodeLerp(
(Result.Fields[I] as TSFNode),
(Model1.Fields[I] as TSFNode),
(Model2.Fields[I] as TSFNode));
end else
if Model1.Fields[I] is TMFNode then
begin
MFNodeLerp(
(Result.Fields[I] as TMFNode),
(Model1.Fields[I] as TMFNode),
(Model2.Fields[I] as TMFNode));
end else
if Model1.Fields[I].CanAssignLerp then
begin
Result.Fields[I].AssignLerp(A, Model1.Fields[I], Model2.Fields[I]);
end else
begin
{ These fields cannot be interpolated.
So just copy to Result.Fields[I]. }
Result.Fields[I].Assign(Model1.Fields[I]);
end;
end;
except
FreeAndNil(Result);
raise;
end;
end;
var
SceneStatic: boolean;
function CreateOneScene(Node: TX3DRootNode;
OwnsRootNode: boolean): TAnimationScene;
begin
Result := TAnimationScene.CreateForAnimation(
Node, OwnsRootNode, Renderer, Self, SceneStatic);
end;
var
I: Integer;
StructurallyEqual, RootNodesEqual: boolean;
LastSceneIndex: Integer;
LastSceneRootNode, NewRootNode: TX3DRootNode;
LastTime, NewTime: Single;
SceneIndex: Integer;
begin
Close;
FOwnsFirstRootNode := AOwnsFirstRootNode;
{ We want all the scenes to be dynamic only when
(TryFirstSceneDynamic and (FScenes.Count = 1)).
We don't know yet FScenes.Count, but FScenes.Count = 1 is quite special:
it only (if and only if) occurs if RootNodesCount = 1. }
SceneStatic := not (TryFirstSceneDynamic and (RootNodesCount = 1));
FScenes := TCastleSceneList.Create(false);
{ calculate FScenes contents now }
{ RootNodes[0] goes to FScenes[0], that's easy }
GetRootNodeWithTime(0, NewRootNode, NewTime);
FScenes.Count := 1;
FScenes[0] := CreateOneScene(NewRootNode, OwnsFirstRootNode);
LastSceneIndex := 0;
LastTime := NewTime;
LastSceneRootNode := NewRootNode;
{ calculate TimeBegin at this point }
FTimeBegin := NewTime;
for I := 1 to RootNodesCount - 1 do
begin
{ Now add RootNodes[I] }
GetRootNodeWithTime(I, NewRootNode, NewTime);
StructurallyEqual := false;
try
CheckVRMLModelsStructurallyEqual(LastSceneRootNode, NewRootNode);
StructurallyEqual := true;
except
on E: EModelsStructureDifferent do
begin
if Log then
WritelnLog('PrecalculatedAnimation', Format(
'Nodes %d and %d structurally different, so animation will not be smoothed between them: ',
[I - 1, I]) + E.Message);
end;
end;
FScenes.Count := FScenes.Count +
Max(1, Round((NewTime - LastTime) * ScenesPerTime));
if StructurallyEqual then
begin
{ Try to merge it with LastSceneRootNode.
Then initialize FScenes[LastSceneIndex + 1 to FScenes.Count - 1]. }
RootNodesEqual := VRMLModelsMerge(NewRootNode, LastSceneRootNode);
if RootNodesEqual then
begin
{ In this case I don't waste memory, and I'm simply reusing
LastSceneRootNode. Actually, I'm just copying FScenes[LastSceneIndex].
This way I have a series of the same instances of TCastleScene
along the way. When freeing FScenes, we will be smart and
avoid deallocating the same pointer twice. }
FreeAndNil(NewRootNode);
for SceneIndex := LastSceneIndex + 1 to FScenes.Count - 1 do
FScenes[SceneIndex] := FScenes[LastSceneIndex];
end else
begin
for SceneIndex := LastSceneIndex + 1 to FScenes.Count - 2 do
FScenes[SceneIndex] := CreateOneScene(VRMLModelLerp(
MapRange(SceneIndex, LastSceneIndex, FScenes.Count - 1, 0.0, 1.0),
LastSceneRootNode, NewRootNode) as TX3DRootNode, true);
FScenes[FScenes.Count - 1] := CreateOneScene(NewRootNode, true);
LastSceneRootNode := NewRootNode;
end;
end else
begin
{ We cannot interpolate between last and new node.
So just duplicate last node until FScenes.Count - 2,
and at FScenes.Last insert new node. }
for SceneIndex := LastSceneIndex + 1 to FScenes.Count - 2 do
FScenes[SceneIndex] := FScenes[LastSceneIndex];
FScenes[FScenes.Count - 1] := CreateOneScene(NewRootNode, true);
LastSceneRootNode := NewRootNode;
end;
LastTime := NewTime;
LastSceneIndex := FScenes.Count - 1;
end;
{ calculate TimeEnd at this point }
FTimeEnd := NewTime;
FLoaded := true;
end;
procedure TCastlePrecalculatedAnimation.Load_GetRootNodeWithTime(const Index: Cardinal;
out RootNode: TX3DRootNode; out Time: Single);
begin
RootNode := Load_RootNodes[Index] as TX3DRootNode;
Time := Load_Times[Index];
end;
procedure TCastlePrecalculatedAnimation.Load(
RootNodes: TX3DNodeList;
AOwnsFirstRootNode: boolean;
ATimes: TSingleList;
ScenesPerTime: Cardinal;
const EqualityEpsilon: Single);
begin
Assert(RootNodes.Count = ATimes.Count);
Load_RootNodes := RootNodes;
Load_Times := ATimes;
LoadCore(@Load_GetRootNodeWithTime, RootNodes.Count,
AOwnsFirstRootNode, ScenesPerTime, EqualityEpsilon);
end;
procedure TCastlePrecalculatedAnimation.LoadFromEvents_GetRootNodeWithTime(
const Index: Cardinal;
out RootNode: TX3DRootNode; out Time: Single);
begin
Time := LoadFromEvents_TimeBegin;
if LoadFromEvents_ScenesPerTime <> 0 then
Time += Index / LoadFromEvents_ScenesPerTime;
if Index = 0 then
LoadFromEvents_Scene.ResetTime(Time) else
LoadFromEvents_Scene.SetTime(Time);
RootNode := LoadFromEvents_Scene.RootNode.DeepCopy as TX3DRootNode;
end;
procedure TCastlePrecalculatedAnimation.LoadFromEvents_GetRootNodeWithTime_Progress(
const Index: Cardinal;
out RootNode: TX3DRootNode; out Time: Single);
begin
LoadFromEvents_GetRootNodeWithTime(Index, RootNode, Time);
Progress.Step;
end;
procedure TCastlePrecalculatedAnimation.LoadFromEvents(
RootNode: TX3DRootNode;
AOwnsRootNode: boolean;
const ATimeBegin, ATimeEnd: Single;
ScenesPerTime: Cardinal;
const EqualityEpsilon: Single;
const ProgressTitle: string);
var
Count: Cardinal;
begin
LoadFromEvents_ScenesPerTime := ScenesPerTime;
LoadFromEvents_TimeBegin := ATimeBegin;
LoadFromEvents_Scene := TCastleSceneCore.Create(nil);
try
LoadFromEvents_Scene.Load(RootNode, AOwnsRootNode);
Count := Max(1, Round((ATimeEnd - ATimeBegin) * ScenesPerTime));
LoadFromEvents_Scene.ProcessEvents := true;
if ProgressTitle <> '' then
begin
Progress.Init(Count, ProgressTitle);
try
LoadCore(@LoadFromEvents_GetRootNodeWithTime_Progress, Count,
true, 0, EqualityEpsilon);
finally
Progress.Fini;
end;
end else
begin
LoadCore(@LoadFromEvents_GetRootNodeWithTime, Count,
true, 0, EqualityEpsilon);
end;
{ Although LoadCore sets FTimeEnd already, it may be a little
smaller than ATimeEnd if ScenesPerTime is very small.
Last scene generated by LoadFromEvents_GetRootNodeWithTime
will not necessarily "hit" exactly TimeEnd.
In particular, when ScenesPerTime = 0, LoadCore will just set
FTimeEnd to TimeBegin...
Since we guarantee in the interface that FTimeEnd will be exactly
equal to ATimeEnd after LoadFromEvents, we fix it here. }
FTimeEnd := ATimeEnd;
finally FreeAndNil(LoadFromEvents_Scene) end;
end;
procedure TCastlePrecalculatedAnimation.LoadStatic(
RootNode: TX3DNode;
AOwnsRootNode: boolean);
var
RootNodes: TX3DNodeList;
ATimes: TSingleList;
begin
RootNodes := TX3DNodeList.Create(false);
try
ATimes := TSingleList.Create;
try
RootNodes.Add(RootNode);
ATimes.Add(0);
Load(RootNodes, AOwnsRootNode, ATimes, 1, 0.0);
finally FreeAndNil(ATimes) end;
finally FreeAndNil(RootNodes) end;
end;
procedure TCastlePrecalculatedAnimation.LoadFromFile(const URL: string;
const AllowStdIn, LoadTime: boolean; const Smoothness: Float);
var
Times: TSingleList;
RootNodes: TX3DNodeList;
ScenesPerTime: Cardinal;
EqualityEpsilon: Single;
NewTimeLoop, NewTimeBackwards: boolean;
begin
Times := TSingleList.Create;
RootNodes := TX3DNodeList.Create(false);
try
Load3DSequence(URL, AllowStdIn,
RootNodes, Times, ScenesPerTime, EqualityEpsilon,
NewTimeLoop, NewTimeBackwards);
ScenesPerTime := Round(ScenesPerTime * Smoothness);
Load(RootNodes, true, Times, ScenesPerTime, EqualityEpsilon);
if LoadTime then
begin
TimeLoop := NewTimeLoop;
TimeBackwards := NewTimeBackwards;
end;
WritelnLog('PrecalculatedAnimation', 'Loaded %s: %d precalculated scenes',
[URL, ScenesCount]);
finally
FreeAndNil(Times);
FreeAndNil(RootNodes);
end;
end;
procedure TCastlePrecalculatedAnimation.LoadFromFile(const URL: string;
const AllowStdIn: boolean; const LoadTime: boolean);
begin
LoadFromFile(URL, AllowStdIn, LoadTime, AnimationSmoothness);
end;
procedure TCastlePrecalculatedAnimation.Close;
var
I: Integer;
begin
{ This is called from destructor, so this must always check whether
things are <> nil before trying to free them. }
GLContextClose;
if FScenes <> nil then
begin
{ Although FScenes.Count should always be > 0 when FScenes allocated,
this is a destructor so we must handle various abnormal situations
if we exit with exception. }
if FScenes.Count <> 0 then
begin
{ Now we must note that we may have a sequences of the same scenes
on FScenes. So we must deallocate smartly, to avoid deallocating
the same pointer more than once. }
for I := 0 to FScenes.Count - 2 do
begin
if FScenes[I] = FScenes[I+1] then
FScenes[I] := nil { set to nil, just for safety } else
FPGObjectList_FreeAndNilItem(FScenes, I);
end;
FPGObjectList_FreeAndNilItem(FScenes, FScenes.Count - 1);
end;
FreeAndNil(FScenes);
end;
ValidBoundingBox := false;
FLoaded := false;
end;
function TCastlePrecalculatedAnimation.GetScenes(I: Integer): TCastleScene;
begin
Result := FScenes[I];
end;
function TCastlePrecalculatedAnimation.ScenesCount: Integer;
begin
if Loaded then
Result := FScenes.Count else
Result := 0;
end;
function TCastlePrecalculatedAnimation.FirstScene: TCastleScene;
begin
Result := FScenes.First;
end;
function TCastlePrecalculatedAnimation.LastScene: TCastleScene;
begin
Result := FScenes.Last;
end;
procedure TCastlePrecalculatedAnimation.PrepareResources(Options: TPrepareResourcesOptions;
ProgressStep: boolean; BaseLights: TAbstractLightInstancesList);
var
I: Integer;
SceneOptions: TPrepareResourcesOptions;
begin
if not Loaded then Exit;
for I := 0 to FScenes.Count - 1 do
begin
{ For I <> 0, we don't want to pass prManifoldAndBorderEdges to scenes. }
SceneOptions := Options;
if I <> 0 then
Exclude(SceneOptions, prManifoldAndBorderEdges);
FScenes[I].PrepareResources(SceneOptions, false, BaseLights);
{ TODO: this isn't so simple, since not all scenes have to
be structurally equal anymore. }
if (prManifoldAndBorderEdges in Options) and (I <> 0) then
FScenes[I].ShareManifoldAndBorderEdges(
FScenes[0].ManifoldEdges, FScenes[0].BorderEdges);
if ProgressStep then
Progress.Step;
end;
end;
function TCastlePrecalculatedAnimation.PrepareResourcesSteps: Cardinal;
begin
Result := ScenesCount;
end;
procedure TCastlePrecalculatedAnimation.FreeResources(Resources: TSceneFreeResources);
var
I: Integer;
begin
for I := 0 to FScenes.Count - 1 do
FScenes[I].FreeResources(Resources);
end;
procedure TCastlePrecalculatedAnimation.GLContextClose;
{ Note that this is called from destructor, so we must be extra careful
here and check is everything <> nil before freeing it. }
begin
if FScenes <> nil then
FScenes.GLContextClose;
if Renderer <> nil then
Renderer.UnprepareAll;
end;
function TCastlePrecalculatedAnimation.TimeDuration: Single;
begin
Result := TimeEnd - TimeBegin;
end;
function TCastlePrecalculatedAnimation.TimeDurationWithBack: Single;
begin
Result := TimeDuration;
if TimeBackwards then
Result *= 2;
end;
function TCastlePrecalculatedAnimation.Scene(const Time: Single): TCastleScene;
begin
Result := Scene(Time, TimeLoop);
end;
function TCastlePrecalculatedAnimation.Scene(const Time: Single;
const Loop: boolean): TCastleScene;
var
SceneNumber: Integer;
DivResult: SmallInt;
ModResult: Word;
begin
if FScenes.Count = 1 then
begin
{ In this case TimeBegin = TimeEnd, so it's better to not perform
any MapRange(Time, TimeBegin, TimeEnd, ...) calculation here
and just treat this as a special case. }
SceneNumber := 0;
end else
begin
{ I use FScenes.Count, not FScenes.Count - 1 as the highest range value.
This is critical. On the short range (not looping), it may seem
that FScenes.Count - 1 is more appropriate, since the last scene
corresponds exactly to TimeEnd. But that's not good for looping:
in effect float range TimeDuration would contain one scene less,
and so when looking at large Time values, the scenes are slightly shifted
within time.
This causes problems when code relies on the meaning of some time
values. E.g. if TimeBegin = 0, you expect that Time = k * TimeEnd,
for any k, will result in the LastScene generated (assuming
backwards is @true). This is needed for tricks like smooth animations
concatenation, see "the rift" in RiftCreatures unit.
When using FScenes.Count - 1, we would break this, as scenes are shifted
by one in each range. }
SceneNumber := Floor(MapRange(Time, TimeBegin, TimeEnd, 0, FScenes.Count));
DivUnsignedMod(SceneNumber, FScenes.Count, DivResult, ModResult);
if Loop then
begin
if TimeBackwards and Odd(DivResult) then
SceneNumber := FScenes.Count - 1 - ModResult else
SceneNumber := ModResult;
end else
begin
if TimeBackwards then
begin
if (DivResult < 0) or (DivResult > 1) then
SceneNumber := 0 else
if DivResult = 1 then
SceneNumber := FScenes.Count - 1 - ModResult;
{ else DivResult = 0, so SceneNumber is already correct }
end else
begin
if DivResult < 0 then
SceneNumber := 0 else
if DivResult > 0 then
SceneNumber := FScenes.Count - 1;
end;
end;
end;
Result := FScenes[SceneNumber];
end;
function TCastlePrecalculatedAnimation.Attributes: TSceneRenderingAttributes;
begin
Result := TSceneRenderingAttributes(Renderer.Attributes);
end;
function TCastlePrecalculatedAnimation.BoundingBox: TBox3D;
procedure ValidateBoundingBox;
var
I: Integer;
begin
FBoundingBox := FScenes[0].BoundingBox;
for I := 1 to FScenes.Count - 1 do
FBoundingBox.Add(FScenes[I].BoundingBox);
ValidBoundingBox := true;
end;
begin
if Loaded and GetExists then
begin
if not ValidBoundingBox then
ValidateBoundingBox;
Result := FBoundingBox;
end else
Result := EmptyBox3D;
end;
procedure TCastlePrecalculatedAnimation.BeforeNodesFree;
var
I: Integer;
begin
for I := 0 to FScenes.Count - 1 do
FScenes[I].BeforeNodesFree;
end;
procedure TCastlePrecalculatedAnimation.ChangedAll;
var
I: Integer;
begin
for I := 0 to FScenes.Count - 1 do
FScenes[I].ChangedAll;
ValidBoundingBox := false;
end;
function TCastlePrecalculatedAnimation.InfoBoundingBox: string;
var
BBox: TBox3D;
begin
BBox := BoundingBox;
Result := 'Bounding box (of the whole animation) : ' + BBox.ToNiceStr;
if not BBox.IsEmpty then
begin
Result += ', average size : ' + FloatToNiceStr(BBox.AverageSize);
end;
Result += NL;
end;
function TCastlePrecalculatedAnimation.Info(
ATriangleVerticesCounts,
ABoundingBox,
AManifoldAndBorderEdges: boolean): string;
begin
Result := '';
if ATriangleVerticesCounts then
begin
Result += FirstScene.InfoTriangleVerticesCounts;
end;
if ABoundingBox then
begin
if Result <> '' then Result += NL;
{ We do not call FirstScene.InfoBoundingBox here, instead we want
to get full bounding box of the animation. }
Result += InfoBoundingBox;
end;
if AManifoldAndBorderEdges then
begin
if Result <> '' then Result += NL;
Result += FirstScene.InfoManifoldAndBorderEdges;
end;
end;
procedure TCastlePrecalculatedAnimation.SetOwnsFirstRootNode(const Value: boolean);
var
I: Integer;
begin
if Value <> FOwnsFirstRootNode then
begin
FOwnsFirstRootNode := Value;
if FScenes <> nil then
begin
Assert(FScenes.Count > 0, 'When the Scenes <> nil, anim is loaded so should always have at least one scene');
{ OwnsFirstRootNode corresponds to FScenes[0].OwnsRootNode.
But note that it's allowed to have duplicates of scenes
in one consecutive range of FScenes. So we have to iterate over
the first FScenes (while they are equal to FScenes[0]). }
for I := 0 to FScenes.Count - 1 do
begin
if FScenes[I] = FScenes[0] then
FScenes[I].OwnsRootNode := Value else
Break;
end;
end;
end;
end;
function TCastlePrecalculatedAnimation.Press(const Event: TInputPressRelease): boolean;
begin
if ScenesCount = 1 then
Result := Scenes[0].Press(Event) else
Result := false;
end;
function TCastlePrecalculatedAnimation.Release(const Event: TInputPressRelease): boolean;
begin
if ScenesCount = 1 then
Result := Scenes[0].Release(Event) else
Result := false;
end;
procedure TCastlePrecalculatedAnimation.ResetTimeAtLoad(const ForceTimeOrigin: boolean = false);
function TimeOriginAtLoad: boolean;
var
N: TNavigationInfoNode;
begin
Result := false;
if Loaded then
begin
N := Scenes[0].NavigationInfoStack.Top;
if (N <> nil) and
(N is TKambiNavigationInfoNode) then
Result := TKambiNavigationInfoNode(N).FdTimeOriginAtLoad.Value;
end;
end;
begin
if (ScenesCount > 1) or ForceTimeOrigin or TimeOriginAtLoad then
begin
FTimeAtLoad := 0.0;
ResetTime(TimeBegin);
end else
begin
FTimeAtLoad := DateTimeToUnix(Now);
ResetTime(TimeAtLoad);
end;
end;
procedure TCastlePrecalculatedAnimation.ResetTime(const NewValue: TFloatTime);
begin
FTime := NewValue;
{ Ignored when SceneAnimation.ScenesCount <> 1, as scenes' ProcessEvents
is always false then and Time wouldn't have much sense anyway. }
if ScenesCount = 1 then
Scenes[0].ResetTime(NewValue);
end;
procedure TCastlePrecalculatedAnimation.Update(const SecondsPassed: Single; var RemoveMe: TRemoveType);
var
OldTime: TFloatTime;
begin
inherited;
{ Ignore Update calls when SecondsPassed is precisely zero
(this may happen, and is good, see TFramesPerSecond.ZeroNextSecondsPassed).
In this case, time increase will be zero so the whole code
will not do anything anyway. }
if Loaded and TimePlaying and (SecondsPassed <> 0) then
begin
OldTime := FTime;
FTime += TimePlayingSpeed * SecondsPassed;
{ When ScenesCount = 1, it's sensible for single scene to receive
events, to increase it's time. Note that TCastleSceneCore.SetTime
will signal when redisplay will be needed (something visible changed),
we don't have to worry about it.
We call Scenes[0].SetTime direcly, instead of calling Scenes[0].Update.
This way we do not have to worry to set scene's initial time, TimePlaying,
TimePlayingSpeed to our values. }
if ScenesCount = 1 then
Scenes[0].SetTime(Time);
{ Call VisibleChangeHere only if the displayed animation frame actually changed.
This way, we avoid wasting CPU cycles if the loaded scene is actually
still, or if the animation stopped running. }
if (Scene(OldTime) <> Scene(Time)) then
VisibleChangeHere([vcVisibleGeometry, vcVisibleNonGeometry]);
end;
end;
function TCastlePrecalculatedAnimation.CurrentScene: TCastleScene;
begin
Result := Scene(Time);
end;
procedure TCastlePrecalculatedAnimation.Render(const Frustum: TFrustum; const Params: TRenderParams);
begin
if Loaded and GetExists then
CurrentScene.Render(Frustum, Params);
end;
procedure TCastlePrecalculatedAnimation.RenderShadowVolume(
ShadowVolumeRenderer: TBaseShadowVolumeRenderer;
const ParentTransformIsIdentity: boolean;
const ParentTransform: TMatrix4Single);
begin
if Loaded and GetExists and CastShadowVolumes then
CurrentScene.RenderShadowVolume(ShadowVolumeRenderer,
ParentTransformIsIdentity, ParentTransform);
end;
{ We have to typecast TAnimationScene to get access to it's protected methods.
Instead of macros, this could be solved by making TAnimationScene an internal
class of TCastlePrecalculatedAnimation, but only for new FPC versions. }
{$define FirstAnimScene := TAnimationScene(FirstScene)}
{$define LastAnimScene := TAnimationScene(LastScene)}
function TCastlePrecalculatedAnimation.HeightCollision(
const Position, GravityUp: TVector3Single;
const TrianglesToIgnoreFunc: T3DTriangleIgnoreFunc;
out AboveHeight: Single; out AboveGround: P3DTriangle): boolean;
procedure MakeScene(Scene: TAnimationScene);
var
NewResult: boolean;
NewAboveHeight: Single;
NewAboveGround: PTriangle;
begin
NewResult := Scene.HeightCollision(
Position, GravityUp, TrianglesToIgnoreFunc, NewAboveHeight, NewAboveGround);
if NewAboveHeight < AboveHeight then
begin
Result := NewResult;
AboveHeight := NewAboveHeight;
AboveGround := NewAboveGround;
end;
end;
begin
Result := false;
AboveHeight := MaxSingle;
AboveGround := nil;
if Loaded and GetCollides then
begin
MakeScene(FirstAnimScene);
if CollisionUseLastScene then
MakeScene(LastAnimScene);
end;
end;
function TCastlePrecalculatedAnimation.MoveCollision(
const OldPos, ProposedNewPos: TVector3Single; out NewPos: TVector3Single;
const IsRadius: boolean; const Radius: Single;
const OldBox, NewBox: TBox3D;
const TrianglesToIgnoreFunc: T3DTriangleIgnoreFunc): boolean;
begin
if Loaded and GetCollides then
begin
Result := FirstAnimScene.MoveCollision(OldPos, ProposedNewPos, NewPos,
IsRadius, Radius, OldBox, NewBox, TrianglesToIgnoreFunc);
{ On the LastScene use MoveCollision without wall sliding.
Reason: see T3DList.MoveCollision implementation. }
if Result and CollisionUseLastScene then
begin
Result := LastAnimScene.MoveCollision(OldPos, NewPos,
IsRadius, Radius, OldBox, NewBox, TrianglesToIgnoreFunc);
end;
end else
begin
Result := true;
NewPos := ProposedNewPos;
end;
end;
function TCastlePrecalculatedAnimation.MoveCollision(
const OldPos, NewPos: TVector3Single;
const IsRadius: boolean; const Radius: Single;
const OldBox, NewBox: TBox3D;
const TrianglesToIgnoreFunc: T3DTriangleIgnoreFunc): boolean;
begin
Result := (not Loaded) or (not GetCollides) or
(FirstAnimScene.MoveCollision(OldPos, NewPos,
IsRadius, Radius, OldBox, NewBox, TrianglesToIgnoreFunc) and
( (not CollisionUseLastScene) or
LastAnimScene.MoveCollision(OldPos, NewPos,
IsRadius, Radius, OldBox, NewBox, TrianglesToIgnoreFunc) ));
end;
function TCastlePrecalculatedAnimation.SegmentCollision(const Pos1, Pos2: TVector3Single;
const TrianglesToIgnoreFunc: T3DTriangleIgnoreFunc;
const ALineOfSight: boolean): boolean;
begin
Result := Loaded and
(GetCollides or (ALineOfSight and GetExists)) and
( FirstAnimScene.SegmentCollision(Pos1, Pos2, TrianglesToIgnoreFunc, ALineOfSight) or
(CollisionUseLastScene and
(LastAnimScene.SegmentCollision(Pos1, Pos2, TrianglesToIgnoreFunc, ALineOfSight)))
);
end;
function TCastlePrecalculatedAnimation.SphereCollision(
const Pos: TVector3Single; const Radius: Single;
const TrianglesToIgnoreFunc: T3DTriangleIgnoreFunc): boolean;
begin
Result := Loaded and GetCollides and
( FirstAnimScene.SphereCollision(Pos, Radius, TrianglesToIgnoreFunc) or
(CollisionUseLastScene and
(LastAnimScene.SphereCollision(Pos, Radius, TrianglesToIgnoreFunc)))
);
end;
function TCastlePrecalculatedAnimation.BoxCollision(
const Box: TBox3D;
const TrianglesToIgnoreFunc: T3DTriangleIgnoreFunc): boolean;
begin
Result := Loaded and GetCollides and
( FirstAnimScene.BoxCollision(Box, TrianglesToIgnoreFunc) or
(CollisionUseLastScene and
(LastAnimScene.BoxCollision(Box, TrianglesToIgnoreFunc)))
);
end;
function TCastlePrecalculatedAnimation.RayCollision(
const RayOrigin, RayDirection: TVector3Single;
const TrianglesToIgnoreFunc: T3DTriangleIgnoreFunc): TRayCollision;
var
NewResult: TRayCollision;
NewNode, PreviousNode: PRayCollisionNode;
begin
Result := nil;
if Loaded and GetExists then
begin
Result := FirstAnimScene.RayCollision(RayOrigin, RayDirection, TrianglesToIgnoreFunc);
if CollisionUseLastScene then
begin
{ try the same thing on LastScene }
NewResult := LastAnimScene.RayCollision(RayOrigin, RayDirection, TrianglesToIgnoreFunc);
if NewResult <> nil then
begin
if (Result = nil) or (NewResult.Distance < Result.Distance) then
begin
SysUtils.FreeAndNil(Result);
Result := NewResult;
end else
FreeAndNil(NewResult);
end;
end;
if Result <> nil then
begin
NewNode := Result.Add;
PreviousNode := @(Result.List^[Result.Count - 2]);
NewNode^.Item := Self;
NewNode^.Point := PreviousNode^.Point;
NewNode^.Triangle := nil;
NewNode^.RayOrigin := PreviousNode^.RayOrigin;
NewNode^.RayDirection := PreviousNode^.RayDirection;
end;
end;
end;
procedure TCastlePrecalculatedAnimation.UpdateGeneratedTextures(
const RenderFunc: TRenderFromViewFunction;
const ProjectionNear, ProjectionFar: Single;
const OriginalViewport: TRectangle);
begin
inherited;
if Loaded then
CurrentScene.UpdateGeneratedTextures(RenderFunc, ProjectionNear, ProjectionFar,
OriginalViewport);
end;
procedure TCastlePrecalculatedAnimation.VisibleChangeNotification(const Changes: TVisibleChanges);
begin
inherited;
if Loaded then
CurrentScene.VisibleChangeNotification(Changes);
end;
function TCastlePrecalculatedAnimation.Dragging: boolean;
begin
Result := inherited;
if Result then Exit;
if Loaded then
Result := CurrentScene.Dragging;
end;
procedure TCastlePrecalculatedAnimation.SetShadowMaps(const Value: boolean);
var
I: Integer;
begin
if Value <> FShadowMaps then
begin
FShadowMaps := Value;
if FScenes <> nil then
begin
for I := 0 to FScenes.Count - 1 do
FScenes[I].ShadowMaps := Value;
end;
end;
end;
procedure TCastlePrecalculatedAnimation.SetShadowMapsDefaultSize(const Value: Cardinal);
var
I: Integer;
begin
if Value <> FShadowMapsDefaultSize then
begin
FShadowMapsDefaultSize := Value;
if FScenes <> nil then
begin
for I := 0 to FScenes.Count - 1 do
FScenes[I].ShadowMapsDefaultSize := Value;
end;
end;
end;
type
TConfigOptions = class
class procedure LoadFromConfig(const Config: TCastleConfig);
class procedure SaveToConfig(const Config: TCastleConfig);
end;
class procedure TConfigOptions.LoadFromConfig(const Config: TCastleConfig);
begin
AnimationSmoothness := Config.GetFloat(
'video_options/animation_smoothness', DefaultAnimationSmoothness);
end;
class procedure TConfigOptions.SaveToConfig(const Config: TCastleConfig);
begin
Config.SetDeleteFloat(
'video_options/animation_smoothness',
AnimationSmoothness, DefaultAnimationSmoothness);
end;
initialization
Config.OnLoad.Add(@TConfigOptions(nil).LoadFromConfig);
Config.OnSave.Add(@TConfigOptions(nil).SaveToConfig);
end.
|