Compare commits

..

204 Commits

Author SHA1 Message Date
mafiesto4 4e34524a08 Add profile event 2023-12-19 13:02:48 +01:00
mafiesto4 dfd3bcdcb7 Minor improvements for play mode 2023-12-19 12:57:23 +01:00
mafiesto4 3f1d851f82 Fix regression from bc2e130281 2023-12-19 12:37:47 +01:00
mafiesto4 ceb564a5e2 Update build number 2023-12-19 11:16:59 +01:00
mafiesto4 8a94e053a8 Fix crash when resizing navmesh capacity with crowd created for that navmesh 2023-12-18 21:45:27 +01:00
mafiesto4 bc2e130281 Fix calling script OnDestroy when removing actors or scripts from the scene 2023-12-18 21:43:13 +01:00
mafiesto4 72f45afa45 Merge branch 'Menotdan-fix-spriterender-prefab' 2023-12-17 20:42:10 +01:00
Menotdan 51c36223e6 Rename 'text' to 'sprite' to avoid confusion in the future. 2023-12-17 12:03:44 -05:00
Menotdan ea744ab4ac Prevent performing default spawn behavior when spawning from a prefab. 2023-12-17 12:02:56 -05:00
Menotdan 9ea5ed79f8 Merge branch 'master' into fix-spriterender-prefab 2023-12-17 11:52:31 -05:00
mafiesto4 59e2afd992 Merge branch 'Tryibion-saved-colors' 2023-12-17 13:06:08 +01:00
mafiesto4 aade14270e Merge branch 'saved-colors' of https://github.com/Tryibion/FlaxEngine into Tryibion-saved-colors 2023-12-17 13:03:57 +01:00
mafiesto4 d5a6083a55 Fix project initialization with -new if it already exists
#2092
2023-12-17 12:47:46 +01:00
mafiesto4 e448692eb9 Fix crash in UI prefab changes apply when reparenting controls
#2082
2023-12-17 12:41:51 +01:00
mafiesto4 7bcf78d0c0 Fix various crashes 2023-12-17 12:38:09 +01:00
Menotdan 9f460cd651 Fix sprite render actors spawning with overridden defaults when they are part of a prefab. 2023-12-17 04:30:54 -05:00
Tryibion b85ec46545 Change selected color. 2023-12-16 22:05:04 -06:00
Tryibion 272a147c2e Add saved colors to color picker. 2023-12-16 20:36:27 -06:00
mafiesto4 8418ca56e8 Missing comment part 2023-12-16 18:16:00 +01:00
mafiesto4 0dd7e86537 Add better Visual Script debugger tooltips display 2023-12-16 18:14:16 +01:00
mafiesto4 64e391df24 Refactor Visual Script debugger apis to use bindings generator 2023-12-16 17:58:18 +01:00
mafiesto4 c145042f52 Fix invalid BT node decorator linkage after removing it
#2059
2023-12-16 17:08:54 +01:00
mafiesto4 d8856ddaa7 Merge branch 'GoaLitiuM-dotnet8_packaging_fix' 2023-12-16 16:43:43 +01:00
mafiesto4 4263f61e85 Merge branch 'dotnet8_packaging_fix' of https://github.com/GoaLitiuM/FlaxEngine into GoaLitiuM-dotnet8_packaging_fix 2023-12-16 16:43:36 +01:00
mafiesto4 fc8c29b982 Merge branch 'GoaLitiuM-vs_csharp_build_fixes' 2023-12-16 16:43:31 +01:00
mafiesto4 4e0daab310 Merge branch 'vs_csharp_build_fixes' of https://github.com/GoaLitiuM/FlaxEngine into GoaLitiuM-vs_csharp_build_fixes 2023-12-16 16:43:21 +01:00
mafiesto4 6d5d615894 Fix crash when drawing terrain without cached neighbor chunks
#2087
2023-12-16 16:38:16 +01:00
mafiesto4 2bef880e21 Fix render target pool over-allocation when changing render resolution frequently
#2077
2023-12-16 16:15:52 +01:00
GoaLitiuM f38245b834 Fix .NET runtime packaging with installed .NET 8 SDK 2023-12-16 16:16:01 +02:00
GoaLitiuM 639803480e Improve .NET related errors during cooking process 2023-12-16 16:11:56 +02:00
GoaLitiuM 84249b3b57 Skip building main C#-project in VS solution when C++-project is present
Flax.Build is invoked twice, once for C++-project and one more time for
C#-project. Skip the C#-project by using the custom .targets file to not
break Rider's solution analysis feature.
2023-12-16 14:48:21 +02:00
GoaLitiuM d614232f8d Fix VS build issues with C# projects when engine path has spaces 2023-12-16 14:45:28 +02:00
mafiesto4 074ad171ba Merge branch 'nothingTVatYT-load-additional-scene' 2023-12-16 12:49:48 +01:00
mafiesto4 efebb29ac0 Minor tweak for #2003 2023-12-16 12:49:39 +01:00
mafiesto4 c3dd05211b Merge branch 'load-additional-scene' of https://github.com/nothingTVatYT/FlaxEngine into nothingTVatYT-load-additional-scene 2023-12-16 12:40:34 +01:00
mafiesto4 b9c145b499 Merge branch 'nothingTVatYT-fix-linuxwindow' 2023-12-16 12:40:14 +01:00
mafiesto4 c0ef2a1f58 Cleamnup code for #2020 and use internal pointer for current tracking window 2023-12-16 12:39:10 +01:00
mafiesto4 141bec0259 Merge branch 'fix-linuxwindow' of https://github.com/nothingTVatYT/FlaxEngine into nothingTVatYT-fix-linuxwindow 2023-12-16 12:35:26 +01:00
mafiesto4 9c4857a205 Fix typo in Tag comparison function
#2081
2023-12-15 15:54:18 +01:00
mafiesto4 ae5fa9619b Remove TextureUtils 2023-12-14 18:23:17 +01:00
mafiesto4 a5e072da31 Fix importing .dds files as-isif the compressed image is too small for the engine (block size validation)
#2057
2023-12-14 17:04:47 +01:00
mafiesto4 df8418da25 Add more profiling events 2023-12-14 17:00:55 +01:00
mafiesto4 d7b17ae0a6 Fix deadlock in asset thumbnails rendering queue when texture streaming fails
#2057
2023-12-14 16:05:15 +01:00
mafiesto4 60202a3add Fix deadlock when loading block-compressed texture that is smaller than block size
#2057
2023-12-14 15:14:49 +01:00
mafiesto4 014c811903 Add PixelFormatExtensions::ComputeBlockSize 2023-12-14 15:02:13 +01:00
mafiesto4 fe1a655654 Add support for displaying and reverting array values to prefab value in properties panel
#1548
2023-12-14 13:57:16 +01:00
mafiesto4 190bafea28 Merge branch 'Menotdan-boxcollider-autosize' 2023-12-14 11:05:14 +01:00
mafiesto4 51fc4c68cd Use tooltip from native function docs and support multiple colliders selected at once #2063 2023-12-14 11:04:36 +01:00
mafiesto4 b87a7d16fb Move managed code into native impl for #2063 2023-12-14 11:03:58 +01:00
mafiesto4 8a5a7851cf Merge branch 'boxcollider-autosize' of https://github.com/Menotdan/FlaxEngine into Menotdan-boxcollider-autosize 2023-12-14 10:49:46 +01:00
mafiesto4 a1e13cd2c8 Fix MissingScript to be added only when object type exists (skip for prefab instances) 2023-12-14 10:47:49 +01:00
mafiesto4 e0a085adfe Add support for loading prefab instance if the root was changed or deleted
#2050
2023-12-14 10:47:22 +01:00
mafiesto4 1874382816 Various fixes to prefabs 2023-12-13 11:05:29 +01:00
Menotdan 9454385683 Use the BoxColliderNode class for handling actor spawn events. 2023-12-12 15:46:15 -05:00
mafiesto4 d26b9818d8 Fix spawned prefab name after drag&drop into prefab window
#1865
2023-12-12 19:13:47 +01:00
mafiesto4 b297b9f185 Fix not supported dragging prefab actors between windows
#2065
2023-12-12 18:59:52 +01:00
mafiesto4 8aaa5710df Fix dark outline around Screen Space Reflections alpha blending area 2023-12-12 16:02:53 +01:00
mafiesto4 778dd2d3f0 Fix shader file include path resolve on cache load 2023-12-11 22:55:36 +01:00
mafiesto4 ff195eeccb Fix deadlock in Asset.WaitForLoaded when loading task hangs in the loading queue for a main thread sync
#2057
2023-12-11 22:35:51 +01:00
mafiesto4 a63abb534f Merge branch 'Menotdan-fix-default-prefab-instance' 2023-12-11 22:06:20 +01:00
mafiesto4 2afdb5b978 Merge branch 'fix-default-prefab-instance' of https://github.com/Menotdan/FlaxEngine into Menotdan-fix-default-prefab-instance 2023-12-11 22:06:14 +01:00
mafiesto4 ea287e9fc5 Merge branch 'nothingTVatYT-fix-plugin-clone' 2023-12-11 19:51:32 +01:00
mafiesto4 14632ecb66 Merge branch 'fix-plugin-clone' of https://github.com/nothingTVatYT/FlaxEngine into nothingTVatYT-fix-plugin-clone 2023-12-11 19:51:10 +01:00
mafiesto4 80b5e9d02a Fix doc code example
https://forum.flaxengine.com/t/could-be-a-deprecated-document-hint-of-custompostfx-on-mainrendertask/1510
2023-12-11 19:48:06 +01:00
mafiesto4 022c935eef Merge branch 'Tryibion-android-orientation' 2023-12-11 17:34:57 +01:00
mafiesto4 ad7d7f371e Merge branch 'android-orientation' of https://github.com/Tryibion/FlaxEngine into Tryibion-android-orientation 2023-12-11 17:34:47 +01:00
Tryibion 4725f51431 Move android screen orientation into platform settings 2023-12-11 10:00:19 -06:00
mafiesto4 0bb1126f1b Merge branch 'Just-Feeshy-master' 2023-12-11 16:42:54 +01:00
mafiesto4 462eb9803f Merge branch 'master' of https://github.com/Just-Feeshy/FlaxEngine into Just-Feeshy-master 2023-12-11 16:42:49 +01:00
mafiesto4 b1d4d50d47 Merge branch 'Tryibion-dd-scale' 2023-12-11 16:38:09 +01:00
mafiesto4 2ed79c9218 Merge branch 'dd-scale' of https://github.com/Tryibion/FlaxEngine into Tryibion-dd-scale 2023-12-11 16:38:05 +01:00
mafiesto4 6dd72cdf32 Add removing thumbnails for deleted assets
#1729
2023-12-11 14:14:55 +01:00
mafiesto4 865945806a Merge branch 'mtszkarbowiak-fix/swapping' 2023-12-11 11:28:32 +01:00
mafiesto4 d974998528 Merge branch 'fix/swapping' of https://github.com/mtszkarbowiak/FlaxEngine into mtszkarbowiak-fix/swapping 2023-12-11 11:28:27 +01:00
mafiesto4 efad58370f Merge branch 'Menotdan-model-prefab-freeze-fix' 2023-12-11 11:20:49 +01:00
mafiesto4 51e92a49ad Merge branch 'model-prefab-freeze-fix' of https://github.com/Menotdan/FlaxEngine into Menotdan-model-prefab-freeze-fix 2023-12-11 11:20:42 +01:00
mafiesto4 1195fe8507 Merge branch 'NoriteSC-Animaction' 2023-12-11 11:16:13 +01:00
mafiesto4 cbecd605e9 Merge branch 'Animaction' of https://github.com/NoriteSC/FlaxEngineFork into NoriteSC-Animaction 2023-12-11 11:16:01 +01:00
mafiesto4 0bcbcdb912 Merge branch 'Tryibion-invert-green' 2023-12-11 11:15:37 +01:00
nothingTVatYT 0df00fd881 wait for git processes to end 2023-12-11 04:14:17 +01:00
Menotdan fe19ffddd9 Fix freeze when prefab ID is empty. 2023-12-10 18:12:41 -05:00
Menotdan 19dbd3c4e4 Add button to resize collider manually. 2023-12-09 21:00:45 -05:00
Menotdan 01b233af10 Factor out AutoResize() function for external use. Fix build regression due to unwanted import. 2023-12-09 19:57:52 -05:00
Menotdan c895e310cb Improve box collider creation behavior to account for child actors of the collider's parent. 2023-12-09 19:19:03 -05:00
NoriteSC e508fb8cd0 added Note and Tip for blender users to Description 2023-12-10 00:23:43 +01:00
Menotdan 2f50042523 Simplify code and allow any actor as parent. 2023-12-09 17:56:09 -05:00
Menotdan 4e54e945ef Implement auto-sizing for box colliders when they are added to the scene. 2023-12-09 17:43:06 -05:00
NoriteSC 0cb064bfb3 fixed Blend Additive
problem mesh was just exploding
+ code should be faster
2023-12-09 23:41:17 +01:00
Tryibion b8ce9e8c59 Scale up drop down items with text height as needed. 2023-12-08 17:01:48 -06:00
Tryibion df83491313 Add ability to change default Android screen orientation. 2023-12-08 15:48:43 -06:00
Menotdan b1cbaf7e13 Fix default prefab instance not taking into account root position. 2023-12-08 15:46:09 -05:00
Mateusz Karbowiak f3497a2a55 Fix swapping core collections 2023-12-08 20:50:52 +01:00
Mateusz Karbowiak 86fbf05b09 Fix general swapping function 2023-12-08 20:49:47 +01:00
Diego Fonseca c17ff3926a Update Time.cpp 2023-12-08 14:35:10 -05:00
Diego Fonseca 4707f98fab Update Time.cpp 2023-12-08 14:13:50 -05:00
Tryibion a32effff1c add missing result in texture tool 2023-12-08 09:44:40 -06:00
Tryibion f346dbc9bf add comment 2023-12-08 09:39:21 -06:00
Tryibion f24b335c45 Add invert green channel option to texture tool. 2023-12-08 09:19:05 -06:00
mafiesto4 306dd43b18 Merge branch 'Menotdan-bt_infinite_loop' 2023-12-08 10:34:38 +01:00
mafiesto4 c3b6dd9884 Merge branch 'bt_infinite_loop' of https://github.com/Menotdan/FlaxEngine into Menotdan-bt_infinite_loop 2023-12-08 10:34:33 +01:00
mafiesto4 f7e9465ce8 Avoid memory alloc #2042 2023-12-08 10:29:22 +01:00
mafiesto4 f9ad6e00c2 Merge branch 'Tryibion-add-blend-anim-fix' 2023-12-08 10:28:16 +01:00
mafiesto4 73a3e147ca Merge branch 'add-blend-anim-fix' of https://github.com/Tryibion/FlaxEngine into Tryibion-add-blend-anim-fix 2023-12-08 10:26:34 +01:00
mafiesto4 59fe448987 Fix script add undo regression 9bde0f9f9b
#2041
2023-12-08 10:23:17 +01:00
Menotdan 629ebacd64 Hide loop count options if "Infinite Loop" is checked. 2023-12-08 02:01:13 -05:00
Tryibion a6a94d5f77 2nd pass on additive blend math. 2023-12-08 00:59:41 -06:00
Menotdan 4a50111f9b Add infinite loop to behavior tree loop decorator. 2023-12-08 01:46:55 -05:00
Tryibion 91033a6468 Simplify 2023-12-07 20:51:24 -06:00
Tryibion 73074b6e44 Enable Transform Lerp 2023-12-07 20:31:18 -06:00
Tryibion e9285410e2 Fix blending additive animations. 2023-12-07 20:27:24 -06:00
mafiesto4 526edb83de Add Async to anim events (false by default) to delay events execution into main thread and prevent multi-threading issues by default
#2033
2023-12-07 15:21:03 +01:00
mafiesto4 7db0ae59bb Fix managed method delegate creation to be thread-safe 2023-12-07 14:35:13 +01:00
mafiesto4 eb7d5e5df3 Fix crash when spawning actor from asyc thread into the SceneObject
#2033
2023-12-07 11:50:24 +01:00
mafiesto4 83ef9791d4 Fix missing bounds update on SkyLight radius modification 2023-12-07 11:44:05 +01:00
mafiesto4 228239632a Fix tooltip location check when wraps over the screen
#2016
2023-12-07 11:43:50 +01:00
mafiesto4 3749b35aba Fix crash in Content Storage async job when someone is using file storage and access cannot be freed 2023-12-07 11:18:18 +01:00
mafiesto4 d847dfda61 Fix issue with import scale on prefab model 2023-12-07 10:25:59 +01:00
mafiesto4 cb92110976 Add ModelPrefab to imported model prefab for reimporting functionality 2023-12-07 10:25:45 +01:00
mafiesto4 74b77bfa4c Fix regression from 38a0718b70 2023-12-06 14:34:34 +01:00
mafiesto4 32ced6e68a Fix missing surface graph edited flag after removing anim graph state transition
#2035
2023-12-06 14:27:14 +01:00
mafiesto4 23a72f2ade Fix not showing primary context menu on Visject surface if child control handled input event 2023-12-06 13:03:37 +01:00
mafiesto4 af4c662738 Merge branch 'Tryibion-scale-default' 2023-12-06 12:51:29 +01:00
mafiesto4 af4a6b80a8 Merge branch 'scale-default' of https://github.com/Tryibion/FlaxEngine into Tryibion-scale-default 2023-12-06 12:51:23 +01:00
mafiesto4 84e25a9e90 Merge branch 'Tryibion-multi-add-script' 2023-12-06 12:50:16 +01:00
mafiesto4 1eca03e50c Merge branch 'multi-add-script' of https://github.com/Tryibion/FlaxEngine into Tryibion-multi-add-script 2023-12-06 12:50:07 +01:00
mafiesto4 0cd5627845 Merge branch 'Tryibion-refac-coll-serialization' 2023-12-06 12:42:35 +01:00
mafiesto4 49c21082a1 Merge branch 'refac-coll-serialization' of https://github.com/Tryibion/FlaxEngine into Tryibion-refac-coll-serialization 2023-12-06 12:42:29 +01:00
mafiesto4 96e64b2d0a Merge branch 'Tryibion-dont-clamp-vel' 2023-12-06 12:37:35 +01:00
mafiesto4 19db8c04ad Merge branch 'dont-clamp-vel' of https://github.com/Tryibion/FlaxEngine into Tryibion-dont-clamp-vel 2023-12-06 12:36:49 +01:00
mafiesto4 a5ffde8863 Merge branch 'Tryibion-fix-select' 2023-12-06 12:32:59 +01:00
mafiesto4 9e593195c0 Merge branch 'fix-select' of https://github.com/Tryibion/FlaxEngine into Tryibion-fix-select 2023-12-06 12:31:05 +01:00
mafiesto4 9dc3889631 Merge branch 'Withaust-halffixes' 2023-12-06 12:07:01 +01:00
mafiesto4 3328c678c1 Merge branch 'halffixes' of https://github.com/Withaust/FlaxEngine into Withaust-halffixes 2023-12-06 12:06:56 +01:00
mafiesto4 4f3c7a43ab Merge branch 'Tryibion-default-not-sloppy' 2023-12-06 12:03:20 +01:00
mafiesto4 727ff155c2 Merge branch 'default-not-sloppy' of https://github.com/Tryibion/FlaxEngine into Tryibion-default-not-sloppy 2023-12-06 12:03:07 +01:00
mafiesto4 8faaaaaf54 Fix incorrect structure usage for hostfxr params siize
#2037
2023-12-06 11:20:32 +01:00
mafiesto4 7a7a43b897 Fix selecting prefab object when object from prefab is already selected 2023-12-06 11:20:01 +01:00
mafiesto4 63773f2ddf Add **option to import model file as Prefab**
#1909 #1329 #1973
2023-12-06 11:19:42 +01:00
mafiesto4 3f632b7d15 Fix incorrect empty meshes/LODs removal after auto-lod generation 2023-12-06 10:34:29 +01:00
mafiesto4 bcbc1cd413 Fix crash in mesh LOD generator if generated mesh has more indices 2023-12-06 10:33:58 +01:00
mafiesto4 7886069783 Update meshoptimizer to v0.20 2023-12-06 10:33:14 +01:00
mafiesto4 4a3be5a743 Fix crash when updating prefabs from async thread 2023-12-06 00:30:37 +01:00
mafiesto4 fdfca5156b Various fixes and stability improvements 2023-12-06 00:28:03 +01:00
mafiesto4 38a0718b70 Fix invalid tracy events from C# profiling api when profiler gets connected mid-event 2023-12-06 00:24:30 +01:00
mafiesto4 2285116bae Remove old warnings about invalid model instance buffer 2023-12-06 00:19:48 +01:00
mafiesto4 5575917c4b Fix prefab window performance with large hierarchies 2023-12-05 23:44:45 +01:00
mafiesto4 63ddf53ad3 Fix model asset thumbnail if mesh is not centered around origin 2023-12-05 23:43:54 +01:00
mafiesto4 3e940c28df Refactor prefab's objectsCache to be explicitly SceneObject values 2023-12-04 13:56:36 +01:00
mafiesto4 f654d507e5 Add Where, Select and RemoveAll to ArrayExtensions 2023-12-03 14:09:58 +01:00
mafiesto4 d6dc1f9998 Various minor tweaks 2023-12-03 14:09:23 +01:00
mafiesto4 1843689a88 Add various profiler events to analyze models importing workflow 2023-12-03 11:23:45 +01:00
mafiesto4 022a69aaf2 Continue refactoring model tool to support importing multiple objects properly 2023-12-03 10:55:40 +01:00
Tryibion a6caa9dbfa Remove unused includes 2023-12-02 12:03:30 -06:00
Tryibion 73d33e4af0 Refactor Physics Colliders to use auto serialization. 2023-12-02 12:01:32 -06:00
Tryibion c5c20c8e28 Remove zero clamp on hinge velocity. 2023-12-02 10:50:59 -06:00
Tryibion 9bde0f9f9b Fix layout of editor updating when adding a script to multiple actors in a scene. 2023-12-02 09:40:33 -06:00
Tryibion 7d15944381 Add break 2023-12-02 09:27:13 -06:00
Tryibion 58bfd1954e Fix UICanvas to only spawn CanvasScalar if it doesnt already have one. 2023-12-02 09:24:04 -06:00
Tryibion f67c0d2ac0 Change ScaleWithResolution defaults 2023-12-02 09:23:43 -06:00
Tryibion 9a712ba3cf Fix selecting objects/gizmos with high far plane. 2023-12-02 09:12:38 -06:00
Wiktor Kocielski 3ab7d7fcc4 Add Half to Vector2/Vector4 2023-12-02 15:29:49 +03:00
nothingTVatYT 78d9262b05 skip WM for non-regular windows and add mouse tracking 2023-12-01 21:25:00 +01:00
mafiesto4 c8dd2c045c Simplify sorting arrays code 2023-12-01 13:57:34 +01:00
mafiesto4 a808bcdbf6 Refactor objects splitting in models importing to be handled by ModelTool not the importer code itself 2023-12-01 13:57:08 +01:00
Tryibion 640e01262f Make SloppyOptimization false by default. Lower LODTargetError default. 2023-11-30 20:40:02 -06:00
mafiesto4 6e92d3103c Replace ImportedModelData with ModelData for model importing 2023-11-30 11:46:07 +01:00
mafiesto4 c5df7ad689 Add various improvements to models importing code 2023-11-30 11:31:58 +01:00
mafiesto4 712c400e43 Add new mac icon 2023-11-29 21:51:07 +01:00
mafiesto4 7f87e9794b Fix job system buffer allocation data 2023-11-29 19:12:58 +01:00
mafiesto4 cebd28c3a7 Revert fd938e8284 2023-11-29 18:46:18 +01:00
mafiesto4 eb508fdc73 Fix Json serialzier regression after hot-reload from 0f14672e3b 2023-11-29 12:28:30 +01:00
mafiesto4 b7e4fe3e85 Add automatic code modules references adding when cloning plugin project
#1335
2023-11-29 12:28:19 +01:00
mafiesto4 c6017a21f3 Fix constant value sliders in material graphs to not be used due to shader compilations 2023-11-28 23:19:47 +01:00
mafiesto4 d3a77c7a55 Fix regressions 2023-11-28 17:38:06 +01:00
mafiesto4 8ff4f95cef Optimize some code and cleanup code style in natvis file 2023-11-28 16:02:51 +01:00
mafiesto4 4f8aff4352 Refactor memory allocators to use dedicated path when moving collection data that is not blittable
#2001 #1920
2023-11-28 16:02:36 +01:00
mafiesto4 0aeac36f09 Fix __cplusplus macro on MSVC and add logging C++ version used during compilation 2023-11-28 15:55:34 +01:00
mafiesto4 cf8b7a20c2 Improve 47b8c9978f to handle non-vec4 cases
#2000
2023-11-28 14:55:26 +01:00
mafiesto4 47b8c9978f Fix missing channel masking in material Scene Texture node
#2000
2023-11-28 11:30:04 +01:00
mafiesto4 a909b57e82 Fix deadlock in NetworkManager when network peer returns invalid event type
#1992
2023-11-28 11:24:46 +01:00
mafiesto4 35ebdb0ffe Refactor INetworkDriver::PopEvent to use network event as output parameter rather than raw pointer
#1992
2023-11-28 11:24:17 +01:00
mafiesto4 fd938e8284 Fix incorrect pointer marshalling from Variant to managed runtime
#1992
2023-11-28 11:22:14 +01:00
mafiesto4 17dca8c5c7 Fix invalid codegen for array reference passed as output parameter in scripting interface method 2023-11-28 11:21:29 +01:00
mafiesto4 8ffc86ef88 Fix missing output parameters conversion when calling interface implementation in scripting
#1992
2023-11-28 11:20:07 +01:00
nothingTVatYT a3f1dc2694 removed unnecessary check 2023-11-28 00:50:44 +01:00
nothingTVatYT 84f3d50925 moved a comment line back to the suitable place 2023-11-28 00:43:55 +01:00
nothingTVatYT a06a079804 change submenu name to the shorter and less complicated "Add Scene" 2023-11-28 00:37:29 +01:00
nothingTVatYT 475453aa60 add load scene add. to context menus 2023-11-28 00:06:28 +01:00
mafiesto4 39dc5939e3 Fix crash when boxing native non-POD structure into managed format
#1992
2023-11-27 17:08:07 +01:00
mafiesto4 437819bfce Merge branch 'GoaLitiuM-dotnet_symbol_vscode_fixes' 2023-11-27 14:33:21 +01:00
mafiesto4 fa972a3c77 Merge branch 'dotnet_symbol_vscode_fixes' of https://github.com/GoaLitiuM/FlaxEngine into GoaLitiuM-dotnet_symbol_vscode_fixes 2023-11-27 14:33:06 +01:00
mafiesto4 926297a63f Merge branch 'Tryibion-remove-delete' 2023-11-27 14:32:44 +01:00
mafiesto4 fd76c63a24 Merge branch 'remove-delete' of https://github.com/Tryibion/FlaxEngine into Tryibion-remove-delete 2023-11-27 14:32:34 +01:00
mafiesto4 2f4b956d78 Fix crash when unloading texture that has active streaming tasks 2023-11-27 11:53:21 +01:00
GoaLitiuM c577c78f3f Fix running Flax.Build with .NET 8 runtime 2023-11-26 20:47:54 +02:00
GoaLitiuM ef8bb33105 Compile C# scripts with latest detected C# language version 2023-11-26 20:27:54 +02:00
GoaLitiuM aab88a746d Use detected .NET and C# language version in generated project files 2023-11-26 20:27:54 +02:00
GoaLitiuM 0bcd0154e1 Remove wrong .NET SDK preprocessor definitions and support NET8_0
Only `X_OR_GREATER` symbols should be defined for all versions, and only
the latest detected SDK symbol should be generated.
2023-11-26 20:27:54 +02:00
GoaLitiuM e5b1a10d34 Fix VSCode intellisense not finding generated C# bindings definitions 2023-11-26 20:27:53 +02:00
Tryibion 2ddef2c6be make vars public 2023-11-24 10:53:43 -06:00
Tryibion 53aae90d45 Code style fix 2023-11-24 07:50:00 -06:00
Tryibion 94f1dff497 Add main content nodes to not be duplicated. 2023-11-21 15:40:34 -06:00
Tryibion fe53317ec7 Fix issue of options not showing up for regular content items. 2023-11-21 12:10:44 -06:00
Tryibion 317886e893 Remove ability to delete content and source folders. Limit CM options on those folders only to ones that make sense. 2023-11-21 12:03:01 -06:00
225 changed files with 6482 additions and 3723 deletions
Binary file not shown.
+2 -2
View File
@@ -3,8 +3,8 @@
"Version": {
"Major": 1,
"Minor": 7,
"Revision": 1,
"Build": 6406
"Revision": 2,
"Build": 6407
},
"Company": "Flax",
"Copyright": "Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.",
@@ -12,13 +12,14 @@ namespace FlaxEngine.Tools
{
partial struct Options
{
private bool ShowGeometry => Type == ModelTool.ModelType.Model || Type == ModelTool.ModelType.SkinnedModel;
private bool ShowModel => Type == ModelTool.ModelType.Model;
private bool ShowSkinnedModel => Type == ModelTool.ModelType.SkinnedModel;
private bool ShowAnimation => Type == ModelTool.ModelType.Animation;
private bool ShowGeometry => Type == ModelType.Model || Type == ModelType.SkinnedModel || Type == ModelType.Prefab;
private bool ShowModel => Type == ModelType.Model || Type == ModelType.Prefab;
private bool ShowSkinnedModel => Type == ModelType.SkinnedModel || Type == ModelType.Prefab;
private bool ShowAnimation => Type == ModelType.Animation || Type == ModelType.Prefab;
private bool ShowSmoothingNormalsAngle => ShowGeometry && CalculateNormals;
private bool ShowSmoothingTangentsAngle => ShowGeometry && CalculateTangents;
private bool ShowFramesRange => ShowAnimation && Duration == ModelTool.AnimationDuration.Custom;
private bool ShowFramesRange => ShowAnimation && Duration == AnimationDuration.Custom;
private bool ShowSplitting => Type != ModelType.Prefab;
}
}
}
+13 -6
View File
@@ -5,6 +5,7 @@
#include "Engine/Graphics/GPUContext.h"
#include "Engine/Threading/Threading.h"
#include "Engine/Graphics/RenderTools.h"
#include "Engine/Content/Content.h"
#include "Engine/Content/Factories/BinaryAssetFactory.h"
#include "Engine/ContentImporters/AssetsImportingManager.h"
#include "Engine/Content/Upgraders/TextureAssetUpgrader.h"
@@ -92,15 +93,12 @@ SpriteHandle PreviewsCache::FindSlot(const Guid& id)
{
if (WaitForLoaded())
return SpriteHandle::Invalid;
// Find entry
int32 index;
if (_assets.Find(id, index))
{
const String spriteName = StringUtils::ToString(index);
return FindSprite(spriteName);
}
return SpriteHandle::Invalid;
}
@@ -114,6 +112,17 @@ Asset::LoadResult PreviewsCache::load()
return LoadResult::Failed;
_assets.Set(previewsMetaChunk->Get<Guid>(), ASSETS_ICONS_PER_ATLAS);
// Verify if cached assets still exist (don't store thumbnails for removed files)
AssetInfo assetInfo;
for (Guid& id : _assets)
{
if (id.IsValid() && Content::GetAsset(id) == nullptr && !Content::GetAssetInfo(id, assetInfo))
{
// Free slot (no matter the texture contents)
id = Guid::Empty;
}
}
// Setup atlas sprites array
Sprite sprite;
sprite.Area.Size = static_cast<float>(ASSET_ICON_SIZE) / ASSETS_ICONS_ATLAS_SIZE;
@@ -162,7 +171,7 @@ SpriteHandle PreviewsCache::OccupySlot(GPUTexture* source, const Guid& id)
if (WaitForLoaded())
return SpriteHandle::Invalid;
// Find free slot and for that asset
// Find this asset slot or use the first empty
int32 index = _assets.Find(id);
if (index == INVALID_INDEX)
index = _assets.Find(Guid::Empty);
@@ -201,14 +210,12 @@ bool PreviewsCache::ReleaseSlot(const Guid& id)
{
bool result = false;
ScopeLock lock(Locker);
int32 index = _assets.Find(id);
if (index != INVALID_INDEX)
{
_assets[index] = Guid::Empty;
result = true;
}
return result;
}
+5 -1
View File
@@ -72,7 +72,10 @@ namespace FlaxEditor.Content
{
if (_preview == null)
{
_preview = new ModelPreview(false);
_preview = new ModelPreview(false)
{
ScaleToFit = false,
};
InitAssetPreview(_preview);
}
@@ -91,6 +94,7 @@ namespace FlaxEditor.Content
_preview.Model = (Model)request.Asset;
_preview.Parent = guiRoot;
_preview.SyncBackbufferSize();
_preview.ViewportCamera.SetArcBallView(_preview.Model.GetBox());
_preview.Task.OnDraw();
}
+11
View File
@@ -1,6 +1,7 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using System;
using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.Windows;
using FlaxEngine;
@@ -68,5 +69,15 @@ namespace FlaxEditor.Content
{
return new SceneItem(path, id);
}
/// <inheritdoc />
public override void OnContentWindowContextMenu(ContextMenu menu, ContentItem item)
{
var id = ((SceneItem)item).ID;
if (Level.FindScene(id) == null)
{
menu.AddButton("Open (additive)", () => { Editor.Instance.Scene.OpenScene(id, true); });
}
}
}
}
@@ -35,6 +35,11 @@ namespace FlaxEditor.Content.Thumbnails
/// The finalized state.
/// </summary>
Disposed,
/// <summary>
/// The request has failed (eg. asset cannot be loaded).
/// </summary>
Failed,
};
/// <summary>
@@ -78,6 +83,14 @@ namespace FlaxEditor.Content.Thumbnails
Proxy = proxy;
}
internal void Update()
{
if (State == States.Prepared && (!Asset || Asset.LastLoadFailed))
{
State = States.Failed;
}
}
/// <summary>
/// Prepares this request.
/// </summary>
@@ -85,11 +98,8 @@ namespace FlaxEditor.Content.Thumbnails
{
if (State != States.Created)
throw new InvalidOperationException();
// Prepare
Asset = FlaxEngine.Content.LoadAsync(Item.Path);
Proxy.OnThumbnailDrawPrepare(this);
State = States.Prepared;
}
@@ -101,9 +111,7 @@ namespace FlaxEditor.Content.Thumbnails
{
if (State != States.Prepared)
throw new InvalidOperationException();
Item.Thumbnail = icon;
State = States.Rendered;
}
@@ -21,15 +21,11 @@ namespace FlaxEditor.Content.Thumbnails
/// </summary>
public const float MinimumRequiredResourcesQuality = 0.8f;
// TODO: free atlas slots for deleted assets
private readonly List<PreviewsCache> _cache = new List<PreviewsCache>(4);
private readonly string _cacheFolder;
private DateTime _lastFlushTime;
private readonly List<ThumbnailRequest> _requests = new List<ThumbnailRequest>(128);
private readonly PreviewRoot _guiRoot = new PreviewRoot();
private DateTime _lastFlushTime;
private RenderTask _task;
private GPUTexture _output;
@@ -88,7 +84,6 @@ namespace FlaxEditor.Content.Thumbnails
}
}
// Add request
AddRequest(assetItem, proxy);
}
}
@@ -118,15 +113,15 @@ namespace FlaxEditor.Content.Thumbnails
for (int i = 0; i < _cache.Count; i++)
{
if (_cache[i].ReleaseSlot(assetItem.ID))
{
break;
}
}
}
}
internal static bool HasMinimumQuality(TextureBase asset)
{
if (asset.HasStreamingError)
return true; // Don't block thumbnails queue when texture fails to stream in (eg. unsupported format)
var mipLevels = asset.MipLevels;
var minMipLevels = Mathf.Min(mipLevels, 7);
return asset.IsLoaded && asset.ResidentMipLevels >= Mathf.Max(minMipLevels, (int)(mipLevels * MinimumRequiredResourcesQuality));
@@ -198,13 +193,7 @@ namespace FlaxEditor.Content.Thumbnails
/// <inheritdoc />
void IContentItemOwner.OnItemDeleted(ContentItem item)
{
if (item is AssetItem assetItem)
{
lock (_requests)
{
RemoveRequest(assetItem);
}
}
DeletePreview(item);
}
/// <inheritdoc />
@@ -494,10 +483,7 @@ namespace FlaxEditor.Content.Thumbnails
{
// Wait some frames before start generating previews (late init feature)
if (Time.TimeSinceStartup < 1.0f || HasAllAtlasesLoaded() == false)
{
// Back
return;
}
lock (_requests)
{
@@ -515,6 +501,7 @@ namespace FlaxEditor.Content.Thumbnails
var request = _requests[i];
try
{
request.Update();
if (request.IsReady)
{
isAnyReady = true;
@@ -523,6 +510,10 @@ namespace FlaxEditor.Content.Thumbnails
{
request.Prepare();
}
else if (request.State == ThumbnailRequest.States.Failed)
{
_requests.RemoveAt(i--);
}
}
catch (Exception ex)
{
+12 -2
View File
@@ -24,6 +24,16 @@ namespace FlaxEditor.Content
/// </summary>
protected ContentFolder _folder;
/// <summary>
/// Whether this node can be deleted.
/// </summary>
public virtual bool CanDelete => true;
/// <summary>
/// Whether this node can be duplicated.
/// </summary>
public virtual bool CanDuplicate => true;
/// <summary>
/// Gets the content folder item.
/// </summary>
@@ -301,7 +311,7 @@ namespace FlaxEditor.Content
StartRenaming();
return true;
case KeyboardKeys.Delete:
if (Folder.Exists)
if (Folder.Exists && CanDelete)
Editor.Instance.Windows.ContentWin.Delete(Folder);
return true;
}
@@ -310,7 +320,7 @@ namespace FlaxEditor.Content
switch (key)
{
case KeyboardKeys.D:
if (Folder.Exists)
if (Folder.Exists && CanDuplicate)
Editor.Instance.Windows.ContentWin.Duplicate(Folder);
return true;
}
@@ -12,6 +12,12 @@ namespace FlaxEditor.Content
{
private FileSystemWatcher _watcher;
/// <inheritdoc />
public override bool CanDelete => false;
/// <inheritdoc />
public override bool CanDuplicate => false;
/// <summary>
/// Initializes a new instance of the <see cref="MainContentTreeNode"/> class.
/// </summary>
@@ -169,6 +169,30 @@ bool AndroidPlatformTools::OnPostProcess(CookingData& data)
permissions += String::Format(TEXT("\n <uses-permission android:name=\"{0}\" />"), e.Item);
}
// Setup default Android screen orientation
auto defaultOrienation = platformSettings->DefaultOrientation;
String orientation = String("fullSensor");
switch (defaultOrienation)
{
case AndroidPlatformSettings::ScreenOrientation::Portrait:
orientation = String("portrait");
break;
case AndroidPlatformSettings::ScreenOrientation::PortraitReverse:
orientation = String("reversePortrait");
break;
case AndroidPlatformSettings::ScreenOrientation::LandscapeRight:
orientation = String("landscape");
break;
case AndroidPlatformSettings::ScreenOrientation::LandscapeLeft:
orientation = String("reverseLandscape");
break;
case AndroidPlatformSettings::ScreenOrientation::AutoRotation:
orientation = String("fullSensor");
break;
default:
break;
}
// Setup Android application attributes
String attributes;
if (data.Configuration != BuildConfiguration::Release)
@@ -223,6 +247,7 @@ bool AndroidPlatformTools::OnPostProcess(CookingData& data)
EditorUtilities::ReplaceInFile(manifestPath, TEXT("${PackageName}"), packageName);
EditorUtilities::ReplaceInFile(manifestPath, TEXT("${ProjectVersion}"), projectVersion);
EditorUtilities::ReplaceInFile(manifestPath, TEXT("${AndroidPermissions}"), permissions);
EditorUtilities::ReplaceInFile(manifestPath, TEXT("${DefaultOrientation}"), orientation);
EditorUtilities::ReplaceInFile(manifestPath, TEXT("${AndroidAttributes}"), attributes);
const String stringsPath = data.OriginalOutputPath / TEXT("app/src/main/res/values/strings.xml");
EditorUtilities::ReplaceInFile(stringsPath, TEXT("${ProjectName}"), gameSettings->ProductName);
@@ -1270,7 +1270,7 @@ bool CookAssetsStep::Perform(CookingData& data)
{
Array<CookingData::AssetTypeStatistics> assetTypes;
data.Stats.AssetStats.GetValues(assetTypes);
Sorting::QuickSort(assetTypes.Get(), assetTypes.Count());
Sorting::QuickSort(assetTypes);
LOG(Info, "");
LOG(Info, "Top assets types stats:");
+24 -18
View File
@@ -48,21 +48,21 @@ bool DeployDataStep::Perform(CookingData& data)
}
if (buildSettings.SkipDotnetPackaging && data.Tools->UseSystemDotnet())
{
// Use system-installed .Net Runtime
// Use system-installed .NET Runtime
FileSystem::DeleteDirectory(dstDotnet);
}
else
{
// Deploy .Net Runtime files
// Deploy .NET Runtime files
FileSystem::CreateDirectory(dstDotnet);
String srcDotnet = depsRoot / TEXT("Dotnet");
if (FileSystem::DirectoryExists(srcDotnet))
{
// Use prebuilt .Net installation for that platform
LOG(Info, "Using .Net Runtime {} at {}", data.Tools->GetName(), srcDotnet);
// Use prebuilt .NET installation for that platform
LOG(Info, "Using .NET Runtime {} at {}", data.Tools->GetName(), srcDotnet);
if (EditorUtilities::CopyDirectoryIfNewer(dstDotnet, srcDotnet, true))
{
data.Error(TEXT("Failed to copy .Net runtime data files."));
data.Error(TEXT("Failed to copy .NET runtime data files."));
return true;
}
}
@@ -85,7 +85,7 @@ bool DeployDataStep::Perform(CookingData& data)
}
if (canUseSystemDotnet && (aotMode == DotNetAOTModes::None || aotMode == DotNetAOTModes::ILC))
{
// Ask Flax.Build to provide .Net SDK location for the current platform
// Ask Flax.Build to provide .NET SDK location for the current platform
String sdks;
bool failed = ScriptsBuilder::RunBuildTool(String::Format(TEXT("-log -logMessagesOnly -logFileWithConsole -logfile=SDKs.txt -printSDKs {}"), GAME_BUILD_DOTNET_VER), data.CacheDirectory);
failed |= File::ReadAllText(data.CacheDirectory / TEXT("SDKs.txt"), sdks);
@@ -101,7 +101,7 @@ bool DeployDataStep::Perform(CookingData& data)
}
if (failed || !FileSystem::DirectoryExists(srcDotnet))
{
data.Error(TEXT("Failed to get .Net SDK location for a current platform."));
data.Error(TEXT("Failed to get .NET SDK location for the current host platform."));
return true;
}
@@ -110,19 +110,25 @@ bool DeployDataStep::Perform(CookingData& data)
FileSystem::GetChildDirectories(versions, srcDotnet / TEXT("host/fxr"));
if (versions.Count() == 0)
{
data.Error(TEXT("Failed to get .Net SDK location for a current platform."));
data.Error(TEXT("Failed to find any .NET hostfxr versions for the current host platform."));
return true;
}
for (String& version : versions)
{
version = String(StringUtils::GetFileName(version));
if (!version.StartsWith(TEXT("7.")))
if (!version.StartsWith(TEXT("7.")) && !version.StartsWith(TEXT("8."))) // .NET 7 or .NET 8
version.Clear();
}
Sorting::QuickSort(versions.Get(), versions.Count());
Sorting::QuickSort(versions);
const String version = versions.Last();
if (version.IsEmpty())
{
data.Error(TEXT("Failed to find supported .NET hostfxr version for the current host platform."));
return true;
}
FileSystem::NormalizePath(srcDotnet);
LOG(Info, "Using .Net Runtime {} at {}", version, srcDotnet);
LOG(Info, "Using .NET Runtime {} at {}", version, srcDotnet);
// Check if previously deployed files are valid (eg. system-installed .NET was updated from version 7.0.3 to 7.0.5)
{
@@ -158,13 +164,13 @@ bool DeployDataStep::Perform(CookingData& data)
}
if (failed)
{
data.Error(TEXT("Failed to copy .Net runtime data files."));
data.Error(TEXT("Failed to copy .NET runtime data files."));
return true;
}
}
else
{
// Ask Flax.Build to provide .Net Host Runtime location for the target platform
// Ask Flax.Build to provide .NET Host Runtime location for the target platform
String sdks;
const Char *platformName, *archName;
data.GetBuildPlatformName(platformName, archName);
@@ -180,11 +186,11 @@ bool DeployDataStep::Perform(CookingData& data)
}
if (failed || !FileSystem::DirectoryExists(srcDotnet))
{
data.Error(TEXT("Failed to get .Net SDK location for a current platform."));
data.Error(TEXT("Failed to get .NET SDK location for the current host platform."));
return true;
}
FileSystem::NormalizePath(srcDotnet);
LOG(Info, "Using .Net Runtime {} at {}", TEXT("Host"), srcDotnet);
LOG(Info, "Using .NET Runtime {} at {}", TEXT("Host"), srcDotnet);
// Deploy runtime files
const Char* corlibPrivateName = TEXT("System.Private.CoreLib.dll");
@@ -249,7 +255,7 @@ bool DeployDataStep::Perform(CookingData& data)
DEPLOY_NATIVE_FILE("libmonosgen-2.0.dylib");
DEPLOY_NATIVE_FILE("libSystem.IO.Compression.Native.dylib");
DEPLOY_NATIVE_FILE("libSystem.Native.dylib");
DEPLOY_NATIVE_FILE("libSystem.Net.Security.Native.dylib");
DEPLOY_NATIVE_FILE("libSystem.NET.Security.Native.dylib");
DEPLOY_NATIVE_FILE("libSystem.Security.Cryptography.Native.Apple.dylib");
break;
#undef DEPLOY_NATIVE_FILE
@@ -257,7 +263,7 @@ bool DeployDataStep::Perform(CookingData& data)
}
if (failed)
{
data.Error(TEXT("Failed to copy .Net runtime data files."));
data.Error(TEXT("Failed to copy .NET runtime data files."));
return true;
}
}
@@ -278,7 +284,7 @@ bool DeployDataStep::Perform(CookingData& data)
}
if (ScriptsBuilder::RunBuildTool(args))
{
data.Error(TEXT("Failed to optimize .Net class library."));
data.Error(TEXT("Failed to optimize .NET class library."));
return true;
}
}
+36 -41
View File
@@ -7,9 +7,8 @@ using FlaxEditor.CustomEditors.GUI;
using FlaxEditor.Scripting;
using FlaxEngine;
using FlaxEngine.GUI;
using FlaxEngine.Json;
using FlaxEngine.Utilities;
using Newtonsoft.Json;
using JsonSerializer = FlaxEngine.Json.JsonSerializer;
namespace FlaxEditor.CustomEditors
{
@@ -386,22 +385,22 @@ namespace FlaxEditor.CustomEditors
LinkedLabel = label;
}
private void RevertDiffToDefault(CustomEditor editor)
{
if (editor.ChildrenEditors.Count == 0)
{
// Skip if no change detected
if (!editor.Values.IsDefaultValueModified)
return;
/// <summary>
/// If true, the value reverting to default/reference will be handled via iteration over children editors, instead of for a whole object at once.
/// </summary>
public virtual bool RevertValueWithChildren => ChildrenEditors.Count != 0;
editor.SetValueToDefault();
private void RevertDiffToDefault()
{
if (RevertValueWithChildren)
{
foreach (var child in ChildrenEditors)
child.RevertDiffToDefault();
}
else
{
for (int i = 0; i < editor.ChildrenEditors.Count; i++)
{
RevertDiffToDefault(editor.ChildrenEditors[i]);
}
if (Values.IsDefaultValueModified)
SetValueToDefault();
}
}
@@ -414,11 +413,6 @@ namespace FlaxEditor.CustomEditors
{
if (!Values.IsDefaultValueModified)
return false;
// Skip array items (show diff only on a bottom level properties and fields)
if (ParentEditor is Editors.ArrayEditor)
return false;
return true;
}
}
@@ -430,7 +424,7 @@ namespace FlaxEditor.CustomEditors
{
if (!Values.HasDefaultValue)
return;
RevertDiffToDefault(this);
RevertDiffToDefault();
}
/// <summary>
@@ -468,22 +462,17 @@ namespace FlaxEditor.CustomEditors
}
}
private void RevertDiffToReference(CustomEditor editor)
private void RevertDiffToReference()
{
if (editor.ChildrenEditors.Count == 0)
if (RevertValueWithChildren)
{
// Skip if no change detected
if (!editor.Values.IsReferenceValueModified)
return;
editor.SetValueToReference();
foreach (var child in ChildrenEditors)
child.RevertDiffToReference();
}
else
{
for (int i = 0; i < editor.ChildrenEditors.Count; i++)
{
RevertDiffToReference(editor.ChildrenEditors[i]);
}
if (Values.IsReferenceValueModified)
SetValueToReference();
}
}
@@ -496,11 +485,6 @@ namespace FlaxEditor.CustomEditors
{
if (!Values.IsReferenceValueModified)
return false;
// Skip array items (show diff only on a bottom level properties and fields)
if (ParentEditor is Editors.ArrayEditor)
return false;
return true;
}
}
@@ -512,7 +496,7 @@ namespace FlaxEditor.CustomEditors
{
if (!Values.HasReferenceValue)
return;
RevertDiffToReference(this);
RevertDiffToReference();
}
/// <summary>
@@ -657,7 +641,7 @@ namespace FlaxEditor.CustomEditors
// Default
try
{
obj = JsonConvert.DeserializeObject(text, TypeUtils.GetType(Values.Type), JsonSerializer.Settings);
obj = Newtonsoft.Json.JsonConvert.DeserializeObject(text, TypeUtils.GetType(Values.Type), JsonSerializer.Settings);
}
catch
{
@@ -762,7 +746,7 @@ namespace FlaxEditor.CustomEditors
/// </summary>
public void SetValueToDefault()
{
SetValue(Values.DefaultValue);
SetValueCloned(Values.DefaultValue);
}
/// <summary>
@@ -799,7 +783,19 @@ namespace FlaxEditor.CustomEditors
return;
}
SetValue(Values.ReferenceValue);
SetValueCloned(Values.ReferenceValue);
}
private void SetValueCloned(object value)
{
// For objects (eg. arrays) we need to clone them to prevent editing default/reference value within editor
if (value != null && !value.GetType().IsValueType)
{
var json = JsonSerializer.Serialize(value);
value = JsonSerializer.Deserialize(json, value.GetType());
}
SetValue(value);
}
/// <summary>
@@ -811,7 +807,6 @@ namespace FlaxEditor.CustomEditors
{
if (_isSetBlocked)
return;
if (OnDirty(this, value, token))
{
_hasValueDirty = true;
@@ -0,0 +1,84 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using System;
using System.IO;
using FlaxEditor.Content;
using FlaxEditor.CustomEditors.Editors;
using FlaxEngine;
using FlaxEngine.GUI;
using FlaxEngine.Tools;
namespace FlaxEditor.CustomEditors.Dedicated;
/// <summary>
/// The missing script editor.
/// </summary>
[CustomEditor(typeof(ModelPrefab)), DefaultEditor]
public class ModelPrefabEditor : GenericEditor
{
private Guid _prefabId;
private Button _reimportButton;
private string _importPath;
/// <inheritdoc />
public override void Initialize(LayoutElementsContainer layout)
{
base.Initialize(layout);
var modelPrefab = Values[0] as ModelPrefab;
if (modelPrefab == null)
return;
_prefabId = modelPrefab.PrefabID;
while (true)
{
if (_prefabId == Guid.Empty)
{
break;
}
var prefab = FlaxEngine.Content.Load<Prefab>(_prefabId);
if (prefab)
{
var prefabObjectId = modelPrefab.PrefabObjectID;
var prefabObject = prefab.GetDefaultInstance(ref prefabObjectId);
if (prefabObject.PrefabID == _prefabId)
break;
_prefabId = prefabObject.PrefabID;
}
}
var button = layout.Button("Reimport", "Reimports the source asset as prefab.");
_reimportButton = button.Button;
_reimportButton.Clicked += OnReimport;
}
private void OnReimport()
{
var prefab = FlaxEngine.Content.Load<Prefab>(_prefabId);
var modelPrefab = (ModelPrefab)Values[0];
var importPath = modelPrefab.ImportPath;
var editor = Editor.Instance;
if (editor.ContentImporting.GetReimportPath("Model Prefab", ref importPath))
return;
var folder = editor.ContentDatabase.Find(Path.GetDirectoryName(prefab.Path)) as ContentFolder;
if (folder == null)
return;
var importOptions = modelPrefab.ImportOptions;
importOptions.Type = ModelTool.ModelType.Prefab;
_importPath = importPath;
_reimportButton.Enabled = false;
editor.ContentImporting.ImportFileEnd += OnImportFileEnd;
editor.ContentImporting.Import(importPath, folder, true, importOptions);
}
private void OnImportFileEnd(IFileEntryAction entry, bool failed)
{
if (entry.SourceUrl == _importPath)
{
// Restore button
_importPath = null;
_reimportButton.Enabled = true;
Editor.Instance.ContentImporting.ImportFileEnd -= OnImportFileEnd;
}
}
}
@@ -246,6 +246,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
var multiAction = new MultiUndoAction(actions);
multiAction.Do();
var presenter = ScriptsEditor.Presenter;
ScriptsEditor.ParentEditor?.RebuildLayout();
if (presenter != null)
{
presenter.Undo.AddAction(multiAction);
@@ -87,6 +87,7 @@ namespace FlaxEditor.CustomEditors.Editors
protected bool NotNullItems;
private IntegerValueElement _size;
private PropertyNameLabel _sizeLabel;
private Color _background;
private int _elementsCount;
private bool _readOnly;
@@ -109,6 +110,9 @@ namespace FlaxEditor.CustomEditors.Editors
}
}
/// <inheritdoc />
public override bool RevertValueWithChildren => false; // Always revert value for a whole collection
/// <inheritdoc />
public override void Initialize(LayoutElementsContainer layout)
{
@@ -174,7 +178,9 @@ namespace FlaxEditor.CustomEditors.Editors
}
else
{
_size = dragArea.IntegerValue("Size");
var sizeProperty = dragArea.AddPropertyItem("Size");
_sizeLabel = sizeProperty.Labels.Last();
_size = sizeProperty.IntegerValue();
_size.IntValue.MinValue = 0;
_size.IntValue.MaxValue = ushort.MaxValue;
_size.IntValue.Value = size;
@@ -274,6 +280,15 @@ namespace FlaxEditor.CustomEditors.Editors
}
}
/// <inheritdoc />
protected override void Deinitialize()
{
_size = null;
_sizeLabel = null;
base.Deinitialize();
}
/// <summary>
/// Rebuilds the parent layout if its collection.
/// </summary>
@@ -296,7 +311,6 @@ namespace FlaxEditor.CustomEditors.Editors
{
if (IsSetBlocked)
return;
Resize(_size.IntValue.Value);
}
@@ -311,11 +325,9 @@ namespace FlaxEditor.CustomEditors.Editors
return;
var cloned = CloneValues();
var tmp = cloned[dstIndex];
cloned[dstIndex] = cloned[srcIndex];
cloned[srcIndex] = tmp;
SetValue(cloned);
}
@@ -371,6 +383,17 @@ namespace FlaxEditor.CustomEditors.Editors
if (HasDifferentValues || HasDifferentTypes)
return;
// Update reference/default value indicator
if (_sizeLabel != null)
{
var color = Color.Transparent;
if (Values.HasReferenceValue && Values.ReferenceValue is IList referenceValue && referenceValue.Count != Count)
color = FlaxEngine.GUI.Style.Current.BackgroundSelected;
else if (Values.HasDefaultValue && Values.DefaultValue is IList defaultValue && defaultValue.Count != Count)
color = Color.Yellow * 0.8f;
_sizeLabel.HighlightStripColor = color;
}
// Check if collection has been resized (by UI or from external source)
if (Count != _elementsCount)
{
@@ -73,7 +73,6 @@ namespace FlaxEditor.CustomEditors
{
if (instanceValues == null || instanceValues.Count != Count)
throw new ArgumentException();
for (int i = 0; i < Count; i++)
{
var v = instanceValues[i];
+11 -1
View File
@@ -404,13 +404,23 @@ int32 Editor::LoadProduct()
// Create new project option
if (CommandLine::Options.NewProject)
{
Array<String> projectFiles;
FileSystem::DirectoryGetFiles(projectFiles, projectPath, TEXT("*.flaxproj"), DirectorySearchOption::TopDirectoryOnly);
if (projectFiles.Count() == 1)
{
// Skip creating new project if it already exists
LOG(Info, "Skip creatinng new project because it already exists");
CommandLine::Options.NewProject.Reset();
}
}
if (CommandLine::Options.NewProject)
{
if (projectPath.IsEmpty())
projectPath = Platform::GetWorkingDirectory();
else if (!FileSystem::DirectoryExists(projectPath))
FileSystem::CreateDirectory(projectPath);
FileSystem::NormalizePath(projectPath);
String folderName = StringUtils::GetFileName(projectPath);
String tmpName;
for (int32 i = 0; i < folderName.Length(); i++)
-117
View File
@@ -1343,108 +1343,6 @@ namespace FlaxEditor
public float AutoRebuildNavMeshTimeoutMs;
}
[StructLayout(LayoutKind.Sequential)]
[NativeMarshalling(typeof(VisualScriptLocalMarshaller))]
internal struct VisualScriptLocal
{
public string Value;
public string ValueTypeName;
public uint NodeId;
public int BoxId;
}
[CustomMarshaller(typeof(VisualScriptLocal), MarshalMode.Default, typeof(VisualScriptLocalMarshaller))]
internal static class VisualScriptLocalMarshaller
{
[StructLayout(LayoutKind.Sequential)]
internal struct VisualScriptLocalNative
{
public IntPtr Value;
public IntPtr ValueTypeName;
public uint NodeId;
public int BoxId;
}
internal static VisualScriptLocal ConvertToManaged(VisualScriptLocalNative unmanaged) => ToManaged(unmanaged);
internal static VisualScriptLocalNative ConvertToUnmanaged(VisualScriptLocal managed) => ToNative(managed);
internal static VisualScriptLocal ToManaged(VisualScriptLocalNative managed)
{
return new VisualScriptLocal()
{
Value = ManagedString.ToManaged(managed.Value),
ValueTypeName = ManagedString.ToManaged(managed.ValueTypeName),
NodeId = managed.NodeId,
BoxId = managed.BoxId,
};
}
internal static VisualScriptLocalNative ToNative(VisualScriptLocal managed)
{
return new VisualScriptLocalNative()
{
Value = ManagedString.ToNative(managed.Value),
ValueTypeName = ManagedString.ToNative(managed.ValueTypeName),
NodeId = managed.NodeId,
BoxId = managed.BoxId,
};
}
internal static void Free(VisualScriptLocalNative unmanaged)
{
ManagedString.Free(unmanaged.Value);
ManagedString.Free(unmanaged.ValueTypeName);
}
}
[StructLayout(LayoutKind.Sequential)]
[NativeMarshalling(typeof(VisualScriptStackFrameMarshaller))]
internal struct VisualScriptStackFrame
{
public VisualScript Script;
public uint NodeId;
public int BoxId;
}
[CustomMarshaller(typeof(VisualScriptStackFrame), MarshalMode.Default, typeof(VisualScriptStackFrameMarshaller))]
internal static class VisualScriptStackFrameMarshaller
{
[StructLayout(LayoutKind.Sequential)]
internal struct VisualScriptStackFrameNative
{
public IntPtr Script;
public uint NodeId;
public int BoxId;
}
internal static VisualScriptStackFrame ConvertToManaged(VisualScriptStackFrameNative unmanaged) => ToManaged(unmanaged);
internal static VisualScriptStackFrameNative ConvertToUnmanaged(VisualScriptStackFrame managed) => ToNative(managed);
internal static VisualScriptStackFrame ToManaged(VisualScriptStackFrameNative managed)
{
return new VisualScriptStackFrame()
{
Script = VisualScriptMarshaller.ConvertToManaged(managed.Script),
NodeId = managed.NodeId,
BoxId = managed.BoxId,
};
}
internal static VisualScriptStackFrameNative ToNative(VisualScriptStackFrame managed)
{
return new VisualScriptStackFrameNative()
{
Script = VisualScriptMarshaller.ConvertToUnmanaged(managed.Script),
NodeId = managed.NodeId,
BoxId = managed.BoxId,
};
}
internal static void Free(VisualScriptStackFrameNative unmanaged)
{
}
}
internal void BuildCommand(string arg)
{
if (TryBuildCommand(arg))
@@ -1723,21 +1621,6 @@ namespace FlaxEditor
[LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_RunVisualScriptBreakpointLoopTick", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))]
internal static partial void Internal_RunVisualScriptBreakpointLoopTick(float deltaTime);
[LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_GetVisualScriptLocals", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))]
[return: MarshalUsing(typeof(FlaxEngine.Interop.ArrayMarshaller<,>), CountElementName = "localsCount")]
internal static partial VisualScriptLocal[] Internal_GetVisualScriptLocals(out int localsCount);
[LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_GetVisualScriptStackFrames", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))]
[return: MarshalUsing(typeof(FlaxEngine.Interop.ArrayMarshaller<,>), CountElementName = "stackFrameCount")]
internal static partial VisualScriptStackFrame[] Internal_GetVisualScriptStackFrames(out int stackFrameCount);
[LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_GetVisualScriptPreviousScopeFrame", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))]
internal static partial VisualScriptStackFrame Internal_GetVisualScriptPreviousScopeFrame();
[LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_EvaluateVisualScriptLocal", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))]
[return: MarshalAs(UnmanagedType.U1)]
internal static partial bool Internal_EvaluateVisualScriptLocal(IntPtr script, ref VisualScriptLocal local);
[LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_DeserializeSceneObject", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))]
internal static partial void Internal_DeserializeSceneObject(IntPtr sceneObject, string json);
@@ -3,6 +3,8 @@
using FlaxEditor.GUI.Input;
using FlaxEngine;
using FlaxEngine.GUI;
using FlaxEngine.Json;
using System.Collections.Generic;
namespace FlaxEditor.GUI.Dialogs
{
@@ -30,6 +32,8 @@ namespace FlaxEditor.GUI.Dialogs
private const float HSVMargin = 0.0f;
private const float ChannelsMargin = 4.0f;
private const float ChannelTextWidth = 12.0f;
private const float SavedColorButtonWidth = 20.0f;
private const float SavedColorButtonHeight = 20.0f;
private Color _initialValue;
private Color _value;
@@ -52,6 +56,9 @@ namespace FlaxEditor.GUI.Dialogs
private Button _cOK;
private Button _cEyedropper;
private List<Color> _savedColors = new List<Color>();
private List<Button> _savedColorButtons = new List<Button>();
/// <summary>
/// Gets the selected color.
/// </summary>
@@ -111,6 +118,12 @@ namespace FlaxEditor.GUI.Dialogs
_onChanged = colorChanged;
_onClosed = pickerClosed;
// Get saved colors if they exist
if (Editor.Instance.ProjectCache.TryGetCustomData("ColorPickerSavedColors", out var savedColors))
{
_savedColors = JsonSerializer.Deserialize<List<Color>>(savedColors);
}
// Selector
_cSelector = new ColorSelectorWithSliders(180, 18)
{
@@ -195,6 +208,9 @@ namespace FlaxEditor.GUI.Dialogs
};
_cOK.Clicked += OnSubmit;
// Create saved color buttons
CreateAllSaveButtons();
// Eyedropper button
var style = Style.Current;
_cEyedropper = new Button(_cOK.X - EyedropperMargin, _cHex.Bottom + PickerMargin)
@@ -216,6 +232,50 @@ namespace FlaxEditor.GUI.Dialogs
SelectedColor = initialValue;
}
private void OnSavedColorButtonClicked(Button button)
{
if (button.Tag == null)
{
// Prevent setting same color 2 times... because why...
foreach (var color in _savedColors)
{
if (color == _value)
{
return;
}
}
// Set color of button to current value;
button.BackgroundColor = _value;
button.BackgroundColorHighlighted = _value;
button.BackgroundColorSelected = _value.RGBMultiplied(0.8f);
button.Text = "";
button.Tag = _value;
// Save new colors
_savedColors.Add(_value);
var savedColors = JsonSerializer.Serialize(_savedColors, typeof(List<Color>));
Editor.Instance.ProjectCache.SetCustomData("ColorPickerSavedColors", savedColors);
// create new + button
if (_savedColorButtons.Count < 8)
{
var savedColorButton = new Button(PickerMargin * (_savedColorButtons.Count + 1) + SavedColorButtonWidth * _savedColorButtons.Count, Height - SavedColorButtonHeight - PickerMargin, SavedColorButtonWidth, SavedColorButtonHeight)
{
Text = "+",
Parent = this,
Tag = null,
};
savedColorButton.ButtonClicked += (b) => OnSavedColorButtonClicked(b);
_savedColorButtons.Add(savedColorButton);
}
}
else
{
SelectedColor = (Color)button.Tag;
}
}
private void OnColorPicked(Color32 colorPicked)
{
if (_activeEyedropper)
@@ -340,6 +400,111 @@ namespace FlaxEditor.GUI.Dialogs
return base.OnKeyDown(key);
}
/// <inheritdoc />
public override bool OnMouseUp(Float2 location, MouseButton button)
{
if (base.OnMouseUp(location, button))
{
return true;
}
var child = GetChildAtRecursive(location);
if (button == MouseButton.Right && child is Button b && b.Tag is Color c)
{
// Show menu
var menu = new ContextMenu.ContextMenu();
var replaceButton = menu.AddButton("Replace");
replaceButton.Clicked += () => OnSavedColorReplace(b);
var deleteButton = menu.AddButton("Delete");
deleteButton.Clicked += () => OnSavedColorDelete(b);
_disableEvents = true;
menu.Show(this, location);
menu.VisibleChanged += (c) => _disableEvents = false;
return true;
}
return false;
}
private void OnSavedColorReplace(Button button)
{
// Prevent setting same color 2 times... because why...
foreach (var color in _savedColors)
{
if (color == _value)
{
return;
}
}
// Set new Color in spot
for (int i = 0; i < _savedColors.Count; i++)
{
var color = _savedColors[i];
if (color == (Color)button.Tag)
{
color = _value;
}
}
// Set color of button to current value;
button.BackgroundColor = _value;
button.BackgroundColorHighlighted = _value;
button.Text = "";
button.Tag = _value;
// Save new colors
var savedColors = JsonSerializer.Serialize(_savedColors, typeof(List<Color>));
Editor.Instance.ProjectCache.SetCustomData("ColorPickerSavedColors", savedColors);
}
private void OnSavedColorDelete(Button button)
{
_savedColors.Remove((Color)button.Tag);
foreach (var b in _savedColorButtons)
{
Children.Remove(b);
}
_savedColorButtons.Clear();
CreateAllSaveButtons();
// Save new colors
var savedColors = JsonSerializer.Serialize(_savedColors, typeof(List<Color>));
Editor.Instance.ProjectCache.SetCustomData("ColorPickerSavedColors", savedColors);
}
private void CreateAllSaveButtons()
{
// Create saved color buttons
for (int i = 0; i < _savedColors.Count; i++)
{
var savedColor = _savedColors[i];
var savedColorButton = new Button(PickerMargin * (i + 1) + SavedColorButtonWidth * i, Height - SavedColorButtonHeight - PickerMargin, SavedColorButtonWidth, SavedColorButtonHeight)
{
Parent = this,
Tag = savedColor,
BackgroundColor = savedColor,
BackgroundColorHighlighted = savedColor,
BackgroundColorSelected = savedColor.RGBMultiplied(0.8f),
};
savedColorButton.ButtonClicked += (b) => OnSavedColorButtonClicked(b);
_savedColorButtons.Add(savedColorButton);
}
if (_savedColors.Count < 8)
{
var savedColorButton = new Button(PickerMargin * (_savedColors.Count + 1) + SavedColorButtonWidth * _savedColors.Count, Height - SavedColorButtonHeight - PickerMargin, SavedColorButtonWidth, SavedColorButtonHeight)
{
Text = "+",
Parent = this,
Tag = null,
};
savedColorButton.ButtonClicked += (b) => OnSavedColorButtonClicked(b);
_savedColorButtons.Add(savedColorButton);
}
}
/// <inheritdoc />
public override void OnSubmit()
{
+15 -1
View File
@@ -201,7 +201,21 @@ namespace FlaxEditor.Gizmo
ActorNode prefabRoot = GetPrefabRootInParent(actorNode);
if (prefabRoot != null && actorNode != prefabRoot)
{
hit = WalkUpAndFindActorNodeBeforeSelection(actorNode, prefabRoot);
bool isPrefabInSelection = false;
foreach (var e in sceneEditing.Selection)
{
if (e is ActorNode ae && GetPrefabRootInParent(ae) == prefabRoot)
{
isPrefabInSelection = true;
break;
}
}
// Skip selecting prefab root if we already had object from that prefab selected
if (!isPrefabInSelection)
{
hit = WalkUpAndFindActorNodeBeforeSelection(actorNode, prefabRoot);
}
}
}
@@ -526,133 +526,6 @@ DEFINE_INTERNAL_CALL(void) EditorInternal_RunVisualScriptBreakpointLoopTick(floa
Engine::OnDraw();
}
struct VisualScriptLocalManaged
{
MString* Value;
MString* ValueTypeName;
uint32 NodeId;
int32 BoxId;
};
DEFINE_INTERNAL_CALL(MArray*) EditorInternal_GetVisualScriptLocals(int* localsCount)
{
MArray* result = nullptr;
*localsCount = 0;
const auto stack = VisualScripting::GetThreadStackTop();
if (stack && stack->Scope)
{
const int32 count = stack->Scope->Parameters.Length() + stack->Scope->ReturnedValues.Count();
const MClass* mclass = ((NativeBinaryModule*)GetBinaryModuleFlaxEngine())->Assembly->GetClass("FlaxEditor.Editor+VisualScriptLocal");
ASSERT(mclass);
result = MCore::Array::New(mclass, count);
VisualScriptLocalManaged local;
local.NodeId = MAX_uint32;
if (stack->Scope->Parameters.Length() != 0)
{
auto s = stack;
while (s->PreviousFrame && s->PreviousFrame->Scope == stack->Scope)
s = s->PreviousFrame;
if (s)
local.NodeId = s->Node->ID;
}
VisualScriptLocalManaged* resultPtr = MCore::Array::GetAddress<VisualScriptLocalManaged>(result);
for (int32 i = 0; i < stack->Scope->Parameters.Length(); i++)
{
auto& v = stack->Scope->Parameters[i];
local.BoxId = i + 1;
local.Value = MUtils::ToString(v.ToString());
local.ValueTypeName = MUtils::ToString(v.Type.GetTypeName());
resultPtr[i] = local;
}
for (int32 i = 0; i < stack->Scope->ReturnedValues.Count(); i++)
{
auto& v = stack->Scope->ReturnedValues[i];
local.NodeId = v.NodeId;
local.BoxId = v.BoxId;
local.Value = MUtils::ToString(v.Value.ToString());
local.ValueTypeName = MUtils::ToString(v.Value.Type.GetTypeName());
resultPtr[stack->Scope->Parameters.Length() + i] = local;
}
*localsCount = count;
}
return result;
}
struct VisualScriptStackFrameManaged
{
MObject* Script;
uint32 NodeId;
int32 BoxId;
};
DEFINE_INTERNAL_CALL(MArray*) EditorInternal_GetVisualScriptStackFrames(int* stackFramesCount)
{
MArray* result = nullptr;
*stackFramesCount = 0;
const auto stack = VisualScripting::GetThreadStackTop();
if (stack)
{
int32 count = 0;
auto s = stack;
while (s)
{
s = s->PreviousFrame;
count++;
}
const MClass* mclass = ((NativeBinaryModule*)GetBinaryModuleFlaxEngine())->Assembly->GetClass("FlaxEditor.Editor+VisualScriptStackFrame");
ASSERT(mclass);
result = MCore::Array::New(mclass, count);
VisualScriptStackFrameManaged* resultPtr = MCore::Array::GetAddress<VisualScriptStackFrameManaged>(result);
s = stack;
count = 0;
while (s)
{
VisualScriptStackFrameManaged frame;
frame.Script = s->Script->GetOrCreateManagedInstance();
frame.NodeId = s->Node->ID;
frame.BoxId = s->Box ? s->Box->ID : MAX_uint32;
resultPtr[count] = frame;
s = s->PreviousFrame;
count++;
}
*stackFramesCount = count;
}
return result;
}
DEFINE_INTERNAL_CALL(VisualScriptStackFrameManaged) EditorInternal_GetVisualScriptPreviousScopeFrame()
{
VisualScriptStackFrameManaged frame;
Platform::MemoryClear(&frame, sizeof(frame));
const auto stack = VisualScripting::GetThreadStackTop();
if (stack)
{
auto s = stack;
while (s->PreviousFrame && s->PreviousFrame->Scope == stack->Scope)
s = s->PreviousFrame;
if (s && s->PreviousFrame)
{
s = s->PreviousFrame;
frame.Script = s->Script->GetOrCreateManagedInstance();
frame.NodeId = s->Node->ID;
frame.BoxId = s->Box ? s->Box->ID : MAX_uint32;
}
}
return frame;
}
DEFINE_INTERNAL_CALL(bool) EditorInternal_EvaluateVisualScriptLocal(VisualScript* script, VisualScriptLocalManaged* local)
{
Variant v;
if (VisualScripting::Evaluate(script, VisualScripting::GetThreadStackTop()->Instance, local->NodeId, local->BoxId, v))
{
local->Value = MUtils::ToString(v.ToString());
local->ValueTypeName = MUtils::ToString(v.Type.GetTypeName());
return true;
}
return false;
}
DEFINE_INTERNAL_CALL(void) EditorInternal_DeserializeSceneObject(SceneObject* sceneObject, MString* jsonObj)
{
PROFILE_CPU_NAMED("DeserializeSceneObject");
@@ -769,7 +642,7 @@ bool ManagedEditor::TryRestoreImportOptions(ModelTool::Options& options, String
// Get options from model
FileSystem::NormalizePath(assetPath);
return ImportModelFile::TryGetImportOptions(assetPath, options);
return ImportModel::TryGetImportOptions(assetPath, options);
}
bool ManagedEditor::Import(const String& inputPath, const String& outputPath, const AudioTool::Options& options)
+89
View File
@@ -489,6 +489,95 @@ void ManagedEditor::RequestStartPlayOnEditMode()
Internal_RequestStartPlayOnEditMode->Invoke(GetManagedInstance(), nullptr, nullptr);
}
Array<ManagedEditor::VisualScriptStackFrame> ManagedEditor::GetVisualScriptStackFrames()
{
Array<VisualScriptStackFrame> result;
const auto stack = VisualScripting::GetThreadStackTop();
auto s = stack;
while (s)
{
VisualScriptStackFrame& frame = result.AddOne();
frame.Script = s->Script;
frame.NodeId = s->Node->ID;
frame.BoxId = s->Box ? s->Box->ID : MAX_uint32;
s = s->PreviousFrame;
}
return result;
}
ManagedEditor::VisualScriptStackFrame ManagedEditor::GetVisualScriptPreviousScopeFrame()
{
VisualScriptStackFrame frame;
Platform::MemoryClear(&frame, sizeof(frame));
const auto stack = VisualScripting::GetThreadStackTop();
if (stack)
{
auto s = stack;
while (s->PreviousFrame && s->PreviousFrame->Scope == stack->Scope)
s = s->PreviousFrame;
if (s && s->PreviousFrame)
{
s = s->PreviousFrame;
frame.Script = s->Script;
frame.NodeId = s->Node->ID;
frame.BoxId = s->Box ? s->Box->ID : MAX_uint32;
}
}
return frame;
}
Array<ManagedEditor::VisualScriptLocal> ManagedEditor::GetVisualScriptLocals()
{
Array<VisualScriptLocal> result;
const auto stack = VisualScripting::GetThreadStackTop();
if (stack && stack->Scope)
{
const int32 count = stack->Scope->Parameters.Length() + stack->Scope->ReturnedValues.Count();
result.Resize(count);
VisualScriptLocal local;
local.NodeId = MAX_uint32;
if (stack->Scope->Parameters.Length() != 0)
{
auto s = stack;
while (s->PreviousFrame && s->PreviousFrame->Scope == stack->Scope)
s = s->PreviousFrame;
if (s)
local.NodeId = s->Node->ID;
}
for (int32 i = 0; i < stack->Scope->Parameters.Length(); i++)
{
auto& v = stack->Scope->Parameters[i];
local.BoxId = i + 1;
local.Value = v.ToString();
local.ValueTypeName = v.Type.GetTypeName();
result[i] = local;
}
for (int32 i = 0; i < stack->Scope->ReturnedValues.Count(); i++)
{
auto& v = stack->Scope->ReturnedValues[i];
local.NodeId = v.NodeId;
local.BoxId = v.BoxId;
local.Value = v.Value.ToString();
local.ValueTypeName = v.Value.Type.GetTypeName();
result[stack->Scope->Parameters.Length() + i] = local;
}
}
return result;
}
bool ManagedEditor::EvaluateVisualScriptLocal(VisualScript* script, VisualScriptLocal& local)
{
Variant v;
const auto stack = VisualScripting::GetThreadStackTop();
if (stack && VisualScripting::Evaluate(script, stack->Instance, local.NodeId, local.BoxId, v))
{
local.Value = v.ToString();
local.ValueTypeName = v.Type.GetTypeName();
return true;
}
return false;
}
void ManagedEditor::OnEditorAssemblyLoaded(MAssembly* assembly)
{
ASSERT(!HasManagedInstance());
+25
View File
@@ -210,6 +210,31 @@ public:
API_FUNCTION() static bool TryRestoreImportOptions(API_PARAM(Ref) AudioTool::Options& options, String assetPath);
#endif
public:
API_STRUCT(Internal, NoDefault) struct VisualScriptStackFrame
{
DECLARE_SCRIPTING_TYPE_MINIMAL(VisualScriptStackFrame);
API_FIELD() class VisualScript* Script;
API_FIELD() uint32 NodeId;
API_FIELD() int32 BoxId;
};
API_STRUCT(Internal, NoDefault) struct VisualScriptLocal
{
DECLARE_SCRIPTING_TYPE_MINIMAL(VisualScriptLocal);
API_FIELD() String Value;
API_FIELD() String ValueTypeName;
API_FIELD() uint32 NodeId;
API_FIELD() int32 BoxId;
};
API_FUNCTION(Internal) static Array<VisualScriptStackFrame> GetVisualScriptStackFrames();
API_FUNCTION(Internal) static VisualScriptStackFrame GetVisualScriptPreviousScopeFrame();
API_FUNCTION(Internal) static Array<VisualScriptLocal> GetVisualScriptLocals();
API_FUNCTION(Internal) static bool EvaluateVisualScriptLocal(VisualScript* script, API_PARAM(Ref) VisualScriptLocal& local);
private:
void OnEditorAssemblyLoaded(MAssembly* assembly);
+25 -19
View File
@@ -126,29 +126,35 @@ namespace FlaxEditor.Modules
{
if (item != null && !item.GetImportPath(out string importPath))
{
// Check if input file is missing
if (!System.IO.File.Exists(importPath))
{
Editor.LogWarning(string.Format("Cannot reimport asset \'{0}\'. File \'{1}\' does not exist.", item.Path, importPath));
if (skipSettingsDialog)
return;
// Ask user to select new file location
var title = string.Format("Please find missing \'{0}\' file for asset \'{1}\'", importPath, item.ShortName);
if (FileSystem.ShowOpenFileDialog(Editor.Windows.MainWindow, null, "All files (*.*)\0*.*\0", false, title, out var files))
return;
if (files != null && files.Length > 0)
importPath = files[0];
// Validate file path again
if (!System.IO.File.Exists(importPath))
return;
}
if (GetReimportPath(item.ShortName, ref importPath, skipSettingsDialog))
return;
Import(importPath, item.Path, true, skipSettingsDialog, settings);
}
}
internal bool GetReimportPath(string contextName, ref string importPath, bool skipSettingsDialog = false)
{
// Check if input file is missing
if (!System.IO.File.Exists(importPath))
{
Editor.LogWarning(string.Format("Cannot reimport asset \'{0}\'. File \'{1}\' does not exist.", contextName, importPath));
if (skipSettingsDialog)
return true;
// Ask user to select new file location
var title = string.Format("Please find missing \'{0}\' file for asset \'{1}\'", importPath, contextName);
if (FileSystem.ShowOpenFileDialog(Editor.Windows.MainWindow, null, "All files (*.*)\0*.*\0", false, title, out var files))
return true;
if (files != null && files.Length > 0)
importPath = files[0];
// Validate file path again
if (!System.IO.File.Exists(importPath))
return true;
}
return false;
}
/// <summary>
/// Imports the specified files.
/// </summary>
+11 -4
View File
@@ -20,7 +20,7 @@ namespace FlaxEditor.Modules
private bool _updateOrFixedUpdateWasCalled;
private long _breakpointHangFlag;
private EditorWindow _enterPlayFocusedWindow;
private Scene[] _scenesToReload;
private Guid[] _scenesToReload;
internal SimulationModule(Editor editor)
: base(editor)
@@ -138,8 +138,15 @@ namespace FlaxEditor.Modules
Editor.Simulation.RequestStartPlayScenes();
return;
}
if (!FlaxEngine.Content.GetAssetInfo(firstScene.ID, out var info))
{
Editor.LogWarning("Invalid First Scene in Game Settings.");
}
_scenesToReload = Level.Scenes;
// Load scenes after entering the play mode
_scenesToReload = new Guid[Level.ScenesCount];
for (int i = 0; i < _scenesToReload.Length; i++)
_scenesToReload[i] = Level.GetScene(i).ID;
Level.UnloadAllScenes();
Level.LoadScene(firstScene);
@@ -153,8 +160,8 @@ namespace FlaxEditor.Modules
Level.UnloadAllScenes();
foreach (var scene in _scenesToReload)
Level.LoadScene(scene.ID);
foreach (var sceneId in _scenesToReload)
Level.LoadScene(sceneId);
}
/// <summary>
@@ -7,9 +7,37 @@ using Real = System.Single;
#endif
using FlaxEngine;
using FlaxEditor.CustomEditors.Dedicated;
using FlaxEditor.CustomEditors;
using FlaxEditor.Scripting;
namespace FlaxEditor.SceneGraph.Actors
{
/// <summary>
/// Dedicated custom editor for BoxCollider objects.
/// </summary>
[CustomEditor(typeof(BoxCollider)), DefaultEditor]
public class BoxColliderEditor : ActorEditor
{
/// <inheritdoc />
public override void Initialize(LayoutElementsContainer layout)
{
base.Initialize(layout);
layout.Space(20f);
layout.Button("Resize to Fit", Editor.Instance.CodeDocs.GetTooltip(new ScriptMemberInfo(typeof(BoxCollider).GetMethod("AutoResize")))).Button.Clicked += OnResizeClicked;
}
private void OnResizeClicked()
{
foreach (var value in Values)
{
if (value is BoxCollider collider)
collider.AutoResize();
}
}
}
/// <summary>
/// Scene tree node for <see cref="BoxCollider"/> actor type.
/// </summary>
@@ -37,5 +65,18 @@ namespace FlaxEditor.SceneGraph.Actors
return base.RayCastSelf(ref ray, out distance, out normal);
}
/// <inheritdoc />
public override void PostSpawn()
{
base.PostSpawn();
if (Actor.HasPrefabLink)
{
return;
}
((BoxCollider)Actor).AutoResize();
}
}
}
@@ -334,6 +334,11 @@ namespace FlaxEditor.SceneGraph.Actors
{
base.PostSpawn();
if (Actor.HasPrefabLink)
{
return;
}
// Setup for an initial spline
var spline = (Spline)Actor;
spline.AddSplineLocalPoint(Vector3.Zero, false);
@@ -61,10 +61,15 @@ namespace FlaxEditor.SceneGraph.Actors
{
base.PostSpawn();
if (Actor.HasPrefabLink)
{
return;
}
// Setup for default values
var text = (SpriteRender)Actor;
text.Material = FlaxEngine.Content.LoadAsyncInternal<MaterialBase>(EditorAssets.DefaultSpriteMaterial);
text.Image = FlaxEngine.Content.LoadAsyncInternal<Texture>(EditorAssets.FlaxIconTexture);
var sprite = (SpriteRender)Actor;
sprite.Material = FlaxEngine.Content.LoadAsyncInternal<MaterialBase>(EditorAssets.DefaultSpriteMaterial);
sprite.Image = FlaxEngine.Content.LoadAsyncInternal<Texture>(EditorAssets.FlaxIconTexture);
}
}
}
@@ -22,6 +22,11 @@ namespace FlaxEditor.SceneGraph.Actors
{
base.PostSpawn();
if (Actor.HasPrefabLink)
{
return;
}
// Setup for default values
var text = (TextRender)Actor;
text.Text = "My Text";
@@ -29,15 +29,33 @@ namespace FlaxEditor.SceneGraph.Actors
{
base.PostSpawn();
if (Actor.HasPrefabLink)
{
return;
}
// Rotate to match the space (GUI uses upper left corner as a root)
Actor.LocalOrientation = Quaternion.Euler(0, -180, -180);
var uiControl = new UIControl
bool canSpawn = true;
foreach (var uiControl in Actor.GetChildren<UIControl>())
{
Name = "Canvas Scalar",
Transform = Actor.Transform,
Control = new CanvasScaler()
};
Root.Spawn(uiControl, Actor);
if (uiControl.Get<CanvasScaler>() == null)
continue;
canSpawn = false;
break;
}
if (canSpawn)
{
var uiControl = new UIControl
{
Name = "Canvas Scalar",
Transform = Actor.Transform,
Control = new CanvasScaler()
};
Root.Spawn(uiControl, Actor);
}
_treeNode.Expand();
}
/// <inheritdoc />
+16 -4
View File
@@ -85,12 +85,20 @@ namespace FlaxEditor.SceneGraph.GUI
{
if (Parent is ActorTreeNode parent)
{
for (int i = 0; i < parent.ChildrenCount; i++)
var anyChanged = false;
var children = parent.Children;
for (int i = 0; i < children.Count; i++)
{
if (parent.Children[i] is ActorTreeNode child && child.Actor)
child._orderInParent = child.Actor.OrderInParent;
if (children[i] is ActorTreeNode child && child.Actor)
{
var orderInParent = child.Actor.OrderInParent;
anyChanged |= child._orderInParent != orderInParent;
if (anyChanged)
child._orderInParent = orderInParent;
}
}
parent.SortChildren();
if (anyChanged)
parent.SortChildren();
}
else if (Actor)
{
@@ -690,6 +698,10 @@ namespace FlaxEditor.SceneGraph.GUI
if (thisHasScene != otherHasScene)
return false;
// Reject dragging actors between prefab windows (different roots)
if (!thisHasScene && ActorNode.Root != actorNode.Root)
return false;
// Reject dragging parents and itself
return actorNode.Actor != null && actorNode != ActorNode && actorNode.Find(Actor) == null;
}
@@ -1335,6 +1335,7 @@ namespace FlaxEditor.Surface.Archetypes
Surface?.AddBatchedUndoAction(action);
action.Do();
Surface?.OnNodesConnected(this, other);
Surface?.MarkAsEdited();
}
}
@@ -1911,6 +1912,7 @@ namespace FlaxEditor.Surface.Archetypes
{
var action = new StateMachineStateBase.AddRemoveTransitionAction(this);
SourceState.Surface?.AddBatchedUndoAction(action);
SourceState.Surface?.MarkAsEdited();
action.Do();
}
@@ -493,7 +493,15 @@ namespace FlaxEditor.Surface.Archetypes
{
TypeID = 10,
Title = "Blend Additive",
Description = "Blend animation poses (with additive mode)",
Description =
"Blend animation poses (with additive mode)" +
"\n" +
"\nNote: " +
"\nOrder of nodes matters, because Additive animation is appplayed on top of curent frame." +
"\n" +
"\nTip for blender users:" +
"\nInside NLA the the order is bottom (first node in flax) to the top (last node in flax)" +
"\nu need to place it in this order to get correct resoults",
Flags = NodeFlags.AnimGraph,
Size = new Float2(170, 80),
DefaultValues = new object[]
@@ -755,6 +755,29 @@ namespace FlaxEditor.Surface.Archetypes
}
}
/// <inheritdoc />
public override void OnDeleted(SurfaceNodeActions action)
{
// Unlink from the current parent (when deleted by user)
var node = Node;
if (node != null)
{
if (action == SurfaceNodeActions.User)
{
var decorators = node.DecoratorIds;
decorators.Remove(ID);
node.DecoratorIds = decorators;
}
else
{
node._decorators = null;
node.ResizeAuto();
}
}
base.OnDeleted(action);
}
public override void OnSurfaceCanEditChanged(bool canEdit)
{
base.OnSurfaceCanEditChanged(canEdit);
@@ -33,6 +33,10 @@ namespace FlaxEditor.Surface.Elements
Archetype = archetype;
ParentNode.ValuesChanged += OnNodeValuesChanged;
// Disable slider if surface doesn't allow it
if (!ParentNode.Surface.CanLivePreviewValueChanges)
_slideSpeed = 0.0f;
}
private void OnNodeValuesChanged()
+3
View File
@@ -22,6 +22,9 @@ namespace FlaxEditor.Surface
{
}
/// <inheritdoc />
public override bool CanLivePreviewValueChanges => false;
/// <inheritdoc />
public override string GetTypeName(ScriptType type)
{
@@ -19,6 +19,7 @@ namespace FlaxEditor.Surface.Undo
private ushort _typeId;
private Float2 _nodeLocation;
private object[] _nodeValues;
private SurfaceNodeActions _actionType = SurfaceNodeActions.User; // Action usage flow is first to apply user effect such as removing/adding node, then we use Undo type so node can react to this
public AddRemoveNodeAction(SurfaceNode node, bool isAdd)
{
@@ -38,6 +39,7 @@ namespace FlaxEditor.Surface.Undo
Add();
else
Remove();
_actionType = SurfaceNodeActions.Undo;
}
/// <inheritdoc />
@@ -67,8 +69,8 @@ namespace FlaxEditor.Surface.Undo
else if (_nodeValues != null && _nodeValues.Length != 0)
throw new InvalidOperationException("Invalid node values.");
node.Location = _nodeLocation;
context.OnControlLoaded(node, SurfaceNodeActions.Undo);
node.OnSurfaceLoaded(SurfaceNodeActions.Undo);
context.OnControlLoaded(node, _actionType);
node.OnSurfaceLoaded(_actionType);
context.MarkAsModified();
}
@@ -89,7 +91,7 @@ namespace FlaxEditor.Surface.Undo
// Remove node
context.Nodes.Remove(node);
context.OnControlDeleted(node, SurfaceNodeActions.Undo);
context.OnControlDeleted(node, _actionType);
context.MarkAsModified();
}
@@ -533,6 +533,7 @@ namespace FlaxEditor.Surface
UpdateSelectionRectangle();
}
}
bool showPrimaryMenu = false;
if (_rightMouseDown && button == MouseButton.Right)
{
_rightMouseDown = false;
@@ -546,8 +547,7 @@ namespace FlaxEditor.Surface
_cmStartPos = location;
if (controlUnderMouse == null)
{
// Show primary context menu
ShowPrimaryMenu(_cmStartPos);
showPrimaryMenu = true;
}
}
_mouseMoveAmount = 0;
@@ -573,8 +573,13 @@ namespace FlaxEditor.Surface
return true;
}
// If none of the child controls handled this show the primary context menu
if (showPrimaryMenu)
{
ShowPrimaryMenu(_cmStartPos);
}
// Letting go of a connection or right clicking while creating a connection
if (!_isMovingSelection && _connectionInstigator != null && !IsPrimaryMenuOpened)
else if (!_isMovingSelection && _connectionInstigator != null && !IsPrimaryMenuOpened)
{
_cmStartPos = location;
Cursor = CursorType.Default;
+6 -1
View File
@@ -534,6 +534,11 @@ namespace FlaxEditor.Surface
/// </summary>
public virtual bool CanSetParameters => false;
/// <summary>
/// Gets a value indicating whether surface supports/allows live previewing graph modifications due to value sliders and color pickers. True by default but disabled for shader surfaces that generate and compile shader source at flight.
/// </summary>
public virtual bool CanLivePreviewValueChanges => true;
/// <summary>
/// Determines whether the specified node archetype can be used in the surface.
/// </summary>
@@ -832,7 +837,7 @@ namespace FlaxEditor.Surface
actions.Add(action);
}
Undo.AddAction(new MultiUndoAction(actions, nodes.Count == 1 ? "Remove node" : "Remove nodes"));
AddBatchedUndoAction(new MultiUndoAction(actions, nodes.Count == 1 ? "Remove node" : "Remove nodes"));
}
}
+41 -4
View File
@@ -62,6 +62,19 @@ namespace FlaxEditor.Surface
return true;
}
private string GetBoxDebuggerTooltip(ref Editor.VisualScriptLocal local)
{
if (string.IsNullOrEmpty(local.ValueTypeName))
{
if (string.IsNullOrEmpty(local.Value))
return string.Empty;
return local.Value;
}
if (string.IsNullOrEmpty(local.Value))
return $"({local.ValueTypeName})";
return $"{local.Value}\n({local.ValueTypeName})";
}
/// <inheritdoc />
public override void OnNodeBreakpointEdited(SurfaceNode node)
{
@@ -95,7 +108,7 @@ namespace FlaxEditor.Surface
ref var local = ref state.Locals[i];
if (local.BoxId == box.ID && local.NodeId == box.ParentNode.ID)
{
text = $"{local.Value ?? string.Empty} ({local.ValueTypeName})";
text = GetBoxDebuggerTooltip(ref local);
return true;
}
}
@@ -107,7 +120,7 @@ namespace FlaxEditor.Surface
ref var local = ref state.Locals[i];
if (local.BoxId == connectedBox.ID && local.NodeId == connectedBox.ParentNode.ID)
{
text = $"{local.Value ?? string.Empty} ({local.ValueTypeName})";
text = GetBoxDebuggerTooltip(ref local);
return true;
}
}
@@ -123,9 +136,33 @@ namespace FlaxEditor.Surface
BoxId = box.ID,
};
var script = ((Windows.Assets.VisualScriptWindow)box.Surface.Owner).Asset;
if (Editor.Internal_EvaluateVisualScriptLocal(Object.GetUnmanagedPtr(script), ref local))
if (Editor.EvaluateVisualScriptLocal(script, ref local))
{
text = $"{local.Value ?? string.Empty} ({local.ValueTypeName})";
// Check if got no value (null)
if (string.IsNullOrEmpty(local.ValueTypeName) && string.Equals(local.Value, "null", StringComparison.Ordinal))
{
var connections = box.Connections;
if (connections.Count == 0 && box.Archetype.ValueIndex >= 0 && box.ParentNode.Values != null && box.Archetype.ValueIndex < box.ParentNode.Values.Length)
{
// Special case when there is no value but the box has no connection and uses default value
var defaultValue = box.ParentNode.Values[box.Archetype.ValueIndex];
if (defaultValue != null)
{
local.Value = defaultValue.ToString();
local.ValueTypeName = defaultValue.GetType().FullName;
}
}
else if (connections.Count == 1)
{
// Special case when there is no value but the box has a connection with valid value to try to use it instead
box = connections[0];
local.NodeId = box.ParentNode.ID;
local.BoxId = box.ID;
Editor.EvaluateVisualScriptLocal(script, ref local);
}
}
text = GetBoxDebuggerTooltip(ref local);
return true;
}
}
+2 -2
View File
@@ -1374,8 +1374,8 @@ namespace FlaxEditor.Viewport
ivp.Invert();
// Create near and far points
var nearPoint = new Vector3(mousePosition, 0.0f);
var farPoint = new Vector3(mousePosition, 1.0f);
var nearPoint = new Vector3(mousePosition, _nearPlane);
var farPoint = new Vector3(mousePosition, _farPlane);
viewport.Unproject(ref nearPoint, ref ivp, out nearPoint);
viewport.Unproject(ref farPoint, ref ivp, out farPoint);
@@ -368,7 +368,7 @@ namespace FlaxEditor.Windows.Assets
actor.Layer = parentActor.Layer;
// Rename actor to identify it easily
actor.Name = Utilities.Utils.IncrementNameNumber(actor.GetType().Name, x => parentActor.GetChild(x) == null);
actor.Name = Utilities.Utils.IncrementNameNumber(actor.Name, x => parentActor.GetChild(x) == null);
}
// Spawn it
@@ -428,11 +428,9 @@ namespace FlaxEditor.Windows.Assets
private void Update(ActorNode actorNode)
{
if (actorNode.Actor)
{
actorNode.TreeNode.UpdateText();
actorNode.TreeNode.OnOrderInParentChanged();
}
actorNode.TreeNode.UpdateText();
if (actorNode.TreeNode.IsCollapsed)
return;
for (int i = 0; i < actorNode.ChildNodes.Count; i++)
{
@@ -440,6 +440,7 @@ namespace FlaxEditor.Windows.Assets
{
try
{
FlaxEngine.Profiler.BeginEvent("PrefabWindow.Update");
if (Graph.Main != null)
{
// Due to fact that actors in prefab editor are only created but not added to gameplay
@@ -468,6 +469,10 @@ namespace FlaxEditor.Windows.Assets
Graph.Root.TreeNode.ExpandAll(true);
}
}
finally
{
FlaxEngine.Profiler.EndEvent();
}
// Auto fit
if (_focusCamera && _viewport.Task.FrameCount > 1)
@@ -797,11 +797,12 @@ namespace FlaxEditor.Windows.Assets
}
// Check if any breakpoint was hit
for (int i = 0; i < Surface.Breakpoints.Count; i++)
var breakpoints = Surface.Breakpoints;
for (int i = 0; i < breakpoints.Count; i++)
{
if (Surface.Breakpoints[i].ID == flowInfo.NodeId)
if (breakpoints[i].ID == flowInfo.NodeId)
{
OnDebugBreakpointHit(ref flowInfo, Surface.Breakpoints[i]);
OnDebugBreakpointHit(ref flowInfo, breakpoints[i]);
break;
}
}
@@ -820,7 +821,7 @@ namespace FlaxEditor.Windows.Assets
var state = (BreakpointHangState)Editor.Instance.Simulation.BreakpointHangTag;
if (state.Locals == null)
{
state.Locals = Editor.Internal_GetVisualScriptLocals(out var _);
state.Locals = Editor.GetVisualScriptLocals();
Editor.Instance.Simulation.BreakpointHangTag = state;
}
return state;
@@ -831,7 +832,7 @@ namespace FlaxEditor.Windows.Assets
var state = (BreakpointHangState)Editor.Instance.Simulation.BreakpointHangTag;
if (state.StackFrames == null)
{
state.StackFrames = Editor.Internal_GetVisualScriptStackFrames(out var _);
state.StackFrames = Editor.GetVisualScriptStackFrames();
Editor.Instance.Simulation.BreakpointHangTag = state;
}
return state;
@@ -976,7 +977,7 @@ namespace FlaxEditor.Windows.Assets
return;
// Break on any of the output connects from the previous scope node
var frame = Editor.Internal_GetVisualScriptPreviousScopeFrame();
var frame = Editor.GetVisualScriptPreviousScopeFrame();
if (frame.Script != null)
{
if (_debugStepOutNodesIds == null)
@@ -114,18 +114,32 @@ namespace FlaxEditor.Windows
}
}
cm.AddButton("Delete", () => Delete(item));
if (isFolder && folder.Node is MainContentTreeNode)
{
cm.AddSeparator();
}
else
{
cm.AddButton("Delete", () => Delete(item));
cm.AddSeparator();
cm.AddSeparator();
cm.AddButton("Duplicate", _view.Duplicate);
cm.AddButton("Duplicate", _view.Duplicate);
cm.AddButton("Copy", _view.Copy);
cm.AddButton("Copy", _view.Copy);
}
b = cm.AddButton("Paste", _view.Paste);
b.Enabled = _view.CanPaste();
cm.AddButton("Rename", () => Rename(item));
if (isFolder && folder.Node is MainContentTreeNode)
{
// Do nothing
}
else
{
cm.AddButton("Rename", () => Rename(item));
}
// Custom options
ContextMenuShow?.Invoke(cm, item);
+46 -9
View File
@@ -381,6 +381,7 @@ namespace FlaxEditor.Windows
Arguments = $"clone {gitPath} \"{clonePath}\"",
ShellExecute = false,
LogOutput = true,
WaitForEnd = true
};
Platform.CreateProcess(ref settings);
}
@@ -391,7 +392,7 @@ namespace FlaxEditor.Windows
}
Editor.Log("Plugin project has been cloned.");
try
{
// Start git submodule clone
@@ -402,6 +403,7 @@ namespace FlaxEditor.Windows
Arguments = "submodule update --init",
ShellExecute = false,
LogOutput = true,
WaitForEnd = true
};
Platform.CreateProcess(ref settings);
}
@@ -412,24 +414,28 @@ namespace FlaxEditor.Windows
}
// Find project config file. Could be different then what the user named the folder.
var files = Directory.GetFiles(clonePath);
string pluginProjectName = "";
foreach (var file in files)
foreach (var file in Directory.GetFiles(clonePath))
{
if (file.Contains(".flaxproj", StringComparison.OrdinalIgnoreCase))
{
pluginProjectName = Path.GetFileNameWithoutExtension(file);
Debug.Log(pluginProjectName);
break;
}
}
if (string.IsNullOrEmpty(pluginProjectName))
Editor.LogError("Failed to find plugin project file to add to Project config. Please add manually.");
else
{
await AddReferenceToProject(pluginName, pluginProjectName);
MessageBox.Show($"{pluginName} has been successfully cloned. Restart editor for changes to take effect.", "Plugin Project Created", MessageBoxButtons.OK);
Editor.LogError("Failed to find plugin project file to add to Project config. Please add manually.");
return;
}
await AddModuleReferencesInGameModule(clonePath);
await AddReferenceToProject(pluginName, pluginProjectName);
if (Editor.Options.Options.SourceCode.AutoGenerateScriptsProjectFiles)
Editor.ProgressReporting.GenerateScriptsProjectFiles.RunAsync();
MessageBox.Show($"{pluginName} has been successfully cloned. Restart editor for changes to take effect.", "Plugin Project Created", MessageBoxButtons.OK);
}
private void OnAddButtonClicked()
@@ -749,6 +755,37 @@ namespace FlaxEditor.Windows
MessageBox.Show($"{pluginName} has been successfully created. Restart editor for changes to take effect.", "Plugin Project Created", MessageBoxButtons.OK);
}
private async Task AddModuleReferencesInGameModule(string pluginFolderPath)
{
// Common game build script location
var gameScript = Path.Combine(Globals.ProjectFolder, "Source/Game/Game.Build.cs");
if (File.Exists(gameScript))
{
var gameScriptContents = await File.ReadAllTextAsync(gameScript);
var insertLocation = gameScriptContents.IndexOf("base.Setup(options);", StringComparison.Ordinal);
if (insertLocation != -1)
{
insertLocation += 20;
var modifiedAny = false;
// Find all code modules in a plugin to auto-reference them in game build script
foreach (var subDir in Directory.GetDirectories(Path.Combine(pluginFolderPath, "Source")))
{
var pluginModuleName = Path.GetFileName(subDir);
var pluginModuleScriptPath = Path.Combine(subDir, pluginModuleName + ".Build.cs");
if (File.Exists(pluginModuleScriptPath))
{
gameScriptContents = gameScriptContents.Insert(insertLocation, $"\n options.PublicDependencies.Add(\"{pluginModuleName}\");");
modifiedAny = true;
}
}
if (modifiedAny)
await File.WriteAllTextAsync(gameScript, gameScriptContents, Encoding.UTF8);
}
}
}
private async Task AddReferenceToProject(string pluginFolderName, string pluginName)
{
// Project flax config file
@@ -1,6 +1,8 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using System;
using System.Collections.Generic;
using System.Linq;
using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.SceneGraph;
using FlaxEngine;
@@ -146,6 +148,31 @@ namespace FlaxEditor.Windows
contextMenu.AddButton("Break Prefab Link", Editor.Prefabs.BreakLinks);
}
// Load additional scenes option
if (!hasSthSelected)
{
var allScenes = FlaxEngine.Content.GetAllAssetsByType(typeof(SceneAsset));
var loadedSceneIds = Editor.Instance.Scene.Root.ChildNodes.Select(node => node.ID).ToList();
var unloadedScenes = allScenes.Where(sceneId => !loadedSceneIds.Contains(sceneId)).ToList();
if (unloadedScenes.Count > 0)
{
contextMenu.AddSeparator();
var childCM = contextMenu.GetOrAddChildMenu("Open Scene");
foreach (var sceneGuid in unloadedScenes)
{
if (FlaxEngine.Content.GetAssetInfo(sceneGuid, out var unloadedScene))
{
var splitPath = unloadedScene.Path.Split('/');
var sceneName = splitPath[^1];
if (splitPath[^1].EndsWith(".scene"))
sceneName = sceneName[..^6];
childCM.ContextMenu.AddButton(sceneName, () => { Editor.Instance.Scene.OpenScene(sceneGuid, true); });
}
}
}
}
// Spawning actors options
contextMenu.AddSeparator();
+4 -1
View File
@@ -144,7 +144,10 @@ BehaviorKnowledge::~BehaviorKnowledge()
void BehaviorKnowledge::InitMemory(BehaviorTree* tree)
{
ASSERT_LOW_LAYER(!Tree && tree);
if (Tree)
FreeMemory();
if (!tree)
return;
Tree = tree;
Blackboard = Variant::NewValue(tree->Graph.Root->BlackboardType);
RelevantNodes.Resize(tree->Graph.NodesCount, false);
+4 -2
View File
@@ -624,8 +624,10 @@ void BehaviorTreeLoopDecorator::PostUpdate(const BehaviorUpdateContext& context,
if (result == BehaviorUpdateResult::Success)
{
auto state = GetState<State>(context.Memory);
state->Loops--;
if (state->Loops > 0)
if (!InfiniteLoop)
state->Loops--;
if (state->Loops > 0 || InfiniteLoop)
{
// Keep running in a loop but reset node's state (preserve self state)
result = BehaviorUpdateResult::Running;
+8 -4
View File
@@ -305,12 +305,16 @@ API_CLASS(Sealed) class FLAXENGINE_API BehaviorTreeLoopDecorator : public Behavi
DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(BehaviorTreeLoopDecorator, BehaviorTreeDecorator);
API_AUTO_SERIALIZATION();
// Amount of times to execute the node. Unused if LoopCountSelector is used.
API_FIELD(Attributes="EditorOrder(10), Limit(0)")
// Is the loop infinite (until failed)?
API_FIELD(Attributes = "EditorOrder(10)")
bool InfiniteLoop = false;
// Amount of times to execute the node. Unused if LoopCountSelector is used or if InfiniteLoop is used.
API_FIELD(Attributes="EditorOrder(20), Limit(0), VisibleIf(nameof(InfiniteLoop), true)")
int32 LoopCount = 3;
// Amount of times to execute the node from behavior's knowledge (blackboard, goal or sensor). If set, overrides LoopCount.
API_FIELD(Attributes="EditorOrder(20)")
// Amount of times to execute the node from behavior's knowledge (blackboard, goal or sensor). If set, overrides LoopCount. Unused if InfiniteLoop is used.
API_FIELD(Attributes="EditorOrder(30), VisibleIf(nameof(InfiniteLoop), true)")
BehaviorKnowledgeSelector<int32> LoopCountSelector;
public:
+5
View File
@@ -17,6 +17,11 @@ API_CLASS(Abstract) class FLAXENGINE_API AnimEvent : public SerializableScriptin
{
DECLARE_SCRIPTING_TYPE(AnimEvent);
/// <summary>
/// Indicates whether the event can be executed in async from a thread that updates the animated model. Otherwise, event execution will be delayed until the sync point of the animated model and called from the main thread. Async events need to precisely handle data access, especially when it comes to editing scene objects with multi-threading.
/// </summary>
API_FIELD(Attributes="HideInEditor, NoSerialize") bool Async = false;
#if USE_EDITOR
/// <summary>
/// Event display color in the Editor.
+9 -5
View File
@@ -98,7 +98,6 @@ public:
/// </summary>
struct AnimationData
{
public:
/// <summary>
/// The duration of the animation (in frames).
/// </summary>
@@ -114,6 +113,11 @@ public:
/// </summary>
bool EnableRootMotion = false;
/// <summary>
/// The animation name.
/// </summary>
String Name;
/// <summary>
/// The custom node name to be used as a root motion source. If not specified the actual root node will be used.
/// </summary>
@@ -131,14 +135,14 @@ public:
FORCE_INLINE float GetLength() const
{
#if BUILD_DEBUG
ASSERT(FramesPerSecond != 0);
ASSERT(FramesPerSecond > ZeroTolerance);
#endif
return static_cast<float>(Duration / FramesPerSecond);
}
uint64 GetMemoryUsage() const
{
uint64 result = RootNodeName.Length() * sizeof(Char) + Channels.Capacity() * sizeof(NodeAnimationData);
uint64 result = (Name.Length() + RootNodeName.Length()) * sizeof(Char) + Channels.Capacity() * sizeof(NodeAnimationData);
for (const auto& e : Channels)
result += e.GetMemoryUsage();
return result;
@@ -151,9 +155,7 @@ public:
{
int32 result = 0;
for (int32 i = 0; i < Channels.Count(); i++)
{
result += Channels[i].GetKeyframesCount();
}
return result;
}
@@ -166,6 +168,7 @@ public:
::Swap(Duration, other.Duration);
::Swap(FramesPerSecond, other.FramesPerSecond);
::Swap(EnableRootMotion, other.EnableRootMotion);
::Swap(Name, other.Name);
::Swap(RootNodeName, other.RootNodeName);
Channels.Swap(other.Channels);
}
@@ -175,6 +178,7 @@ public:
/// </summary>
void Dispose()
{
Name.Clear();
Duration = 0.0;
FramesPerSecond = 0.0;
RootNodeName.Clear();
+1
View File
@@ -146,6 +146,7 @@ void AnimationsSystem::PostExecute(TaskGraph* graph)
auto animatedModel = AnimationManagerInstance.UpdateList[index];
if (CanUpdateModel(animatedModel))
{
animatedModel->GraphInstance.InvokeAnimEvents();
animatedModel->OnAnimationUpdated_Sync();
}
}
+52 -21
View File
@@ -38,22 +38,16 @@ void AnimGraphImpulse::SetNodeModelTransformation(SkeletonData& skeleton, int32
void AnimGraphInstanceData::Clear()
{
Version = 0;
LastUpdateTime = -1;
CurrentFrame = 0;
RootTransform = Transform::Identity;
RootMotion = Transform::Identity;
ClearState();
Parameters.Resize(0);
State.Resize(0);
NodesPose.Resize(0);
Slots.Resize(0);
for (const auto& e : Events)
((AnimContinuousEvent*)e.Instance)->OnEnd((AnimatedModel*)Object, e.Anim, 0.0f, 0.0f);
Events.Resize(0);
}
void AnimGraphInstanceData::ClearState()
{
for (const auto& e : ActiveEvents)
OutgoingEvents.Add(e.End((AnimatedModel*)Object));
ActiveEvents.Clear();
InvokeAnimEvents();
Version = 0;
LastUpdateTime = -1;
CurrentFrame = 0;
@@ -62,9 +56,6 @@ void AnimGraphInstanceData::ClearState()
State.Resize(0);
NodesPose.Resize(0);
Slots.Clear();
for (const auto& e : Events)
((AnimContinuousEvent*)e.Instance)->OnEnd((AnimatedModel*)Object, e.Anim, 0.0f, 0.0f);
Events.Clear();
}
void AnimGraphInstanceData::Invalidate()
@@ -73,6 +64,43 @@ void AnimGraphInstanceData::Invalidate()
CurrentFrame = 0;
}
void AnimGraphInstanceData::InvokeAnimEvents()
{
const bool all = IsInMainThread();
for (int32 i = 0; i < OutgoingEvents.Count(); i++)
{
const OutgoingEvent e = OutgoingEvents[i];
if (all || e.Instance->Async)
{
OutgoingEvents.RemoveAtKeepOrder(i);
switch (e.Type)
{
case OutgoingEvent::OnEvent:
e.Instance->OnEvent(e.Actor, e.Anim, e.Time, e.DeltaTime);
break;
case OutgoingEvent::OnBegin:
((AnimContinuousEvent*)e.Instance)->OnBegin(e.Actor, e.Anim, e.Time, e.DeltaTime);
break;
case OutgoingEvent::OnEnd:
((AnimContinuousEvent*)e.Instance)->OnEnd(e.Actor, e.Anim, e.Time, e.DeltaTime);
break;
}
}
}
}
AnimGraphInstanceData::OutgoingEvent AnimGraphInstanceData::ActiveEvent::End(AnimatedModel* actor) const
{
OutgoingEvent out;
out.Instance = Instance;
out.Actor = actor;
out.Anim = Anim;
out.Time = 0.0f;
out.DeltaTime = 0.0f;
out.Type = OutgoingEvent::OnEnd;
return out;
}
AnimGraphImpulse* AnimGraphNode::GetNodes(AnimGraphExecutor* executor)
{
auto& context = AnimGraphExecutor::Context.Get();
@@ -208,7 +236,7 @@ void AnimGraphExecutor::Update(AnimGraphInstanceData& data, float dt)
// Initialize buckets
ResetBuckets(context, &_graph);
}
for (auto& e : data.Events)
for (auto& e : data.ActiveEvents)
e.Hit = false;
// Init empty nodes data
@@ -240,16 +268,17 @@ void AnimGraphExecutor::Update(AnimGraphInstanceData& data, float dt)
if (animResult == nullptr)
animResult = GetEmptyNodes();
}
if (data.Events.Count() != 0)
if (data.ActiveEvents.Count() != 0)
{
ANIM_GRAPH_PROFILE_EVENT("Events");
for (int32 i = data.Events.Count() - 1; i >= 0; i--)
for (int32 i = data.ActiveEvents.Count() - 1; i >= 0; i--)
{
const auto& e = data.Events[i];
const auto& e = data.ActiveEvents[i];
if (!e.Hit)
{
((AnimContinuousEvent*)e.Instance)->OnEnd((AnimatedModel*)context.Data->Object, e.Anim, 0.0f, 0.0f);
data.Events.RemoveAt(i);
// Remove active event that was not hit during this frame (eg. animation using it was not used in blending)
data.OutgoingEvents.Add(e.End((AnimatedModel*)context.Data->Object));
data.ActiveEvents.RemoveAt(i);
}
}
}
@@ -284,7 +313,6 @@ void AnimGraphExecutor::Update(AnimGraphInstanceData& data, float dt)
RetargetSkeletonNode(sourceSkeleton, targetSkeleton, mapping, node, i);
targetNodes[i] = node;
}
}
}
@@ -319,6 +347,9 @@ void AnimGraphExecutor::Update(AnimGraphInstanceData& data, float dt)
data.RootMotion = animResult->RootMotion;
}
// Invoke any async anim events
context.Data->InvokeAnimEvents();
// Cleanup
context.Data = nullptr;
}
+30 -3
View File
@@ -23,6 +23,9 @@ class AnimSubGraph;
class AnimGraphBase;
class AnimGraphNode;
class AnimGraphExecutor;
class AnimatedModel;
class AnimEvent;
class AnimContinuousEvent;
class SkinnedModel;
class SkeletonData;
@@ -349,16 +352,40 @@ public:
/// </summary>
void Invalidate();
/// <summary>
/// Invokes any outgoing AnimEvent and AnimContinuousEvent collected during the last animation update. When called from non-main thread only Async events will be invoked.
/// </summary>
void InvokeAnimEvents();
private:
struct Event
struct OutgoingEvent
{
enum Types
{
OnEvent,
OnBegin,
OnEnd,
};
AnimEvent* Instance;
AnimatedModel* Actor;
Animation* Anim;
float Time, DeltaTime;
Types Type;
};
struct ActiveEvent
{
AnimContinuousEvent* Instance;
Animation* Anim;
AnimGraphNode* Node;
bool Hit;
OutgoingEvent End(AnimatedModel* actor) const;
};
Array<Event, InlinedAllocation<8>> Events;
Array<ActiveEvent, InlinedAllocation<8>> ActiveEvents;
Array<OutgoingEvent, InlinedAllocation<8>> OutgoingEvents;
};
/// <summary>
@@ -441,7 +468,7 @@ public:
/// The invalid transition valid used in Transitions to indicate invalid transition linkage.
/// </summary>
const static uint16 InvalidTransitionIndex = MAX_uint16;
/// <summary>
/// The outgoing transitions from this state to the other states. Each array item contains index of the transition data from the state node graph transitions cache. Value InvalidTransitionIndex is used for last transition to indicate the transitions amount.
/// </summary>
@@ -100,48 +100,50 @@ void AnimGraphExecutor::ProcessAnimEvents(AnimGraphNode* node, bool loop, float
if (!k.Value.Instance)
continue;
const float duration = k.Value.Duration > 1 ? k.Value.Duration : 0.0f;
#define ADD_OUTGOING_EVENT(type) context.Data->OutgoingEvents.Add({ k.Value.Instance, (AnimatedModel*)context.Data->Object, anim, eventTime, eventDeltaTime, AnimGraphInstanceData::OutgoingEvent::type })
if (k.Time <= eventTimeMax && eventTimeMin <= k.Time + duration)
{
int32 stateIndex = -1;
if (duration > 1)
{
// Begin for continuous event
for (stateIndex = 0; stateIndex < context.Data->Events.Count(); stateIndex++)
for (stateIndex = 0; stateIndex < context.Data->ActiveEvents.Count(); stateIndex++)
{
const auto& e = context.Data->Events[stateIndex];
const auto& e = context.Data->ActiveEvents[stateIndex];
if (e.Instance == k.Value.Instance && e.Node == node)
break;
}
if (stateIndex == context.Data->Events.Count())
if (stateIndex == context.Data->ActiveEvents.Count())
{
auto& e = context.Data->Events.AddOne();
e.Instance = k.Value.Instance;
ASSERT(k.Value.Instance->Is<AnimContinuousEvent>());
auto& e = context.Data->ActiveEvents.AddOne();
e.Instance = (AnimContinuousEvent*)k.Value.Instance;
e.Anim = anim;
e.Node = node;
ASSERT(k.Value.Instance->Is<AnimContinuousEvent>());
((AnimContinuousEvent*)k.Value.Instance)->OnBegin((AnimatedModel*)context.Data->Object, anim, eventTime, eventDeltaTime);
ADD_OUTGOING_EVENT(OnBegin);
}
}
// Event
k.Value.Instance->OnEvent((AnimatedModel*)context.Data->Object, anim, eventTime, eventDeltaTime);
ADD_OUTGOING_EVENT(OnEvent);
if (stateIndex != -1)
context.Data->Events[stateIndex].Hit = true;
context.Data->ActiveEvents[stateIndex].Hit = true;
}
else if (duration > 1)
{
// End for continuous event
for (int32 i = 0; i < context.Data->Events.Count(); i++)
for (int32 i = 0; i < context.Data->ActiveEvents.Count(); i++)
{
const auto& e = context.Data->Events[i];
const auto& e = context.Data->ActiveEvents[i];
if (e.Instance == k.Value.Instance && e.Node == node)
{
((AnimContinuousEvent*)k.Value.Instance)->OnEnd((AnimatedModel*)context.Data->Object, anim, eventTime, eventDeltaTime);
context.Data->Events.RemoveAt(i);
ADD_OUTGOING_EVENT(OnEnd);
context.Data->ActiveEvents.RemoveAt(i);
break;
}
}
}
#undef ADD_OUTGOING_EVENT
}
}
}
@@ -1068,20 +1070,26 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
else
{
const auto nodes = node->GetNodes(this);
const auto nodesA = static_cast<AnimGraphImpulse*>(valueA.AsPointer);
const auto nodesB = static_cast<AnimGraphImpulse*>(valueB.AsPointer);
Transform t, tA, tB;
const auto basePoseNodes = static_cast<AnimGraphImpulse*>(valueA.AsPointer);
const auto blendPoseNodes = static_cast<AnimGraphImpulse*>(valueB.AsPointer);
const auto& refrenceNodes = _graph.BaseModel.Get()->GetNodes();
Transform t, basePoseTransform, blendPoseTransform, refrenceTransform;
for (int32 i = 0; i < nodes->Nodes.Count(); i++)
{
tA = nodesA->Nodes[i];
tB = nodesB->Nodes[i];
t.Translation = tA.Translation + tB.Translation;
t.Orientation = tA.Orientation * tB.Orientation;
t.Scale = tA.Scale * tB.Scale;
t.Orientation.Normalize();
Transform::Lerp(tA, t, alpha, nodes->Nodes[i]);
basePoseTransform = basePoseNodes->Nodes[i];
blendPoseTransform = blendPoseNodes->Nodes[i];
refrenceTransform = refrenceNodes[i].LocalTransform;
// base + (blend - refrence) = transform
t.Translation = basePoseTransform.Translation + (blendPoseTransform.Translation - refrenceTransform.Translation);
auto diff = Quaternion::Invert(refrenceTransform.Orientation) * blendPoseTransform.Orientation;
t.Orientation = basePoseTransform.Orientation * diff;
t.Scale = basePoseTransform.Scale + (blendPoseTransform.Scale - refrenceTransform.Scale);
//lerp base and transform
Transform::Lerp(basePoseTransform, t, alpha, nodes->Nodes[i]);
}
Transform::Lerp(nodesA->RootMotion, nodesA->RootMotion + nodesB->RootMotion, alpha, nodes->RootMotion);
Transform::Lerp(basePoseNodes->RootMotion, basePoseNodes->RootMotion + blendPoseNodes->RootMotion, alpha, nodes->RootMotion);
value = nodes;
}
}
+11 -3
View File
@@ -310,6 +310,10 @@ void Asset::ChangeID(const Guid& newId)
if (!IsVirtual())
CRASH;
// ID has to be unique
if (Content::GetAsset(newId) != nullptr)
CRASH;
const Guid oldId = _id;
ManagedScriptingObject::ChangeID(newId);
Content::onAssetChangeId(this, oldId, newId);
@@ -438,12 +442,15 @@ bool Asset::WaitForLoaded(double timeoutInMilliseconds) const
// Note: to reproduce this case just include material into material (use layering).
// So during loading first material it will wait for child materials loaded calling this function
const double timeoutInSeconds = timeoutInMilliseconds * 0.001;
const double startTime = Platform::GetTimeSeconds();
Task* task = loadingTask;
Array<ContentLoadTask*, InlinedAllocation<64>> localQueue;
while (!Engine::ShouldExit())
#define CHECK_CONDITIONS() (!Engine::ShouldExit() && (timeoutInSeconds <= 0.0 || Platform::GetTimeSeconds() - startTime < timeoutInSeconds))
do
{
// Try to execute content tasks
while (task->IsQueued() && !Engine::ShouldExit())
while (task->IsQueued() && CHECK_CONDITIONS())
{
// Dequeue task from the loading queue
ContentLoadTask* tmp;
@@ -494,7 +501,8 @@ bool Asset::WaitForLoaded(double timeoutInMilliseconds) const
break;
}
}
}
} while (CHECK_CONDITIONS());
#undef CHECK_CONDITIONS
}
else
{
-1
View File
@@ -34,7 +34,6 @@
#define CHECK_INVALID_BUFFER(model, buffer) \
if (buffer->IsValidFor(model) == false) \
{ \
LOG(Warning, "Invalid Model Instance Buffer size {0} for Model {1}. It should be {2}. Manual update to proper size.", buffer->Count(), model->ToString(), model->MaterialSlots.Count()); \
buffer->Setup(model); \
}
@@ -23,7 +23,6 @@
#define CHECK_INVALID_BUFFER(model, buffer) \
if (buffer->IsValidFor(model) == false) \
{ \
LOG(Warning, "Invalid Skinned Model Instance Buffer size {0} for Skinned Model {1}. It should be {2}. Manual update to proper size.", buffer->Count(), model->ToString(), model->MaterialSlots.Count()); \
buffer->Setup(model); \
}
@@ -892,6 +892,11 @@ void VisualScriptExecutor::ProcessGroupFunction(Box* boxBase, Node* node, Value&
PrintStack(LogType::Error);
break;
}
if (boxBase->ID == 1)
{
value = instance;
break;
}
// TODO: check if instance is of event type (including inheritance)
// Add Visual Script method to the event bindings table
+7 -1
View File
@@ -230,6 +230,7 @@ bool Content::GetAssetInfo(const Guid& id, AssetInfo& info)
// Find asset in registry
if (Cache.FindAsset(id, info))
return true;
PROFILE_CPU();
// Locking injects some stalls but we need to make it safe (only one thread can pass though it at once)
ScopeLock lock(WorkspaceDiscoveryLocker);
@@ -276,6 +277,7 @@ bool Content::GetAssetInfo(const StringView& path, AssetInfo& info)
// Find asset in registry
if (Cache.FindAsset(path, info))
return true;
PROFILE_CPU();
const auto extension = FileSystem::GetExtension(path).ToLower();
@@ -538,6 +540,8 @@ void Content::DeleteAsset(Asset* asset)
void Content::DeleteAsset(const StringView& path)
{
PROFILE_CPU();
// Try to delete already loaded asset
Asset* asset = GetAsset(path);
if (asset != nullptr)
@@ -566,12 +570,12 @@ void Content::DeleteAsset(const StringView& path)
void Content::deleteFileSafety(const StringView& path, const Guid& id)
{
// Check if given id is invalid
if (!id.IsValid())
{
LOG(Warning, "Cannot remove file \'{0}\'. Given ID is invalid.", path);
return;
}
PROFILE_CPU();
// Ensure that file has the same ID (prevent from deleting different assets)
auto storage = ContentStorageManager::TryGetStorage(path);
@@ -678,6 +682,7 @@ bool Content::FastTmpAssetClone(const StringView& path, String& resultPath)
bool Content::CloneAssetFile(const StringView& dstPath, const StringView& srcPath, const Guid& dstId)
{
PROFILE_CPU();
ASSERT(FileSystem::AreFilePathsEqual(srcPath, dstPath) == false && dstId.IsValid());
LOG(Info, "Cloning asset \'{0}\' to \'{1}\'({2}).", srcPath, dstPath, dstId);
@@ -796,6 +801,7 @@ Asset* Content::CreateVirtualAsset(MClass* type)
Asset* Content::CreateVirtualAsset(const ScriptingTypeHandle& type)
{
PROFILE_CPU();
auto& assetType = type.GetType();
// Init mock asset info
+18 -4
View File
@@ -562,6 +562,7 @@ bool FlaxStorage::Reload()
{
if (!IsLoaded())
return false;
PROFILE_CPU();
OnReloading(this);
@@ -728,7 +729,11 @@ bool FlaxStorage::ChangeAssetID(Entry& e, const Guid& newId)
}
// Close file
CloseFileHandles();
if (CloseFileHandles())
{
LOG(Error, "Cannot close file access for '{}'", _path);
return true;
}
// Change ID
// TODO: here we could extend it and load assets from the storage and call asset ID change event to change references
@@ -776,6 +781,8 @@ FlaxChunk* FlaxStorage::AllocateChunk()
bool FlaxStorage::Create(const StringView& path, const AssetInitData* data, int32 dataCount, bool silentMode, const CustomData* customData)
{
PROFILE_CPU();
ZoneText(*path, path.Length());
LOG(Info, "Creating package at \'{0}\'. Silent Mode: {1}", path, silentMode);
// Prepare to have access to the file
@@ -1296,8 +1303,10 @@ FileReadStream* FlaxStorage::OpenFile()
return stream;
}
void FlaxStorage::CloseFileHandles()
bool FlaxStorage::CloseFileHandles()
{
PROFILE_CPU();
// Note: this is usually called by the content manager when this file is not used or on exit
// In those situations all the async tasks using this storage should be cancelled externally
@@ -1323,10 +1332,12 @@ void FlaxStorage::CloseFileHandles()
waitTime = 100;
while (Platform::AtomicRead(&_chunksLock) != 0 && waitTime-- > 0)
Platform::Sleep(1);
ASSERT(_chunksLock == 0);
if (Platform::AtomicRead(&_chunksLock) != 0)
return true; // Failed, someone is still accessing the file
// Close file handles (from all threads)
_file.DeleteAll();
return false;
}
void FlaxStorage::Dispose()
@@ -1335,7 +1346,10 @@ void FlaxStorage::Dispose()
return;
// Close file
CloseFileHandles();
if (CloseFileHandles())
{
LOG(Error, "Cannot close file access for '{}'", _path);
}
// Release data
_chunks.ClearDelete();
+1 -1
View File
@@ -405,7 +405,7 @@ public:
/// <summary>
/// Closes the file handles (it can be modified from the outside).
/// </summary>
void CloseFileHandles();
bool CloseFileHandles();
/// <summary>
/// Releases storage resources and closes handle to the file.
@@ -15,7 +15,7 @@
#include "Engine/Platform/Platform.h"
#include "Engine/Engine/Globals.h"
#include "ImportTexture.h"
#include "ImportModelFile.h"
#include "ImportModel.h"
#include "ImportAudio.h"
#include "ImportShader.h"
#include "ImportFont.h"
@@ -165,20 +165,7 @@ bool CreateAssetContext::AllocateChunk(int32 index)
void CreateAssetContext::AddMeta(JsonWriter& writer) const
{
writer.JKEY("ImportPath");
if (AssetsImportingManager::UseImportPathRelative && !FileSystem::IsRelative(InputPath)
#if PLATFORM_WINDOWS
// Import path from other drive should be stored as absolute on Windows to prevent issues
&& InputPath.Length() > 2 && Globals::ProjectFolder.Length() > 2 && InputPath[0] == Globals::ProjectFolder[0]
#endif
)
{
const String relativePath = FileSystem::ConvertAbsolutePathToRelative(Globals::ProjectFolder, InputPath);
writer.String(relativePath);
}
else
{
writer.String(InputPath);
}
writer.String(AssetsImportingManager::GetImportPath(InputPath));
writer.JKEY("ImportUsername");
writer.String(Platform::GetUserName());
}
@@ -189,7 +176,12 @@ void CreateAssetContext::ApplyChanges()
auto storage = ContentStorageManager::TryGetStorage(TargetAssetPath);
if (storage && storage->IsLoaded())
{
storage->CloseFileHandles();
if (storage->CloseFileHandles())
{
LOG(Error, "Cannot close file access for '{}'", TargetAssetPath);
_applyChangesResult = CreateAssetResult::CannotSaveFile;
return;
}
}
// Move file
@@ -304,8 +296,24 @@ bool AssetsImportingManager::ImportIfEdited(const StringView& inputPath, const S
return false;
}
String AssetsImportingManager::GetImportPath(const String& path)
{
if (UseImportPathRelative && !FileSystem::IsRelative(path)
#if PLATFORM_WINDOWS
// Import path from other drive should be stored as absolute on Windows to prevent issues
&& path.Length() > 2 && Globals::ProjectFolder.Length() > 2 && path[0] == Globals::ProjectFolder[0]
#endif
)
{
return FileSystem::ConvertAbsolutePathToRelative(Globals::ProjectFolder, path);
}
return path;
}
bool AssetsImportingManager::Create(const Function<CreateAssetResult(CreateAssetContext&)>& callback, const StringView& inputPath, const StringView& outputPath, Guid& assetId, void* arg)
{
PROFILE_CPU();
ZoneText(*outputPath, outputPath.Length());
const auto startTime = Platform::GetTimeSeconds();
// Pick ID if not specified
@@ -369,7 +377,7 @@ bool AssetsImportingManager::Create(const Function<CreateAssetResult(CreateAsset
if (result == CreateAssetResult::Ok)
{
// Register asset
Content::GetRegistry()->RegisterAsset(context.Data.Header, outputPath);
Content::GetRegistry()->RegisterAsset(context.Data.Header, context.TargetAssetPath);
// Done
const auto endTime = Platform::GetTimeSeconds();
@@ -380,7 +388,7 @@ bool AssetsImportingManager::Create(const Function<CreateAssetResult(CreateAsset
// Do nothing
return true;
}
else
else if (result != CreateAssetResult::Skip)
{
LOG(Error, "Cannot import file '{0}'! Result: {1}", inputPath, ::ToString(result));
return true;
@@ -425,37 +433,37 @@ bool AssetsImportingManagerService::Init()
{ TEXT("otf"), ASSET_FILES_EXTENSION, ImportFont::Import },
// Models
{ TEXT("obj"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
{ TEXT("fbx"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
{ TEXT("x"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
{ TEXT("dae"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
{ TEXT("gltf"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
{ TEXT("glb"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
{ TEXT("obj"), ASSET_FILES_EXTENSION, ImportModel::Import },
{ TEXT("fbx"), ASSET_FILES_EXTENSION, ImportModel::Import },
{ TEXT("x"), ASSET_FILES_EXTENSION, ImportModel::Import },
{ TEXT("dae"), ASSET_FILES_EXTENSION, ImportModel::Import },
{ TEXT("gltf"), ASSET_FILES_EXTENSION, ImportModel::Import },
{ TEXT("glb"), ASSET_FILES_EXTENSION, ImportModel::Import },
// gettext PO files
{ TEXT("po"), TEXT("json"), CreateJson::ImportPo },
// Models (untested formats - may fail :/)
{ TEXT("blend"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
{ TEXT("bvh"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
{ TEXT("ase"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
{ TEXT("ply"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
{ TEXT("dxf"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
{ TEXT("ifc"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
{ TEXT("nff"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
{ TEXT("smd"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
{ TEXT("vta"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
{ TEXT("mdl"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
{ TEXT("md2"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
{ TEXT("md3"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
{ TEXT("md5mesh"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
{ TEXT("q3o"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
{ TEXT("q3s"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
{ TEXT("ac"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
{ TEXT("stl"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
{ TEXT("lwo"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
{ TEXT("lws"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
{ TEXT("lxo"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
{ TEXT("blend"), ASSET_FILES_EXTENSION, ImportModel::Import },
{ TEXT("bvh"), ASSET_FILES_EXTENSION, ImportModel::Import },
{ TEXT("ase"), ASSET_FILES_EXTENSION, ImportModel::Import },
{ TEXT("ply"), ASSET_FILES_EXTENSION, ImportModel::Import },
{ TEXT("dxf"), ASSET_FILES_EXTENSION, ImportModel::Import },
{ TEXT("ifc"), ASSET_FILES_EXTENSION, ImportModel::Import },
{ TEXT("nff"), ASSET_FILES_EXTENSION, ImportModel::Import },
{ TEXT("smd"), ASSET_FILES_EXTENSION, ImportModel::Import },
{ TEXT("vta"), ASSET_FILES_EXTENSION, ImportModel::Import },
{ TEXT("mdl"), ASSET_FILES_EXTENSION, ImportModel::Import },
{ TEXT("md2"), ASSET_FILES_EXTENSION, ImportModel::Import },
{ TEXT("md3"), ASSET_FILES_EXTENSION, ImportModel::Import },
{ TEXT("md5mesh"), ASSET_FILES_EXTENSION, ImportModel::Import },
{ TEXT("q3o"), ASSET_FILES_EXTENSION, ImportModel::Import },
{ TEXT("q3s"), ASSET_FILES_EXTENSION, ImportModel::Import },
{ TEXT("ac"), ASSET_FILES_EXTENSION, ImportModel::Import },
{ TEXT("stl"), ASSET_FILES_EXTENSION, ImportModel::Import },
{ TEXT("lwo"), ASSET_FILES_EXTENSION, ImportModel::Import },
{ TEXT("lws"), ASSET_FILES_EXTENSION, ImportModel::Import },
{ TEXT("lxo"), ASSET_FILES_EXTENSION, ImportModel::Import },
};
AssetsImportingManager::Importers.Add(InBuildImporters, ARRAY_COUNT(InBuildImporters));
@@ -473,7 +481,7 @@ bool AssetsImportingManagerService::Init()
{ AssetsImportingManager::CreateMaterialInstanceTag, CreateMaterialInstance::Create },
// Models
{ AssetsImportingManager::CreateModelTag, ImportModelFile::Create },
{ AssetsImportingManager::CreateModelTag, ImportModel::Create },
// Other
{ AssetsImportingManager::CreateRawDataTag, CreateRawData::Create },
@@ -236,6 +236,9 @@ public:
return ImportIfEdited(inputPath, outputPath, id, arg);
}
// Converts source files path into the relative format if enabled by the project settings. Result path can be stored in asset for reimports.
static String GetImportPath(const String& path);
private:
static bool Create(const CreateAssetFunction& callback, const StringView& inputPath, const StringView& outputPath, Guid& assetId, void* arg);
};
@@ -53,7 +53,7 @@ bool CreateJson::Create(const StringView& path, const StringAnsiView& data, cons
{
if (FileSystem::CreateDirectory(directory))
{
LOG(Warning, "Failed to create directory");
LOG(Warning, "Failed to create directory '{}'", directory);
return true;
}
}
@@ -0,0 +1,743 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
#include "ImportModel.h"
#if COMPILE_WITH_ASSETS_IMPORTER
#include "Engine/Core/Log.h"
#include "Engine/Core/Collections/Sorting.h"
#include "Engine/Core/Collections/ArrayExtensions.h"
#include "Engine/Serialization/MemoryWriteStream.h"
#include "Engine/Serialization/JsonWriters.h"
#include "Engine/Graphics/Models/ModelData.h"
#include "Engine/Content/Assets/Model.h"
#include "Engine/Content/Assets/SkinnedModel.h"
#include "Engine/Content/Storage/ContentStorageManager.h"
#include "Engine/Content/Assets/Animation.h"
#include "Engine/Content/Content.h"
#include "Engine/Level/Actors/EmptyActor.h"
#include "Engine/Level/Actors/StaticModel.h"
#include "Engine/Level/Prefabs/Prefab.h"
#include "Engine/Level/Prefabs/PrefabManager.h"
#include "Engine/Level/Scripts/ModelPrefab.h"
#include "Engine/Platform/FileSystem.h"
#include "Engine/Utilities/RectPack.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "AssetsImportingManager.h"
bool ImportModel::TryGetImportOptions(const StringView& path, Options& options)
{
if (FileSystem::FileExists(path))
{
// Try to load asset file and asset info
auto tmpFile = ContentStorageManager::GetStorage(path);
AssetInitData data;
if (tmpFile
&& tmpFile->GetEntriesCount() == 1
&& (
(tmpFile->GetEntry(0).TypeName == Model::TypeName && !tmpFile->LoadAssetHeader(0, data) && data.SerializedVersion >= 4)
||
(tmpFile->GetEntry(0).TypeName == SkinnedModel::TypeName && !tmpFile->LoadAssetHeader(0, data) && data.SerializedVersion >= 1)
||
(tmpFile->GetEntry(0).TypeName == Animation::TypeName && !tmpFile->LoadAssetHeader(0, data) && data.SerializedVersion >= 1)
))
{
// Check import meta
rapidjson_flax::Document metadata;
metadata.Parse((const char*)data.Metadata.Get(), data.Metadata.Length());
if (metadata.HasParseError() == false)
{
options.Deserialize(metadata, nullptr);
return true;
}
}
}
return false;
}
struct PrefabObject
{
int32 NodeIndex;
String Name;
String AssetPath;
};
void RepackMeshLightmapUVs(ModelData& data)
{
// Use weight-based coordinates space placement and rect-pack to allocate more space for bigger meshes in the model lightmap chart
int32 lodIndex = 0;
auto& lod = data.LODs[lodIndex];
// Build list of meshes with their area
struct LightmapUVsPack : RectPack<LightmapUVsPack, float>
{
LightmapUVsPack(float x, float y, float width, float height)
: RectPack<LightmapUVsPack, float>(x, y, width, height)
{
}
void OnInsert()
{
}
};
struct MeshEntry
{
MeshData* Mesh;
float Area;
float Size;
LightmapUVsPack* Slot;
};
Array<MeshEntry> entries;
entries.Resize(lod.Meshes.Count());
float areaSum = 0;
for (int32 meshIndex = 0; meshIndex < lod.Meshes.Count(); meshIndex++)
{
auto& entry = entries[meshIndex];
entry.Mesh = lod.Meshes[meshIndex];
entry.Area = entry.Mesh->CalculateTrianglesArea();
entry.Size = Math::Sqrt(entry.Area);
areaSum += entry.Area;
}
if (areaSum > ZeroTolerance)
{
// Pack all surfaces into atlas
float atlasSize = Math::Sqrt(areaSum) * 1.02f;
int32 triesLeft = 10;
while (triesLeft--)
{
bool failed = false;
const float chartsPadding = (4.0f / 256.0f) * atlasSize;
LightmapUVsPack root(chartsPadding, chartsPadding, atlasSize - chartsPadding, atlasSize - chartsPadding);
for (auto& entry : entries)
{
entry.Slot = root.Insert(entry.Size, entry.Size, chartsPadding);
if (entry.Slot == nullptr)
{
// Failed to insert surface, increase atlas size and try again
atlasSize *= 1.5f;
failed = true;
break;
}
}
if (!failed)
{
// Transform meshes lightmap UVs into the slots in the whole atlas
const float atlasSizeInv = 1.0f / atlasSize;
for (const auto& entry : entries)
{
Float2 uvOffset(entry.Slot->X * atlasSizeInv, entry.Slot->Y * atlasSizeInv);
Float2 uvScale((entry.Slot->Width - chartsPadding) * atlasSizeInv, (entry.Slot->Height - chartsPadding) * atlasSizeInv);
// TODO: SIMD
for (auto& uv : entry.Mesh->LightmapUVs)
{
uv = uv * uvScale + uvOffset;
}
}
break;
}
}
}
}
void TryRestoreMaterials(CreateAssetContext& context, ModelData& modelData)
{
// Skip if file is missing
if (!FileSystem::FileExists(context.TargetAssetPath))
return;
// Try to load asset that gets reimported
AssetReference<Asset> asset = Content::LoadAsync<Asset>(context.TargetAssetPath);
if (asset == nullptr)
return;
if (asset->WaitForLoaded())
return;
// Get model object
ModelBase* model = nullptr;
if (asset.Get()->GetTypeName() == Model::TypeName)
{
model = ((Model*)asset.Get());
}
else if (asset.Get()->GetTypeName() == SkinnedModel::TypeName)
{
model = ((SkinnedModel*)asset.Get());
}
if (!model)
return;
// Peek materials
for (int32 i = 0; i < modelData.Materials.Count(); i++)
{
auto& dstSlot = modelData.Materials[i];
if (model->MaterialSlots.Count() > i)
{
auto& srcSlot = model->MaterialSlots[i];
dstSlot.Name = srcSlot.Name;
dstSlot.ShadowsMode = srcSlot.ShadowsMode;
dstSlot.AssetID = srcSlot.Material.GetID();
}
}
}
void SetupMaterialSlots(ModelData& data, const Array<MaterialSlotEntry>& materials)
{
Array<int32> materialSlotsTable;
materialSlotsTable.Resize(materials.Count());
materialSlotsTable.SetAll(-1);
for (auto& lod : data.LODs)
{
for (MeshData* mesh : lod.Meshes)
{
int32 newSlotIndex = materialSlotsTable[mesh->MaterialSlotIndex];
if (newSlotIndex == -1)
{
newSlotIndex = data.Materials.Count();
data.Materials.AddOne() = materials[mesh->MaterialSlotIndex];
}
mesh->MaterialSlotIndex = newSlotIndex;
}
}
}
bool SortMeshGroups(IGrouping<StringView, MeshData*> const& i1, IGrouping<StringView, MeshData*> const& i2)
{
return i1.GetKey().Compare(i2.GetKey()) < 0;
}
CreateAssetResult ImportModel::Import(CreateAssetContext& context)
{
// Get import options
Options options;
if (context.CustomArg != nullptr)
{
// Copy import options from argument
options = *static_cast<Options*>(context.CustomArg);
}
else
{
// Restore the previous settings or use default ones
if (!TryGetImportOptions(context.TargetAssetPath, options))
{
LOG(Warning, "Missing model import options. Using default values.");
}
}
// Import model file
ModelData* data = options.Cached ? options.Cached->Data : nullptr;
ModelData dataThis;
Array<IGrouping<StringView, MeshData*>>* meshesByNamePtr = options.Cached ? (Array<IGrouping<StringView, MeshData*>>*)options.Cached->MeshesByName : nullptr;
Array<IGrouping<StringView, MeshData*>> meshesByNameThis;
String autoImportOutput;
if (!data)
{
String errorMsg;
autoImportOutput = StringUtils::GetDirectoryName(context.TargetAssetPath);
autoImportOutput /= options.SubAssetFolder.HasChars() ? options.SubAssetFolder.TrimTrailing() : String(StringUtils::GetFileNameWithoutExtension(context.InputPath));
if (ModelTool::ImportModel(context.InputPath, dataThis, options, errorMsg, autoImportOutput))
{
LOG(Error, "Cannot import model file. {0}", errorMsg);
return CreateAssetResult::Error;
}
data = &dataThis;
// Group meshes by the name (the same mesh name can be used by multiple meshes that use different materials)
if (data->LODs.Count() != 0)
{
const Function<StringView(MeshData* const&)> f = [](MeshData* const& x) -> StringView
{
return x->Name;
};
ArrayExtensions::GroupBy(data->LODs[0].Meshes, f, meshesByNameThis);
Sorting::QuickSort(meshesByNameThis.Get(), meshesByNameThis.Count(), &SortMeshGroups);
}
meshesByNamePtr = &meshesByNameThis;
}
Array<IGrouping<StringView, MeshData*>>& meshesByName = *meshesByNamePtr;
// Import objects from file separately
ModelTool::Options::CachedData cached = { data, (void*)meshesByNamePtr };
Array<PrefabObject> prefabObjects;
if (options.Type == ModelTool::ModelType::Prefab)
{
// Normalize options
options.SplitObjects = false;
options.ObjectIndex = -1;
// Import all of the objects recursive but use current model data to skip loading file again
options.Cached = &cached;
Function<bool(Options& splitOptions, const StringView& objectName, String& outputPath)> splitImport = [&context, &autoImportOutput](Options& splitOptions, const StringView& objectName, String& outputPath)
{
// Recursive importing of the split object
String postFix = objectName;
const int32 splitPos = postFix.FindLast(TEXT('|'));
if (splitPos != -1)
postFix = postFix.Substring(splitPos + 1);
// TODO: check for name collisions with material/texture assets
outputPath = autoImportOutput / String(StringUtils::GetFileNameWithoutExtension(context.TargetAssetPath)) + TEXT(" ") + postFix + TEXT(".flax");
splitOptions.SubAssetFolder = TEXT(" "); // Use the same folder as asset as they all are imported to the subdir for the prefab (see SubAssetFolder usage above)
return AssetsImportingManager::Import(context.InputPath, outputPath, &splitOptions);
};
auto splitOptions = options;
LOG(Info, "Splitting imported {0} meshes", meshesByName.Count());
PrefabObject prefabObject;
for (int32 groupIndex = 0; groupIndex < meshesByName.Count(); groupIndex++)
{
auto& group = meshesByName[groupIndex];
// Cache object options (nested sub-object import removes the meshes)
prefabObject.NodeIndex = group.First()->NodeIndex;
prefabObject.Name = group.First()->Name;
splitOptions.Type = ModelTool::ModelType::Model;
splitOptions.ObjectIndex = groupIndex;
if (!splitImport(splitOptions, group.GetKey(), prefabObject.AssetPath))
{
prefabObjects.Add(prefabObject);
}
}
LOG(Info, "Splitting imported {0} animations", data->Animations.Count());
for (int32 i = 0; i < data->Animations.Count(); i++)
{
auto& animation = data->Animations[i];
splitOptions.Type = ModelTool::ModelType::Animation;
splitOptions.ObjectIndex = i;
splitImport(splitOptions, animation.Name, prefabObject.AssetPath);
}
}
else if (options.SplitObjects)
{
// Import the first object within this call
options.SplitObjects = false;
options.ObjectIndex = 0;
// Import rest of the objects recursive but use current model data to skip loading file again
options.Cached = &cached;
Function<bool(Options& splitOptions, const StringView& objectName)> splitImport;
splitImport.Bind([&context](Options& splitOptions, const StringView& objectName)
{
// Recursive importing of the split object
String postFix = objectName;
const int32 splitPos = postFix.FindLast(TEXT('|'));
if (splitPos != -1)
postFix = postFix.Substring(splitPos + 1);
const String outputPath = String(StringUtils::GetPathWithoutExtension(context.TargetAssetPath)) + TEXT(" ") + postFix + TEXT(".flax");
return AssetsImportingManager::Import(context.InputPath, outputPath, &splitOptions);
});
auto splitOptions = options;
switch (options.Type)
{
case ModelTool::ModelType::Model:
case ModelTool::ModelType::SkinnedModel:
LOG(Info, "Splitting imported {0} meshes", meshesByName.Count());
for (int32 groupIndex = 1; groupIndex < meshesByName.Count(); groupIndex++)
{
auto& group = meshesByName[groupIndex];
splitOptions.ObjectIndex = groupIndex;
splitImport(splitOptions, group.GetKey());
}
break;
case ModelTool::ModelType::Animation:
LOG(Info, "Splitting imported {0} animations", data->Animations.Count());
for (int32 i = 1; i < data->Animations.Count(); i++)
{
auto& animation = data->Animations[i];
splitOptions.ObjectIndex = i;
splitImport(splitOptions, animation.Name);
}
break;
}
}
// When importing a single object as model asset then select a specific mesh group
Array<MeshData*> meshesToDelete;
if (options.ObjectIndex >= 0 &&
options.ObjectIndex < meshesByName.Count() &&
(options.Type == ModelTool::ModelType::Model || options.Type == ModelTool::ModelType::SkinnedModel))
{
auto& group = meshesByName[options.ObjectIndex];
if (&dataThis == data)
{
// Use meshes only from the the grouping (others will be removed manually)
{
auto& lod = dataThis.LODs[0];
meshesToDelete.Add(lod.Meshes);
lod.Meshes.Clear();
for (MeshData* mesh : group)
{
lod.Meshes.Add(mesh);
meshesToDelete.Remove(mesh);
}
}
for (int32 lodIndex = 1; lodIndex < dataThis.LODs.Count(); lodIndex++)
{
auto& lod = dataThis.LODs[lodIndex];
Array<MeshData*> lodMeshes = lod.Meshes;
lod.Meshes.Clear();
for (MeshData* lodMesh : lodMeshes)
{
if (lodMesh->Name == group.GetKey())
lod.Meshes.Add(lodMesh);
else
meshesToDelete.Add(lodMesh);
}
}
// Use only materials references by meshes from the first grouping
{
auto materials = dataThis.Materials;
dataThis.Materials.Clear();
SetupMaterialSlots(dataThis, materials);
}
}
else
{
// Copy data from others data
dataThis.Skeleton = data->Skeleton;
dataThis.Nodes = data->Nodes;
// Move meshes from this group (including any LODs of them)
{
auto& lod = dataThis.LODs.AddOne();
lod.ScreenSize = data->LODs[0].ScreenSize;
lod.Meshes.Add(group);
for (MeshData* mesh : group)
data->LODs[0].Meshes.Remove(mesh);
}
for (int32 lodIndex = 1; lodIndex < data->LODs.Count(); lodIndex++)
{
Array<MeshData*> lodMeshes = data->LODs[lodIndex].Meshes;
for (int32 i = lodMeshes.Count() - 1; i >= 0; i--)
{
MeshData* lodMesh = lodMeshes[i];
if (lodMesh->Name == group.GetKey())
data->LODs[lodIndex].Meshes.Remove(lodMesh);
else
lodMeshes.RemoveAtKeepOrder(i);
}
if (lodMeshes.Count() == 0)
break; // No meshes of that name in this LOD so skip further ones
auto& lod = dataThis.LODs.AddOne();
lod.ScreenSize = data->LODs[lodIndex].ScreenSize;
lod.Meshes.Add(lodMeshes);
}
// Copy materials used by the meshes
SetupMaterialSlots(dataThis, data->Materials);
}
data = &dataThis;
}
// Check if restore materials on model reimport
if (options.RestoreMaterialsOnReimport && data->Materials.HasItems())
{
TryRestoreMaterials(context, *data);
}
// When using generated lightmap UVs those coordinates needs to be moved so all meshes are in unique locations in [0-1]x[0-1] coordinates space
// Model importer generates UVs in [0-1] space for each mesh so now we need to pack them inside the whole model (only when using multiple meshes)
if (options.Type == ModelTool::ModelType::Model && options.LightmapUVsSource == ModelLightmapUVsSource::Generate && data->LODs.HasItems() && data->LODs[0].Meshes.Count() > 1)
{
RepackMeshLightmapUVs(*data);
}
// Create destination asset type
CreateAssetResult result = CreateAssetResult::InvalidTypeID;
switch (options.Type)
{
case ModelTool::ModelType::Model:
result = CreateModel(context, *data, &options);
break;
case ModelTool::ModelType::SkinnedModel:
result = CreateSkinnedModel(context, *data, &options);
break;
case ModelTool::ModelType::Animation:
result = CreateAnimation(context, *data, &options);
break;
case ModelTool::ModelType::Prefab:
result = CreatePrefab(context, *data, options, prefabObjects);
break;
}
for (auto mesh : meshesToDelete)
Delete(mesh);
if (result != CreateAssetResult::Ok)
return result;
// Create json with import context
rapidjson_flax::StringBuffer importOptionsMetaBuffer;
importOptionsMetaBuffer.Reserve(256);
CompactJsonWriter importOptionsMetaObj(importOptionsMetaBuffer);
JsonWriter& importOptionsMeta = importOptionsMetaObj;
importOptionsMeta.StartObject();
{
context.AddMeta(importOptionsMeta);
options.Serialize(importOptionsMeta, nullptr);
}
importOptionsMeta.EndObject();
context.Data.Metadata.Copy((const byte*)importOptionsMetaBuffer.GetString(), (uint32)importOptionsMetaBuffer.GetSize());
return CreateAssetResult::Ok;
}
CreateAssetResult ImportModel::Create(CreateAssetContext& context)
{
ASSERT(context.CustomArg != nullptr);
auto& modelData = *(ModelData*)context.CustomArg;
// Ensure model has any meshes
if ((modelData.LODs.IsEmpty() || modelData.LODs[0].Meshes.IsEmpty()))
{
LOG(Warning, "Models has no valid meshes");
return CreateAssetResult::Error;
}
// Auto calculate LODs transition settings
modelData.CalculateLODsScreenSizes();
return CreateModel(context, modelData);
}
CreateAssetResult ImportModel::CreateModel(CreateAssetContext& context, ModelData& modelData, const Options* options)
{
PROFILE_CPU();
IMPORT_SETUP(Model, Model::SerializedVersion);
// Save model header
MemoryWriteStream stream(4096);
if (modelData.Pack2ModelHeader(&stream))
return CreateAssetResult::Error;
if (context.AllocateChunk(0))
return CreateAssetResult::CannotAllocateChunk;
context.Data.Header.Chunks[0]->Data.Copy(stream.GetHandle(), stream.GetPosition());
// Pack model LODs data
const auto lodCount = modelData.GetLODsCount();
for (int32 lodIndex = 0; lodIndex < lodCount; lodIndex++)
{
stream.SetPosition(0);
// Pack meshes
auto& meshes = modelData.LODs[lodIndex].Meshes;
for (int32 meshIndex = 0; meshIndex < meshes.Count(); meshIndex++)
{
if (meshes[meshIndex]->Pack2Model(&stream))
{
LOG(Warning, "Cannot pack mesh.");
return CreateAssetResult::Error;
}
}
const int32 chunkIndex = lodIndex + 1;
if (context.AllocateChunk(chunkIndex))
return CreateAssetResult::CannotAllocateChunk;
context.Data.Header.Chunks[chunkIndex]->Data.Copy(stream.GetHandle(), stream.GetPosition());
}
// Generate SDF
if (options && options->GenerateSDF)
{
stream.SetPosition(0);
if (!ModelTool::GenerateModelSDF(nullptr, &modelData, options->SDFResolution, lodCount - 1, nullptr, &stream, context.TargetAssetPath))
{
if (context.AllocateChunk(15))
return CreateAssetResult::CannotAllocateChunk;
context.Data.Header.Chunks[15]->Data.Copy(stream.GetHandle(), stream.GetPosition());
}
}
return CreateAssetResult::Ok;
}
CreateAssetResult ImportModel::CreateSkinnedModel(CreateAssetContext& context, ModelData& modelData, const Options* options)
{
PROFILE_CPU();
IMPORT_SETUP(SkinnedModel, SkinnedModel::SerializedVersion);
// Save skinned model header
MemoryWriteStream stream(4096);
if (modelData.Pack2SkinnedModelHeader(&stream))
return CreateAssetResult::Error;
if (context.AllocateChunk(0))
return CreateAssetResult::CannotAllocateChunk;
context.Data.Header.Chunks[0]->Data.Copy(stream.GetHandle(), stream.GetPosition());
// Pack model LODs data
const auto lodCount = modelData.GetLODsCount();
for (int32 lodIndex = 0; lodIndex < lodCount; lodIndex++)
{
stream.SetPosition(0);
// Mesh Data Version
stream.WriteByte(1);
// Pack meshes
auto& meshes = modelData.LODs[lodIndex].Meshes;
for (int32 meshIndex = 0; meshIndex < meshes.Count(); meshIndex++)
{
if (meshes[meshIndex]->Pack2SkinnedModel(&stream))
{
LOG(Warning, "Cannot pack mesh.");
return CreateAssetResult::Error;
}
}
const int32 chunkIndex = lodIndex + 1;
if (context.AllocateChunk(chunkIndex))
return CreateAssetResult::CannotAllocateChunk;
context.Data.Header.Chunks[chunkIndex]->Data.Copy(stream.GetHandle(), stream.GetPosition());
}
return CreateAssetResult::Ok;
}
CreateAssetResult ImportModel::CreateAnimation(CreateAssetContext& context, ModelData& modelData, const Options* options)
{
PROFILE_CPU();
IMPORT_SETUP(Animation, Animation::SerializedVersion);
// Save animation data
MemoryWriteStream stream(8182);
const int32 animIndex = options && options->ObjectIndex != -1 ? options->ObjectIndex : 0; // Single animation per asset
if (modelData.Pack2AnimationHeader(&stream, animIndex))
return CreateAssetResult::Error;
if (context.AllocateChunk(0))
return CreateAssetResult::CannotAllocateChunk;
context.Data.Header.Chunks[0]->Data.Copy(stream.GetHandle(), stream.GetPosition());
return CreateAssetResult::Ok;
}
CreateAssetResult ImportModel::CreatePrefab(CreateAssetContext& context, ModelData& data, const Options& options, const Array<PrefabObject>& prefabObjects)
{
PROFILE_CPU();
if (data.Nodes.Count() == 0)
return CreateAssetResult::Error;
// If that prefab already exists then we need to use it as base to preserve object IDs and local changes applied by user
const String outputPath = String(StringUtils::GetPathWithoutExtension(context.TargetAssetPath)) + DEFAULT_PREFAB_EXTENSION_DOT;
auto* prefab = FileSystem::FileExists(outputPath) ? Content::Load<Prefab>(outputPath) : nullptr;
if (prefab)
{
// Ensure that prefab has Default Instance so ObjectsCache is valid (used below)
prefab->GetDefaultInstance();
}
// Create prefab structure
Dictionary<int32, Actor*> nodeToActor;
Array<Actor*> nodeActors;
Actor* rootActor = nullptr;
for (int32 nodeIndex = 0; nodeIndex < data.Nodes.Count(); nodeIndex++)
{
const auto& node = data.Nodes[nodeIndex];
// Create actor(s) for this node
nodeActors.Clear();
for (const PrefabObject& e : prefabObjects)
{
if (e.NodeIndex == nodeIndex)
{
auto* actor = New<StaticModel>();
actor->SetName(e.Name);
if (auto* model = Content::LoadAsync<Model>(e.AssetPath))
{
actor->Model = model;
}
nodeActors.Add(actor);
}
}
Actor* nodeActor = nodeActors.Count() == 1 ? nodeActors[0] : New<EmptyActor>();
if (nodeActors.Count() > 1)
{
for (Actor* e : nodeActors)
{
e->SetParent(nodeActor);
}
}
if (nodeActors.Count() != 1)
{
// Include default actor to iterate over it properly in code below
nodeActors.Add(nodeActor);
}
// Setup node in hierarchy
nodeToActor.Add(nodeIndex, nodeActor);
nodeActor->SetName(node.Name);
nodeActor->SetLocalTransform(node.LocalTransform);
if (nodeIndex == 0)
{
// Special case for root actor to link any unlinked nodes
nodeToActor.Add(-1, nodeActor);
rootActor = nodeActor;
}
else
{
Actor* parentActor;
if (nodeToActor.TryGet(node.ParentIndex, parentActor))
nodeActor->SetParent(parentActor);
}
// Link with object from prefab (if reimporting)
if (prefab)
{
for (Actor* a : nodeActors)
{
for (const auto& i : prefab->ObjectsCache)
{
if (i.Value->GetTypeHandle() != a->GetTypeHandle()) // Type match
continue;
auto* o = (Actor*)i.Value;
if (o->GetName() != a->GetName()) // Name match
continue;
// Mark as this object already exists in prefab so will be preserved when updating it
a->LinkPrefab(o->GetPrefabID(), o->GetPrefabObjectID());
break;
}
}
}
}
ASSERT_LOW_LAYER(rootActor);
{
// Add script with import options
auto* modelPrefabScript = New<ModelPrefab>();
modelPrefabScript->SetParent(rootActor);
modelPrefabScript->ImportPath = AssetsImportingManager::GetImportPath(context.InputPath);
modelPrefabScript->ImportOptions = options;
// Link with existing prefab instance
if (prefab)
{
for (const auto& i : prefab->ObjectsCache)
{
if (i.Value->GetTypeHandle() == modelPrefabScript->GetTypeHandle())
{
modelPrefabScript->LinkPrefab(i.Value->GetPrefabID(), i.Value->GetPrefabObjectID());
break;
}
}
}
}
// Create prefab instead of native asset
bool failed;
if (prefab)
{
failed = prefab->ApplyAll(rootActor);
}
else
{
failed = PrefabManager::CreatePrefab(rootActor, outputPath, false);
}
// Cleanup objects from memory
rootActor->DeleteObjectNow();
if (failed)
return CreateAssetResult::Error;
return CreateAssetResult::Skip;
}
#endif
+5 -9
View File
@@ -8,15 +8,10 @@
#include "Engine/Tools/ModelTool/ModelTool.h"
/// <summary>
/// Enable/disable caching model import options
/// </summary>
#define IMPORT_MODEL_CACHE_OPTIONS 1
/// <summary>
/// Importing models utility
/// </summary>
class ImportModelFile
class ImportModel
{
public:
typedef ModelTool::Options Options;
@@ -45,9 +40,10 @@ public:
static CreateAssetResult Create(CreateAssetContext& context);
private:
static CreateAssetResult ImportModel(CreateAssetContext& context, ModelData& modelData, const Options* options = nullptr);
static CreateAssetResult ImportSkinnedModel(CreateAssetContext& context, ModelData& modelData, const Options* options = nullptr);
static CreateAssetResult ImportAnimation(CreateAssetContext& context, ModelData& modelData, const Options* options = nullptr);
static CreateAssetResult CreateModel(CreateAssetContext& context, ModelData& data, const Options* options = nullptr);
static CreateAssetResult CreateSkinnedModel(CreateAssetContext& context, ModelData& data, const Options* options = nullptr);
static CreateAssetResult CreateAnimation(CreateAssetContext& context, ModelData& data, const Options* options = nullptr);
static CreateAssetResult CreatePrefab(CreateAssetContext& context, ModelData& data, const Options& options, const Array<struct PrefabObject>& prefabObjects);
};
#endif
@@ -1,307 +0,0 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
#include "ImportModel.h"
#if COMPILE_WITH_ASSETS_IMPORTER
#include "Engine/Core/Log.h"
#include "Engine/Serialization/MemoryWriteStream.h"
#include "Engine/Serialization/JsonWriters.h"
#include "Engine/Graphics/Models/ModelData.h"
#include "Engine/Content/Assets/Model.h"
#include "Engine/Content/Assets/SkinnedModel.h"
#include "Engine/Content/Storage/ContentStorageManager.h"
#include "Engine/Content/Assets/Animation.h"
#include "Engine/Content/Content.h"
#include "Engine/Platform/FileSystem.h"
#include "AssetsImportingManager.h"
bool ImportModelFile::TryGetImportOptions(const StringView& path, Options& options)
{
#if IMPORT_MODEL_CACHE_OPTIONS
if (FileSystem::FileExists(path))
{
// Try to load asset file and asset info
auto tmpFile = ContentStorageManager::GetStorage(path);
AssetInitData data;
if (tmpFile
&& tmpFile->GetEntriesCount() == 1
&& (
(tmpFile->GetEntry(0).TypeName == Model::TypeName && !tmpFile->LoadAssetHeader(0, data) && data.SerializedVersion >= 4)
||
(tmpFile->GetEntry(0).TypeName == SkinnedModel::TypeName && !tmpFile->LoadAssetHeader(0, data) && data.SerializedVersion >= 1)
||
(tmpFile->GetEntry(0).TypeName == Animation::TypeName && !tmpFile->LoadAssetHeader(0, data) && data.SerializedVersion >= 1)
))
{
// Check import meta
rapidjson_flax::Document metadata;
metadata.Parse((const char*)data.Metadata.Get(), data.Metadata.Length());
if (metadata.HasParseError() == false)
{
options.Deserialize(metadata, nullptr);
return true;
}
}
}
#endif
return false;
}
void TryRestoreMaterials(CreateAssetContext& context, ModelData& modelData)
{
// Skip if file is missing
if (!FileSystem::FileExists(context.TargetAssetPath))
return;
// Try to load asset that gets reimported
AssetReference<Asset> asset = Content::LoadAsync<Asset>(context.TargetAssetPath);
if (asset == nullptr)
return;
if (asset->WaitForLoaded())
return;
// Get model object
ModelBase* model = nullptr;
if (asset.Get()->GetTypeName() == Model::TypeName)
{
model = ((Model*)asset.Get());
}
else if (asset.Get()->GetTypeName() == SkinnedModel::TypeName)
{
model = ((SkinnedModel*)asset.Get());
}
if (!model)
return;
// Peek materials
for (int32 i = 0; i < modelData.Materials.Count(); i++)
{
auto& dstSlot = modelData.Materials[i];
if (model->MaterialSlots.Count() > i)
{
auto& srcSlot = model->MaterialSlots[i];
dstSlot.Name = srcSlot.Name;
dstSlot.ShadowsMode = srcSlot.ShadowsMode;
dstSlot.AssetID = srcSlot.Material.GetID();
}
}
}
CreateAssetResult ImportModelFile::Import(CreateAssetContext& context)
{
// Get import options
Options options;
if (context.CustomArg != nullptr)
{
// Copy import options from argument
options = *static_cast<Options*>(context.CustomArg);
}
else
{
// Restore the previous settings or use default ones
if (!TryGetImportOptions(context.TargetAssetPath, options))
{
LOG(Warning, "Missing model import options. Using default values.");
}
}
if (options.SplitObjects)
{
options.OnSplitImport.Bind([&context](Options& splitOptions, const String& objectName)
{
// Recursive importing of the split object
String postFix = objectName;
const int32 splitPos = postFix.FindLast(TEXT('|'));
if (splitPos != -1)
postFix = postFix.Substring(splitPos + 1);
const String outputPath = String(StringUtils::GetPathWithoutExtension(context.TargetAssetPath)) + TEXT(" ") + postFix + TEXT(".flax");
return AssetsImportingManager::Import(context.InputPath, outputPath, &splitOptions);
});
}
// Import model file
ModelData modelData;
String errorMsg;
String autoImportOutput(StringUtils::GetDirectoryName(context.TargetAssetPath));
autoImportOutput /= options.SubAssetFolder.HasChars() ? options.SubAssetFolder.TrimTrailing() : String(StringUtils::GetFileNameWithoutExtension(context.InputPath));
if (ModelTool::ImportModel(context.InputPath, modelData, options, errorMsg, autoImportOutput))
{
LOG(Error, "Cannot import model file. {0}", errorMsg);
return CreateAssetResult::Error;
}
// Check if restore materials on model reimport
if (options.RestoreMaterialsOnReimport && modelData.Materials.HasItems())
{
TryRestoreMaterials(context, modelData);
}
// Auto calculate LODs transition settings
modelData.CalculateLODsScreenSizes();
// Create destination asset type
CreateAssetResult result = CreateAssetResult::InvalidTypeID;
switch (options.Type)
{
case ModelTool::ModelType::Model:
result = ImportModel(context, modelData, &options);
break;
case ModelTool::ModelType::SkinnedModel:
result = ImportSkinnedModel(context, modelData, &options);
break;
case ModelTool::ModelType::Animation:
result = ImportAnimation(context, modelData, &options);
break;
}
if (result != CreateAssetResult::Ok)
return result;
#if IMPORT_MODEL_CACHE_OPTIONS
// Create json with import context
rapidjson_flax::StringBuffer importOptionsMetaBuffer;
importOptionsMetaBuffer.Reserve(256);
CompactJsonWriter importOptionsMetaObj(importOptionsMetaBuffer);
JsonWriter& importOptionsMeta = importOptionsMetaObj;
importOptionsMeta.StartObject();
{
context.AddMeta(importOptionsMeta);
options.Serialize(importOptionsMeta, nullptr);
}
importOptionsMeta.EndObject();
context.Data.Metadata.Copy((const byte*)importOptionsMetaBuffer.GetString(), (uint32)importOptionsMetaBuffer.GetSize());
#endif
return CreateAssetResult::Ok;
}
CreateAssetResult ImportModelFile::Create(CreateAssetContext& context)
{
ASSERT(context.CustomArg != nullptr);
auto& modelData = *(ModelData*)context.CustomArg;
// Ensure model has any meshes
if ((modelData.LODs.IsEmpty() || modelData.LODs[0].Meshes.IsEmpty()))
{
LOG(Warning, "Models has no valid meshes");
return CreateAssetResult::Error;
}
// Auto calculate LODs transition settings
modelData.CalculateLODsScreenSizes();
// Import
return ImportModel(context, modelData);
}
CreateAssetResult ImportModelFile::ImportModel(CreateAssetContext& context, ModelData& modelData, const Options* options)
{
// Base
IMPORT_SETUP(Model, Model::SerializedVersion);
// Save model header
MemoryWriteStream stream(4096);
if (modelData.Pack2ModelHeader(&stream))
return CreateAssetResult::Error;
if (context.AllocateChunk(0))
return CreateAssetResult::CannotAllocateChunk;
context.Data.Header.Chunks[0]->Data.Copy(stream.GetHandle(), stream.GetPosition());
// Pack model LODs data
const auto lodCount = modelData.GetLODsCount();
for (int32 lodIndex = 0; lodIndex < lodCount; lodIndex++)
{
stream.SetPosition(0);
// Pack meshes
auto& meshes = modelData.LODs[lodIndex].Meshes;
for (int32 meshIndex = 0; meshIndex < meshes.Count(); meshIndex++)
{
if (meshes[meshIndex]->Pack2Model(&stream))
{
LOG(Warning, "Cannot pack mesh.");
return CreateAssetResult::Error;
}
}
const int32 chunkIndex = lodIndex + 1;
if (context.AllocateChunk(chunkIndex))
return CreateAssetResult::CannotAllocateChunk;
context.Data.Header.Chunks[chunkIndex]->Data.Copy(stream.GetHandle(), stream.GetPosition());
}
// Generate SDF
if (options && options->GenerateSDF)
{
stream.SetPosition(0);
if (!ModelTool::GenerateModelSDF(nullptr, &modelData, options->SDFResolution, lodCount - 1, nullptr, &stream, context.TargetAssetPath))
{
if (context.AllocateChunk(15))
return CreateAssetResult::CannotAllocateChunk;
context.Data.Header.Chunks[15]->Data.Copy(stream.GetHandle(), stream.GetPosition());
}
}
return CreateAssetResult::Ok;
}
CreateAssetResult ImportModelFile::ImportSkinnedModel(CreateAssetContext& context, ModelData& modelData, const Options* options)
{
// Base
IMPORT_SETUP(SkinnedModel, SkinnedModel::SerializedVersion);
// Save skinned model header
MemoryWriteStream stream(4096);
if (modelData.Pack2SkinnedModelHeader(&stream))
return CreateAssetResult::Error;
if (context.AllocateChunk(0))
return CreateAssetResult::CannotAllocateChunk;
context.Data.Header.Chunks[0]->Data.Copy(stream.GetHandle(), stream.GetPosition());
// Pack model LODs data
const auto lodCount = modelData.GetLODsCount();
for (int32 lodIndex = 0; lodIndex < lodCount; lodIndex++)
{
stream.SetPosition(0);
// Mesh Data Version
stream.WriteByte(1);
// Pack meshes
auto& meshes = modelData.LODs[lodIndex].Meshes;
for (int32 meshIndex = 0; meshIndex < meshes.Count(); meshIndex++)
{
if (meshes[meshIndex]->Pack2SkinnedModel(&stream))
{
LOG(Warning, "Cannot pack mesh.");
return CreateAssetResult::Error;
}
}
const int32 chunkIndex = lodIndex + 1;
if (context.AllocateChunk(chunkIndex))
return CreateAssetResult::CannotAllocateChunk;
context.Data.Header.Chunks[chunkIndex]->Data.Copy(stream.GetHandle(), stream.GetPosition());
}
return CreateAssetResult::Ok;
}
CreateAssetResult ImportModelFile::ImportAnimation(CreateAssetContext& context, ModelData& modelData, const Options* options)
{
// Base
IMPORT_SETUP(Animation, Animation::SerializedVersion);
// Save animation data
MemoryWriteStream stream(8182);
if (modelData.Pack2AnimationHeader(&stream))
return CreateAssetResult::Error;
if (context.AllocateChunk(0))
return CreateAssetResult::CannotAllocateChunk;
context.Data.Header.Chunks[0]->Data.Copy(stream.GetHandle(), stream.GetPosition());
return CreateAssetResult::Ok;
}
#endif
@@ -1,54 +0,0 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
#pragma once
#include "Types.h"
#if COMPILE_WITH_ASSETS_IMPORTER
#include "Engine/Content/Assets/Model.h"
#include "Engine/Tools/ModelTool/ModelTool.h"
/// <summary>
/// Enable/disable caching model import options
/// </summary>
#define IMPORT_MODEL_CACHE_OPTIONS 1
/// <summary>
/// Importing models utility
/// </summary>
class ImportModelFile
{
public:
typedef ModelTool::Options Options;
public:
/// <summary>
/// Tries the get model import options from the target location asset.
/// </summary>
/// <param name="path">The asset path.</param>
/// <param name="options">The options.</param>
/// <returns>True if success, otherwise false.</returns>
static bool TryGetImportOptions(String path, Options& options);
/// <summary>
/// Imports the model file.
/// </summary>
/// <param name="context">The importing context.</param>
/// <returns>Result.</returns>
static CreateAssetResult Import(CreateAssetContext& context);
/// <summary>
/// Creates the model asset from the ModelData storage (input argument should be pointer to ModelData).
/// </summary>
/// <param name="context">The importing context.</param>
/// <returns>Result.</returns>
static CreateAssetResult Create(CreateAssetContext& context);
private:
static CreateAssetResult ImportModel(CreateAssetContext& context, ModelData& modelData);
static CreateAssetResult ImportSkinnedModel(CreateAssetContext& context, ModelData& modelData);
static CreateAssetResult ImportAnimation(CreateAssetContext& context, ModelData& modelData);
};
#endif
@@ -8,7 +8,6 @@
#include "Engine/Serialization/MemoryWriteStream.h"
#include "Engine/Serialization/MemoryReadStream.h"
#include "Engine/Graphics/Textures/TextureData.h"
#include "Engine/Graphics/Textures/TextureUtils.h"
#include "Engine/Graphics/PixelFormatExtensions.h"
#include "Engine/Content/Storage/ContentStorageManager.h"
#include "Engine/ContentImporters/ImportIES.h"
+1 -1
View File
@@ -18,7 +18,7 @@ class CreateAssetContext;
/// <summary>
/// Create/Import new asset callback result
/// </summary>
DECLARE_ENUM_7(CreateAssetResult, Ok, Abort, Error, CannotSaveFile, InvalidPath, CannotAllocateChunk, InvalidTypeID);
DECLARE_ENUM_8(CreateAssetResult, Ok, Abort, Error, CannotSaveFile, InvalidPath, CannotAllocateChunk, InvalidTypeID, Skip);
/// <summary>
/// Create/Import new asset callback function
+25 -7
View File
@@ -25,6 +25,19 @@ private:
int32 _capacity;
AllocationData _allocation;
FORCE_INLINE static void MoveToEmpty(AllocationData& to, AllocationData& from, int32 fromCount, int32 fromCapacity)
{
if IF_CONSTEXPR (AllocationType::HasSwap)
to.Swap(from);
else
{
to.Allocate(fromCapacity);
Memory::MoveItems(to.Get(), from.Get(), fromCount);
Memory::DestructItems(from.Get(), fromCount);
from.Free();
}
}
public:
/// <summary>
/// Initializes a new instance of the <see cref="Array"/> class.
@@ -134,7 +147,7 @@ public:
_capacity = other._capacity;
other._count = 0;
other._capacity = 0;
_allocation.Swap(other._allocation);
MoveToEmpty(_allocation, other._allocation, _count, _capacity);
}
/// <summary>
@@ -191,7 +204,7 @@ public:
_capacity = other._capacity;
other._count = 0;
other._capacity = 0;
_allocation.Swap(other._allocation);
MoveToEmpty(_allocation, other._allocation, _count, _capacity);
}
return *this;
}
@@ -713,9 +726,16 @@ public:
/// <param name="other">The other collection.</param>
void Swap(Array& other)
{
::Swap(_count, other._count);
::Swap(_capacity, other._capacity);
_allocation.Swap(other._allocation);
if IF_CONSTEXPR (AllocationType::HasSwap)
{
_allocation.Swap(other._allocation);
::Swap(_count, other._count);
::Swap(_capacity, other._capacity);
}
else
{
::Swap(other, *this);
}
}
/// <summary>
@@ -726,9 +746,7 @@ public:
T* data = _allocation.Get();
const int32 count = _count / 2;
for (int32 i = 0; i < count; i++)
{
::Swap(data[i], data[_count - i - 1]);
}
}
public:
@@ -55,9 +55,7 @@ public:
for (int32 i = 0; i < obj.Count(); i++)
{
if (predicate(obj[i]))
{
return i;
}
}
return INVALID_INDEX;
}
@@ -74,9 +72,7 @@ public:
for (int32 i = 0; i < obj.Count(); i++)
{
if (predicate(obj[i]))
{
return true;
}
}
return false;
}
@@ -93,13 +89,101 @@ public:
for (int32 i = 0; i < obj.Count(); i++)
{
if (!predicate(obj[i]))
{
return false;
}
}
return true;
}
/// <summary>
/// Filters a sequence of values based on a predicate.
/// </summary>
/// <param name="obj">The target collection.</param>
/// <param name="predicate">The prediction function. Return true for elements that should be included in result list.</param>
/// <param name="result">The result list with items that passed the predicate.</param>
template<typename T, typename AllocationType>
static void Where(const Array<T, AllocationType>& obj, const Function<bool(const T&)>& predicate, Array<T, AllocationType>& result)
{
for (const T& i : obj)
{
if (predicate(i))
result.Add(i);
}
}
/// <summary>
/// Filters a sequence of values based on a predicate.
/// </summary>
/// <param name="obj">The target collection.</param>
/// <param name="predicate">The prediction function. Return true for elements that should be included in result list.</param>
/// <returns>The result list with items that passed the predicate.</returns>
template<typename T, typename AllocationType>
static Array<T, AllocationType> Where(const Array<T, AllocationType>& obj, const Function<bool(const T&)>& predicate)
{
Array<T, AllocationType> result;
Where(obj, predicate, result);
return result;
}
/// <summary>
/// Projects each element of a sequence into a new form.
/// </summary>
/// <param name="obj">The target collection.</param>
/// <param name="selector">A transform function to apply to each source element; the second parameter of the function represents the index of the source element.</param>
/// <param name="result">The result list whose elements are the result of invoking the transform function on each element of source.</param>
template<typename TResult, typename TSource, typename AllocationType>
static void Select(const Array<TSource, AllocationType>& obj, const Function<TResult(const TSource&)>& selector, Array<TResult, AllocationType>& result)
{
for (const TSource& i : obj)
result.Add(MoveTemp(selector(i)));
}
/// <summary>
/// Projects each element of a sequence into a new form.
/// </summary>
/// <param name="obj">The target collection.</param>
/// <param name="selector">A transform function to apply to each source element; the second parameter of the function represents the index of the source element.</param>
/// <returns>The result list whose elements are the result of invoking the transform function on each element of source.</returns>
template<typename TResult, typename TSource, typename AllocationType>
static Array<TResult, AllocationType> Select(const Array<TSource, AllocationType>& obj, const Function<TResult(const TSource&)>& selector)
{
Array<TResult, AllocationType> result;
Select(obj, selector, result);
return result;
}
/// <summary>
/// Removes all the elements that match the conditions defined by the specified predicate.
/// </summary>
/// <param name="obj">The target collection to modify.</param>
/// <param name="predicate">A transform function that defines the conditions of the elements to remove.</param>
template<typename T, typename AllocationType>
static void RemoveAll(Array<T, AllocationType>& obj, const Function<bool(const T&)>& predicate)
{
for (int32 i = obj.Count() - 1; i >= 0; i--)
{
if (predicate(obj[i]))
obj.RemoveAtKeepOrder(i);
}
}
/// <summary>
/// Removes all the elements that match the conditions defined by the specified predicate.
/// </summary>
/// <param name="obj">The target collection to process.</param>
/// <param name="predicate">A transform function that defines the conditions of the elements to remove.</param>
/// <returns>The result list whose elements are the result of invoking the transform function on each element of source.</returns>
template<typename T, typename AllocationType>
static Array<T, AllocationType> RemoveAll(const Array<T, AllocationType>& obj, const Function<bool(const T&)>& predicate)
{
Array<T, AllocationType> result;
for (const T& i : obj)
{
if (!predicate(i))
result.Ass(i);
}
return result;
}
/// <summary>
/// Groups the elements of a sequence according to a specified key selector function.
/// </summary>
@@ -109,7 +193,7 @@ public:
template<typename TSource, typename TKey, typename AllocationType>
static void GroupBy(const Array<TSource, AllocationType>& obj, const Function<TKey(TSource const&)>& keySelector, Array<IGrouping<TKey, TSource>, AllocationType>& result)
{
Dictionary<TKey, IGrouping<TKey, TSource>> data(static_cast<int32>(obj.Count() * 3.0f));
Dictionary<TKey, IGrouping<TKey, TSource>> data;
for (int32 i = 0; i < obj.Count(); i++)
{
const TKey key = keySelector(obj[i]);
+42 -8
View File
@@ -110,6 +110,33 @@ private:
int32 _size = 0;
AllocationData _allocation;
FORCE_INLINE static void MoveToEmpty(AllocationData& to, AllocationData& from, int32 fromSize)
{
if IF_CONSTEXPR (AllocationType::HasSwap)
to.Swap(from);
else
{
to.Allocate(fromSize);
Bucket* toData = to.Get();
Bucket* fromData = from.Get();
for (int32 i = 0; i < fromSize; i++)
{
Bucket& fromBucket = fromData[i];
if (fromBucket.IsOccupied())
{
Bucket& toBucket = toData[i];
Memory::MoveItems(&toBucket.Key, &fromBucket.Key, 1);
Memory::MoveItems(&toBucket.Value, &fromBucket.Value, 1);
toBucket._state = Bucket::Occupied;
Memory::DestructItem(&fromBucket.Key);
Memory::DestructItem(&fromBucket.Value);
fromBucket._state = Bucket::Empty;
}
}
from.Free();
}
}
public:
/// <summary>
/// Initializes a new instance of the <see cref="Dictionary"/> class.
@@ -139,7 +166,7 @@ public:
other._elementsCount = 0;
other._deletedCount = 0;
other._size = 0;
_allocation.Swap(other._allocation);
MoveToEmpty(_allocation, other._allocation, _size);
}
/// <summary>
@@ -180,7 +207,7 @@ public:
other._elementsCount = 0;
other._deletedCount = 0;
other._size = 0;
_allocation.Swap(other._allocation);
MoveToEmpty(_allocation, other._allocation, _size);
}
return *this;
}
@@ -510,7 +537,7 @@ public:
return;
ASSERT(capacity >= 0);
AllocationData oldAllocation;
oldAllocation.Swap(_allocation);
MoveToEmpty(oldAllocation, _allocation, _size);
const int32 oldSize = _size;
const int32 oldElementsCount = _elementsCount;
_deletedCount = _elementsCount = 0;
@@ -580,10 +607,17 @@ public:
/// <param name="other">The other collection.</param>
void Swap(Dictionary& other)
{
::Swap(_elementsCount, other._elementsCount);
::Swap(_deletedCount, other._deletedCount);
::Swap(_size, other._size);
_allocation.Swap(other._allocation);
if IF_CONSTEXPR (AllocationType::HasSwap)
{
::Swap(_elementsCount, other._elementsCount);
::Swap(_deletedCount, other._deletedCount);
::Swap(_size, other._size);
_allocation.Swap(other._allocation);
}
else
{
::Swap(other, *this);
}
}
public:
@@ -930,7 +964,7 @@ private:
{
// Rebuild entire table completely
AllocationData oldAllocation;
oldAllocation.Swap(_allocation);
MoveToEmpty(oldAllocation, _allocation, _size);
_allocation.Allocate(_size);
Bucket* data = _allocation.Get();
for (int32 i = 0; i < _size; i++)
+42 -8
View File
@@ -93,6 +93,31 @@ private:
int32 _size = 0;
AllocationData _allocation;
FORCE_INLINE static void MoveToEmpty(AllocationData& to, AllocationData& from, int32 fromSize)
{
if IF_CONSTEXPR (AllocationType::HasSwap)
to.Swap(from);
else
{
to.Allocate(fromSize);
Bucket* toData = to.Get();
Bucket* fromData = from.Get();
for (int32 i = 0; i < fromSize; i++)
{
Bucket& fromBucket = fromData[i];
if (fromBucket.IsOccupied())
{
Bucket& toBucket = toData[i];
Memory::MoveItems(&toBucket.Item, &fromBucket.Item, 1);
toBucket._state = Bucket::Occupied;
Memory::DestructItem(&fromBucket.Item);
fromBucket._state = Bucket::Empty;
}
}
from.Free();
}
}
public:
/// <summary>
/// Initializes a new instance of the <see cref="HashSet"/> class.
@@ -122,7 +147,7 @@ public:
other._elementsCount = 0;
other._deletedCount = 0;
other._size = 0;
_allocation.Swap(other._allocation);
MoveToEmpty(_allocation, other._allocation, _size);
}
/// <summary>
@@ -163,7 +188,7 @@ public:
other._elementsCount = 0;
other._deletedCount = 0;
other._size = 0;
_allocation.Swap(other._allocation);
MoveToEmpty(_allocation, other._allocation, _size);
}
return *this;
}
@@ -389,7 +414,7 @@ public:
return;
ASSERT(capacity >= 0);
AllocationData oldAllocation;
oldAllocation.Swap(_allocation);
MoveToEmpty(oldAllocation, _allocation, _size);
const int32 oldSize = _size;
const int32 oldElementsCount = _elementsCount;
_deletedCount = _elementsCount = 0;
@@ -458,10 +483,19 @@ public:
/// <param name="other">The other collection.</param>
void Swap(HashSet& other)
{
::Swap(_elementsCount, other._elementsCount);
::Swap(_deletedCount, other._deletedCount);
::Swap(_size, other._size);
_allocation.Swap(other._allocation);
if IF_CONSTEXPR (AllocationType::HasSwap)
{
::Swap(_elementsCount, other._elementsCount);
::Swap(_deletedCount, other._deletedCount);
::Swap(_size, other._size);
_allocation.Swap(other._allocation);
}
else
{
HashSet tmp = MoveTemp(other);
other = *this;
*this = MoveTemp(tmp);
}
}
public:
@@ -726,7 +760,7 @@ private:
{
// Rebuild entire table completely
AllocationData oldAllocation;
oldAllocation.Swap(_allocation);
MoveToEmpty(oldAllocation, _allocation, _size);
_allocation.Allocate(_size);
Bucket* data = _allocation.Get();
for (int32 i = 0; i < _size; i++)
+10
View File
@@ -45,6 +45,16 @@ public:
};
public:
/// <summary>
/// Sorts the linear data array using Quick Sort algorithm (non recursive version, uses temporary stack collection).
/// </summary>
/// <param name="data">The data container.</param>
template<typename T, typename AllocationType = HeapAllocation>
FORCE_INLINE static void QuickSort(Array<T, AllocationType>& data)
{
QuickSort(data.Get(), data.Count());
}
/// <summary>
/// Sorts the linear data array using Quick Sort algorithm (non recursive version, uses temporary stack collection).
/// </summary>
+7
View File
@@ -93,3 +93,10 @@
#endif
#define PACK_STRUCT(__Declaration__) PACK_BEGIN() __Declaration__ PACK_END()
// C++ 17
#if __cplusplus >= 201703L
#define IF_CONSTEXPR constexpr
#else
#define IF_CONSTEXPR
#endif
+1 -1
View File
@@ -58,7 +58,7 @@ bool Log::Logger::Init()
int32 remaining = oldLogs.Count() - maxLogFiles + 1;
if (remaining > 0)
{
Sorting::QuickSort(oldLogs.Get(), oldLogs.Count());
Sorting::QuickSort(oldLogs);
// Delete the oldest logs
int32 i = 0;
+2
View File
@@ -15,6 +15,8 @@ const Float2 Float2::Zero(0.0f);
template<>
const Float2 Float2::One(1.0f);
template<>
const Float2 Float2::Half(0.5f);
template<>
const Float2 Float2::UnitX(1.0f, 0.0f);
template<>
const Float2 Float2::UnitY(0.0f, 1.0f);
+3
View File
@@ -44,6 +44,9 @@ public:
// Vector with all components equal 1
static FLAXENGINE_API const Vector2Base<T> One;
// Vector with all components equal 0.5
static FLAXENGINE_API const Vector2Base<T> Half;
// Vector X=1, Y=0
static FLAXENGINE_API const Vector2Base<T> UnitX;
+2
View File
@@ -17,6 +17,8 @@ const Float4 Float4::Zero(0.0f);
template<>
const Float4 Float4::One(1.0f);
template<>
const Float4 Float4::Half(0.5f);
template<>
const Float4 Float4::UnitX(1.0f, 0.0f, 0.0f, 0.0f);
template<>
const Float4 Float4::UnitY(0.0f, 1.0f, 0.0f, 0.0f);
+3
View File
@@ -54,6 +54,9 @@ public:
// Vector with all components equal 1
static FLAXENGINE_API const Vector4Base<T> One;
// Vector with all components equal 0.5
static FLAXENGINE_API const Vector4Base<T> Half;
// Vector X=1, Y=0, Z=0, W=0
static FLAXENGINE_API const Vector4Base<T> UnitX;
+10 -12
View File
@@ -12,6 +12,8 @@ template<int Capacity>
class FixedAllocation
{
public:
enum { HasSwap = false };
template<typename T>
class Data
{
@@ -61,12 +63,9 @@ public:
{
}
FORCE_INLINE void Swap(Data& other)
void Swap(Data& other)
{
byte tmp[Capacity * sizeof(T)];
Platform::MemoryCopy(tmp, _data, Capacity * sizeof(T));
Platform::MemoryCopy(_data, other._data, Capacity * sizeof(T));
Platform::MemoryCopy(other._data, tmp, Capacity * sizeof(T));
// Not supported
}
};
};
@@ -77,6 +76,8 @@ public:
class HeapAllocation
{
public:
enum { HasSwap = true };
template<typename T>
class Data
{
@@ -179,6 +180,8 @@ template<int Capacity, typename OtherAllocator = HeapAllocation>
class InlinedAllocation
{
public:
enum { HasSwap = false };
template<typename T>
class Data
{
@@ -267,14 +270,9 @@ public:
}
}
FORCE_INLINE void Swap(Data& other)
void Swap(Data& other)
{
byte tmp[Capacity * sizeof(T)];
Platform::MemoryCopy(tmp, _data, Capacity * sizeof(T));
Platform::MemoryCopy(_data, other._data, Capacity * sizeof(T));
Platform::MemoryCopy(other._data, tmp, Capacity * sizeof(T));
::Swap(_useOther, other._useOther);
_other.Swap(other._other);
// Not supported
}
};
};
+1 -1
View File
@@ -304,7 +304,7 @@ template<typename T>
inline void Swap(T& a, T& b) noexcept
{
T tmp = MoveTemp(a);
a = b;
a = MoveTemp(b);
b = MoveTemp(tmp);
}
+7 -1
View File
@@ -522,7 +522,13 @@ void EngineImpl::InitLog()
LOG(Info, "Compiled for Dev Environment");
#endif
LOG(Info, "Version " FLAXENGINE_VERSION_TEXT);
LOG(Info, "Compiled: {0} {1}", TEXT(__DATE__), TEXT(__TIME__));
const Char* cpp = TEXT("?");
if (__cplusplus == 202101L) cpp = TEXT("C++23");
else if (__cplusplus == 202002L) cpp = TEXT("C++20");
else if (__cplusplus == 201703L) cpp = TEXT("C++17");
else if (__cplusplus == 201402L) cpp = TEXT("C++14");
else if (__cplusplus == 201103L) cpp = TEXT("C++11");
LOG(Info, "Compiled: {0} {1} {2}", TEXT(__DATE__), TEXT(__TIME__), cpp);
#ifdef _MSC_VER
const String mcsVer = StringUtils::ToString(_MSC_FULL_VER);
LOG(Info, "Compiled with Visual C++ {0}.{1}.{2}.{3:0^2d}", mcsVer.Substring(0, 2), mcsVer.Substring(2, 2), mcsVer.Substring(4, 5), _MSC_BUILD);
@@ -1271,6 +1271,9 @@ namespace FlaxEngine.Interop
case Type _ when type == typeof(IntPtr):
monoType = MTypes.Ptr;
break;
case Type _ when type.IsPointer:
monoType = MTypes.Ptr;
break;
case Type _ when type.IsEnum:
monoType = MTypes.Enum;
break;
+40 -29
View File
@@ -1135,7 +1135,7 @@ namespace FlaxEngine.Interop
marshallers[i](fields[i], offsets[i], ref managedValue, fieldPtr, out int fieldSize);
fieldPtr += fieldSize;
}
Assert.IsTrue((fieldPtr - nativePtr) <= Unsafe.SizeOf<T>());
//Assert.IsTrue((fieldPtr - nativePtr) <= GetTypeSize(typeof(T)));
}
internal static void ToManaged(ref T managedValue, IntPtr nativePtr, bool byRef)
@@ -1182,7 +1182,7 @@ namespace FlaxEngine.Interop
marshallers[i](fields[i], offsets[i], ref managedValue, nativePtr, out int fieldSize);
nativePtr += fieldSize;
}
Assert.IsTrue((nativePtr - fieldPtr) <= Unsafe.SizeOf<T>());
//Assert.IsTrue((nativePtr - fieldPtr) <= GetTypeSize(typeof(T)));
}
internal static void ToNative(ref T managedValue, IntPtr nativePtr)
@@ -1331,39 +1331,50 @@ namespace FlaxEngine.Interop
// Skip using in-built delegate for value types (eg. Transform) to properly handle instance value passing to method
if (invokeDelegate == null && !method.DeclaringType.IsValueType)
{
List<Type> methodTypes = new List<Type>();
if (!method.IsStatic)
methodTypes.Add(method.DeclaringType);
if (returnType != typeof(void))
methodTypes.Add(returnType);
methodTypes.AddRange(parameterTypes);
List<Type> genericParamTypes = new List<Type>();
foreach (var type in methodTypes)
// Thread-safe creation
lock (typeCache)
{
if (type.IsByRef)
genericParamTypes.Add(type.GetElementType());
else if (type.IsPointer)
genericParamTypes.Add(typeof(IntPtr));
else
genericParamTypes.Add(type);
}
string invokerTypeName = $"{typeof(Invoker).FullName}+Invoker{(method.IsStatic ? "Static" : "")}{(returnType != typeof(void) ? "Ret" : "NoRet")}{parameterTypes.Length}{(genericParamTypes.Count > 0 ? "`" + genericParamTypes.Count : "")}";
Type invokerType = Type.GetType(invokerTypeName);
if (invokerType != null)
{
if (genericParamTypes.Count != 0)
invokerType = invokerType.MakeGenericType(genericParamTypes.ToArray());
invokeDelegate = invokerType.GetMethod(nameof(Invoker.InvokerStaticNoRet0.MarshalAndInvoke), BindingFlags.Static | BindingFlags.NonPublic).CreateDelegate<Invoker.MarshalAndInvokeDelegate>();
delegInvoke = invokerType.GetMethod(nameof(Invoker.InvokerStaticNoRet0.CreateDelegate), BindingFlags.Static | BindingFlags.NonPublic).Invoke(null, new object[] { method });
if (invokeDelegate == null)
{
TryCreateDelegate();
}
}
}
outDeleg = invokeDelegate;
outDelegInvoke = delegInvoke;
return outDeleg != null;
}
private void TryCreateDelegate()
{
var methodTypes = new List<Type>();
if (!method.IsStatic)
methodTypes.Add(method.DeclaringType);
if (returnType != typeof(void))
methodTypes.Add(returnType);
methodTypes.AddRange(parameterTypes);
var genericParamTypes = new List<Type>();
foreach (var type in methodTypes)
{
if (type.IsByRef)
genericParamTypes.Add(type.GetElementType());
else if (type.IsPointer)
genericParamTypes.Add(typeof(IntPtr));
else
genericParamTypes.Add(type);
}
string invokerTypeName = $"{typeof(Invoker).FullName}+Invoker{(method.IsStatic ? "Static" : "")}{(returnType != typeof(void) ? "Ret" : "NoRet")}{parameterTypes.Length}{(genericParamTypes.Count > 0 ? "`" + genericParamTypes.Count : "")}";
Type invokerType = Type.GetType(invokerTypeName);
if (invokerType != null)
{
if (genericParamTypes.Count != 0)
invokerType = invokerType.MakeGenericType(genericParamTypes.ToArray());
delegInvoke = invokerType.GetMethod(nameof(Invoker.InvokerStaticNoRet0.CreateDelegate), BindingFlags.Static | BindingFlags.NonPublic).Invoke(null, new object[] { method });
invokeDelegate = invokerType.GetMethod(nameof(Invoker.InvokerStaticNoRet0.MarshalAndInvoke), BindingFlags.Static | BindingFlags.NonPublic).CreateDelegate<Invoker.MarshalAndInvokeDelegate>();
}
}
#endif
}
@@ -1580,7 +1591,7 @@ namespace FlaxEngine.Interop
private static IntPtr PinValue<T>(T value) where T : struct
{
// Store the converted value in unmanaged memory so it will not be relocated by the garbage collector.
int size = Unsafe.SizeOf<T>();
int size = GetTypeSize(typeof(T));
uint index = Interlocked.Increment(ref pinnedAllocationsPointer) % (uint)pinnedAllocations.Length;
ref (IntPtr ptr, int size) alloc = ref pinnedAllocations[index];
if (alloc.size < size)
+2 -2
View File
@@ -104,7 +104,7 @@ bool Time::TickData::OnTickBegin(float targetFps, float maxDeltaTime)
if (targetFps > ZeroTolerance)
{
int skip = (int)(1 + (time - NextBegin) / (1.0 / targetFps));
int skip = (int)(1 + (time - NextBegin) * targetFps);
NextBegin += (1.0 / targetFps) * skip;
}
}
@@ -160,7 +160,7 @@ bool Time::FixedStepTickData::OnTickBegin(float targetFps, float maxDeltaTime)
if (targetFps > ZeroTolerance)
{
int skip = (int)(1 + (time - NextBegin) / (1.0 / targetFps));
int skip = (int)(1 + (time - NextBegin) * targetFps);
NextBegin += (1.0 / targetFps) * skip;
}
}
@@ -10,6 +10,7 @@
#include "Engine/Core/Collections/BitArray.h"
#include "Engine/Tools/ModelTool/ModelTool.h"
#include "Engine/Tools/ModelTool/VertexTriangleAdjacency.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Platform/Platform.h"
#define USE_MIKKTSPACE 1
#include "ThirdParty/MikkTSpace/mikktspace.h"
@@ -78,6 +79,7 @@ void RemapArrayHelper(Array<T>& target, const std::vector<uint32_t>& remap)
bool MeshData::GenerateLightmapUVs()
{
PROFILE_CPU();
#if PLATFORM_WINDOWS
// Prepare
HRESULT hr;
@@ -235,6 +237,7 @@ void RemapBuffer(Array<T>& src, Array<T>& dst, const Array<int32>& mapping, int3
void MeshData::BuildIndexBuffer()
{
PROFILE_CPU();
const auto startTime = Platform::GetTimeSeconds();
const int32 vertexCount = Positions.Count();
@@ -341,6 +344,7 @@ bool MeshData::GenerateNormals(float smoothingAngle)
LOG(Warning, "Missing vertex or index data to generate normals.");
return true;
}
PROFILE_CPU();
const auto startTime = Platform::GetTimeSeconds();
@@ -520,6 +524,7 @@ bool MeshData::GenerateTangents(float smoothingAngle)
LOG(Warning, "Missing normals or texcoors data to generate tangents.");
return true;
}
PROFILE_CPU();
const auto startTime = Platform::GetTimeSeconds();
const int32 vertexCount = Positions.Count();
@@ -706,6 +711,7 @@ void MeshData::ImproveCacheLocality()
if (Positions.IsEmpty() || Indices.IsEmpty() || Positions.Count() <= VertexCacheSize)
return;
PROFILE_CPU();
const auto startTime = Platform::GetTimeSeconds();
@@ -886,6 +892,7 @@ void MeshData::ImproveCacheLocality()
float MeshData::CalculateTrianglesArea() const
{
PROFILE_CPU();
float sum = 0;
// TODO: use SIMD
for (int32 i = 0; i + 2 < Indices.Count(); i += 3)
+31 -19
View File
@@ -625,6 +625,11 @@ bool MaterialSlotEntry::UsesProperties() const
Normals.TextureIndex != -1;
}
ModelLodData::~ModelLodData()
{
Meshes.ClearDelete();
}
BoundingBox ModelLodData::GetBox() const
{
if (Meshes.IsEmpty())
@@ -644,11 +649,9 @@ void ModelData::CalculateLODsScreenSizes()
{
const float autoComputeLodPowerBase = 0.5f;
const int32 lodCount = LODs.Count();
for (int32 lodIndex = 0; lodIndex < lodCount; lodIndex++)
{
auto& lod = LODs[lodIndex];
if (lodIndex == 0)
{
lod.ScreenSize = 1.0f;
@@ -675,6 +678,8 @@ void ModelData::TransformBuffer(const Matrix& matrix)
}
}
#if USE_EDITOR
bool ModelData::Pack2ModelHeader(WriteStream* stream) const
{
// Validate input
@@ -724,7 +729,12 @@ bool ModelData::Pack2ModelHeader(WriteStream* stream) const
// Amount of meshes
const int32 meshes = lod.Meshes.Count();
if (meshes == 0 || meshes > MODEL_MAX_MESHES)
if (meshes == 0)
{
LOG(Warning, "Empty LOD.");
return true;
}
if (meshes > MODEL_MAX_MESHES)
{
LOG(Warning, "Too many meshes per LOD.");
return true;
@@ -880,20 +890,21 @@ bool ModelData::Pack2SkinnedModelHeader(WriteStream* stream) const
return false;
}
bool ModelData::Pack2AnimationHeader(WriteStream* stream) const
bool ModelData::Pack2AnimationHeader(WriteStream* stream, int32 animIndex) const
{
// Validate input
if (stream == nullptr)
if (stream == nullptr || animIndex < 0 || animIndex >= Animations.Count())
{
Log::ArgumentNullException();
return true;
}
if (Animation.Duration <= ZeroTolerance || Animation.FramesPerSecond <= ZeroTolerance)
auto& anim = Animations.Get()[animIndex];
if (anim.Duration <= ZeroTolerance || anim.FramesPerSecond <= ZeroTolerance)
{
Log::InvalidOperationException(TEXT("Invalid animation duration."));
return true;
}
if (Animation.Channels.IsEmpty())
if (anim.Channels.IsEmpty())
{
Log::ArgumentOutOfRangeException(TEXT("Channels"), TEXT("Animation channels collection cannot be empty."));
return true;
@@ -901,22 +912,23 @@ bool ModelData::Pack2AnimationHeader(WriteStream* stream) const
// Info
stream->WriteInt32(100); // Header version (for fast version upgrades without serialization format change)
stream->WriteDouble(Animation.Duration);
stream->WriteDouble(Animation.FramesPerSecond);
stream->WriteBool(Animation.EnableRootMotion);
stream->WriteString(Animation.RootNodeName, 13);
stream->WriteDouble(anim.Duration);
stream->WriteDouble(anim.FramesPerSecond);
stream->WriteBool(anim.EnableRootMotion);
stream->WriteString(anim.RootNodeName, 13);
// Animation channels
stream->WriteInt32(Animation.Channels.Count());
for (int32 i = 0; i < Animation.Channels.Count(); i++)
stream->WriteInt32(anim.Channels.Count());
for (int32 i = 0; i < anim.Channels.Count(); i++)
{
auto& anim = Animation.Channels[i];
stream->WriteString(anim.NodeName, 172);
Serialization::Serialize(*stream, anim.Position);
Serialization::Serialize(*stream, anim.Rotation);
Serialization::Serialize(*stream, anim.Scale);
auto& channel = anim.Channels[i];
stream->WriteString(channel.NodeName, 172);
Serialization::Serialize(*stream, channel.Position);
Serialization::Serialize(*stream, channel.Rotation);
Serialization::Serialize(*stream, channel.Scale);
}
return false;
}
#endif
+34 -27
View File
@@ -366,12 +366,32 @@ struct FLAXENGINE_API MaterialSlotEntry
bool UsesProperties() const;
};
/// <summary>
/// Data container for model hierarchy node.
/// </summary>
struct FLAXENGINE_API ModelDataNode
{
/// <summary>
/// The parent node index. The root node uses value -1.
/// </summary>
int32 ParentIndex;
/// <summary>
/// The local transformation of the node, relative to the parent node.
/// </summary>
Transform LocalTransform;
/// <summary>
/// The name of this node.
/// </summary>
String Name;
};
/// <summary>
/// Data container for LOD metadata and sub meshes.
/// </summary>
class FLAXENGINE_API ModelLodData
struct FLAXENGINE_API ModelLodData
{
public:
/// <summary>
/// The screen size to switch LODs. Bottom limit of the model screen size to render this LOD.
/// </summary>
@@ -382,21 +402,10 @@ public:
/// </summary>
Array<MeshData*> Meshes;
public:
/// <summary>
/// Initializes a new instance of the <see cref="ModelLodData"/> class.
/// </summary>
ModelLodData()
{
}
/// <summary>
/// Finalizes an instance of the <see cref="ModelLodData"/> class.
/// </summary>
~ModelLodData()
{
Meshes.ClearDelete();
}
~ModelLodData();
/// <summary>
/// Gets the bounding box combined for all meshes in this model LOD.
@@ -426,7 +435,7 @@ public:
Array<MaterialSlotEntry> Materials;
/// <summary>
/// Array with all LODs. The first element is the top most LOD0 followed by the LOD1, LOD2, etc.
/// Array with all Level Of Details that contain meshes. The first element is the top most LOD0 followed by the LOD1, LOD2, etc.
/// </summary>
Array<ModelLodData> LODs;
@@ -435,24 +444,20 @@ public:
/// </summary>
SkeletonData Skeleton;
/// <summary>
/// The scene nodes (in hierarchy).
/// </summary>
Array<ModelDataNode> Nodes;
/// <summary>
/// The node animations.
/// </summary>
AnimationData Animation;
public:
/// <summary>
/// Initializes a new instance of the <see cref="ModelData"/> class.
/// </summary>
ModelData()
{
}
Array<AnimationData> Animations;
public:
/// <summary>
/// Gets the valid level of details count.
/// </summary>
/// <returns>The LOD count.</returns>
FORCE_INLINE int32 GetLODsCount() const
{
return LODs.Count();
@@ -461,7 +466,6 @@ public:
/// <summary>
/// Determines whether this instance has valid skeleton structure.
/// </summary>
/// <returns>True if has skeleton, otherwise false.</returns>
FORCE_INLINE bool HasSkeleton() const
{
return Skeleton.Bones.HasItems();
@@ -479,6 +483,7 @@ public:
/// <param name="matrix">The matrix to use for the transformation.</param>
void TransformBuffer(const Matrix& matrix);
#if USE_EDITOR
public:
/// <summary>
/// Pack mesh data to the header stream
@@ -498,6 +503,8 @@ public:
/// Pack animation data to the header stream
/// </summary>
/// <param name="stream">Output stream</param>
/// <param name="animIndex">Index of animation.</param>
/// <returns>True if cannot save data, otherwise false</returns>
bool Pack2AnimationHeader(WriteStream* stream) const;
bool Pack2AnimationHeader(WriteStream* stream, int32 animIndex = 0) const;
#endif
};
@@ -36,7 +36,7 @@ public:
/// </summary>
/// <param name="sourceSkeleton">The source model skeleton.</param>
/// <param name="targetSkeleton">The target skeleton. May be null to disable nodes mapping.</param>
SkeletonMapping(Items& sourceSkeleton, Items* targetSkeleton)
SkeletonMapping(const Items& sourceSkeleton, const Items* targetSkeleton)
{
Size = sourceSkeleton.Count();
SourceToTarget.Resize(Size); // model => skeleton mapping
@@ -660,6 +660,37 @@ int PixelFormatExtensions::ComputeComponentsCount(const PixelFormat format)
}
}
int32 PixelFormatExtensions::ComputeBlockSize(PixelFormat format)
{
switch (format)
{
case PixelFormat::BC1_Typeless:
case PixelFormat::BC1_UNorm:
case PixelFormat::BC1_UNorm_sRGB:
case PixelFormat::BC2_Typeless:
case PixelFormat::BC2_UNorm:
case PixelFormat::BC2_UNorm_sRGB:
case PixelFormat::BC3_Typeless:
case PixelFormat::BC3_UNorm:
case PixelFormat::BC3_UNorm_sRGB:
case PixelFormat::BC4_Typeless:
case PixelFormat::BC4_UNorm:
case PixelFormat::BC4_SNorm:
case PixelFormat::BC5_Typeless:
case PixelFormat::BC5_UNorm:
case PixelFormat::BC5_SNorm:
case PixelFormat::BC6H_Typeless:
case PixelFormat::BC6H_Uf16:
case PixelFormat::BC6H_Sf16:
case PixelFormat::BC7_Typeless:
case PixelFormat::BC7_UNorm:
case PixelFormat::BC7_UNorm_sRGB:
return 4;
default:
return 1;
}
}
PixelFormat PixelFormatExtensions::TosRGB(const PixelFormat format)
{
switch (format)
@@ -173,6 +173,13 @@ public:
/// <returns>The components count.</returns>
API_FUNCTION() static int ComputeComponentsCount(PixelFormat format);
/// <summary>
/// Computes the amount of pixels per-axis stored in the a single block of the format (eg. 4 for BC-family). Returns 1 for uncompressed formats.
/// </summary>
/// <param name="format">The <see cref="PixelFormat"/>.</param>
/// <returns>The block pixels count.</returns>
API_FUNCTION() static int32 ComputeBlockSize(PixelFormat format);
/// <summary>
/// Finds the equivalent sRGB format to the provided format.
/// </summary>
+1 -1
View File
@@ -13,7 +13,7 @@ struct RenderContext;
/// Custom PostFx which can modify final image by processing it with material based filters. The base class for all post process effects used by the graphics pipeline. Allows to extend frame rendering logic and apply custom effects such as outline, night vision, contrast etc.
/// </summary>
/// <remarks>
/// Override this class and implement custom post fx logic. Use <b>MainRenderTask.Instance.CustomPostFx.Add(myPostFx)</b> to attach your script to rendering or add script to camera actor.
/// Override this class and implement custom post fx logic. Use <b>MainRenderTask.Instance.AddCustomPostFx(myPostFx)</b> to attach your script to rendering or add script to camera actor.
/// </remarks>
API_CLASS(Abstract) class FLAXENGINE_API PostProcessEffect : public Script
{
+3
View File
@@ -192,6 +192,9 @@ bool RenderBuffers::Init(int32 width, int32 height)
_viewport = Viewport(0, 0, static_cast<float>(width), static_cast<float>(height));
LastEyeAdaptationTime = 0;
// Flush any pool render targets to prevent over-allocating GPU memory when resizing game viewport
RenderTargetPool::Flush(false, 4);
return result;
}
+28 -39
View File
@@ -7,35 +7,31 @@
struct Entry
{
bool IsOccupied;
GPUTexture* RT;
uint64 LastFrameTaken;
uint64 LastFrameReleased;
uint32 DescriptionHash;
bool IsOccupied;
};
namespace
{
Array<Entry> TemporaryRTs(64);
Array<Entry> TemporaryRTs;
}
void RenderTargetPool::Flush(bool force)
void RenderTargetPool::Flush(bool force, int32 framesOffset)
{
const uint64 framesOffset = 3 * 60;
if (framesOffset < 0)
framesOffset = 3 * 60; // For how many frames RTs should be cached (by default)
const uint64 maxReleaseFrame = Engine::FrameCount - framesOffset;
force |= Engine::ShouldExit();
for (int32 i = 0; i < TemporaryRTs.Count(); i++)
{
auto& tmp = TemporaryRTs[i];
if (!tmp.IsOccupied && (force || (tmp.LastFrameReleased < maxReleaseFrame)))
const auto& e = TemporaryRTs[i];
if (!e.IsOccupied && (force || e.LastFrameReleased < maxReleaseFrame))
{
// Release
tmp.RT->DeleteObjectNow();
TemporaryRTs.RemoveAt(i);
i--;
e.RT->DeleteObjectNow();
TemporaryRTs.RemoveAt(i--);
if (TemporaryRTs.IsEmpty())
break;
}
@@ -48,19 +44,14 @@ GPUTexture* RenderTargetPool::Get(const GPUTextureDescription& desc)
const uint32 descHash = GetHash(desc);
for (int32 i = 0; i < TemporaryRTs.Count(); i++)
{
auto& tmp = TemporaryRTs[i];
if (!tmp.IsOccupied && tmp.DescriptionHash == descHash)
auto& e = TemporaryRTs[i];
if (!e.IsOccupied && e.DescriptionHash == descHash)
{
ASSERT(tmp.RT);
// Mark as used
tmp.IsOccupied = true;
tmp.LastFrameTaken = Engine::FrameCount;
return tmp.RT;
e.IsOccupied = true;
return e.RT;
}
}
#if !BUILD_RELEASE
if (TemporaryRTs.Count() > 2000)
{
@@ -71,24 +62,23 @@ GPUTexture* RenderTargetPool::Get(const GPUTextureDescription& desc)
// Create new rt
const String name = TEXT("TemporaryRT_") + StringUtils::ToString(TemporaryRTs.Count());
auto newRenderTarget = GPUDevice::Instance->CreateTexture(name);
if (newRenderTarget->Init(desc))
GPUTexture* rt = GPUDevice::Instance->CreateTexture(name);
if (rt->Init(desc))
{
Delete(newRenderTarget);
Delete(rt);
LOG(Error, "Cannot create temporary render target. Description: {0}", desc.ToString());
return nullptr;
}
// Create temporary rt entry
Entry entry;
entry.IsOccupied = true;
entry.LastFrameReleased = 0;
entry.LastFrameTaken = Engine::FrameCount;
entry.RT = newRenderTarget;
entry.DescriptionHash = descHash;
TemporaryRTs.Add(entry);
Entry e;
e.IsOccupied = true;
e.LastFrameReleased = 0;
e.RT = rt;
e.DescriptionHash = descHash;
TemporaryRTs.Add(e);
return newRenderTarget;
return rt;
}
void RenderTargetPool::Release(GPUTexture* rt)
@@ -98,14 +88,13 @@ void RenderTargetPool::Release(GPUTexture* rt)
for (int32 i = 0; i < TemporaryRTs.Count(); i++)
{
auto& tmp = TemporaryRTs[i];
if (tmp.RT == rt)
auto& e = TemporaryRTs[i];
if (e.RT == rt)
{
// Mark as free
ASSERT(tmp.IsOccupied);
tmp.IsOccupied = false;
tmp.LastFrameReleased = Engine::FrameCount;
ASSERT(e.IsOccupied);
e.IsOccupied = false;
e.LastFrameReleased = Engine::FrameCount;
return;
}
}

Some files were not shown because too many files have changed in this diff Show More