mirror of
https://github.com/GTA-ASM/SanAndreasUnity
synced 2024-11-10 06:34:16 +00:00
new world loading system (#110)
* wip * much faster world creation * add StaticGeometryInspector * disable child/parent logic and fading * rename * (de)activate objects based on parent * set draw distance based on layers * ... * wip * wip * wip * remove unused param * prevent concurrent modification * ... * catch exceptions when notifying * ... * notify about area, not objects * limit public access to Area * ... * ... * allow public access * add public api * adapt code * pass callback to ctor * adapt focus points * fix * fix intersection check * support rectangles * adjust parameters in prefab * this should fix IsInsideOf() * ... * ... * fix getting area by index * create area if not exists * ... * ... * ... * wip for distance levels * remove constraint on generic parameter * add some validation * fix * fix draw distance per level * change time of day in which lights are visible * add todos * don't use id for UnRegisterFocusPoint() * use hash set for storing focus points * add 1 more level * mark area for update only if visibility changes * profile WorldSystem calls * add some profiling sections * limit time per frame for LoadingThread * switch custom concurrent queue * copy jobs to buffer * rename * change max draw distance setting * wait one more frame * try to remove 801 distance level to remove holes * attempt to hide interiors, but failed * delete no longer needed script * optimization * some error checking * add camera as focus point * dont add camera as focus point in headless mode * working on load priority * fix bug - load priority finished * ... * small optimizations * ... * ... * remove unneeded variable * add fading * dont do fading in headless mode * fadeRate available in inspector * change fade rate * take into account if geometry is loaded when checking if object should be visible, and if fading should be done * small optimization * cache IsInHeadlessMode * display Instance info in inspector * move interiors up in the sky * rename * adapt code to different y pos of interiors * refactor * fix finding matched enex for enexes that lead to the same interior level * display new world stats * rename * rename class * ... * ... * extract function * extract parameters into a struct * add focus point to dead body * add focus point to vehicle * add focus point to vehicle detached parts * remove OutOfRangeDestroyer from vehicle, and destroy vehicle if it falls below the map * dont use focus points on vehicle and vehicle detached parts, when not on server * add focus point for npc peds * add possibility to set timeout during which focus point keeps revealing after it's destroyed * adapt UnRegisterFocusPoint() to timeout * rename * adapt code * cleanup MapObject class * ... * converting to `lock()` * optimize method: use 1 lock instead of 3 * call OnObjectFinishedLoading() instead of AddToLoadedObjects() * ... * make sure it's main thread * AsyncLoader is no longer thread safe * convert static members to non-static in LoadingThread * fix * ... * store indexes for each area * impl GetAreaCenter() * calculate load priority based on distance to area, not objects ; limit time per frame ; sort area in Cell, not in concurrent SortedSet ; * add support for changing draw distance at runtime * delay setting the new value by 0.2 s * have a separate default max draw distance for mobile platforms * adjust y axis world params so that number of visible areas is reduced * remove "camera far clip plane" setting * rename * document flags * rename * disable shadow casting and receiving for some objects * allow casting shadows for LODs with large draw distance * remove "WorldSystem" layer * revert layer
This commit is contained in:
parent
bac100ebcb
commit
32c0be1af2
68 changed files with 2620 additions and 1110 deletions
|
@ -48,8 +48,8 @@ MonoBehaviour:
|
|||
m_EditorClassIdentifier:
|
||||
serverOnly: 0
|
||||
localPlayerAuthority: 0
|
||||
m_AssetId:
|
||||
m_SceneId: 3719402579
|
||||
m_AssetId: 14d22e60f4926f64cbd36f393fe11b9a
|
||||
m_SceneId: 0
|
||||
--- !u!114 &3458215582039164093
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
|
@ -62,6 +62,10 @@ MonoBehaviour:
|
|||
m_Script: {fileID: 11500000, guid: 52246fc08ef54f047800aa1a2be91fd2, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
syncInterval: 0.1
|
||||
focusPointParameters:
|
||||
hasRevealRadius: 1
|
||||
revealRadius: 150
|
||||
--- !u!114 &3928823046603185135
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!1 &652499336889633433
|
||||
--- !u!1 &4194179655117615784
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
|
@ -8,9 +8,9 @@ GameObject:
|
|||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 3575875094125856578}
|
||||
- component: {fileID: 3628301541464980759}
|
||||
- component: {fileID: 8390198767576974155}
|
||||
- component: {fileID: 187624993197227891}
|
||||
- component: {fileID: 99305259034019110}
|
||||
- component: {fileID: 5140126490860469114}
|
||||
m_Layer: 0
|
||||
m_Name: box2
|
||||
m_TagString: Untagged
|
||||
|
@ -18,35 +18,35 @@ GameObject:
|
|||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!4 &3575875094125856578
|
||||
--- !u!4 &187624993197227891
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 652499336889633433}
|
||||
m_GameObject: {fileID: 4194179655117615784}
|
||||
m_LocalRotation: {x: 0, y: 0, z: -0.17364825, w: 0.9848078}
|
||||
m_LocalPosition: {x: 0.15, y: 0, z: 0}
|
||||
m_LocalScale: {x: 0.1, y: 0.7, z: 0.39999998}
|
||||
m_Children: []
|
||||
m_Father: {fileID: 8167605406775738267}
|
||||
m_Father: {fileID: 4783866566622431146}
|
||||
m_RootOrder: 1
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: -20}
|
||||
--- !u!33 &3628301541464980759
|
||||
--- !u!33 &99305259034019110
|
||||
MeshFilter:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 652499336889633433}
|
||||
m_GameObject: {fileID: 4194179655117615784}
|
||||
m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0}
|
||||
--- !u!23 &8390198767576974155
|
||||
--- !u!23 &5140126490860469114
|
||||
MeshRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 652499336889633433}
|
||||
m_GameObject: {fileID: 4194179655117615784}
|
||||
m_Enabled: 1
|
||||
m_CastShadows: 0
|
||||
m_ReceiveShadows: 0
|
||||
|
@ -77,7 +77,7 @@ MeshRenderer:
|
|||
m_SortingLayerID: 0
|
||||
m_SortingLayer: 0
|
||||
m_SortingOrder: 0
|
||||
--- !u!1 &2538922681126103069
|
||||
--- !u!1 &5154345777629047764
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
|
@ -85,52 +85,52 @@ GameObject:
|
|||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 7884669128420726770}
|
||||
- component: {fileID: 7184170559896548561}
|
||||
- component: {fileID: 808976458662788928}
|
||||
- component: {fileID: 414177935848565884}
|
||||
- component: {fileID: 3257317628062351182}
|
||||
- component: {fileID: 7305951874729311165}
|
||||
- component: {fileID: 31071295224695786}
|
||||
- component: {fileID: 2351660952039690332}
|
||||
m_Layer: 0
|
||||
m_Name: Enex
|
||||
m_Name: Logic
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!4 &7884669128420726770
|
||||
--- !u!4 &3257317628062351182
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 2538922681126103069}
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_GameObject: {fileID: 5154345777629047764}
|
||||
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_Children:
|
||||
- {fileID: 8167605406775738267}
|
||||
- {fileID: 4783866566622431146}
|
||||
m_Father: {fileID: 0}
|
||||
m_RootOrder: 0
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!65 &7184170559896548561
|
||||
--- !u!65 &7305951874729311165
|
||||
BoxCollider:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 2538922681126103069}
|
||||
m_GameObject: {fileID: 5154345777629047764}
|
||||
m_Material: {fileID: 0}
|
||||
m_IsTrigger: 1
|
||||
m_Enabled: 1
|
||||
serializedVersion: 2
|
||||
m_Size: {x: 1, y: 1, z: 1}
|
||||
m_Center: {x: 0, y: 0, z: 0}
|
||||
--- !u!54 &808976458662788928
|
||||
--- !u!54 &31071295224695786
|
||||
Rigidbody:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 2538922681126103069}
|
||||
m_GameObject: {fileID: 5154345777629047764}
|
||||
serializedVersion: 2
|
||||
m_Mass: 0.0000001
|
||||
m_Drag: 0
|
||||
|
@ -140,13 +140,13 @@ Rigidbody:
|
|||
m_Interpolate: 0
|
||||
m_Constraints: 0
|
||||
m_CollisionDetection: 0
|
||||
--- !u!114 &414177935848565884
|
||||
--- !u!114 &2351660952039690332
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 2538922681126103069}
|
||||
m_GameObject: {fileID: 5154345777629047764}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: d1c9d26ffa1f86c4392cbc4f10792f8d, type: 3}
|
||||
|
@ -155,9 +155,9 @@ MonoBehaviour:
|
|||
Flags: []
|
||||
minArrowPos: -0.15
|
||||
maxArrowPos: 0.15
|
||||
arrowTransform: {fileID: 8167605406775738267}
|
||||
arrowTransform: {fileID: 4783866566622431146}
|
||||
arrowMoveSpeed: 0.2
|
||||
--- !u!1 &4678743238979879263
|
||||
--- !u!1 &8346472704221690222
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
|
@ -165,7 +165,7 @@ GameObject:
|
|||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 8167605406775738267}
|
||||
- component: {fileID: 4783866566622431146}
|
||||
m_Layer: 0
|
||||
m_Name: Graphics
|
||||
m_TagString: Untagged
|
||||
|
@ -173,23 +173,23 @@ GameObject:
|
|||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!4 &8167605406775738267
|
||||
--- !u!4 &4783866566622431146
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 4678743238979879263}
|
||||
m_LocalRotation: {x: 0, y: 0.7071068, z: 0, w: 0.7071068}
|
||||
m_GameObject: {fileID: 8346472704221690222}
|
||||
m_LocalRotation: {x: -0, y: 0.7071068, z: -0, w: 0.7071068}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 2, z: 1}
|
||||
m_Children:
|
||||
- {fileID: 7155344991509732878}
|
||||
- {fileID: 3575875094125856578}
|
||||
m_Father: {fileID: 7884669128420726770}
|
||||
- {fileID: 5797954369941300799}
|
||||
- {fileID: 187624993197227891}
|
||||
m_Father: {fileID: 3257317628062351182}
|
||||
m_RootOrder: 0
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 90, z: 0}
|
||||
--- !u!1 &5283615154995685745
|
||||
--- !u!1 &8820782974559310144
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
|
@ -197,9 +197,9 @@ GameObject:
|
|||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 7155344991509732878}
|
||||
- component: {fileID: 2310926300048183979}
|
||||
- component: {fileID: 7210435366399545569}
|
||||
- component: {fileID: 5797954369941300799}
|
||||
- component: {fileID: 1380515625963776666}
|
||||
- component: {fileID: 6280833039519983824}
|
||||
m_Layer: 0
|
||||
m_Name: box1
|
||||
m_TagString: Untagged
|
||||
|
@ -207,35 +207,35 @@ GameObject:
|
|||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!4 &7155344991509732878
|
||||
--- !u!4 &5797954369941300799
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 5283615154995685745}
|
||||
m_GameObject: {fileID: 8820782974559310144}
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0.17364816, w: 0.9848078}
|
||||
m_LocalPosition: {x: -0.15, y: 0, z: 0}
|
||||
m_LocalScale: {x: 0.1, y: 0.7, z: 0.4}
|
||||
m_Children: []
|
||||
m_Father: {fileID: 8167605406775738267}
|
||||
m_Father: {fileID: 4783866566622431146}
|
||||
m_RootOrder: 0
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 20}
|
||||
--- !u!33 &2310926300048183979
|
||||
--- !u!33 &1380515625963776666
|
||||
MeshFilter:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 5283615154995685745}
|
||||
m_GameObject: {fileID: 8820782974559310144}
|
||||
m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0}
|
||||
--- !u!23 &7210435366399545569
|
||||
--- !u!23 &6280833039519983824
|
||||
MeshRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 5283615154995685745}
|
||||
m_GameObject: {fileID: 8820782974559310144}
|
||||
m_Enabled: 1
|
||||
m_CastShadows: 0
|
||||
m_ReceiveShadows: 0
|
||||
|
|
|
@ -34,6 +34,7 @@ GameObject:
|
|||
- component: {fileID: 4992771638147279046}
|
||||
- component: {fileID: 2664216849870465946}
|
||||
- component: {fileID: 3560199336842227889}
|
||||
- component: {fileID: 7933397811870977063}
|
||||
m_Layer: 0
|
||||
m_Name: GameManager
|
||||
m_TagString: Untagged
|
||||
|
@ -156,12 +157,20 @@ MonoBehaviour:
|
|||
groundFindingIgnoredLayerMask:
|
||||
serializedVersion: 2
|
||||
m_Bits: 31286
|
||||
playerPedFocusPointParameters:
|
||||
hasRevealRadius: 0
|
||||
revealRadius: 0
|
||||
timeToKeepRevealingAfterRemoved: 3
|
||||
npcPedFocusPointParameters:
|
||||
hasRevealRadius: 1
|
||||
revealRadius: 150
|
||||
timeToKeepRevealingAfterRemoved: 3
|
||||
cameraDistanceFromPed: 4
|
||||
minCameraDistanceFromPed: 2
|
||||
maxCameraDistanceFromPed: 30
|
||||
cameraRaycastIgnoredLayerMask:
|
||||
serializedVersion: 2
|
||||
m_Bits: 31542
|
||||
m_Bits: 64310
|
||||
legAndArmDamageMultiplier: 0.8
|
||||
stomachAndChestDamageMultiplier: 1
|
||||
headDamageMultiplier: 4
|
||||
|
@ -177,8 +186,6 @@ MonoBehaviour:
|
|||
healthBackgroundColor: {r: 0.5, g: 0, b: 0, a: 1}
|
||||
AIStoppingDistance: 3
|
||||
AIVehicleEnterDistance: 1.25
|
||||
AIOutOfRangeTimeout: 5
|
||||
AIOutOfRangeDistance: 250
|
||||
pedSyncRate: 20
|
||||
ragdollPrefab: {fileID: 2141754834917738923, guid: 14d22e60f4926f64cbd36f393fe11b9a,
|
||||
type: 3}
|
||||
|
@ -215,6 +222,7 @@ MonoBehaviour:
|
|||
m_Script: {fileID: 11500000, guid: be87ebc3b426867448164f659f750671, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
maxTimePerFrameMs: 10
|
||||
--- !u!114 &114463745687567314
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
|
@ -294,6 +302,7 @@ MonoBehaviour:
|
|||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
serverChatNick: <color=green>Server</color>
|
||||
maxChatMessageLength: 50
|
||||
--- !u!114 &7442340376164908290
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
|
@ -452,3 +461,17 @@ MonoBehaviour:
|
|||
pedLimitInterval: 2
|
||||
weaponLimitInterval: 1
|
||||
maxVehiclesPerPlayer: 3
|
||||
--- !u!114 &7933397811870977063
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1297494511425690}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 9dd4217c14fd11f4e8b46fe4a95f7aa5, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
_defaultMaxDrawDistance: 1500
|
||||
_defaultMaxDrawDistanceOnMobile: 1000
|
||||
|
|
59
Assets/Prefabs/MapObjectActivator.prefab
Normal file
59
Assets/Prefabs/MapObjectActivator.prefab
Normal file
|
@ -0,0 +1,59 @@
|
|||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!1 &1450017544071322502
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 6243884556424213097}
|
||||
- component: {fileID: 4881020080364246716}
|
||||
- component: {fileID: 2031804573005969323}
|
||||
m_Layer: 15
|
||||
m_Name: MapObjectActivator
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!4 &6243884556424213097
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1450017544071322502}
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_RootOrder: 0
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!114 &4881020080364246716
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1450017544071322502}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 38d6efa58345a004eb09a90ed4a28e2f, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
--- !u!135 &2031804573005969323
|
||||
SphereCollider:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1450017544071322502}
|
||||
m_Material: {fileID: 0}
|
||||
m_IsTrigger: 1
|
||||
m_Enabled: 1
|
||||
serializedVersion: 2
|
||||
m_Radius: 0.00001
|
||||
m_Center: {x: 0, y: 0, z: 0}
|
7
Assets/Prefabs/MapObjectActivator.prefab.meta
Normal file
7
Assets/Prefabs/MapObjectActivator.prefab.meta
Normal file
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 136804ed85ed64546a9f516463106810
|
||||
PrefabImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
45
Assets/Prefabs/StaticGeometry.prefab
Normal file
45
Assets/Prefabs/StaticGeometry.prefab
Normal file
|
@ -0,0 +1,45 @@
|
|||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!1 &5341989147525447730
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 1810686241933207467}
|
||||
- component: {fileID: 7540295739339550804}
|
||||
m_Layer: 0
|
||||
m_Name: StaticGeometry
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!4 &1810686241933207467
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 5341989147525447730}
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_RootOrder: 0
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!114 &7540295739339550804
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 5341989147525447730}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: c2400fe08bb4191409f1e53b32fedcc1, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
7
Assets/Prefabs/StaticGeometry.prefab.meta
Normal file
7
Assets/Prefabs/StaticGeometry.prefab.meta
Normal file
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: f6207237b8bbdaa49b948fa374797093
|
||||
PrefabImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -14,6 +14,8 @@ GameObject:
|
|||
- component: {fileID: 9183683351129218624}
|
||||
- component: {fileID: 575085982327711637}
|
||||
- component: {fileID: 6891581410426086992}
|
||||
- component: {fileID: 1742792654273641741}
|
||||
- component: {fileID: 9038525027628313851}
|
||||
m_Layer: 0
|
||||
m_Name: Vehicle
|
||||
m_TagString: Untagged
|
||||
|
@ -186,3 +188,32 @@ AudioSource:
|
|||
m_PreInfinity: 2
|
||||
m_PostInfinity: 2
|
||||
m_RotationOrder: 4
|
||||
--- !u!114 &1742792654273641741
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 5841438317718401360}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 8643b1a14caafa547beb3c2e2780085f, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
parameters:
|
||||
hasRevealRadius: 1
|
||||
revealRadius: 150
|
||||
--- !u!114 &9038525027628313851
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 5841438317718401360}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 6d68c88ae66cbc64c997da0c710b080c, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
componentsToDestroy:
|
||||
- {fileID: 1742792654273641741}
|
||||
|
|
|
@ -12,6 +12,8 @@ GameObject:
|
|||
- component: {fileID: 6750907620125006804}
|
||||
- component: {fileID: 3359912204732796237}
|
||||
- component: {fileID: 7107055912798612425}
|
||||
- component: {fileID: 1658066758197886442}
|
||||
- component: {fileID: 9051170896248035060}
|
||||
m_Layer: 13
|
||||
m_Name: VehicleExplosionLeftoverPart
|
||||
m_TagString: Untagged
|
||||
|
@ -77,3 +79,32 @@ MonoBehaviour:
|
|||
syncInterval: 0.0667
|
||||
disableCollisionDetectionOnClients: 1
|
||||
disableGravityOnClients: 1
|
||||
--- !u!114 &1658066758197886442
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 5998353205578393877}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 8643b1a14caafa547beb3c2e2780085f, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
parameters:
|
||||
hasRevealRadius: 1
|
||||
revealRadius: 150
|
||||
--- !u!114 &9051170896248035060
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 5998353205578393877}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 6d68c88ae66cbc64c997da0c710b080c, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
componentsToDestroy:
|
||||
- {fileID: 1658066758197886442}
|
||||
|
|
|
@ -471,18 +471,30 @@ MonoBehaviour:
|
|||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
PreviewCamera: {fileID: 0}
|
||||
focusPoints: []
|
||||
maxTimeToUpdatePerFrameMs: 10
|
||||
Water: {fileID: 11455068}
|
||||
divisionLoadOrderDistanceFactor: 16
|
||||
divisionRefreshDistanceDelta: 1
|
||||
divisionsUpdateInterval: 0.3
|
||||
maxDrawDistance: 500
|
||||
drawDistancesPerLayers:
|
||||
- 101
|
||||
- 301
|
||||
- 1501
|
||||
yWorldSystemSize: 7000
|
||||
yWorldSystemNumAreas: 1
|
||||
xzWorldSystemNumAreasPerDrawDistanceLevel: 640064006400
|
||||
interiorHeightOffset: 5000
|
||||
fadeRate: 4
|
||||
loadParkedVehicles: 0
|
||||
enexPrefab: {fileID: 2538922681126103069, guid: 1858efe8eaf422544a4651b2fba2937a,
|
||||
mapObjectActivatorPrefab: {fileID: 0}
|
||||
staticGeometryPrefab: {fileID: 5341989147525447730, guid: f6207237b8bbdaa49b948fa374797093,
|
||||
type: 3}
|
||||
enexPrefab: {fileID: 5154345777629047764, guid: 1858efe8eaf422544a4651b2fba2937a,
|
||||
type: 3}
|
||||
lightSourcePrefab: {fileID: 5315691503008682268, guid: be3276cb0ed03dc4cb44bd04cde4bcfb,
|
||||
type: 3}
|
||||
lightScaleMultiplier: 0.5
|
||||
redTrafficLightDuration: 7
|
||||
yellowTrafficLightDuration: 2
|
||||
greenTrafficLightDuration: 7
|
||||
--- !u!114 &11450014
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
|
|
|
@ -125,6 +125,8 @@ GameObject:
|
|||
- component: {fileID: 1117230322}
|
||||
- component: {fileID: 1117230321}
|
||||
- component: {fileID: 1117230325}
|
||||
- component: {fileID: 1117230326}
|
||||
- component: {fileID: 1117230327}
|
||||
m_Layer: 0
|
||||
m_Name: Camera
|
||||
m_TagString: MainCamera
|
||||
|
@ -221,6 +223,34 @@ MonoBehaviour:
|
|||
displayGizmos: 0
|
||||
zOnShader: {fileID: 4800000, guid: 6602491fb85d04f7facf2932f1831720, type: 3}
|
||||
zOffShader: {fileID: 4800000, guid: 12cb60961770c4d7e8cc1662ca101fec, type: 3}
|
||||
--- !u!114 &1117230326
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1117230320}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: f261590297a9b11459cc24b5b949c608, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
hasRevealRadius: 0
|
||||
revealRadius: 50
|
||||
--- !u!114 &1117230327
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1117230320}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 4bb8f0fea23c3fe44828518a6ea9ad15, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
componentsToDestroy:
|
||||
- {fileID: 1117230326}
|
||||
--- !u!108 &1430544495 stripped
|
||||
Light:
|
||||
m_CorrespondingSourceObject: {fileID: 10808534, guid: b6900baf9f552ba46ad27d4ada4d5489,
|
||||
|
@ -297,18 +327,6 @@ PrefabInstance:
|
|||
m_Modification:
|
||||
m_TransformParent: {fileID: 0}
|
||||
m_Modifications:
|
||||
- target: {fileID: 11436500, guid: b6900baf9f552ba46ad27d4ada4d5489, type: 3}
|
||||
propertyPath: focusPoints.Array.size
|
||||
value: 1
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 11436500, guid: b6900baf9f552ba46ad27d4ada4d5489, type: 3}
|
||||
propertyPath: Focus
|
||||
value:
|
||||
objectReference: {fileID: 1117230324}
|
||||
- target: {fileID: 11436500, guid: b6900baf9f552ba46ad27d4ada4d5489, type: 3}
|
||||
propertyPath: focusPoints.Array.data[0]
|
||||
value:
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 499186, guid: b6900baf9f552ba46ad27d4ada4d5489, type: 3}
|
||||
propertyPath: m_LocalPosition.x
|
||||
value: 0
|
||||
|
@ -341,6 +359,18 @@ PrefabInstance:
|
|||
propertyPath: m_RootOrder
|
||||
value: 1
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 11436500, guid: b6900baf9f552ba46ad27d4ada4d5489, type: 3}
|
||||
propertyPath: focusPoints.Array.size
|
||||
value: 1
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 11436500, guid: b6900baf9f552ba46ad27d4ada4d5489, type: 3}
|
||||
propertyPath: Focus
|
||||
value:
|
||||
objectReference: {fileID: 1117230324}
|
||||
- target: {fileID: 11436500, guid: b6900baf9f552ba46ad27d4ada4d5489, type: 3}
|
||||
propertyPath: focusPoints.Array.data[0]
|
||||
value:
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 11455068, guid: b6900baf9f552ba46ad27d4ada4d5489, type: 3}
|
||||
propertyPath: WaterPrefab
|
||||
value:
|
||||
|
|
|
@ -116,7 +116,6 @@ namespace SanAndreasUnity.Behaviours
|
|||
AddLoadingStep( new LoadingStep( () => Cell.Instance.InitStaticGeometry (), "Init static geometry", 0.35f ) );
|
||||
AddLoadingStep( new LoadingStep( () => Cell.Instance.LoadParkedVehicles (), "Loading parked vehicles", 0.2f ) );
|
||||
AddLoadingStep( new LoadingStep( () => Cell.Instance.CreateEnexes (), "Creating enexes", 0.1f ) );
|
||||
AddLoadingStep( new LoadingStep( () => Cell.Instance.AddMapObjectsToDivisions (), "Adding map objects to divisions", 0.85f ) );
|
||||
AddLoadingStep( new LoadingStep( () => Cell.Instance.LoadWater (), "Loading water", 0.08f ) );
|
||||
AddLoadingStep( new LoadingStep( () => Cell.Instance.FinalizeLoad (), "Finalize world loading", 0.01f ) );
|
||||
|
||||
|
@ -149,7 +148,12 @@ namespace SanAndreasUnity.Behaviours
|
|||
|
||||
var stopwatchForSteps = new System.Diagnostics.Stopwatch ();
|
||||
|
||||
foreach (var step in m_loadingSteps) {
|
||||
foreach (var step in m_loadingSteps)
|
||||
{
|
||||
|
||||
// wait some more time before going to next step, because sometimes Unity does something
|
||||
// in the background at the end of a frame, eg. it updates Collider positions if you changed them
|
||||
yield return null;
|
||||
|
||||
// update description
|
||||
LoadingStatus = step.Description;
|
||||
|
|
|
@ -1,41 +1,25 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics;
|
||||
using SanAndreasUnity.Behaviours.World;
|
||||
using UnityEngine;
|
||||
using Debug = UnityEngine.Debug;
|
||||
using UnityEngine.Profiling;
|
||||
using Object = UnityEngine.Object;
|
||||
using Profiler = UnityEngine.Profiling.Profiler;
|
||||
|
||||
namespace SanAndreasUnity.Behaviours
|
||||
{
|
||||
public abstract class MapObject : MonoBehaviour, IComparable<MapObject>
|
||||
public abstract class MapObject : MonoBehaviour
|
||||
{
|
||||
private static Texture2D _sNoiseTex;
|
||||
|
||||
private static bool ShouldGenerateNoiseTex
|
||||
{
|
||||
get { return _sNoiseTex == null || _sNoiseTex.width != Screen.width || _sNoiseTex.height != Screen.height; }
|
||||
}
|
||||
private static bool ShouldGenerateNoiseTex => _sNoiseTex == null || _sNoiseTex.width != Screen.width || _sNoiseTex.height != Screen.height;
|
||||
|
||||
private static int _sBreakableLayer = -1;
|
||||
public static int BreakableLayer => _sBreakableLayer == -1 ? _sBreakableLayer = LayerMask.NameToLayer("Breakable") : _sBreakableLayer;
|
||||
|
||||
public static int BreakableLayer
|
||||
{
|
||||
get { return _sBreakableLayer == -1 ? _sBreakableLayer = LayerMask.NameToLayer("Breakable") : _sBreakableLayer; }
|
||||
}
|
||||
private static int _sNoiseTexPropertyId = -1;
|
||||
protected static int NoiseTexPropertyId => _sNoiseTexPropertyId == -1 ? _sNoiseTexPropertyId = Shader.PropertyToID("_NoiseTex") : _sNoiseTexPropertyId;
|
||||
|
||||
private static int _sNoiseTexId = -1;
|
||||
|
||||
protected static int NoiseTexId
|
||||
{
|
||||
get { return _sNoiseTexId == -1 ? _sNoiseTexId = Shader.PropertyToID("_NoiseTex") : _sNoiseTexId; }
|
||||
}
|
||||
|
||||
private static int _sFadeId = -1;
|
||||
|
||||
protected static int FadeId
|
||||
{
|
||||
get { return _sFadeId == -1 ? _sFadeId = Shader.PropertyToID("_Fade") : _sFadeId; }
|
||||
}
|
||||
private static int _sFadePropertyId = -1;
|
||||
protected static int FadePropertyId => _sFadePropertyId == -1 ? _sFadePropertyId = Shader.PropertyToID("_Fade") : _sFadePropertyId;
|
||||
|
||||
private static void GenerateNoiseTex()
|
||||
{
|
||||
|
@ -74,58 +58,68 @@ namespace SanAndreasUnity.Behaviours
|
|||
}
|
||||
}
|
||||
|
||||
private static readonly System.Random _sRandom = new System.Random(0x54e03b19);
|
||||
public bool IsVisibleInMapSystem { get; private set; } = false;
|
||||
|
||||
public float LoadPriority { get; private set; } = float.PositiveInfinity;
|
||||
|
||||
private bool _loaded;
|
||||
public bool HasLoaded => _loaded;
|
||||
|
||||
public bool HasLoaded { get { return _loaded; } }
|
||||
public Vector3 CachedPosition { get; private set; }
|
||||
|
||||
public List<String> Flags;
|
||||
|
||||
public Vector2 CellPos { get; private set; }
|
||||
|
||||
public int RandomInt { get; private set; }
|
||||
|
||||
internal float LoadOrder { get; private set; }
|
||||
protected static T Create<T>(GameObject prefab)
|
||||
where T : MapObject
|
||||
{
|
||||
GameObject go = Object.Instantiate(prefab, Cell.Instance.transform);
|
||||
return go.GetComponent<T>();
|
||||
}
|
||||
|
||||
protected void Initialize(Vector3 pos, Quaternion rot)
|
||||
{
|
||||
transform.position = pos;
|
||||
transform.localRotation = rot;
|
||||
this.transform.position = pos;
|
||||
this.transform.localRotation = rot;
|
||||
|
||||
CellPos = new Vector2(pos.x, pos.z);
|
||||
|
||||
RandomInt = _sRandom.Next();
|
||||
this.CachedPosition = pos;
|
||||
|
||||
_loaded = false;
|
||||
}
|
||||
|
||||
public bool RefreshLoadOrder(Vector3 from)
|
||||
public void SetDrawDistance(float f)
|
||||
{
|
||||
Profiler.BeginSample ("MapObject.RefreshLoadOrder", this);
|
||||
LoadOrder = OnRefreshLoadOrder(from);
|
||||
Profiler.EndSample ();
|
||||
return !float.IsPositiveInfinity(LoadOrder);
|
||||
|
||||
}
|
||||
|
||||
protected abstract float OnRefreshLoadOrder(Vector3 from);
|
||||
|
||||
public void Show()
|
||||
public void Show(float loadPriority)
|
||||
{
|
||||
if (this.IsVisibleInMapSystem)
|
||||
return;
|
||||
|
||||
this.IsVisibleInMapSystem = true;
|
||||
|
||||
this.LoadPriority = loadPriority;
|
||||
|
||||
if (!_loaded)
|
||||
{
|
||||
_loaded = true;
|
||||
|
||||
Profiler.BeginSample ("OnLoad", this);
|
||||
OnLoad();
|
||||
this.OnLoad();
|
||||
Profiler.EndSample ();
|
||||
}
|
||||
|
||||
Profiler.BeginSample ("OnShow", this);
|
||||
OnShow();
|
||||
this.OnShow();
|
||||
Profiler.EndSample ();
|
||||
}
|
||||
|
||||
LoadOrder = float.PositiveInfinity;
|
||||
public void UnShow()
|
||||
{
|
||||
if (!this.IsVisibleInMapSystem)
|
||||
return;
|
||||
|
||||
this.IsVisibleInMapSystem = false;
|
||||
|
||||
this.OnUnShow();
|
||||
}
|
||||
|
||||
protected virtual void OnLoad()
|
||||
|
@ -134,11 +128,12 @@ namespace SanAndreasUnity.Behaviours
|
|||
|
||||
protected virtual void OnShow()
|
||||
{
|
||||
this.gameObject.SetActive(true);
|
||||
}
|
||||
|
||||
public int CompareTo(MapObject other)
|
||||
protected virtual void OnUnShow()
|
||||
{
|
||||
return LoadOrder > other.LoadOrder ? 1 : LoadOrder == other.LoadOrder ? 0 : -1;
|
||||
this.gameObject.SetActive(false);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Mirror;
|
||||
using SanAndreasUnity.Behaviours.World;
|
||||
using SanAndreasUnity.Importing.Items;
|
||||
using SanAndreasUnity.Importing.Items.Definitions;
|
||||
using SanAndreasUnity.Net;
|
||||
|
@ -17,6 +18,8 @@ namespace SanAndreasUnity.Behaviours.Peds
|
|||
public static IEnumerable<DeadBody> DeadBodies => _deadBodies;
|
||||
public static int NumDeadBodies => _deadBodies.Count;
|
||||
|
||||
public FocusPoint.Parameters focusPointParameters = FocusPoint.Parameters.Default;
|
||||
|
||||
public PushableByDamage PushableByDamage { get; private set; }
|
||||
|
||||
public readonly struct BoneInfo
|
||||
|
@ -233,6 +236,8 @@ namespace SanAndreasUnity.Behaviours.Peds
|
|||
deadBody.m_rigidBodiesDict.Add(pair.Key, new BoneInfo(rb.transform));
|
||||
}
|
||||
|
||||
FocusPoint.Create(ragdollTransform.gameObject, deadBody.focusPointParameters);
|
||||
|
||||
deadBody.InitSyncVarsOnServer(ped);
|
||||
|
||||
NetManager.Spawn(ragdollGameObject);
|
||||
|
|
|
@ -115,7 +115,7 @@ namespace SanAndreasUnity.Behaviours
|
|||
this.raycastDistance = raycastDistance;
|
||||
}
|
||||
|
||||
public static FindGroundParams DefaultBasedOnLoadedWorld => new FindGroundParams((null == Cell.Instance || Cell.Instance.HasExterior));
|
||||
public static FindGroundParams DefaultBasedOnLoadedWorld => new FindGroundParams((null == Cell.Instance || Cell.Instance.HasMainExterior));
|
||||
|
||||
}
|
||||
|
||||
|
@ -173,17 +173,14 @@ namespace SanAndreasUnity.Behaviours
|
|||
{
|
||||
if (NetStatus.IsServer)
|
||||
{
|
||||
// only register if this ped is owned by some player
|
||||
if (Player.GetOwningPlayer(this) != null)
|
||||
this.Cell.focusPoints.AddIfNotPresent(this.transform);
|
||||
if (this.PlayerOwner != null)
|
||||
this.Cell.RegisterFocusPoint(this.transform, PedManager.Instance.playerPedFocusPointParameters);
|
||||
else
|
||||
this.Cell.RegisterFocusPoint(this.transform, PedManager.Instance.npcPedFocusPointParameters);
|
||||
}
|
||||
else if (NetStatus.IsClientActive())
|
||||
{
|
||||
// only register if this ped is owned by local player
|
||||
// TODO: IsControlledByLocalPlayer may not return true, because syncvar in Player script may
|
||||
// not be updated yet
|
||||
if (this.IsControlledByLocalPlayer)
|
||||
this.Cell.focusPoints.AddIfNotPresent(this.transform);
|
||||
// no need to register on client, because camera will be used as a focus point
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -689,9 +686,7 @@ namespace SanAndreasUnity.Behaviours
|
|||
}
|
||||
else
|
||||
{
|
||||
var matchingEnexes = Importing.Items.Item.Enexes.Where(e => e.Name == enex.Info.Name && e != enex.Info);
|
||||
Debug.LogFormat("Matching enexes:\n{0}", string.Join("\n", matchingEnexes.Select(e => e.TargetInterior + " - " + e.Flags)));
|
||||
var counterPart = matchingEnexes.FirstOrDefault(e => e.TargetInterior != enex.Info.TargetInterior);
|
||||
var counterPart = enex.FindMatchingEnex();
|
||||
if (counterPart != null)
|
||||
{
|
||||
// found a counterpart where we can teleport
|
||||
|
|
|
@ -32,10 +32,6 @@ namespace SanAndreasUnity.Behaviours
|
|||
ped.PlayerModel.StartingPedId = def.Id;
|
||||
ped.EnterVehicleRadius = PedManager.Instance.AIVehicleEnterDistance;
|
||||
|
||||
var destroyer = ped.gameObject.GetOrAddComponent<OutOfRangeDestroyer> ();
|
||||
destroyer.timeUntilDestroyed = PedManager.Instance.AIOutOfRangeTimeout;
|
||||
destroyer.range = PedManager.Instance.AIOutOfRangeDistance;
|
||||
|
||||
if (spawnOnNetwork)
|
||||
Net.NetManager.Spawn(go);
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System.Collections.Generic;
|
||||
using SanAndreasUnity.Behaviours.World;
|
||||
using UnityEngine;
|
||||
|
||||
namespace SanAndreasUnity.Behaviours
|
||||
|
@ -16,6 +17,9 @@ namespace SanAndreasUnity.Behaviours
|
|||
|
||||
public LayerMask groundFindingIgnoredLayerMask = 0;
|
||||
|
||||
public FocusPoint.Parameters playerPedFocusPointParameters = new FocusPoint.Parameters(false, 0f, 3f);
|
||||
public FocusPoint.Parameters npcPedFocusPointParameters = FocusPoint.Parameters.Default;
|
||||
|
||||
[Header("Camera")]
|
||||
|
||||
public float cameraDistanceFromPed = 3f;
|
||||
|
@ -48,8 +52,6 @@ namespace SanAndreasUnity.Behaviours
|
|||
|
||||
public float AIStoppingDistance = 3f;
|
||||
public float AIVehicleEnterDistance = 1.25f;
|
||||
public float AIOutOfRangeTimeout = 5f;
|
||||
public float AIOutOfRangeDistance = 250f;
|
||||
|
||||
[Header("Net")]
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ namespace SanAndreasUnity.Behaviours
|
|||
{
|
||||
public virtual bool GetSpawnPosition(Player player, out TransformDataStruct transformData)
|
||||
{
|
||||
if (null == World.Cell.Instance || World.Cell.Instance.HasExterior)
|
||||
if (null == World.Cell.Instance || World.Cell.Instance.HasMainExterior)
|
||||
return SpawnManager.GetSpawnPositionFromFocus(out transformData);
|
||||
else
|
||||
return SpawnManager.GetSpawnPositionFromInteriors(out transformData);
|
||||
|
|
|
@ -108,7 +108,7 @@ namespace SanAndreasUnity.Behaviours
|
|||
|
||||
public static bool GetSpawnPositionFromInteriors(out TransformDataStruct transformData)
|
||||
{
|
||||
transformData = World.Cell.GetEnexExitTransform(
|
||||
transformData = World.Cell.Instance.GetEnexExitTransform(
|
||||
World.Cell.Instance.GetEnexesFromLoadedInteriors()
|
||||
.ToList()
|
||||
.RandomElement());
|
||||
|
|
|
@ -532,6 +532,11 @@ namespace SanAndreasUnity.Behaviours.Vehicles
|
|||
|
||||
this.UpdateHighDetailMeshes();
|
||||
|
||||
if (Net.NetStatus.IsServer && this.transform.position.y < -2000f)
|
||||
{
|
||||
Object.Destroy(this.gameObject);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void FixedUpdate()
|
||||
|
|
42
Assets/Scripts/Behaviours/Vehicles/VehicleSpawnMapObject.cs
Normal file
42
Assets/Scripts/Behaviours/Vehicles/VehicleSpawnMapObject.cs
Normal file
|
@ -0,0 +1,42 @@
|
|||
using SanAndreasUnity.Importing.Items.Placements;
|
||||
using UnityEngine;
|
||||
|
||||
namespace SanAndreasUnity.Behaviours.Vehicles
|
||||
{
|
||||
public class VehicleSpawnMapObject : MapObject
|
||||
{
|
||||
public static VehicleSpawnMapObject Create(ParkedVehicle info)
|
||||
{
|
||||
var vs = new GameObject().AddComponent<VehicleSpawnMapObject>();
|
||||
vs.Initialize(info);
|
||||
return vs;
|
||||
}
|
||||
|
||||
public ParkedVehicle Info { get; private set; }
|
||||
|
||||
public void Initialize(ParkedVehicle info)
|
||||
{
|
||||
Info = info;
|
||||
|
||||
name = string.Format("Vehicle Spawn ({0})", info.CarId);
|
||||
|
||||
Initialize(info.Position, Quaternion.AngleAxis(info.Angle, Vector3.up));
|
||||
|
||||
this.SetDrawDistance(100f);
|
||||
|
||||
gameObject.SetActive(false);
|
||||
gameObject.isStatic = true;
|
||||
}
|
||||
|
||||
private void OnDrawGizmos()
|
||||
{
|
||||
Gizmos.color = Color.red;
|
||||
Gizmos.DrawCube(transform.position + Vector3.up * 128f, new Vector3(1f, 256f, 1f));
|
||||
}
|
||||
|
||||
protected override void OnLoad()
|
||||
{
|
||||
Vehicle.Create(this);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
//using Facepunch.Networking;
|
||||
using SanAndreasUnity.Importing.Items.Placements;
|
||||
using UnityEngine;
|
||||
|
||||
namespace SanAndreasUnity.Behaviours.Vehicles
|
||||
{
|
||||
public class VehicleSpawner : MapObject
|
||||
{
|
||||
public static VehicleSpawner Create(ParkedVehicle info)
|
||||
{
|
||||
//Debug.Log("-333");
|
||||
var vs = new GameObject().AddComponent<VehicleSpawner>();
|
||||
vs.Initialize(info);
|
||||
return vs;
|
||||
}
|
||||
|
||||
public ParkedVehicle Info { get; private set; }
|
||||
|
||||
public void Initialize(ParkedVehicle info)
|
||||
{
|
||||
Info = info;
|
||||
|
||||
name = string.Format("Vehicle Spawner ({0})", info.CarId);
|
||||
|
||||
Initialize(info.Position, Quaternion.AngleAxis(info.Angle, Vector3.up));
|
||||
|
||||
gameObject.SetActive(false);
|
||||
gameObject.isStatic = true;
|
||||
}
|
||||
|
||||
private void OnDrawGizmos()
|
||||
{
|
||||
Gizmos.color = Color.red;
|
||||
Gizmos.DrawCube(transform.position + Vector3.up * 128f, new Vector3(1f, 256f, 1f));
|
||||
}
|
||||
|
||||
protected override float OnRefreshLoadOrder(Vector3 from)
|
||||
{
|
||||
if (HasLoaded) return float.PositiveInfinity;
|
||||
var dist = Vector3.Distance(from, transform.position);
|
||||
if (dist > 100f) return float.PositiveInfinity;
|
||||
|
||||
var ray = new Ray(transform.position, Vector3.down);
|
||||
if (!Physics.Raycast(ray, 2f)) return float.PositiveInfinity;
|
||||
|
||||
return dist;
|
||||
}
|
||||
|
||||
protected override void OnLoad()
|
||||
{
|
||||
//Debug.Log("-222");
|
||||
Vehicle.Create(this);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -111,7 +111,7 @@ namespace SanAndreasUnity.Behaviours.Vehicles
|
|||
|
||||
}
|
||||
|
||||
public static Vehicle Create(VehicleSpawner spawner)
|
||||
public static Vehicle Create(VehicleSpawnMapObject spawner)
|
||||
{
|
||||
return Create(spawner.Info.CarId, spawner.Info.Colors, spawner.transform.position,
|
||||
spawner.transform.rotation);
|
||||
|
@ -179,10 +179,6 @@ namespace SanAndreasUnity.Behaviours.Vehicles
|
|||
inst.transform.position = position - Vector3.up * inst.AverageWheelHeight;
|
||||
inst.transform.localRotation = rotation;
|
||||
|
||||
OutOfRangeDestroyer destroyer = Utilities.F.GetOrAddComponent<OutOfRangeDestroyer>(inst.gameObject);
|
||||
destroyer.timeUntilDestroyed = 5;
|
||||
destroyer.range = 300;
|
||||
|
||||
return inst;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
using UnityEngine;
|
||||
|
||||
namespace SanAndreasUnity.Behaviours.World
|
||||
{
|
||||
public class AddFocusPointWhenLoaderFinishes : MonoBehaviour
|
||||
{
|
||||
public FocusPoint.Parameters parameters = FocusPoint.Parameters.Default;
|
||||
|
||||
|
||||
private void OnLoaderFinished()
|
||||
{
|
||||
FocusPoint.Create(this.gameObject, this.parameters);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,10 +1,10 @@
|
|||
fileFormatVersion: 2
|
||||
guid: b41c44cfa34ff5d4595e41acf7913a84
|
||||
guid: f261590297a9b11459cc24b5b949c608
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 15200
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
|
@ -1,60 +1,116 @@
|
|||
using SanAndreasUnity.Behaviours.Vehicles;
|
||||
using System;
|
||||
using SanAndreasUnity.Behaviours.Vehicles;
|
||||
using SanAndreasUnity.Importing.Items;
|
||||
using SanAndreasUnity.Importing.Items.Placements;
|
||||
using SanAndreasUnity.Utilities;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
//using Facepunch.Networking;
|
||||
using Profiler = UnityEngine.Profiling.Profiler;
|
||||
|
||||
namespace SanAndreasUnity.Behaviours.World
|
||||
{
|
||||
public class Cell : MonoBehaviour
|
||||
{
|
||||
private Stopwatch _timer;
|
||||
private List<Division> _leaves;
|
||||
|
||||
private Dictionary<Instance, StaticGeometry> m_insts;
|
||||
private Dictionary<Instance, StaticGeometry> m_insts;
|
||||
public int NumStaticGeometries => m_insts.Count;
|
||||
private MapObject[] m_cars;
|
||||
private List<EntranceExitMapObject> m_enexes;
|
||||
|
||||
public Division RootDivision { get; private set; }
|
||||
|
||||
private List<int> CellIds = Enumerable.Range(0, 19).ToList();
|
||||
|
||||
public bool HasExterior => this.CellIds.Contains(0);
|
||||
public bool HasMainExterior => this.CellIds.Contains(0);
|
||||
|
||||
public Camera PreviewCamera;
|
||||
|
||||
public List<Transform> focusPoints = new List<Transform> ();
|
||||
public struct FocusPointInfo
|
||||
{
|
||||
public long id;
|
||||
public Transform transform;
|
||||
public float timeToKeepRevealingAfterRemoved;
|
||||
public float timeWhenRemoved;
|
||||
public bool hasRevealRadius;
|
||||
}
|
||||
|
||||
private List<FocusPointInfo> _focusPoints = new List<FocusPointInfo>();
|
||||
public IReadOnlyList<FocusPointInfo> FocusPoints => _focusPoints;
|
||||
|
||||
private List<FocusPointInfo> _focusPointsToRemoveAfterTimeout = new List<FocusPointInfo>();
|
||||
|
||||
private struct AreaWithDistance
|
||||
{
|
||||
public WorldSystem<MapObject>.Area area;
|
||||
public float distance;
|
||||
}
|
||||
|
||||
private class AreaWithDistanceComparer : IComparer<AreaWithDistance>
|
||||
{
|
||||
public int Compare(AreaWithDistance a, AreaWithDistance b)
|
||||
{
|
||||
if (a.distance < b.distance)
|
||||
return -1;
|
||||
|
||||
if (a.distance > b.distance)
|
||||
return 1;
|
||||
|
||||
// solve potential problem that 2 areas have same distance
|
||||
// - these areas may not be equal, they may belong to different world systems and have same index,
|
||||
// therefore their distances will be equal
|
||||
// - by comparing their ids, we are sure that comparer is always deterministic
|
||||
return a.area.Id.CompareTo(b.area.Id);
|
||||
}
|
||||
}
|
||||
|
||||
private readonly SortedSet<AreaWithDistance> _areasToUpdate = new SortedSet<AreaWithDistance>(new AreaWithDistanceComparer());
|
||||
private readonly AreaWithDistance[] _bufferOfAreasToUpdate = new AreaWithDistance[64];
|
||||
private int _indexOfBufferOfAreasToUpdate = 0;
|
||||
private int _numElementsInBufferOfAreasToUpdate = 0;
|
||||
|
||||
public ushort maxTimeToUpdatePerFrameMs = 10;
|
||||
|
||||
private System.Diagnostics.Stopwatch _updateTimeLimitStopwatch = new System.Diagnostics.Stopwatch();
|
||||
|
||||
public Water Water;
|
||||
|
||||
public static Cell Instance { get ; private set; }
|
||||
|
||||
// Statistics
|
||||
public float divisionRefreshDistanceDelta = 20;
|
||||
|
||||
private int totalNumObjects = 0;
|
||||
private int numLeavesLoadedThisFrame = 0;
|
||||
private int numObjectsLoadedThisFrame = 0;
|
||||
private float[] measuredTimes = new float[3];
|
||||
private int numDivisionsUpdatedLoadOrder = 0;
|
||||
private int numMapObjectsUpdatedLoadOrder = 0;
|
||||
private Division containingDivision = null;
|
||||
private float _maxDrawDistance = 0;
|
||||
public float MaxDrawDistance
|
||||
{
|
||||
get => _maxDrawDistance;
|
||||
set
|
||||
{
|
||||
if (_maxDrawDistance == value)
|
||||
return;
|
||||
|
||||
public float divisionLoadOrderDistanceFactor = 16;
|
||||
_maxDrawDistance = value;
|
||||
|
||||
public float divisionRefreshDistanceDelta = 20;
|
||||
this.OnMaxDrawDistanceChanged();
|
||||
}
|
||||
}
|
||||
|
||||
[Range(0.1f, 3f)]
|
||||
public float divisionsUpdateInterval = 0.3f;
|
||||
public float[] drawDistancesPerLayers = new float[] { 301, 801, 1501 };
|
||||
|
||||
public float maxDrawDistance = 500;
|
||||
private WorldSystemWithDistanceLevels<MapObject> _worldSystem;
|
||||
public WorldSystemWithDistanceLevels<MapObject> WorldSystem => _worldSystem;
|
||||
|
||||
public uint yWorldSystemSize = 25000;
|
||||
public ushort yWorldSystemNumAreas = 3;
|
||||
|
||||
public ushort[] xzWorldSystemNumAreasPerDrawDistanceLevel = { 100, 100, 100 };
|
||||
|
||||
public float interiorHeightOffset = 5000f;
|
||||
|
||||
public float fadeRate = 2f;
|
||||
|
||||
public bool loadParkedVehicles = true;
|
||||
|
||||
|
||||
public GameObject mapObjectActivatorPrefab;
|
||||
|
||||
public GameObject staticGeometryPrefab;
|
||||
|
||||
public GameObject enexPrefab;
|
||||
|
||||
public GameObject lightSourcePrefab;
|
||||
|
@ -74,23 +130,9 @@ namespace SanAndreasUnity.Behaviours.World
|
|||
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
//InvokeRepeating("UpdateDivisions", 0f, 0.1f);
|
||||
StartCoroutine( this.UpdateDivisionsCoroutine () );
|
||||
}
|
||||
|
||||
|
||||
internal void CreateStaticGeometry ()
|
||||
internal void CreateStaticGeometry ()
|
||||
{
|
||||
if (RootDivision == null)
|
||||
{
|
||||
RootDivision = Division.Create(transform);
|
||||
RootDivision.SetBounds(
|
||||
new Vector2(-3000f, -3000f),
|
||||
new Vector2(+3000f, +3000f));
|
||||
}
|
||||
|
||||
var placements = Item.GetPlacements<Instance>(CellIds.ToArray());
|
||||
|
||||
m_insts = new Dictionary<Instance,StaticGeometry> (48 * 1024);
|
||||
|
@ -101,14 +143,25 @@ namespace SanAndreasUnity.Behaviours.World
|
|||
|
||||
UnityEngine.Debug.Log("Num static geometries " + m_insts.Count);
|
||||
|
||||
totalNumObjects = m_insts.Count;
|
||||
uint worldSize = 6000;
|
||||
|
||||
_worldSystem = new WorldSystemWithDistanceLevels<MapObject>(
|
||||
this.drawDistancesPerLayers,
|
||||
this.xzWorldSystemNumAreasPerDrawDistanceLevel.Select(_ => new WorldSystemParams { worldSize = worldSize, numAreasPerAxis = _ }).ToArray(),
|
||||
Enumerable.Range(0, this.drawDistancesPerLayers.Length).Select(_ => new WorldSystemParams { worldSize = this.yWorldSystemSize, numAreasPerAxis = this.yWorldSystemNumAreas }).ToArray(),
|
||||
this.OnAreaChangedVisibility);
|
||||
}
|
||||
|
||||
internal void InitStaticGeometry ()
|
||||
{
|
||||
foreach (var inst in m_insts)
|
||||
{
|
||||
inst.Value.Initialize(inst.Key, m_insts);
|
||||
var staticGeometry = inst.Value;
|
||||
staticGeometry.Initialize(inst.Key, m_insts);
|
||||
_worldSystem.AddObjectToArea(
|
||||
staticGeometry.transform.position,
|
||||
staticGeometry.ObjectDefinition?.DrawDist ?? 0,
|
||||
staticGeometry);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -117,7 +170,7 @@ namespace SanAndreasUnity.Behaviours.World
|
|||
if (loadParkedVehicles)
|
||||
{
|
||||
var parkedVehicles = Item.GetPlacements<ParkedVehicle> (CellIds.ToArray ());
|
||||
m_cars = parkedVehicles.Select (x => VehicleSpawner.Create (x))
|
||||
m_cars = parkedVehicles.Select (x => VehicleSpawnMapObject.Create (x))
|
||||
.Cast<MapObject> ()
|
||||
.ToArray ();
|
||||
|
||||
|
@ -130,25 +183,13 @@ namespace SanAndreasUnity.Behaviours.World
|
|||
m_enexes = new List<EntranceExitMapObject>(256);
|
||||
foreach(var enex in Item.Enexes.Where(enex => this.CellIds.Contains(enex.TargetInterior)))
|
||||
{
|
||||
m_enexes.Add(EntranceExitMapObject.Create(enex));
|
||||
var enexComponent = EntranceExitMapObject.Create(enex);
|
||||
m_enexes.Add(enexComponent);
|
||||
_worldSystem.AddObjectToArea(enexComponent.transform.position, 100f, enexComponent);
|
||||
}
|
||||
}
|
||||
|
||||
internal void AddMapObjectsToDivisions ()
|
||||
{
|
||||
var enumerable = m_insts.Values.Cast<MapObject> ();
|
||||
|
||||
if (m_cars != null)
|
||||
enumerable = enumerable.Concat (m_cars);
|
||||
|
||||
if (m_enexes != null)
|
||||
enumerable = enumerable.Concat(m_enexes);
|
||||
|
||||
RootDivision.AddRange (enumerable);
|
||||
|
||||
}
|
||||
|
||||
internal void LoadWater ()
|
||||
internal void LoadWater ()
|
||||
{
|
||||
if (Water != null)
|
||||
{
|
||||
|
@ -161,29 +202,116 @@ namespace SanAndreasUnity.Behaviours.World
|
|||
// set layer recursively for all game objects
|
||||
// this.gameObject.SetLayerRecursive( this.gameObject.layer );
|
||||
|
||||
_timer = new Stopwatch();
|
||||
_leaves = RootDivision.ToList();
|
||||
}
|
||||
|
||||
|
||||
private void OnAreaChangedVisibility(WorldSystem<MapObject>.Area area, bool visible)
|
||||
{
|
||||
if (null == area.ObjectsInside || area.ObjectsInside.Count == 0)
|
||||
return;
|
||||
|
||||
Profiler.BeginSample("OnAreaChangedVisibility");
|
||||
|
||||
Vector3 areaCenter = area.WorldSystem.GetAreaCenter(area);
|
||||
|
||||
float minSqrDistance = area.FocusPointsThatSeeMe?.Min(f => (areaCenter - f.Position).sqrMagnitude) ?? float.PositiveInfinity;
|
||||
|
||||
_areasToUpdate.Add(new AreaWithDistance
|
||||
{
|
||||
area = area,
|
||||
distance = minSqrDistance,
|
||||
});
|
||||
|
||||
Profiler.EndSample();
|
||||
}
|
||||
|
||||
public void RegisterFocusPoint(Transform tr, FocusPoint.Parameters parameters)
|
||||
{
|
||||
if (!_focusPoints.Exists(f => f.transform == tr))
|
||||
{
|
||||
float revealRadius = parameters.hasRevealRadius ? parameters.revealRadius : this.MaxDrawDistance;
|
||||
long registeredFocusPointId = _worldSystem.RegisterFocusPoint(revealRadius, tr.position);
|
||||
_focusPoints.Add(new FocusPointInfo
|
||||
{
|
||||
id = registeredFocusPointId,
|
||||
transform = tr,
|
||||
timeToKeepRevealingAfterRemoved = parameters.timeToKeepRevealingAfterRemoved,
|
||||
hasRevealRadius = parameters.hasRevealRadius,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void UnRegisterFocusPoint(Transform tr)
|
||||
{
|
||||
int index = _focusPoints.FindIndex(f => f.transform == tr);
|
||||
if (index < 0)
|
||||
return;
|
||||
|
||||
// maybe we could just set transform to null, so it gets removed during next update ?
|
||||
|
||||
var focusPoint = _focusPoints[index];
|
||||
|
||||
if (focusPoint.timeToKeepRevealingAfterRemoved > 0)
|
||||
{
|
||||
focusPoint.timeWhenRemoved = Time.time;
|
||||
_focusPointsToRemoveAfterTimeout.Add(focusPoint);
|
||||
_focusPoints.RemoveAt(index);
|
||||
return;
|
||||
}
|
||||
|
||||
_worldSystem.UnRegisterFocusPoint(focusPoint.id);
|
||||
_focusPoints.RemoveAt(index);
|
||||
}
|
||||
|
||||
private void OnMaxDrawDistanceChanged()
|
||||
{
|
||||
if (null == _worldSystem)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < _focusPoints.Count; i++)
|
||||
{
|
||||
var focusPoint = _focusPoints[i];
|
||||
if (!focusPoint.hasRevealRadius)
|
||||
{
|
||||
_worldSystem.FocusPointChangedRadius(focusPoint.id, this.MaxDrawDistance);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public IEnumerable<EntranceExit> GetEnexesFromLoadedInteriors()
|
||||
{
|
||||
int[] loadedInteriors = this.CellIds.Where(id => id != 0 && id != 13).ToArray();
|
||||
int[] loadedInteriors = this.CellIds.Where(id => !IsExteriorLevel(id)).ToArray();
|
||||
foreach(var enex in Importing.Items.Item.Enexes.Where(enex => loadedInteriors.Contains(enex.TargetInterior)))
|
||||
{
|
||||
yield return enex;
|
||||
}
|
||||
}
|
||||
|
||||
public static TransformDataStruct GetEnexExitTransform(EntranceExit enex)
|
||||
public TransformDataStruct GetEnexExitTransform(EntranceExit enex)
|
||||
{
|
||||
return new TransformDataStruct(enex.ExitPos + Vector3.up * 0.2f, Quaternion.Euler(0f, enex.ExitAngle, 0f));
|
||||
return new TransformDataStruct(
|
||||
this.GetPositionBasedOnInteriorLevel(enex.ExitPos + Vector3.up * 0.2f, enex.TargetInterior),
|
||||
Quaternion.Euler(0f, enex.ExitAngle, 0f));
|
||||
}
|
||||
|
||||
public static TransformDataStruct GetEnexEntranceTransform(EntranceExit enex)
|
||||
public TransformDataStruct GetEnexEntranceTransform(EntranceExit enex)
|
||||
{
|
||||
return new TransformDataStruct(enex.EntrancePos + Vector3.up * 0.2f, Quaternion.Euler(0f, enex.EntranceAngle, 0f));
|
||||
return new TransformDataStruct(
|
||||
this.GetPositionBasedOnInteriorLevel(enex.EntrancePos + Vector3.up * 0.2f, enex.TargetInterior),
|
||||
Quaternion.Euler(0f, enex.EntranceAngle, 0f));
|
||||
}
|
||||
|
||||
public static bool IsExteriorLevel(int interiorLevel)
|
||||
{
|
||||
return interiorLevel == 0 || interiorLevel == 13;
|
||||
}
|
||||
|
||||
public Vector3 GetPositionBasedOnInteriorLevel(Vector3 originalPos, int interiorLevel)
|
||||
{
|
||||
if (!IsExteriorLevel(interiorLevel))
|
||||
originalPos.y += this.interiorHeightOffset;
|
||||
return originalPos;
|
||||
}
|
||||
|
||||
|
||||
|
@ -193,166 +321,135 @@ namespace SanAndreasUnity.Behaviours.World
|
|||
if (!Loader.HasLoaded)
|
||||
return;
|
||||
|
||||
//this.Setup ();
|
||||
_updateTimeLimitStopwatch.Restart();
|
||||
|
||||
if (null == _leaves)
|
||||
return;
|
||||
float timeNow = Time.time;
|
||||
|
||||
_timer.Reset();
|
||||
_timer.Start();
|
||||
numLeavesLoadedThisFrame = 0;
|
||||
numObjectsLoadedThisFrame = 0;
|
||||
UnityEngine.Profiling.Profiler.BeginSample("Update focus points");
|
||||
this._focusPoints.RemoveAll(f =>
|
||||
{
|
||||
if (null == f.transform)
|
||||
{
|
||||
if (f.timeToKeepRevealingAfterRemoved > 0f)
|
||||
{
|
||||
f.timeWhenRemoved = timeNow;
|
||||
_focusPointsToRemoveAfterTimeout.Add(f);
|
||||
return true;
|
||||
}
|
||||
|
||||
this.focusPoints.RemoveDeadObjects();
|
||||
if (this.focusPoints.Count > 0)
|
||||
UnityEngine.Profiling.Profiler.BeginSample("WorldSystem.UnRegisterFocusPoint()");
|
||||
_worldSystem.UnRegisterFocusPoint(f.id);
|
||||
UnityEngine.Profiling.Profiler.EndSample();
|
||||
return true;
|
||||
}
|
||||
|
||||
_worldSystem.FocusPointChangedPosition(f.id, f.transform.position);
|
||||
|
||||
return false;
|
||||
});
|
||||
UnityEngine.Profiling.Profiler.EndSample();
|
||||
|
||||
bool hasElementToRemove = false;
|
||||
_focusPointsToRemoveAfterTimeout.ForEach(_ =>
|
||||
{
|
||||
if (timeNow - _.timeWhenRemoved > _.timeToKeepRevealingAfterRemoved)
|
||||
{
|
||||
hasElementToRemove = true;
|
||||
UnityEngine.Profiling.Profiler.BeginSample("WorldSystem.UnRegisterFocusPoint()");
|
||||
_worldSystem.UnRegisterFocusPoint(_.id);
|
||||
UnityEngine.Profiling.Profiler.EndSample();
|
||||
}
|
||||
});
|
||||
|
||||
if (hasElementToRemove)
|
||||
_focusPointsToRemoveAfterTimeout.RemoveAll(_ => timeNow - _.timeWhenRemoved > _.timeToKeepRevealingAfterRemoved);
|
||||
|
||||
if (this._focusPoints.Count > 0)
|
||||
{
|
||||
// only update divisions loading if there are focus points - because otherwise,
|
||||
// load order of divisions is not updated
|
||||
this.UpdateDivisionsLoading();
|
||||
|
||||
}
|
||||
|
||||
measuredTimes[2] = (float)_timer.Elapsed.TotalMilliseconds;
|
||||
UnityEngine.Profiling.Profiler.BeginSample("WorldSystem.Update()");
|
||||
_worldSystem.Update();
|
||||
UnityEngine.Profiling.Profiler.EndSample();
|
||||
|
||||
}
|
||||
Profiler.BeginSample("UpdateAreasLoop");
|
||||
|
||||
void UpdateDivisionsLoading()
|
||||
{
|
||||
foreach (var div in _leaves)
|
||||
while (true)
|
||||
{
|
||||
if (float.IsPositiveInfinity(div.LoadOrder))
|
||||
break;
|
||||
if (_areasToUpdate.Count == 0 && _numElementsInBufferOfAreasToUpdate == 0)
|
||||
break;
|
||||
|
||||
numObjectsLoadedThisFrame += div.LoadWhile(() => _timer.Elapsed.TotalSeconds < 1d / 60d);
|
||||
if (_updateTimeLimitStopwatch.ElapsedMilliseconds >= this.maxTimeToUpdatePerFrameMs)
|
||||
break;
|
||||
|
||||
if (_timer.Elapsed.TotalSeconds >= 1d / 60d)
|
||||
{
|
||||
// break;
|
||||
}
|
||||
else
|
||||
{
|
||||
numLeavesLoadedThisFrame++;
|
||||
}
|
||||
}
|
||||
}
|
||||
/*_areasToUpdate.CopyTo();
|
||||
_areasToUpdate.Clear(); // very good
|
||||
_areasToUpdate.ExceptWith(); // seems good
|
||||
_areasToUpdate.UnionWith(); // catastrophic*/
|
||||
|
||||
System.Collections.IEnumerator UpdateDivisionsCoroutine ()
|
||||
{
|
||||
if (_numElementsInBufferOfAreasToUpdate == 0)
|
||||
{
|
||||
Profiler.BeginSample("TakeFromSortedSet");
|
||||
|
||||
while (true)
|
||||
{
|
||||
// wait 100 ms
|
||||
float timePassed = 0;
|
||||
while (timePassed < this.divisionsUpdateInterval)
|
||||
{
|
||||
yield return null;
|
||||
timePassed += Time.unscaledDeltaTime;
|
||||
}
|
||||
// we processed all areas from buffer
|
||||
// take some more from SortedSet
|
||||
int numToCopy = Mathf.Min(_bufferOfAreasToUpdate.Length, _areasToUpdate.Count);
|
||||
_areasToUpdate.CopyTo(_bufferOfAreasToUpdate, 0, numToCopy);
|
||||
|
||||
F.RunExceptionSafe (() => this.UpdateDivisions ());
|
||||
for (int i = 0; i < numToCopy; i++)
|
||||
{
|
||||
if (!_areasToUpdate.Remove(_bufferOfAreasToUpdate[i]))
|
||||
throw new Exception($"Failed to remove area {_bufferOfAreasToUpdate[i].area.Id} from SortedSet");
|
||||
}
|
||||
|
||||
}
|
||||
//_areasToUpdate.ExceptWith(new System.ArraySegment<AreaWithDistance>(_bufferOfAreasToUpdate, 0, numToCopy));
|
||||
|
||||
}
|
||||
_indexOfBufferOfAreasToUpdate = 0;
|
||||
_numElementsInBufferOfAreasToUpdate = numToCopy;
|
||||
|
||||
private void UpdateDivisions()
|
||||
{
|
||||
if (!Loader.HasLoaded)
|
||||
return;
|
||||
|
||||
if (_leaves == null) return;
|
||||
Profiler.EndSample();
|
||||
}
|
||||
|
||||
this.focusPoints.RemoveAll (t => null == t);
|
||||
// process 1 area from buffer
|
||||
|
||||
if (this.focusPoints.Count < 1)
|
||||
return;
|
||||
var areaWithDistance = _bufferOfAreasToUpdate[_indexOfBufferOfAreasToUpdate];
|
||||
_indexOfBufferOfAreasToUpdate++;
|
||||
_numElementsInBufferOfAreasToUpdate--;
|
||||
|
||||
numDivisionsUpdatedLoadOrder = 0;
|
||||
numMapObjectsUpdatedLoadOrder = 0;
|
||||
containingDivision = null;
|
||||
this.UpdateArea(areaWithDistance);
|
||||
|
||||
_timer.Reset();
|
||||
_timer.Start();
|
||||
|
||||
List<Vector3> positions = this.focusPoints.Select (f => f.position).ToList ();
|
||||
|
||||
bool toLoad = false; // _leaves.Aggregate(false, (current, leaf) => current | leaf.RefreshLoadOrder(pos));
|
||||
|
||||
UnityEngine.Profiling.Profiler.BeginSample ("Update divisions", this);
|
||||
|
||||
foreach (Division leaf in _leaves)
|
||||
{
|
||||
Vector3 pos = leaf.GetClosestPosition (positions);
|
||||
|
||||
int count = 0;
|
||||
toLoad |= leaf.RefreshLoadOrder(pos, out count);
|
||||
if (count > 0)
|
||||
{
|
||||
numDivisionsUpdatedLoadOrder++;
|
||||
numMapObjectsUpdatedLoadOrder += count;
|
||||
}
|
||||
|
||||
if (null == containingDivision && leaf.Contains(pos))
|
||||
{
|
||||
containingDivision = leaf;
|
||||
}
|
||||
}
|
||||
|
||||
UnityEngine.Profiling.Profiler.EndSample ();
|
||||
Profiler.EndSample();
|
||||
|
||||
measuredTimes[0] = (float)_timer.Elapsed.TotalMilliseconds;
|
||||
|
||||
if (!toLoad) return;
|
||||
|
||||
_timer.Reset();
|
||||
_timer.Start();
|
||||
UnityEngine.Profiling.Profiler.BeginSample ("Sort leaves", this);
|
||||
_leaves.Sort();
|
||||
UnityEngine.Profiling.Profiler.EndSample ();
|
||||
measuredTimes[1] = (float)_timer.Elapsed.TotalMilliseconds;
|
||||
}
|
||||
|
||||
/*
|
||||
private static Rect windowRect = new Rect(10, 10, 250, 330);
|
||||
private const int windowID = 0;
|
||||
|
||||
private void OnGUI()
|
||||
void UpdateArea(AreaWithDistance areaWithDistance)
|
||||
{
|
||||
if (!Loader.HasLoaded)
|
||||
return;
|
||||
var area = areaWithDistance.area;
|
||||
bool visible = area.WasVisibleInLastUpdate;
|
||||
|
||||
if (!PlayerController._showMenu)
|
||||
return;
|
||||
for (int i = 0; i < area.ObjectsInside.Count; i++)
|
||||
{
|
||||
var obj = area.ObjectsInside[i];
|
||||
|
||||
windowRect = GUILayout.Window(windowID, windowRect, showWindow, "World statistics");
|
||||
}
|
||||
*/
|
||||
if (visible == obj.IsVisibleInMapSystem)
|
||||
continue;
|
||||
|
||||
public void showWindow(int windowID)
|
||||
{
|
||||
GUILayout.Label("draw distance " + this.maxDrawDistance);
|
||||
GUILayout.Label("num focus points " + this.focusPoints.Count);
|
||||
GUILayout.Label("total num divisions " + (null == _leaves ? 0 : _leaves.Count));
|
||||
GUILayout.Label("total num objects " + totalNumObjects);
|
||||
GUILayout.Label("geometry parts loaded " + SanAndreasUnity.Importing.Conversion.Geometry.NumGeometryPartsLoaded);
|
||||
GUILayout.Label("num TOBJ objects " + StaticGeometry.TimedObjects.Count);
|
||||
GUILayout.Label("num active objects with lights " + StaticGeometry.ActiveObjectsWithLights.Count);
|
||||
GUILayout.Label("num objects in current division " + (containingDivision != null ? containingDivision.NumObjects : 0));
|
||||
GUILayout.Label("num divisions updated " + numDivisionsUpdatedLoadOrder);
|
||||
GUILayout.Label("num objects updated " + numMapObjectsUpdatedLoadOrder);
|
||||
GUILayout.Label("num divisions loading this frame " + numLeavesLoadedThisFrame);
|
||||
GUILayout.Label("num objects loading this frame " + numObjectsLoadedThisFrame);
|
||||
F.RunExceptionSafe(() =>
|
||||
{
|
||||
if (visible)
|
||||
{
|
||||
obj.Show(areaWithDistance.distance);
|
||||
}
|
||||
else
|
||||
obj.UnShow();
|
||||
});
|
||||
}
|
||||
|
||||
GUILayout.Space(10);
|
||||
|
||||
string[] timeNames = new string[] { "refresh load order ", "sort ", "load / update display " };
|
||||
int i = 0;
|
||||
foreach (float time in measuredTimes)
|
||||
{
|
||||
GUILayout.Label(timeNames[i] + Mathf.RoundToInt(time));
|
||||
i++;
|
||||
}
|
||||
|
||||
GUI.DragWindow();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,372 +0,0 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using SanAndreasUnity.Utilities;
|
||||
|
||||
namespace SanAndreasUnity.Behaviours.World
|
||||
{
|
||||
public class Division : MonoBehaviour, IEnumerable<Division>, IComparable<Division>
|
||||
{
|
||||
private static readonly Comparison<MapObject> _sHorzSort =
|
||||
(a, b) => Math.Sign(a.CellPos.x - b.CellPos.x);
|
||||
|
||||
private static readonly Comparison<MapObject> _sVertSort =
|
||||
(a, b) => Math.Sign(a.CellPos.y - b.CellPos.y);
|
||||
|
||||
public static Division Create(Transform parent)
|
||||
{
|
||||
var obj = new GameObject();
|
||||
var split = obj.AddComponent<Division>();
|
||||
|
||||
obj.transform.SetParent(parent);
|
||||
|
||||
return split;
|
||||
}
|
||||
|
||||
private const int LeafObjectLimit = 127;
|
||||
|
||||
private Division _childA;
|
||||
private Division _childB;
|
||||
|
||||
private List<MapObject> _objects;
|
||||
public int NumObjects { get { return _objects != null ? _objects.Count : 0; } }
|
||||
|
||||
public int NumObjectsIncludingChildren
|
||||
{
|
||||
get
|
||||
{
|
||||
int count = this.NumObjects;
|
||||
if (_childA != null)
|
||||
count += _childA.NumObjectsIncludingChildren;
|
||||
if (_childB != null)
|
||||
count += _childB.NumObjectsIncludingChildren;
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
||||
private bool _isVertSplit;
|
||||
private float _splitVal;
|
||||
|
||||
private Vector3 _lastRefreshPos;
|
||||
|
||||
public Vector2 Min { get; private set; }
|
||||
public Vector2 Max { get; private set; }
|
||||
private Bounds _bounds;
|
||||
|
||||
public bool IsSubdivided { get { return _objects == null; } }
|
||||
|
||||
internal float LoadOrder { get; private set; }
|
||||
|
||||
public void SetBounds(Vector2 min, Vector2 max)
|
||||
{
|
||||
Min = min;
|
||||
Max = max;
|
||||
_bounds = new Bounds(((this.Min + this.Max) * 0.5f).ToVector3XZ(), (this.Max - this.Min).ToVector3XZ().WithY(10000f));
|
||||
|
||||
var mid = (Max + Min) * .5f;
|
||||
|
||||
if (float.IsNaN(mid.x) || float.IsInfinity(mid.x))
|
||||
{
|
||||
mid.x = 0f;
|
||||
}
|
||||
|
||||
if (float.IsNaN(mid.y) || float.IsInfinity(mid.y))
|
||||
{
|
||||
mid.y = 0f;
|
||||
}
|
||||
|
||||
transform.position = new Vector3(mid.x, 0f, mid.y);
|
||||
|
||||
name = String.Format("Split {0}, {1}", min, max);
|
||||
|
||||
_objects = _objects ?? new List<MapObject>();
|
||||
}
|
||||
|
||||
private void Subdivide()
|
||||
{
|
||||
if (IsSubdivided)
|
||||
{
|
||||
throw new InvalidOperationException("Already subdivided");
|
||||
}
|
||||
|
||||
if (_objects.Count == 0)
|
||||
{
|
||||
throw new InvalidOperationException("Cannot subdivide an empty leaf");
|
||||
}
|
||||
|
||||
var min = Max;
|
||||
var max = Min;
|
||||
|
||||
foreach (var obj in _objects)
|
||||
{
|
||||
var pos = obj.CellPos;
|
||||
min.x = Mathf.Min(pos.x, min.x);
|
||||
min.y = Mathf.Min(pos.y, min.y);
|
||||
max.x = Mathf.Max(pos.x, max.x);
|
||||
max.y = Mathf.Max(pos.y, max.y);
|
||||
}
|
||||
|
||||
_isVertSplit = max.x - min.x >= max.y - min.y;
|
||||
|
||||
_objects.Sort(_isVertSplit ? _sHorzSort : _sVertSort);
|
||||
|
||||
_childA = Create(transform);
|
||||
_childB = Create(transform);
|
||||
|
||||
var mid = _objects.Count / 2;
|
||||
var median = (_objects[mid - 1].CellPos + _objects[mid].CellPos) * .5f;
|
||||
|
||||
if (_isVertSplit)
|
||||
{
|
||||
_splitVal = median.x;
|
||||
_childA.SetBounds(Min, new Vector2(_splitVal, Max.y));
|
||||
_childB.SetBounds(new Vector2(_splitVal, Min.y), Max);
|
||||
}
|
||||
else
|
||||
{
|
||||
_splitVal = median.y;
|
||||
_childA.SetBounds(Min, new Vector2(Max.x, _splitVal));
|
||||
_childB.SetBounds(new Vector2(Min.x, _splitVal), Max);
|
||||
}
|
||||
|
||||
_childA._objects = _objects;
|
||||
_childB._objects = new List<MapObject>();
|
||||
|
||||
_childB._objects.AddRange(_childA._objects.Skip(mid));
|
||||
_childA._objects.RemoveRange(mid, _objects.Count - mid);
|
||||
|
||||
_objects = null;
|
||||
}
|
||||
|
||||
private void AddInternal(MapObject obj)
|
||||
{
|
||||
if (IsSubdivided)
|
||||
{
|
||||
var comp = _isVertSplit ? obj.CellPos.x : obj.CellPos.y;
|
||||
(comp < _splitVal ? _childA : _childB).AddInternal(obj);
|
||||
return;
|
||||
}
|
||||
|
||||
_objects.Add(obj);
|
||||
|
||||
if (_objects.Count > LeafObjectLimit) Subdivide();
|
||||
}
|
||||
|
||||
public void Add(MapObject obj)
|
||||
{
|
||||
AddInternal(obj);
|
||||
UpdateParents();
|
||||
}
|
||||
|
||||
public void AddRange(IEnumerable<MapObject> objs)
|
||||
{
|
||||
foreach (var obj in objs.OrderBy(x => x.RandomInt))
|
||||
{
|
||||
AddInternal(obj);
|
||||
}
|
||||
|
||||
UpdateParents();
|
||||
}
|
||||
|
||||
private void UpdateParents()
|
||||
{
|
||||
if (IsSubdivided)
|
||||
{
|
||||
_childA.UpdateParents();
|
||||
_childB.UpdateParents();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_objects.Count == 0) return;
|
||||
|
||||
var sum = _objects.Aggregate(new Vector2(), (s, x) => s + x.CellPos);
|
||||
transform.position = new Vector3(sum.x / _objects.Count, 0f, sum.y / _objects.Count);
|
||||
|
||||
foreach (var obj in _objects)
|
||||
{
|
||||
obj.transform.SetParent(transform, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool Contains(MapObject obj)
|
||||
{
|
||||
return Contains(obj.CellPos);
|
||||
}
|
||||
|
||||
public bool Contains(Vector3 pos)
|
||||
{
|
||||
return pos.x >= Min.x && pos.z >= Min.y && pos.x < Max.x && pos.z < Max.y;
|
||||
}
|
||||
|
||||
public bool Contains(Vector2 pos)
|
||||
{
|
||||
return pos.x >= Min.x && pos.y >= Min.y && pos.x < Max.x && pos.y < Max.y;
|
||||
}
|
||||
|
||||
private void OnDrawGizmosSelected()
|
||||
{
|
||||
|
||||
Gizmos.color = Color.green;
|
||||
Gizmos.DrawWireCube(_bounds.center, _bounds.size.WithY(100f));
|
||||
|
||||
F.HandlesDrawText(_bounds.center,
|
||||
string.Format("total objects {0}, my objects {1}, load order {2}", this.NumObjectsIncludingChildren, this.NumObjects, this.LoadOrder),
|
||||
Color.green);
|
||||
|
||||
/*
|
||||
if (!IsSubdivided) return;
|
||||
|
||||
Gizmos.color = Color.green;
|
||||
|
||||
var min = new Vector2(Math.Max(Min.x, -8192f), Math.Max(Min.y, -8192f));
|
||||
var max = new Vector2(Math.Min(Max.x, +8192f), Math.Min(Max.y, +8192f));
|
||||
|
||||
if (_isVertSplit)
|
||||
{
|
||||
Gizmos.DrawLine(new Vector3(_splitVal, 0f, min.y), new Vector3(_splitVal, 0f, max.y));
|
||||
}
|
||||
else
|
||||
{
|
||||
Gizmos.DrawLine(new Vector3(min.x, 0f, _splitVal), new Vector3(max.x, 0f, _splitVal));
|
||||
}
|
||||
*/
|
||||
|
||||
}
|
||||
|
||||
public float GetDistance(Vector3 pos)
|
||||
{
|
||||
return Mathf.Sqrt(GetDistanceSquared(pos));
|
||||
}
|
||||
|
||||
public float GetDistanceSquared(Vector3 pos)
|
||||
{
|
||||
pos.y = 0f; // only count X and Z axis
|
||||
// get the closest point on bounds
|
||||
// if position is inside bounds, the resulting distance will be 0
|
||||
Vector3 closestPos = _bounds.ClosestPoint(pos);
|
||||
return Vector3.SqrMagnitude(pos - closestPos);
|
||||
}
|
||||
|
||||
public Vector3 GetClosestPosition (List<Vector3> positions)
|
||||
{
|
||||
Vector3 closestPos = positions [0];
|
||||
float smallestDist2 = float.MaxValue;
|
||||
Vector2 center = (this.Min + this.Max) * 0.5f;
|
||||
|
||||
for (int i = 0; i < positions.Count; i++)
|
||||
{
|
||||
float dist2 = Vector2.SqrMagnitude (positions [i].ToVec2WithXAndZ() - center);
|
||||
if (dist2 <= smallestDist2)
|
||||
{
|
||||
smallestDist2 = dist2;
|
||||
closestPos = positions [i];
|
||||
}
|
||||
}
|
||||
|
||||
return closestPos;
|
||||
}
|
||||
|
||||
public bool RefreshLoadOrder(Vector3 from, out int numMapObjectsUpdatedLoadOrder)
|
||||
{
|
||||
UnityEngine.Profiling.Profiler.BeginSample ("Division.RefreshLoadOrder", this);
|
||||
|
||||
var toLoad = false;
|
||||
numMapObjectsUpdatedLoadOrder = 0;
|
||||
|
||||
if (GetDistanceSquared(from) <= Cell.Instance.maxDrawDistance * Cell.Instance.maxDrawDistance)
|
||||
{
|
||||
float divisionRefreshDistanceDeltaSquared = Cell.Instance.divisionRefreshDistanceDelta * Cell.Instance.divisionRefreshDistanceDelta;
|
||||
// float factor = Cell.Instance.divisionLoadOrderDistanceFactor; //16;
|
||||
// if (Vector3.SqrMagnitude(from - _lastRefreshPos) > GetDistanceSquared(from) / (factor*factor)) {
|
||||
if (Vector3.SqrMagnitude(from - _lastRefreshPos) > divisionRefreshDistanceDeltaSquared)
|
||||
{
|
||||
_lastRefreshPos = from;
|
||||
foreach (var obj in _objects)
|
||||
{
|
||||
bool b = obj.RefreshLoadOrder(from);
|
||||
if (b)
|
||||
{
|
||||
toLoad = true;
|
||||
numMapObjectsUpdatedLoadOrder++;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
toLoad = _objects.Any(x => !float.IsPositiveInfinity(x.LoadOrder));
|
||||
}
|
||||
}
|
||||
|
||||
if (toLoad)
|
||||
{
|
||||
_objects.Sort(); // THIS MAY BE PERFORMANCE DROP
|
||||
LoadOrder = _objects[0].LoadOrder;
|
||||
}
|
||||
else
|
||||
{
|
||||
LoadOrder = float.PositiveInfinity;
|
||||
}
|
||||
|
||||
UnityEngine.Profiling.Profiler.EndSample ();
|
||||
|
||||
return toLoad;
|
||||
}
|
||||
|
||||
public int LoadWhile(Func<bool> predicate)
|
||||
{
|
||||
UnityEngine.Profiling.Profiler.BeginSample ("LoadWhile", this);
|
||||
|
||||
int numLoaded = 0;
|
||||
foreach (var toLoad in _objects)
|
||||
{
|
||||
if (float.IsPositiveInfinity(toLoad.LoadOrder))
|
||||
break;
|
||||
|
||||
if (toLoad.HasLoaded)
|
||||
{
|
||||
// this object is loaded, just show it
|
||||
toLoad.Show();
|
||||
}
|
||||
else
|
||||
{
|
||||
// this object is still not loaded from disk
|
||||
// check if we should load it
|
||||
if (predicate())
|
||||
{
|
||||
toLoad.Show();
|
||||
numLoaded++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UnityEngine.Profiling.Profiler.EndSample ();
|
||||
|
||||
// return predicate();
|
||||
// return false ;
|
||||
return numLoaded;
|
||||
}
|
||||
|
||||
public IEnumerator<Division> GetEnumerator()
|
||||
{
|
||||
if (IsSubdivided)
|
||||
{
|
||||
return _childA.Concat(_childB).GetEnumerator();
|
||||
}
|
||||
|
||||
return new[] { this }.AsEnumerable().GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
public int CompareTo(Division other)
|
||||
{
|
||||
return LoadOrder > other.LoadOrder ? 1 : LoadOrder == other.LoadOrder ? 0 : -1;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,14 +2,15 @@
|
|||
using SanAndreasUnity.Importing.Items.Placements;
|
||||
using SanAndreasUnity.Utilities;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace SanAndreasUnity.Behaviours.World
|
||||
{
|
||||
|
||||
public class EntranceExitMapObject : MapObject
|
||||
{
|
||||
static List<EntranceExitMapObject> s_allObjects = new List<EntranceExitMapObject>();
|
||||
public static IEnumerable<EntranceExitMapObject> AllObjects => s_allObjects;
|
||||
static List<EntranceExitMapObject> _sAllActiveObjects = new List<EntranceExitMapObject>();
|
||||
public static IReadOnlyList<EntranceExitMapObject> AllActiveObjects => _sAllActiveObjects;
|
||||
|
||||
Coroutine m_animateArrowCoroutine;
|
||||
|
||||
|
@ -22,7 +23,7 @@ namespace SanAndreasUnity.Behaviours.World
|
|||
|
||||
public static EntranceExitMapObject Create(EntranceExit info)
|
||||
{
|
||||
var obj = Object.Instantiate(Cell.Instance.enexPrefab).GetComponent<EntranceExitMapObject>();
|
||||
var obj = Create<EntranceExitMapObject>(Cell.Instance.enexPrefab);
|
||||
obj.Initialize(info);
|
||||
return obj;
|
||||
}
|
||||
|
@ -37,7 +38,11 @@ namespace SanAndreasUnity.Behaviours.World
|
|||
|
||||
float height = 2f;
|
||||
|
||||
Initialize(info.EntrancePos + Vector3.up * height * 0.5f, Quaternion.identity);
|
||||
this.Initialize(
|
||||
Cell.Instance.GetPositionBasedOnInteriorLevel(
|
||||
info.EntrancePos + Vector3.up * height * 0.5f,
|
||||
info.TargetInterior),
|
||||
Quaternion.identity);
|
||||
|
||||
gameObject.SetActive(false);
|
||||
gameObject.isStatic = true;
|
||||
|
@ -52,17 +57,19 @@ namespace SanAndreasUnity.Behaviours.World
|
|||
rb.mass = 0f;
|
||||
rb.isKinematic = true;
|
||||
|
||||
this.SetDrawDistance(100f);
|
||||
|
||||
}
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
s_allObjects.Add(this);
|
||||
_sAllActiveObjects.Add(this);
|
||||
m_animateArrowCoroutine = this.StartCoroutine(this.AnimateArrow());
|
||||
}
|
||||
|
||||
void OnDisable()
|
||||
{
|
||||
s_allObjects.Remove(this);
|
||||
_sAllActiveObjects.Remove(this);
|
||||
|
||||
if (m_animateArrowCoroutine != null)
|
||||
this.StopCoroutine(m_animateArrowCoroutine);
|
||||
|
@ -75,19 +82,6 @@ namespace SanAndreasUnity.Behaviours.World
|
|||
Utilities.F.HandlesDrawText(this.transform.position, this.name, Color.yellow);
|
||||
}
|
||||
|
||||
protected override float OnRefreshLoadOrder(Vector3 from)
|
||||
{
|
||||
|
||||
float dist = Vector3.Distance(from, this.transform.position);
|
||||
if (dist > 100f)
|
||||
{
|
||||
this.gameObject.SetActive(false);
|
||||
return float.PositiveInfinity;
|
||||
}
|
||||
|
||||
return dist * dist;
|
||||
}
|
||||
|
||||
protected override void OnLoad()
|
||||
{
|
||||
//Debug.LogFormat("OnLoad() - {0}", this.gameObject.name);
|
||||
|
@ -150,6 +144,32 @@ namespace SanAndreasUnity.Behaviours.World
|
|||
|
||||
}
|
||||
|
||||
public EntranceExit FindMatchingEnex()
|
||||
{
|
||||
EntranceExit firstMatchingWithDifferentInterior = null;
|
||||
EntranceExit firstMatchingWithSameInterior = null;
|
||||
|
||||
foreach (var enex in Importing.Items.Item.Enexes)
|
||||
{
|
||||
if (firstMatchingWithDifferentInterior != null && firstMatchingWithSameInterior != null) // both are found
|
||||
continue;
|
||||
|
||||
if (enex.Name != this.Info.Name)
|
||||
continue;
|
||||
|
||||
if (enex == this.Info)
|
||||
continue;
|
||||
|
||||
if (null == firstMatchingWithDifferentInterior && enex.TargetInterior != this.Info.TargetInterior)
|
||||
firstMatchingWithDifferentInterior = enex;
|
||||
|
||||
if (null == firstMatchingWithSameInterior && enex.TargetInterior == this.Info.TargetInterior)
|
||||
firstMatchingWithSameInterior = enex;
|
||||
}
|
||||
|
||||
return firstMatchingWithDifferentInterior ?? firstMatchingWithSameInterior;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
48
Assets/Scripts/Behaviours/World/FocusPoint.cs
Normal file
48
Assets/Scripts/Behaviours/World/FocusPoint.cs
Normal file
|
@ -0,0 +1,48 @@
|
|||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace SanAndreasUnity.Behaviours.World
|
||||
{
|
||||
[DisallowMultipleComponent]
|
||||
public class FocusPoint : MonoBehaviour
|
||||
{
|
||||
[Serializable]
|
||||
public struct Parameters
|
||||
{
|
||||
public bool hasRevealRadius;
|
||||
public float revealRadius;
|
||||
public float timeToKeepRevealingAfterRemoved;
|
||||
|
||||
public Parameters(bool hasRevealRadius, float revealRadius, float timeToKeepRevealingAfterRemoved)
|
||||
{
|
||||
this.hasRevealRadius = hasRevealRadius;
|
||||
this.revealRadius = revealRadius;
|
||||
this.timeToKeepRevealingAfterRemoved = timeToKeepRevealingAfterRemoved;
|
||||
}
|
||||
|
||||
public static Parameters Default => new Parameters(true, 150f, 3f);
|
||||
}
|
||||
|
||||
public Parameters parameters = Parameters.Default;
|
||||
|
||||
|
||||
public static FocusPoint Create(GameObject targetGameObject, Parameters parameters)
|
||||
{
|
||||
var focusPoint = targetGameObject.AddComponent<FocusPoint>();
|
||||
focusPoint.parameters = parameters;
|
||||
return focusPoint;
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
if (Cell.Instance != null)
|
||||
Cell.Instance.RegisterFocusPoint(this.transform, this.parameters);
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
if (Cell.Instance != null)
|
||||
Cell.Instance.UnRegisterFocusPoint(this.transform);
|
||||
}
|
||||
}
|
||||
}
|
11
Assets/Scripts/Behaviours/World/FocusPoint.cs.meta
Normal file
11
Assets/Scripts/Behaviours/World/FocusPoint.cs.meta
Normal file
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 8643b1a14caafa547beb3c2e2780085f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -8,6 +8,7 @@ using System.Linq;
|
|||
using SanAndreasUnity.Importing.RenderWareStream;
|
||||
using SanAndreasUnity.Utilities;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
using Geometry = SanAndreasUnity.Importing.Conversion.Geometry;
|
||||
using Profiler = UnityEngine.Profiling.Profiler;
|
||||
|
||||
|
@ -23,39 +24,25 @@ namespace SanAndreasUnity.Behaviours.World
|
|||
DayTimeManager.Singleton.onHourChanged += OnHourChanged;
|
||||
}
|
||||
|
||||
return new GameObject().AddComponent<StaticGeometry>();
|
||||
return Create<StaticGeometry>(Cell.Instance.staticGeometryPrefab);
|
||||
}
|
||||
|
||||
private static List<StaticGeometry> s_timedObjects = new List<StaticGeometry>();
|
||||
public static IReadOnlyList<StaticGeometry> TimedObjects => s_timedObjects;
|
||||
|
||||
protected Instance Instance { get; private set; }
|
||||
public Instance Instance { get; private set; }
|
||||
|
||||
public ISimpleObjectDefinition ObjectDefinition { get; private set; }
|
||||
|
||||
private bool _canLoad;
|
||||
private bool _isGeometryLoaded = false;
|
||||
private bool _isVisible;
|
||||
private bool _isFading;
|
||||
private bool _isFading;
|
||||
|
||||
public bool IsVisible
|
||||
{
|
||||
get { return _isVisible; }
|
||||
private set
|
||||
{
|
||||
if (_isVisible == value) return;
|
||||
|
||||
_isVisible = value;
|
||||
|
||||
gameObject.SetActive(true);
|
||||
StartCoroutine(Fade());
|
||||
|
||||
if (value && LodChild != null)
|
||||
{
|
||||
LodChild.Hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
public bool ShouldBeVisibleNow =>
|
||||
this.IsVisibleInMapSystem
|
||||
&& this.IsVisibleBasedOnCurrentDayTime
|
||||
&& _isGeometryLoaded
|
||||
&& (LodParent == null || !LodParent.ShouldBeVisibleNow);
|
||||
|
||||
public StaticGeometry LodParent { get; private set; }
|
||||
public StaticGeometry LodChild { get; private set; }
|
||||
|
@ -77,6 +64,8 @@ namespace SanAndreasUnity.Behaviours.World
|
|||
private bool m_hasTrafficLights = false;
|
||||
private int m_activeTrafficLightIndex = -1;
|
||||
|
||||
public int NumLightSources => m_lightSources?.Length ?? 0;
|
||||
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
|
@ -107,7 +96,9 @@ namespace SanAndreasUnity.Behaviours.World
|
|||
s_timedObjects.Add(this);
|
||||
}
|
||||
|
||||
Initialize(inst.Position, inst.Rotation);
|
||||
this.Initialize(
|
||||
Cell.Instance.GetPositionBasedOnInteriorLevel(inst.Position, inst.InteriorLevel),
|
||||
inst.Rotation);
|
||||
|
||||
_canLoad = ObjectDefinition != null;
|
||||
|
||||
|
@ -122,55 +113,12 @@ namespace SanAndreasUnity.Behaviours.World
|
|||
}
|
||||
}
|
||||
|
||||
_isVisible = false;
|
||||
this.SetDrawDistance(ObjectDefinition?.DrawDist ?? 0);
|
||||
|
||||
gameObject.SetActive(false);
|
||||
gameObject.isStatic = true;
|
||||
}
|
||||
|
||||
public bool ShouldBeVisible(Vector3 from)
|
||||
{
|
||||
if (!_canLoad) return false;
|
||||
|
||||
var obj = ObjectDefinition;
|
||||
|
||||
// if (obj.HasFlag (ObjectFlag.DisableDrawDist))
|
||||
// return true;
|
||||
|
||||
// var dist = Vector3.Distance(from, transform.position);
|
||||
var distSquared = Vector3.SqrMagnitude(from - transform.position);
|
||||
|
||||
if (distSquared > Cell.Instance.maxDrawDistance * Cell.Instance.maxDrawDistance)
|
||||
return false;
|
||||
|
||||
if (distSquared > obj.DrawDist * obj.DrawDist)
|
||||
return false;
|
||||
|
||||
if (!HasLoaded || LodParent == null || !LodParent.IsVisible)
|
||||
return true;
|
||||
|
||||
if (!LodParent.ShouldBeVisible(from))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
|
||||
// return (distSquared <= obj.DrawDist * obj.DrawDist || (obj.DrawDist >= 300 && distSquared < 2560*2560))
|
||||
// && (!HasLoaded || LodParent == null || !LodParent.IsVisible || !LodParent.ShouldBeVisible(from));
|
||||
}
|
||||
|
||||
protected override float OnRefreshLoadOrder(Vector3 from)
|
||||
{
|
||||
var visible = ShouldBeVisible(from);
|
||||
|
||||
if (!IsVisible)
|
||||
{
|
||||
return visible ? Vector3.SqrMagnitude(from - transform.position) : float.PositiveInfinity;
|
||||
}
|
||||
|
||||
if (!visible) Hide();
|
||||
|
||||
return float.PositiveInfinity;
|
||||
}
|
||||
|
||||
protected override void OnLoad()
|
||||
{
|
||||
|
||||
|
@ -179,25 +127,18 @@ namespace SanAndreasUnity.Behaviours.World
|
|||
Profiler.BeginSample ("StaticGeometry.OnLoad", this);
|
||||
|
||||
|
||||
// this was previously placed after loading geometry
|
||||
Flags = Enum.GetValues(typeof(ObjectFlag))
|
||||
.Cast<ObjectFlag>()
|
||||
.Where(x => (ObjectDefinition.Flags & x) == x)
|
||||
.Select(x => x.ToString())
|
||||
.ToList();
|
||||
|
||||
//var geoms = Geometry.Load(Instance.Object.ModelName, Instance.Object.TextureDictionaryName);
|
||||
//OnGeometryLoaded (geoms);
|
||||
|
||||
// we could start loading collision model here
|
||||
// - we can't, because we don't know the name of collision file until clump is loaded
|
||||
|
||||
Geometry.LoadAsync( ObjectDefinition.ModelName, new string[] {ObjectDefinition.TextureDictionaryName}, (geoms) => {
|
||||
Geometry.LoadAsync( ObjectDefinition.ModelName, new string[] {ObjectDefinition.TextureDictionaryName}, this.LoadPriority, (geoms) => {
|
||||
if(geoms != null)
|
||||
{
|
||||
// we can't load collision model asyncly, because it requires a transform to attach to
|
||||
// but, we can load collision file asyncly
|
||||
Importing.Collision.CollisionFile.FromNameAsync( geoms.Collisions != null ? geoms.Collisions.Name : geoms.Name, (cf) => {
|
||||
Importing.Collision.CollisionFile.FromNameAsync( geoms.Collisions != null ? geoms.Collisions.Name : geoms.Name, this.LoadPriority, (cf) => {
|
||||
OnGeometryLoaded (geoms);
|
||||
});
|
||||
}
|
||||
|
@ -216,13 +157,18 @@ namespace SanAndreasUnity.Behaviours.World
|
|||
var mf = gameObject.AddComponent<MeshFilter>();
|
||||
var mr = gameObject.AddComponent<MeshRenderer>();
|
||||
|
||||
mr.receiveShadows = this.ShouldReceiveShadows();
|
||||
mr.shadowCastingMode = this.ShouldCastShadows() ? ShadowCastingMode.On : ShadowCastingMode.Off;
|
||||
|
||||
mf.sharedMesh = geoms.Geometry[0].Mesh;
|
||||
mr.sharedMaterials = geoms.Geometry[0].GetMaterials(ObjectDefinition.Flags, mat => mat.SetTexture(NoiseTexId, NoiseTex));
|
||||
mr.sharedMaterials = geoms.Geometry[0].GetMaterials(ObjectDefinition.Flags, mat => mat.SetTexture(NoiseTexPropertyId, NoiseTex));
|
||||
|
||||
Profiler.EndSample ();
|
||||
|
||||
Profiler.BeginSample("CreateLights()", this);
|
||||
if (!F.IsInHeadlessMode)
|
||||
this.CreateLights(geoms);
|
||||
Profiler.EndSample();
|
||||
|
||||
geoms.AttachCollisionModel(transform);
|
||||
|
||||
|
@ -237,6 +183,7 @@ namespace SanAndreasUnity.Behaviours.World
|
|||
|
||||
_isGeometryLoaded = true;
|
||||
|
||||
this.UpdateVisibility();
|
||||
}
|
||||
|
||||
private void OnCollisionModelAttached ()
|
||||
|
@ -248,11 +195,18 @@ namespace SanAndreasUnity.Behaviours.World
|
|||
protected override void OnShow()
|
||||
{
|
||||
Profiler.BeginSample ("StaticGeometry.OnShow");
|
||||
IsVisible = LodParent == null || !LodParent.IsVisible;
|
||||
|
||||
this.UpdateVisibility();
|
||||
|
||||
Profiler.EndSample ();
|
||||
}
|
||||
|
||||
private IEnumerator Fade()
|
||||
protected override void OnUnShow()
|
||||
{
|
||||
this.UpdateVisibility();
|
||||
}
|
||||
|
||||
private IEnumerator FadeCoroutine()
|
||||
{
|
||||
if (_isFading) yield break;
|
||||
|
||||
|
@ -269,47 +223,110 @@ namespace SanAndreasUnity.Behaviours.World
|
|||
yield break;
|
||||
}
|
||||
|
||||
const float fadeRate = 2f;
|
||||
float fadeRate = Cell.Instance.fadeRate;
|
||||
|
||||
var pb = new MaterialPropertyBlock();
|
||||
|
||||
// continuously change transparency until object becomes fully opaque or fully transparent
|
||||
|
||||
var val = IsVisible ? 0f : -1f;
|
||||
float val = this.ShouldBeVisibleNow ? 0f : -1f;
|
||||
|
||||
for (; ; )
|
||||
{
|
||||
var dest = IsVisible ? 1f : 0f;
|
||||
float dest = this.ShouldBeVisibleNow ? 1f : 0f;
|
||||
var sign = Math.Sign(dest - val);
|
||||
val += sign * fadeRate * Time.deltaTime;
|
||||
|
||||
if (sign == 0 || sign == 1 && val >= dest || sign == -1 && val <= dest) break;
|
||||
if (sign == 0 || sign == 1 && val >= dest || sign == -1 && val <= dest)
|
||||
break;
|
||||
|
||||
pb.SetFloat(FadeId, (float)val);
|
||||
pb.SetFloat(FadePropertyId, val);
|
||||
mr.SetPropertyBlock(pb);
|
||||
yield return new WaitForEndOfFrame();
|
||||
}
|
||||
|
||||
mr.SetPropertyBlock(null);
|
||||
|
||||
if (!IsVisible || !IsVisibleBasedOnCurrentDayTime)
|
||||
{
|
||||
gameObject.SetActive(false);
|
||||
yield return null;
|
||||
}
|
||||
|
||||
_isFading = false;
|
||||
|
||||
mr.SetPropertyBlock(null);
|
||||
|
||||
this.gameObject.SetActive(this.ShouldBeVisibleNow);
|
||||
}
|
||||
|
||||
public void Hide()
|
||||
private void UpdateVisibility()
|
||||
{
|
||||
IsVisible = false;
|
||||
bool needsFading = this.NeedsFading();
|
||||
|
||||
this.gameObject.SetActive(needsFading || this.ShouldBeVisibleNow);
|
||||
|
||||
if (needsFading)
|
||||
{
|
||||
_isFading = false;
|
||||
this.StopCoroutine(nameof(FadeCoroutine));
|
||||
this.StartCoroutine(nameof(FadeCoroutine));
|
||||
}
|
||||
|
||||
if (LodChild != null)
|
||||
LodChild.UpdateVisibility();
|
||||
}
|
||||
|
||||
private bool NeedsFading()
|
||||
{
|
||||
if (F.IsInHeadlessMode)
|
||||
return false;
|
||||
|
||||
// always fade, except when parent should be visible, but he is still loading
|
||||
|
||||
if (LodParent == null)
|
||||
return true;
|
||||
|
||||
if (LodParent.IsVisibleInMapSystem && !LodParent._isGeometryLoaded)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool ShouldReceiveShadows()
|
||||
{
|
||||
if (null == this.ObjectDefinition)
|
||||
return false;
|
||||
|
||||
var flags = ObjectFlag.Additive // transparent
|
||||
| ObjectFlag.NoZBufferWrite // transparent
|
||||
| ObjectFlag.DontReceiveShadows;
|
||||
|
||||
if ((this.ObjectDefinition.Flags & flags) != 0)
|
||||
return false;
|
||||
|
||||
if (LodParent != null) // LOD models should not receive shadows
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool ShouldCastShadows()
|
||||
{
|
||||
if (null == this.ObjectDefinition)
|
||||
return false;
|
||||
|
||||
var flags = ObjectFlag.Additive // transparent
|
||||
| ObjectFlag.NoZBufferWrite; // transparent
|
||||
|
||||
if ((this.ObjectDefinition.Flags & flags) != 0)
|
||||
return false;
|
||||
|
||||
// if object is LOD, only cast shadows if it has large draw distance
|
||||
// - that's because his shadow may be visible from long distance
|
||||
if (LodParent != null && this.ObjectDefinition.DrawDist < 1000f)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void OnHourChanged()
|
||||
{
|
||||
foreach (var timedObject in s_timedObjects)
|
||||
{
|
||||
if (timedObject.IsVisible)
|
||||
if (timedObject.IsVisibleInMapSystem)
|
||||
{
|
||||
timedObject.gameObject.SetActive(timedObject.IsVisibleBasedOnCurrentDayTime);
|
||||
}
|
||||
|
@ -461,7 +478,7 @@ namespace SanAndreasUnity.Behaviours.World
|
|||
if (null == m_lightSources)
|
||||
return;
|
||||
|
||||
bool isDay = DayTimeManager.Singleton.CurrentTimeHours >= 6 && DayTimeManager.Singleton.CurrentTimeHours <= 20;
|
||||
bool isDay = DayTimeManager.Singleton.CurrentTimeHours > 6 && DayTimeManager.Singleton.CurrentTimeHours < 18;
|
||||
var flag = isDay ? TwoDEffect.Light.Flags1.AT_DAY : TwoDEffect.Light.Flags1.AT_NIGHT;
|
||||
|
||||
for (int i = 0; i < m_lightSources.Length; i++)
|
||||
|
|
70
Assets/Scripts/Behaviours/World/WorldManager.cs
Normal file
70
Assets/Scripts/Behaviours/World/WorldManager.cs
Normal file
|
@ -0,0 +1,70 @@
|
|||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace SanAndreasUnity.Behaviours.World
|
||||
{
|
||||
public class WorldManager : MonoBehaviour
|
||||
{
|
||||
public static WorldManager Singleton { get; private set; }
|
||||
|
||||
public const float MinMaxDrawDistance = 250f;
|
||||
public const float MaxMaxDrawDistance = 1500f;
|
||||
|
||||
[SerializeField]
|
||||
[Range(MinMaxDrawDistance, MaxMaxDrawDistance)]
|
||||
private float _defaultMaxDrawDistance = 1500f;
|
||||
|
||||
[SerializeField]
|
||||
[Range(MinMaxDrawDistance, MaxMaxDrawDistance)]
|
||||
private float _defaultMaxDrawDistanceOnMobile = 1000f;
|
||||
|
||||
private float _maxDrawDistance = 0f;
|
||||
public float MaxDrawDistance
|
||||
{
|
||||
get => _maxDrawDistance;
|
||||
set
|
||||
{
|
||||
if (value == _maxDrawDistance)
|
||||
return;
|
||||
|
||||
_maxDrawDistance = value;
|
||||
|
||||
this.ApplyMaxDrawDistance(Cell.Instance);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
Singleton = this;
|
||||
|
||||
_maxDrawDistance = Application.isMobilePlatform ? _defaultMaxDrawDistanceOnMobile : _defaultMaxDrawDistance;
|
||||
|
||||
UnityEngine.SceneManagement.SceneManager.activeSceneChanged += (s1, s2) => OnActiveSceneChanged();
|
||||
}
|
||||
|
||||
void OnActiveSceneChanged()
|
||||
{
|
||||
// apply settings
|
||||
|
||||
// we need to find Cell with FindObjectOfType(), because it's Awake() method may have not been called yet
|
||||
Cell cell = Object.FindObjectOfType<Cell>();
|
||||
this.ApplyMaxDrawDistance(cell);
|
||||
|
||||
}
|
||||
|
||||
void ApplyMaxDrawDistance(Cell cell)
|
||||
{
|
||||
if (cell != null)
|
||||
cell.MaxDrawDistance = this.MaxDrawDistance;
|
||||
|
||||
var cam = Camera.main;
|
||||
if (cam != null)
|
||||
cam.farClipPlane = this.MaxDrawDistance;
|
||||
|
||||
cam = Camera.current;
|
||||
if (cam != null)
|
||||
cam.farClipPlane = this.MaxDrawDistance;
|
||||
}
|
||||
}
|
||||
}
|
11
Assets/Scripts/Behaviours/World/WorldManager.cs.meta
Normal file
11
Assets/Scripts/Behaviours/World/WorldManager.cs.meta
Normal file
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 9dd4217c14fd11f4e8b46fe4a95f7aa5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
895
Assets/Scripts/Behaviours/World/WorldSystem.cs
Normal file
895
Assets/Scripts/Behaviours/World/WorldSystem.cs
Normal file
|
@ -0,0 +1,895 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using SanAndreasUnity.Utilities;
|
||||
using UnityEngine;
|
||||
|
||||
namespace SanAndreasUnity.Behaviours.World
|
||||
{
|
||||
public struct WorldSystemParams
|
||||
{
|
||||
public uint worldSize;
|
||||
public ushort numAreasPerAxis;
|
||||
}
|
||||
|
||||
public class WorldSystemWithDistanceLevels<T>
|
||||
{
|
||||
private readonly WorldSystem<T>[] _worldSystems;
|
||||
public IReadOnlyList<WorldSystem<T>> WorldSystems => _worldSystems;
|
||||
private readonly float[] _distanceLevels;
|
||||
public IReadOnlyList<float> DistanceLevels => _distanceLevels;
|
||||
private readonly Dictionary<long, WorldSystem<T>.FocusPoint[]> _focusPointsPerLevel = new Dictionary<long, WorldSystem<T>.FocusPoint[]>();
|
||||
private long _lastFocusPointId = 1;
|
||||
|
||||
public WorldSystemWithDistanceLevels(
|
||||
float[] distanceLevels,
|
||||
WorldSystemParams[] worldSystemParamsXZ,
|
||||
WorldSystemParams[] worldSystemParamsY,
|
||||
System.Action<WorldSystem<T>.Area, bool> onAreaChangedVisibility)
|
||||
{
|
||||
int num = distanceLevels.Length;
|
||||
if (num != worldSystemParamsXZ.Length || num != worldSystemParamsY.Length)
|
||||
throw new ArgumentException("Input arrays must be of same size");
|
||||
|
||||
if (num == 0)
|
||||
throw new ArgumentException("You must specify distance levels");
|
||||
|
||||
if (!distanceLevels.OrderBy(l => l).SequenceEqual(distanceLevels))
|
||||
throw new ArgumentException("Input arrays must be sorted ascending by distance level");
|
||||
|
||||
if (distanceLevels.Distinct().Count() != distanceLevels.Length)
|
||||
throw new ArgumentException("Distance levels must be distinct");
|
||||
|
||||
_distanceLevels = distanceLevels.ToArray();
|
||||
|
||||
_worldSystems = new WorldSystem<T>[num];
|
||||
for (int i = 0; i < num; i++)
|
||||
{
|
||||
_worldSystems[i] = new WorldSystem<T>(worldSystemParamsXZ[i], worldSystemParamsY[i], onAreaChangedVisibility);
|
||||
}
|
||||
}
|
||||
|
||||
public int GetLevelIndexFromDrawDistance(float drawDistance)
|
||||
{
|
||||
if (drawDistance <= 0)
|
||||
return 0;
|
||||
|
||||
int index = System.Array.FindIndex(_distanceLevels, f => f > drawDistance);
|
||||
if (index >= 0)
|
||||
return index;
|
||||
|
||||
// draw distance is higher than all levels
|
||||
// these object should go into last level
|
||||
return _distanceLevels.Length - 1;
|
||||
}
|
||||
|
||||
public long RegisterFocusPoint(float radius, Vector3 pos)
|
||||
{
|
||||
var focusPoints = new WorldSystem<T>.FocusPoint[_worldSystems.Length];
|
||||
for (int i = 0; i < _worldSystems.Length; i++)
|
||||
focusPoints[i] = _worldSystems[i].RegisterFocusPoint(Mathf.Min(radius, _distanceLevels[i]), pos);
|
||||
|
||||
long id = _lastFocusPointId++;
|
||||
_focusPointsPerLevel[id] = focusPoints;
|
||||
return id;
|
||||
}
|
||||
|
||||
public void UnRegisterFocusPoint(long id)
|
||||
{
|
||||
if (!_focusPointsPerLevel.TryGetValue(id, out var focusPoints))
|
||||
return;
|
||||
|
||||
for (int i = 0; i < _worldSystems.Length; i++)
|
||||
_worldSystems[i].UnRegisterFocusPoint(focusPoints[i]);
|
||||
}
|
||||
|
||||
public void FocusPointChangedPosition(long id, Vector3 newPos)
|
||||
{
|
||||
if (!_focusPointsPerLevel.TryGetValue(id, out var focusPoints))
|
||||
return;
|
||||
|
||||
Vector3 diff = focusPoints[0].Position - newPos;
|
||||
if (diff.x == 0f && diff.y == 0f && diff.z == 0f) // faster than calling '==' operator
|
||||
return;
|
||||
|
||||
for (int i = 0; i < _worldSystems.Length; i++)
|
||||
_worldSystems[i].FocusPointChangedPosition(focusPoints[i], newPos);
|
||||
}
|
||||
|
||||
public void FocusPointChangedRadius(long id, float newRadius)
|
||||
{
|
||||
if (!_focusPointsPerLevel.TryGetValue(id, out var focusPoints))
|
||||
return;
|
||||
|
||||
for (int i = 0; i < _worldSystems.Length; i++)
|
||||
_worldSystems[i].FocusPointChangedRadius(focusPoints[i], Mathf.Min(newRadius, _distanceLevels[i]));
|
||||
}
|
||||
|
||||
public void AddObjectToArea(Vector3 pos, float drawDistance, T obj)
|
||||
{
|
||||
int levelIndex = this.GetLevelIndexFromDrawDistance(drawDistance);
|
||||
_worldSystems[levelIndex].AddObjectToArea(pos, obj);
|
||||
}
|
||||
|
||||
public void RemoveObjectFromArea(Vector3 pos, float drawDistance, T obj)
|
||||
{
|
||||
int levelIndex = this.GetLevelIndexFromDrawDistance(drawDistance);
|
||||
_worldSystems[levelIndex].RemoveObjectFromArea(pos, obj);
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
for (int i = 0; i < _worldSystems.Length; i++)
|
||||
_worldSystems[i].Update();
|
||||
}
|
||||
}
|
||||
|
||||
public class WorldSystem<T>
|
||||
{
|
||||
public struct AreaIndex
|
||||
{
|
||||
public short x, y, z;
|
||||
|
||||
public AreaIndex(short x, short y, short z)
|
||||
{
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
}
|
||||
|
||||
public bool IsEqualTo(AreaIndex other) => this.x == other.x && this.y == other.y && this.z == other.z;
|
||||
|
||||
public override string ToString() => $"({this.x}, {this.y}, {this.z})";
|
||||
}
|
||||
|
||||
public class Area
|
||||
{
|
||||
private static long s_lastId = 0;
|
||||
public long Id { get; } = ++s_lastId;
|
||||
|
||||
public AreaIndex AreaIndex { get; }
|
||||
|
||||
public WorldSystem<T> WorldSystem { get; }
|
||||
|
||||
internal List<T> objectsInside;
|
||||
public IReadOnlyList<T> ObjectsInside => this.objectsInside;
|
||||
|
||||
internal HashSet<FocusPoint> focusPointsThatSeeMe;
|
||||
public IReadOnlyCollection<FocusPoint> FocusPointsThatSeeMe => this.focusPointsThatSeeMe;
|
||||
|
||||
internal bool isMarkedForUpdate;
|
||||
|
||||
public bool WasVisibleInLastUpdate { get; internal set; } = false;
|
||||
|
||||
internal Area(WorldSystem<T> worldSystem, AreaIndex areaIndex)
|
||||
{
|
||||
this.WorldSystem = worldSystem;
|
||||
this.AreaIndex = areaIndex;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class FocusPoint
|
||||
{
|
||||
private static long _lastId = 1;
|
||||
|
||||
public long Id { get; } = _lastId++;
|
||||
public float Radius { get; internal set; }
|
||||
public Vector3 Position { get; internal set; }
|
||||
|
||||
internal FocusPoint()
|
||||
{
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return this.Id.GetHashCode();
|
||||
}
|
||||
}
|
||||
|
||||
private struct Range
|
||||
{
|
||||
public short lower, higher;
|
||||
public short Length => (short) (this.higher - this.lower);
|
||||
|
||||
public Range(short lower, short higher)
|
||||
{
|
||||
if (lower > higher)
|
||||
throw new ArgumentException($"lower {lower} is > than higher {higher}");
|
||||
this.lower = lower;
|
||||
this.higher = higher;
|
||||
}
|
||||
|
||||
public bool EqualsToOther(Range other) => this.lower == other.lower && this.higher == other.higher;
|
||||
|
||||
public bool Overlaps(Range other) => !(this.higher < other.lower || this.lower > other.higher);
|
||||
|
||||
public bool IsInsideOf(Range other) => (this.lower >= other.lower && this.higher < other.higher)
|
||||
|| (this.lower > other.lower && this.higher <= other.higher);
|
||||
|
||||
public bool IsEqualOrInsideOf(Range other) => this.EqualsToOther(other) || this.IsInsideOf(other);
|
||||
}
|
||||
|
||||
private struct AreaIndexes
|
||||
{
|
||||
public Range x, y, z;
|
||||
|
||||
public Range this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
switch (index)
|
||||
{
|
||||
case 0:
|
||||
return this.x;
|
||||
case 1:
|
||||
return this.y;
|
||||
case 2:
|
||||
return this.z;
|
||||
default:
|
||||
throw new System.IndexOutOfRangeException("Invalid index");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int Volume => (this.x.Length + 1) * (this.y.Length + 1) * (this.z.Length + 1);
|
||||
|
||||
public bool EqualsToOther(AreaIndexes other) => this.x.EqualsToOther(other.x) && this.y.EqualsToOther(other.y) && this.z.EqualsToOther(other.z);
|
||||
|
||||
public bool Overlaps(AreaIndexes other) => this.x.Overlaps(other.x) && this.y.Overlaps(other.y) && this.z.Overlaps(other.z);
|
||||
|
||||
public bool IsInsideOf(AreaIndexes other) => !this.EqualsToOther(other)
|
||||
&& (this.x.IsEqualOrInsideOf(other.x) && this.y.IsEqualOrInsideOf(other.y) && this.z.IsEqualOrInsideOf(other.z));
|
||||
}
|
||||
|
||||
// private struct NewAreasResult
|
||||
// {
|
||||
// public (AreaIndexes areaIndexes, bool hasResult) x, y, z;
|
||||
//
|
||||
// public static NewAreasResult WithOne(AreaIndexes areaIndexes) => new NewAreasResult { x = (areaIndexes, true) };
|
||||
// }
|
||||
|
||||
private struct AffectedRangesForAxis
|
||||
{
|
||||
// there can be max 3 ranges
|
||||
// each range can be intersection, or free
|
||||
// there can be max 2 free parts and max 1 intersection part
|
||||
public (Range range, bool isIntersectionPart, bool hasValues) range1, range2, range3;
|
||||
|
||||
// is there intersection on this axis ? if not, cubes do not intersect, and other results from this struct should be ignored
|
||||
public bool hasIntersectionOnAxis;
|
||||
|
||||
public IEnumerable<(Range range, bool isIntersectionPart, bool hasValues)> Ranges => new [] {range1, range2, range3};
|
||||
|
||||
public void ForEachWithValue(Action<(Range range, bool isIntersectionPart)> action)
|
||||
{
|
||||
if (range1.hasValues)
|
||||
action((range1.range, range1.isIntersectionPart));
|
||||
if (range2.hasValues)
|
||||
action((range2.range, range2.isIntersectionPart));
|
||||
if (range3.hasValues)
|
||||
action((range3.range, range3.isIntersectionPart));
|
||||
}
|
||||
|
||||
public void ForEachFree(Action<Range> action)
|
||||
{
|
||||
if (range1.hasValues && !range1.isIntersectionPart)
|
||||
action(range1.range);
|
||||
if (range2.hasValues && !range2.isIntersectionPart)
|
||||
action(range2.range);
|
||||
if (range3.hasValues && !range3.isIntersectionPart)
|
||||
action(range3.range);
|
||||
}
|
||||
}
|
||||
|
||||
private struct AxisInfo
|
||||
{
|
||||
public float worldMin;
|
||||
public float worldMax;
|
||||
public float worldHalfSize;
|
||||
public float areaSize;
|
||||
public ushort numAreasPerAxis;
|
||||
}
|
||||
|
||||
public class ConcurrentModificationException : System.Exception
|
||||
{
|
||||
public ConcurrentModificationException()
|
||||
: base("Can not perform the operation because it would result in concurrent modification of collections")
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private readonly Area[,,] _areas;
|
||||
public int GetNumAreas(int axisIndex) => _areas.GetLength(axisIndex);
|
||||
|
||||
private readonly HashSet<FocusPoint> _focusPoints = new HashSet<FocusPoint>();
|
||||
public IReadOnlyCollection<FocusPoint> FocusPoints => _focusPoints;
|
||||
|
||||
private readonly List<Area> _areasForUpdate = new List<Area>(128);
|
||||
|
||||
private readonly AxisInfo _xzAxisInfo;
|
||||
|
||||
private readonly AxisInfo _yAxisInfo;
|
||||
|
||||
private bool _isInUpdate = false;
|
||||
|
||||
// these buffers are reused every time to avoid memory allocations, but that makes this class non thread safe
|
||||
private AreaIndexes[] _bufferForGettingNewAreas = new AreaIndexes[27]; // 3^3
|
||||
private AreaIndexes[] _bufferForGettingOldAreas = new AreaIndexes[27]; // 3^3
|
||||
|
||||
private readonly System.Action<Area, bool> _onAreaChangedVisibility = null;
|
||||
|
||||
public WorldSystem(
|
||||
WorldSystemParams worldSystemParamsXZ,
|
||||
WorldSystemParams worldSystemParamsY,
|
||||
System.Action<Area, bool> onAreaChangedVisibility)
|
||||
{
|
||||
_xzAxisInfo = CalculateAxisInfo(worldSystemParamsXZ);
|
||||
_yAxisInfo = CalculateAxisInfo(worldSystemParamsY);
|
||||
|
||||
_areas = new Area[_xzAxisInfo.numAreasPerAxis, _yAxisInfo.numAreasPerAxis, _xzAxisInfo.numAreasPerAxis];
|
||||
|
||||
_onAreaChangedVisibility = onAreaChangedVisibility;
|
||||
}
|
||||
|
||||
private static AxisInfo CalculateAxisInfo(WorldSystemParams worldSystemParams)
|
||||
{
|
||||
if (worldSystemParams.worldSize <= 0)
|
||||
throw new ArgumentException("World size must be higher than 0");
|
||||
ushort maxNumAreasPerAxis = ushort.MaxValue - 10;
|
||||
if (worldSystemParams.numAreasPerAxis > maxNumAreasPerAxis)
|
||||
throw new ArgumentException($"Num areas per axis can not be higher than {maxNumAreasPerAxis}");
|
||||
|
||||
AxisInfo axisInfo = new AxisInfo();
|
||||
axisInfo.worldMin = - worldSystemParams.worldSize / 2f;
|
||||
axisInfo.worldMax = worldSystemParams.worldSize / 2f;
|
||||
axisInfo.worldHalfSize = worldSystemParams.worldSize / 2f;
|
||||
axisInfo.areaSize = worldSystemParams.worldSize / (float)worldSystemParams.numAreasPerAxis;
|
||||
axisInfo.numAreasPerAxis = (ushort) (worldSystemParams.numAreasPerAxis + 2); // additional 2 for positions out of bounds
|
||||
return axisInfo;
|
||||
}
|
||||
|
||||
public FocusPoint RegisterFocusPoint(float radius, Vector3 pos)
|
||||
{
|
||||
this.ThrowIfConcurrentModification();
|
||||
|
||||
if (radius < 0)
|
||||
throw new ArgumentException("Radius can not be < 0");
|
||||
|
||||
var focusPoint = new FocusPoint();
|
||||
focusPoint.Radius = radius;
|
||||
focusPoint.Position = pos;
|
||||
|
||||
if (!_focusPoints.Add(focusPoint))
|
||||
throw new Exception("Failed to add focus point to the collection");
|
||||
|
||||
this.ForEachAreaInRadius(pos, radius, true, area =>
|
||||
{
|
||||
AddToFocusPointsThatSeeMe(area, focusPoint);
|
||||
this.MarkAreaForUpdate(area);
|
||||
});
|
||||
|
||||
return focusPoint;
|
||||
}
|
||||
|
||||
public void UnRegisterFocusPoint(FocusPoint focusPoint)
|
||||
{
|
||||
this.ThrowIfConcurrentModification();
|
||||
|
||||
if (!_focusPoints.Remove(focusPoint))
|
||||
return;
|
||||
|
||||
this.ForEachAreaInRadius(focusPoint.Position, focusPoint.Radius, false, area =>
|
||||
{
|
||||
if (null == area)
|
||||
return;
|
||||
RemoveFromFocusPointsThatSeeMe(area, focusPoint);
|
||||
this.MarkAreaForUpdate(area);
|
||||
});
|
||||
}
|
||||
|
||||
public void FocusPointChangedPosition(FocusPoint focusPoint, Vector3 newPos)
|
||||
=> this.FocusPointChangedParameters(focusPoint, newPos, focusPoint.Radius);
|
||||
|
||||
private void FocusPointChangedParameters(FocusPoint focusPoint, Vector3 newPos, float newRadius)
|
||||
{
|
||||
this.ThrowIfConcurrentModification();
|
||||
|
||||
AreaIndexes oldIndexes = GetAreaIndexesInRadius(focusPoint.Position, focusPoint.Radius);
|
||||
AreaIndexes newIndexes = GetAreaIndexesInRadius(newPos, newRadius);
|
||||
|
||||
if (!oldIndexes.EqualsToOther(newIndexes))
|
||||
{
|
||||
// areas changed
|
||||
|
||||
byte numNewAreaIndexes = GetNewAreas(oldIndexes, newIndexes, _bufferForGettingNewAreas);
|
||||
byte numOldAreaIndexes = GetNewAreas(newIndexes, oldIndexes, _bufferForGettingOldAreas);
|
||||
|
||||
for (byte i = 0; i < numNewAreaIndexes; i++)
|
||||
{
|
||||
var areaIndexes = _bufferForGettingNewAreas[i];
|
||||
|
||||
this.ForEachArea(areaIndexes, true, area =>
|
||||
{
|
||||
// this can happen multiple times per single area, but since we use hashset it should be no problem
|
||||
// actually, it should not happen anymore with new implementation
|
||||
AddToFocusPointsThatSeeMe(area, focusPoint);
|
||||
this.MarkAreaForUpdate(area);
|
||||
});
|
||||
}
|
||||
|
||||
for (byte i = 0; i < numOldAreaIndexes; i++)
|
||||
{
|
||||
var areaIndexes = _bufferForGettingOldAreas[i];
|
||||
|
||||
this.ForEachArea(areaIndexes, false, area =>
|
||||
{
|
||||
if (null == area)
|
||||
return;
|
||||
RemoveFromFocusPointsThatSeeMe(area, focusPoint);
|
||||
this.MarkAreaForUpdate(area);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
focusPoint.Position = newPos;
|
||||
focusPoint.Radius = newRadius;
|
||||
}
|
||||
|
||||
public void FocusPointChangedRadius(FocusPoint focusPoint, float newRadius)
|
||||
=> this.FocusPointChangedParameters(focusPoint, focusPoint.Position, newRadius);
|
||||
|
||||
public void AddObjectToArea(Vector3 pos, T obj)
|
||||
{
|
||||
this.ThrowIfConcurrentModification();
|
||||
|
||||
var area = GetAreaAt(pos, true);
|
||||
|
||||
if (null == area.objectsInside)
|
||||
area.objectsInside = new List<T>();
|
||||
area.objectsInside.Add(obj);
|
||||
}
|
||||
|
||||
public void RemoveObjectFromArea(Vector3 pos, T obj)
|
||||
{
|
||||
this.ThrowIfConcurrentModification();
|
||||
|
||||
var area = GetAreaAt(pos, false);
|
||||
|
||||
if (area != null && area.objectsInside != null)
|
||||
{
|
||||
area.objectsInside.Remove(obj);
|
||||
}
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
this.ThrowIfConcurrentModification();
|
||||
|
||||
_isInUpdate = true;
|
||||
|
||||
try
|
||||
{
|
||||
this.UpdateInternal();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isInUpdate = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateInternal()
|
||||
{
|
||||
// check areas that are marked for update
|
||||
for (int i = 0; i < _areasForUpdate.Count; i++)
|
||||
{
|
||||
var area = _areasForUpdate[i];
|
||||
|
||||
if (!area.isMarkedForUpdate) // should not happen, but just in case
|
||||
continue;
|
||||
|
||||
bool isVisible = ShouldAreaBeVisible(area);
|
||||
|
||||
this.NotifyAreaChangedVisibility(area, isVisible);
|
||||
|
||||
area.WasVisibleInLastUpdate = isVisible;
|
||||
area.isMarkedForUpdate = false;
|
||||
}
|
||||
|
||||
_areasForUpdate.Clear();
|
||||
}
|
||||
|
||||
public void ForEachAreaInRadius(Vector3 pos, float radius, System.Action<Area> action)
|
||||
{
|
||||
this.ForEachAreaInRadius(pos, radius, false, action);
|
||||
}
|
||||
|
||||
private void ForEachAreaInRadius(Vector3 pos, float radius, bool createIfNotExists, System.Action<Area> action)
|
||||
{
|
||||
this.ForEachArea(GetAreaIndexesInRadius(pos, radius), createIfNotExists, action);
|
||||
}
|
||||
|
||||
public List<Area> GetAreasInRadius(Vector3 pos, float radius)
|
||||
{
|
||||
var areas = new List<Area>();
|
||||
this.ForEachAreaInRadius(pos, radius, false, a => areas.Add(a));
|
||||
return areas;
|
||||
}
|
||||
|
||||
private void ForEachArea(AreaIndexes areaIndexes, bool createIfNotExists, System.Action<Area> action)
|
||||
{
|
||||
for (short x = areaIndexes.x.lower; x <= areaIndexes.x.higher; x++)
|
||||
{
|
||||
for (short y = areaIndexes.y.lower; y <= areaIndexes.y.higher; y++)
|
||||
{
|
||||
for (short z = areaIndexes.z.lower; z <= areaIndexes.z.higher; z++)
|
||||
{
|
||||
if (!createIfNotExists)
|
||||
{
|
||||
action(_areas[x, y, z]);
|
||||
}
|
||||
else
|
||||
{
|
||||
var area = _areas[x, y, z];
|
||||
if (null == area)
|
||||
_areas[x, y, z] = area = new Area(this, new AreaIndex(x, y, z));
|
||||
action(area);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private AreaIndexes GetAreaIndexesInRadius(Vector3 pos, float radius)
|
||||
{
|
||||
if (radius < 0)
|
||||
throw new ArgumentException("Radius can not be < 0");
|
||||
|
||||
// Vector3 min = new Vector3(pos.x - radius, pos.y - radius, pos.z - radius);
|
||||
// Vector3 max = new Vector3(pos.x + radius, pos.y + radius, pos.z + radius);
|
||||
|
||||
return new AreaIndexes
|
||||
{
|
||||
x = new Range { lower = GetAreaIndex(pos.x - radius), higher = GetAreaIndex(pos.x + radius) },
|
||||
y = new Range { lower = GetAreaIndexForYAxis(pos.y - radius), higher = GetAreaIndexForYAxis(pos.y + radius) },
|
||||
z = new Range { lower = GetAreaIndex(pos.z - radius), higher = GetAreaIndex(pos.z + radius) },
|
||||
};
|
||||
}
|
||||
|
||||
private short GetAreaIndex(float pos)
|
||||
{
|
||||
if (pos < _xzAxisInfo.worldMin)
|
||||
return 0;
|
||||
if (pos > _xzAxisInfo.worldMax)
|
||||
return (short) (_xzAxisInfo.numAreasPerAxis - 1);
|
||||
|
||||
// skip 1st
|
||||
return (short) (1 + Mathf.FloorToInt((pos + _xzAxisInfo.worldHalfSize) / _xzAxisInfo.areaSize));
|
||||
}
|
||||
|
||||
private short GetAreaIndexForYAxis(float pos)
|
||||
{
|
||||
if (pos < _yAxisInfo.worldMin)
|
||||
return 0;
|
||||
if (pos > _yAxisInfo.worldMax)
|
||||
return (short) (_yAxisInfo.numAreasPerAxis - 1);
|
||||
|
||||
// skip 1st
|
||||
return (short) (1 + Mathf.FloorToInt((pos + _yAxisInfo.worldHalfSize) / _yAxisInfo.areaSize));
|
||||
}
|
||||
|
||||
private AreaIndex GetAreaIndex(Vector3 pos)
|
||||
{
|
||||
return new AreaIndex(GetAreaIndex(pos.x), GetAreaIndexForYAxis(pos.y), GetAreaIndex(pos.z));
|
||||
}
|
||||
|
||||
public Vector3 GetAreaCenter(Area area)
|
||||
{
|
||||
Vector3 center = new Vector3(
|
||||
GetAreaCenterForXZAxis(area.AreaIndex.x),
|
||||
GetAreaCenterForYAxis(area.AreaIndex.y),
|
||||
GetAreaCenterForXZAxis(area.AreaIndex.z));
|
||||
|
||||
AreaIndex indexOfCenter = GetAreaIndex(center);
|
||||
if (!indexOfCenter.IsEqualTo(area.AreaIndex)) // just to be sure
|
||||
throw new Exception($"Index of area center {indexOfCenter} does not match original area index {area.AreaIndex}");
|
||||
|
||||
return center;
|
||||
}
|
||||
|
||||
private float GetAreaCenterForXZAxis(short indexForAxis)
|
||||
{
|
||||
if (indexForAxis <= 0) // left infinity
|
||||
return float.NegativeInfinity;
|
||||
|
||||
if (indexForAxis >= _xzAxisInfo.numAreasPerAxis - 1) // right infinity
|
||||
return float.PositiveInfinity;
|
||||
|
||||
return _xzAxisInfo.areaSize * (indexForAxis - 1) - _xzAxisInfo.worldHalfSize + _xzAxisInfo.areaSize * 0.5f;
|
||||
}
|
||||
|
||||
private float GetAreaCenterForYAxis(short indexForAxis)
|
||||
{
|
||||
if (indexForAxis <= 0) // left infinity
|
||||
return float.NegativeInfinity;
|
||||
|
||||
if (indexForAxis >= _yAxisInfo.numAreasPerAxis - 1) // right infinity
|
||||
return float.PositiveInfinity;
|
||||
|
||||
return _yAxisInfo.areaSize * (indexForAxis - 1) - _yAxisInfo.worldHalfSize + _yAxisInfo.areaSize * 0.5f;
|
||||
}
|
||||
|
||||
private Area GetAreaAt(Vector3 pos, bool createIfNotExists)
|
||||
{
|
||||
var index = this.GetAreaIndex(pos);
|
||||
var area = _areas[index.x, index.y, index.z];
|
||||
if (null == area && createIfNotExists)
|
||||
{
|
||||
area = new Area(this, index);
|
||||
_areas[index.x, index.y, index.z] = area;
|
||||
}
|
||||
return area;
|
||||
}
|
||||
|
||||
private byte GetNewAreas(AreaIndexes oldIndexes, AreaIndexes newIndexes, AreaIndexes[] resultBuffer)
|
||||
{
|
||||
var xResult = GetAffectedRangesForAxis(oldIndexes.x, newIndexes.x);
|
||||
ValidateAffectedRangesForAxis(xResult);
|
||||
if (!xResult.hasIntersectionOnAxis)
|
||||
{
|
||||
resultBuffer[0] = newIndexes;
|
||||
return 1;
|
||||
}
|
||||
|
||||
var yResult = GetAffectedRangesForAxis(oldIndexes.y, newIndexes.y);
|
||||
ValidateAffectedRangesForAxis(yResult);
|
||||
if (!yResult.hasIntersectionOnAxis)
|
||||
{
|
||||
resultBuffer[0] = newIndexes;
|
||||
return 1;
|
||||
}
|
||||
|
||||
var zResult = GetAffectedRangesForAxis(oldIndexes.z, newIndexes.z);
|
||||
ValidateAffectedRangesForAxis(zResult);
|
||||
if (!zResult.hasIntersectionOnAxis)
|
||||
{
|
||||
resultBuffer[0] = newIndexes;
|
||||
return 1;
|
||||
}
|
||||
|
||||
// for all combinations, if at least 1 range is free
|
||||
|
||||
byte count = 0;
|
||||
|
||||
xResult.ForEachWithValue(tupleX =>
|
||||
{
|
||||
yResult.ForEachWithValue(tupleY =>
|
||||
{
|
||||
zResult.ForEachWithValue(tupleZ =>
|
||||
{
|
||||
if (!tupleX.isIntersectionPart || !tupleY.isIntersectionPart || !tupleZ.isIntersectionPart)
|
||||
{
|
||||
// at least 1 range is free
|
||||
|
||||
resultBuffer[count] = new AreaIndexes
|
||||
{
|
||||
x = tupleX.range,
|
||||
y = tupleY.range,
|
||||
z = tupleZ.range,
|
||||
};
|
||||
|
||||
count++;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
if (count == 0)
|
||||
{
|
||||
// there are no free ranges - new cube is inside of old cube (or equal) - there are no new areas - return 0
|
||||
|
||||
if (newIndexes.Volume > oldIndexes.Volume)
|
||||
throw new Exception("New cube should be <= than old cube");
|
||||
if (!newIndexes.IsInsideOf(oldIndexes))
|
||||
throw new Exception("New cube should be inside of old cube");
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
private AffectedRangesForAxis GetAffectedRangesForAxis(
|
||||
Range oldRange,
|
||||
Range newRange)
|
||||
{
|
||||
if (oldRange.EqualsToOther(newRange))
|
||||
{
|
||||
// same position and size along this axis
|
||||
return new AffectedRangesForAxis
|
||||
{
|
||||
hasIntersectionOnAxis = true,
|
||||
range1 = (oldRange, true, true),
|
||||
};
|
||||
}
|
||||
|
||||
// check if there is intersection
|
||||
if (oldRange.lower > newRange.higher)
|
||||
return default;
|
||||
if (newRange.lower > oldRange.higher)
|
||||
return default;
|
||||
|
||||
// first find intersection part (max 1)
|
||||
|
||||
var toReturn = new AffectedRangesForAxis { hasIntersectionOnAxis = true };
|
||||
Range totalRange = new Range(
|
||||
Min(oldRange.lower, newRange.lower),
|
||||
Max(oldRange.higher, newRange.higher));
|
||||
short minOfHighers = Min(oldRange.higher, newRange.higher);
|
||||
Range intersectionRange;
|
||||
|
||||
if (oldRange.lower < newRange.lower)
|
||||
{
|
||||
// he is left
|
||||
intersectionRange = new Range(newRange.lower, minOfHighers);
|
||||
}
|
||||
else if (oldRange.lower == newRange.lower)
|
||||
{
|
||||
// they share left edge
|
||||
intersectionRange = new Range(newRange.lower, minOfHighers);
|
||||
}
|
||||
else
|
||||
{
|
||||
// his left edge is more to the right
|
||||
intersectionRange = new Range(oldRange.lower, minOfHighers);
|
||||
}
|
||||
|
||||
// now find free range(s) based on total range and intersection range
|
||||
|
||||
if (intersectionRange.Length >= totalRange.Length) // should not happen
|
||||
throw new Exception($"Intersection range length {intersectionRange.Length} is >= than total range length {totalRange.Length}");
|
||||
|
||||
toReturn.range1 = (intersectionRange, true, true);
|
||||
|
||||
if (newRange.EqualsToOther(intersectionRange))
|
||||
{
|
||||
// new range is inside of old range
|
||||
// there are no free ranges
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
if (newRange.lower >= intersectionRange.lower)
|
||||
{
|
||||
Range freeRange = new Range((short) (intersectionRange.higher + 1), newRange.higher);
|
||||
toReturn.range2 = (freeRange, false, true);
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
// newRange.lower < intersectionRange.lower
|
||||
|
||||
Range freeRange1 = new Range(newRange.lower, (short) (intersectionRange.lower - 1));
|
||||
toReturn.range2 = (freeRange1, false, true);
|
||||
|
||||
if (newRange.higher > intersectionRange.higher)
|
||||
{
|
||||
Range freeRange2 = new Range((short) (intersectionRange.higher + 1), newRange.higher);
|
||||
toReturn.range3 = (freeRange2, false, true);
|
||||
}
|
||||
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
private static void ValidateAffectedRangesForAxis(AffectedRangesForAxis affectedRangesForAxis)
|
||||
{
|
||||
int count = affectedRangesForAxis.Ranges.Count(r => r.hasValues);
|
||||
|
||||
if (!affectedRangesForAxis.hasIntersectionOnAxis && count > 0)
|
||||
throw new Exception("Count > 0 and has no intersection");
|
||||
|
||||
if (!affectedRangesForAxis.hasIntersectionOnAxis)
|
||||
return;
|
||||
|
||||
var intersectionRanges = affectedRangesForAxis.Ranges
|
||||
.Where(r => r.hasValues && r.isIntersectionPart)
|
||||
.ToList();
|
||||
|
||||
var freeRanges = affectedRangesForAxis.Ranges
|
||||
.Where(r => r.hasValues && !r.isIntersectionPart)
|
||||
.ToList();
|
||||
|
||||
if (intersectionRanges.Count != 1)
|
||||
throw new Exception($"Num intersection ranges must be 1, found {intersectionRanges.Count}");
|
||||
|
||||
if (freeRanges.Count > 2)
|
||||
throw new Exception($"Num free ranges is {freeRanges.Count}");
|
||||
|
||||
var allRanges = intersectionRanges.Concat(freeRanges).ToList();
|
||||
for (int i = 0; i < allRanges.Count; i++)
|
||||
{
|
||||
for (int j = i + 1; j < allRanges.Count; j++)
|
||||
{
|
||||
var r1 = allRanges[i].range;
|
||||
var r2 = allRanges[j].range;
|
||||
if (r1.Overlaps(r2))
|
||||
throw new Exception($"Ranges overlap, {r1} and {r2}");
|
||||
}
|
||||
}
|
||||
|
||||
// there must be no space between ranges
|
||||
var orderedRanges = allRanges.OrderBy(r => r.range.lower).ToList();
|
||||
for (int i = 0; i < orderedRanges.Count - 1; i++)
|
||||
{
|
||||
var r1 = orderedRanges[i].range;
|
||||
var r2 = orderedRanges[i+1].range;
|
||||
if (r1.higher + 1 != r2.lower)
|
||||
throw new Exception($"There is space between ranges, higher is {r1.higher}, lower is {r2.lower}");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static void EnsureFocusPointsCollectionInitialized(Area area)
|
||||
{
|
||||
if (null == area.focusPointsThatSeeMe)
|
||||
area.focusPointsThatSeeMe = new HashSet<FocusPoint>();
|
||||
}
|
||||
|
||||
private static bool ShouldAreaBeVisible(Area area)
|
||||
{
|
||||
return area.focusPointsThatSeeMe != null && area.focusPointsThatSeeMe.Count > 0;
|
||||
}
|
||||
|
||||
private void MarkAreaForUpdate(Area area)
|
||||
{
|
||||
this.ThrowIfConcurrentModification(); // just in case
|
||||
|
||||
if (area.isMarkedForUpdate)
|
||||
return;
|
||||
|
||||
if (ShouldAreaBeVisible(area) == area.WasVisibleInLastUpdate) // visibility does not change
|
||||
return;
|
||||
|
||||
_areasForUpdate.Add(area);
|
||||
area.isMarkedForUpdate = true;
|
||||
}
|
||||
|
||||
private static void AddToFocusPointsThatSeeMe(Area area, FocusPoint focusPoint)
|
||||
{
|
||||
EnsureFocusPointsCollectionInitialized(area);
|
||||
if (!area.focusPointsThatSeeMe.Add(focusPoint))
|
||||
throw new Exception($"Failed to add focus point with id {focusPoint.Id} - it already exists");
|
||||
}
|
||||
|
||||
private static void RemoveFromFocusPointsThatSeeMe(Area area, FocusPoint focusPoint)
|
||||
{
|
||||
bool success = false;
|
||||
if (area.focusPointsThatSeeMe != null)
|
||||
{
|
||||
success = area.focusPointsThatSeeMe.Remove(focusPoint);
|
||||
if (area.focusPointsThatSeeMe.Count == 0)
|
||||
area.focusPointsThatSeeMe = null;
|
||||
}
|
||||
|
||||
if (!success)
|
||||
throw new Exception($"Failed to remove focus point with id {focusPoint.Id} - it doesn't exist");
|
||||
}
|
||||
|
||||
private static short Min(short a, short b)
|
||||
{
|
||||
return a <= b ? a : b;
|
||||
}
|
||||
|
||||
private static short Max(short a, short b)
|
||||
{
|
||||
return a >= b ? a : b;
|
||||
}
|
||||
|
||||
private void ThrowIfConcurrentModification()
|
||||
{
|
||||
if (_isInUpdate)
|
||||
throw new ConcurrentModificationException();
|
||||
}
|
||||
|
||||
private void NotifyAreaChangedVisibility(Area area, bool visible)
|
||||
{
|
||||
F.RunExceptionSafe(() => this._onAreaChangedVisibility(area, visible));
|
||||
}
|
||||
}
|
||||
}
|
11
Assets/Scripts/Behaviours/World/WorldSystem.cs.meta
Normal file
11
Assets/Scripts/Behaviours/World/WorldSystem.cs.meta
Normal file
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 3a5d19389522a5f4a9c55307d283b0e7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -1,4 +1,7 @@
|
|||
using System.Reflection;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
|
@ -7,41 +10,82 @@ namespace SanAndreasUnity.Editor
|
|||
public static class EditorUtils
|
||||
{
|
||||
|
||||
public static void DrawAllFieldsInInspector(object objectToDraw)
|
||||
private static List<Type> GetWithBaseTypes(Type type, int maxDepth)
|
||||
{
|
||||
var fieldInfos = objectToDraw
|
||||
.GetType()
|
||||
.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly);
|
||||
var types = new List<Type>();
|
||||
types.Add(type);
|
||||
type = type.BaseType;
|
||||
|
||||
for (int i = 0; i < maxDepth; i++)
|
||||
{
|
||||
if (type == null)
|
||||
break;
|
||||
|
||||
types.Add(type);
|
||||
type = type.BaseType;
|
||||
}
|
||||
|
||||
return types;
|
||||
}
|
||||
|
||||
public static void DrawFieldsAndPropertiesInInspector(object objectToDraw, int inheritanceLevel)
|
||||
{
|
||||
DrawFieldsInInspector(objectToDraw, inheritanceLevel);
|
||||
DrawPropertiesInInspector(objectToDraw, inheritanceLevel);
|
||||
}
|
||||
|
||||
public static void DrawFieldsInInspector(object objectToDraw, int inheritanceLevel)
|
||||
{
|
||||
var fieldInfos = GetWithBaseTypes(objectToDraw.GetType(), inheritanceLevel)
|
||||
.SelectMany(t => t.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly));
|
||||
|
||||
foreach (var fieldInfo in fieldInfos)
|
||||
{
|
||||
var type = fieldInfo.FieldType;
|
||||
var value = fieldInfo.GetValue(objectToDraw);
|
||||
string labelText = $"{fieldInfo.Name}: ";
|
||||
DrawObjectInInspector(fieldInfo.FieldType, fieldInfo.GetValue(objectToDraw), fieldInfo.Name);
|
||||
}
|
||||
}
|
||||
|
||||
if (type.IsAssignableFrom(typeof(Component)))
|
||||
{
|
||||
EditorGUILayout.ObjectField(labelText, value as Component, type, true);
|
||||
}
|
||||
else if (type.IsEnum)
|
||||
{
|
||||
if (type.GetCustomAttribute<System.FlagsAttribute>() != null)
|
||||
EditorGUILayout.EnumFlagsField(labelText, value as System.Enum);
|
||||
else
|
||||
EditorGUILayout.EnumPopup(labelText, value as System.Enum);
|
||||
}
|
||||
else if (type == typeof(Color))
|
||||
{
|
||||
EditorGUILayout.ColorField(labelText, (Color) value);
|
||||
}
|
||||
else if (type == typeof(Color32))
|
||||
{
|
||||
EditorGUILayout.ColorField(labelText, (Color32) value);
|
||||
}
|
||||
public static void DrawPropertiesInInspector(object objectToDraw, int inheritanceLevel)
|
||||
{
|
||||
var properties = GetWithBaseTypes(objectToDraw.GetType(), inheritanceLevel)
|
||||
.SelectMany(t => t.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly))
|
||||
.Where(p => p.CanRead);
|
||||
|
||||
foreach (var propertyInfo in properties)
|
||||
{
|
||||
DrawObjectInInspector(propertyInfo.PropertyType, propertyInfo.GetValue(objectToDraw), propertyInfo.Name);
|
||||
}
|
||||
}
|
||||
|
||||
public static void DrawObjectInInspector(
|
||||
Type type,
|
||||
object value,
|
||||
string name)
|
||||
{
|
||||
string labelText = $"{name}: ";
|
||||
|
||||
if (typeof(Component).IsAssignableFrom(type))
|
||||
{
|
||||
EditorGUILayout.ObjectField(labelText, value as Component, type, true);
|
||||
}
|
||||
else if (type.IsEnum)
|
||||
{
|
||||
if (type.GetCustomAttribute<System.FlagsAttribute>() != null)
|
||||
EditorGUILayout.EnumFlagsField(labelText, value as System.Enum);
|
||||
else
|
||||
{
|
||||
EditorGUILayout.LabelField($"{labelText} {value}");
|
||||
}
|
||||
EditorGUILayout.EnumPopup(labelText, value as System.Enum);
|
||||
}
|
||||
else if (type == typeof(Color))
|
||||
{
|
||||
EditorGUILayout.ColorField(labelText, (Color) value);
|
||||
}
|
||||
else if (type == typeof(Color32))
|
||||
{
|
||||
EditorGUILayout.ColorField(labelText, (Color32) value);
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorGUILayout.LabelField($"{labelText} {value}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ namespace SanAndreasUnity.Editor
|
|||
|
||||
var lightSource = (LightSource) this.target;
|
||||
|
||||
EditorUtils.DrawAllFieldsInInspector(lightSource.LightInfo);
|
||||
EditorUtils.DrawFieldsInInspector(lightSource.LightInfo, 0);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
44
Assets/Scripts/Editor/StaticGeometryInspector.cs
Normal file
44
Assets/Scripts/Editor/StaticGeometryInspector.cs
Normal file
|
@ -0,0 +1,44 @@
|
|||
using SanAndreasUnity.Behaviours.World;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace SanAndreasUnity.Editor
|
||||
{
|
||||
[CustomEditor(typeof(StaticGeometry))]
|
||||
public class StaticGeometryInspector : UnityEditor.Editor
|
||||
{
|
||||
private Vector2 _scrollViewPos;
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
base.DrawDefaultInspector ();
|
||||
|
||||
GUILayout.Space (10);
|
||||
GUILayout.Label("Info:");
|
||||
GUILayout.Space (10);
|
||||
|
||||
var staticGeometry = (StaticGeometry) this.target;
|
||||
|
||||
_scrollViewPos = EditorGUILayout.BeginScrollView(_scrollViewPos, GUILayout.MinHeight(350));
|
||||
|
||||
EditorUtils.DrawPropertiesInInspector(staticGeometry, 1);
|
||||
|
||||
GUILayout.Space (10);
|
||||
GUILayout.Label("Object definition:");
|
||||
GUILayout.Space (10);
|
||||
|
||||
if (staticGeometry.ObjectDefinition != null)
|
||||
EditorUtils.DrawFieldsAndPropertiesInInspector(staticGeometry.ObjectDefinition, 0);
|
||||
|
||||
GUILayout.Space (10);
|
||||
GUILayout.Label("Placement info:");
|
||||
GUILayout.Space (10);
|
||||
|
||||
if (staticGeometry.Instance != null)
|
||||
EditorUtils.DrawFieldsAndPropertiesInInspector(staticGeometry.Instance, 0);
|
||||
|
||||
EditorGUILayout.EndScrollView();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
11
Assets/Scripts/Editor/StaticGeometryInspector.cs.meta
Normal file
11
Assets/Scripts/Editor/StaticGeometryInspector.cs.meta
Normal file
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: a459375e4c87f3f4fa07de1ba6ca67a2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -151,9 +151,10 @@ namespace SanAndreasUnity.Importing.Archive
|
|||
// this method should not be synchronized, because thread would block while
|
||||
// archive is being read, but the thread only wants to register a job and continue
|
||||
// [MethodImpl(MethodImplOptions.Synchronized)]
|
||||
public static void ReadFileAsync(string name, System.Action<Stream> onFinish)
|
||||
public static void ReadFileAsync(string name, float loadPriority, System.Action<Stream> onFinish)
|
||||
{
|
||||
Behaviours.LoadingThread.RegisterJob (new Behaviours.LoadingThread.Job<Stream> () {
|
||||
priority = loadPriority,
|
||||
action = () => ReadFile( name ),
|
||||
callbackFinish = (stream) => { onFinish(stream); },
|
||||
});
|
||||
|
|
|
@ -6,6 +6,7 @@ using System.IO;
|
|||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using Profiler = UnityEngine.Profiling.Profiler;
|
||||
|
||||
namespace SanAndreasUnity.Importing.Collision
|
||||
{
|
||||
|
@ -100,10 +101,10 @@ namespace SanAndreasUnity.Importing.Collision
|
|||
}
|
||||
}
|
||||
|
||||
private static void LoadAsync (string collFileName, CollisionFileInfo collFileInfo, System.Action<CollisionFile> onFinish)
|
||||
private static void LoadAsync (string collFileName, CollisionFileInfo collFileInfo, float loadPriority, System.Action<CollisionFile> onFinish)
|
||||
{
|
||||
|
||||
ArchiveManager.ReadFileAsync (collFileName, (stream) => {
|
||||
ArchiveManager.ReadFileAsync (collFileName, loadPriority, (stream) => {
|
||||
CollisionFile cf = null;
|
||||
try {
|
||||
using(stream)
|
||||
|
@ -128,19 +129,19 @@ namespace SanAndreasUnity.Importing.Collision
|
|||
|
||||
public static CollisionFile FromName(String name)
|
||||
{
|
||||
if (s_asyncLoader.IsObjectLoaded (name))
|
||||
return s_asyncLoader.GetLoadedObject (name);
|
||||
if (s_asyncLoader.TryGetLoadedObject(name, out CollisionFile alreadyLoadedCollisionFile))
|
||||
return alreadyLoadedCollisionFile;
|
||||
|
||||
UnityEngine.Profiling.Profiler.BeginSample ("CollisionFile.FromName()");
|
||||
var cf = _sModelNameDict.ContainsKey(name) ? _sModelNameDict[name].Value : null;
|
||||
UnityEngine.Profiling.Profiler.EndSample ();
|
||||
|
||||
s_asyncLoader.AddToLoadedObjects (name, cf);
|
||||
s_asyncLoader.OnObjectFinishedLoading(name, cf, true);
|
||||
|
||||
return cf;
|
||||
}
|
||||
|
||||
public static void FromNameAsync(String name, System.Action<CollisionFile> onFinish)
|
||||
public static void FromNameAsync(String name, float loadPriority, System.Action<CollisionFile> onFinish)
|
||||
{
|
||||
if (!_sModelNameDict.ContainsKey (name))
|
||||
{
|
||||
|
@ -158,7 +159,7 @@ namespace SanAndreasUnity.Importing.Collision
|
|||
// get the actual name of collision file
|
||||
string collFileName = _sModelNameDict [name].FileName;
|
||||
|
||||
LoadAsync( collFileName, _sModelNameDict[name], (result) =>
|
||||
LoadAsync( collFileName, _sModelNameDict[name], loadPriority, (result) =>
|
||||
{
|
||||
// update _value variable in appropriate CollisionFileInfo object
|
||||
_sModelNameDict[name].Value = result;
|
||||
|
@ -185,6 +186,8 @@ namespace SanAndreasUnity.Importing.Collision
|
|||
|
||||
private CollisionFile(CollisionFileInfo info, Stream stream)
|
||||
{
|
||||
Profiler.BeginSample("CollisionFile()");
|
||||
|
||||
Name = info.Name;
|
||||
ModelId = info.ModelId;
|
||||
|
||||
|
@ -316,6 +319,8 @@ namespace SanAndreasUnity.Importing.Collision
|
|||
{
|
||||
Vertices = new Vertex[0];
|
||||
}
|
||||
|
||||
Profiler.EndSample();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -108,7 +108,7 @@ namespace SanAndreasUnity.Importing.Conversion
|
|||
col.Spawn(destParent, forceConvex);
|
||||
}
|
||||
|
||||
public static void LoadAsync(string name, CollisionFile file, Transform destParent, bool forceConvex, System.Action onFinish)
|
||||
public static void LoadAsync(string name, CollisionFile file, Transform destParent, bool forceConvex, float loadPriority, System.Action onFinish)
|
||||
{
|
||||
// load collision file asyncly, and when it's ready just call the other function
|
||||
|
||||
|
@ -123,7 +123,7 @@ namespace SanAndreasUnity.Importing.Conversion
|
|||
}
|
||||
|
||||
// load collision file asyncly
|
||||
CollisionFile.FromNameAsync (name, (cf) => {
|
||||
CollisionFile.FromNameAsync (name, loadPriority, (cf) => {
|
||||
// loading finished
|
||||
// call other function
|
||||
if(cf != null)
|
||||
|
|
|
@ -6,8 +6,9 @@ using SanAndreasUnity.Importing.RenderWareStream;
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using SanAndreasUnity.Utilities;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Profiling;
|
||||
using Profiler = UnityEngine.Profiling.Profiler;
|
||||
|
||||
namespace SanAndreasUnity.Importing.Conversion
|
||||
{
|
||||
|
@ -398,6 +399,8 @@ namespace SanAndreasUnity.Importing.Conversion
|
|||
|
||||
public GeometryParts(string name, Clump clump, TextureDictionary[] txds)
|
||||
{
|
||||
Profiler.BeginSample("GeometryParts()");
|
||||
|
||||
Name = name;
|
||||
|
||||
Geometry = clump.GeometryList.Geometry
|
||||
|
@ -409,6 +412,8 @@ namespace SanAndreasUnity.Importing.Conversion
|
|||
.ToArray();
|
||||
|
||||
_collisions = clump.Collision;
|
||||
|
||||
Profiler.EndSample();
|
||||
}
|
||||
|
||||
public void AttachCollisionModel(Transform destParent, bool forceConvex = false)
|
||||
|
@ -522,34 +527,32 @@ namespace SanAndreasUnity.Importing.Conversion
|
|||
return Load(modelName, texDictNames.Select(x => TextureDictionary.Load(x)).ToArray());
|
||||
}
|
||||
|
||||
public static void LoadAsync(string modelName, string[] texDictNames, System.Action<GeometryParts> onFinish)
|
||||
public static void LoadAsync(string modelName, string[] texDictNames, float loadPriority, System.Action<GeometryParts> onFinish)
|
||||
{
|
||||
// load each texture asyncly (or load them all at once ?)
|
||||
|
||||
// copy array to local variable
|
||||
texDictNames = texDictNames.ToArray ();
|
||||
// copy array to local variable
|
||||
texDictNames = texDictNames.Length > 0 ? texDictNames.ToArray() : Array.Empty<string>();
|
||||
|
||||
if (0 == texDictNames.Length)
|
||||
{
|
||||
LoadAsync( modelName, new TextureDictionary[0], onFinish );
|
||||
LoadAsync( modelName, Array.Empty<TextureDictionary>(), loadPriority, onFinish );
|
||||
return;
|
||||
}
|
||||
|
||||
// requesting a load for both clump and TXD on the same frame will not give much better performance (probably),
|
||||
// so no need to do it
|
||||
|
||||
var loadedTextDicts = new List<TextureDictionary> ();
|
||||
|
||||
for (int i = 0; i < texDictNames.Length; i++)
|
||||
{
|
||||
// bool isLast = i == texDictNames.Length - 1;
|
||||
|
||||
TextureDictionary.LoadAsync (texDictNames [i], (texDict) => {
|
||||
TextureDictionary.LoadAsync (texDictNames [i], loadPriority, (texDict) => {
|
||||
|
||||
loadedTextDicts.Add (texDict);
|
||||
|
||||
if (loadedTextDicts.Count == texDictNames.Length)
|
||||
{
|
||||
// finished loading all tex dicts
|
||||
LoadAsync (modelName, loadedTextDicts.ToArray (), onFinish);
|
||||
LoadAsync (modelName, loadedTextDicts.ToArray (), loadPriority, onFinish);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -558,11 +561,11 @@ namespace SanAndreasUnity.Importing.Conversion
|
|||
|
||||
public static GeometryParts Load(string modelName, params TextureDictionary[] txds)
|
||||
{
|
||||
modelName = modelName.ToLower();
|
||||
modelName = modelName.ToLowerIfNotLower();
|
||||
|
||||
if (s_asyncLoader.IsObjectLoaded(modelName))
|
||||
if (s_asyncLoader.TryGetLoadedObject(modelName, out GeometryParts alreadyLoadedObject))
|
||||
{
|
||||
return s_asyncLoader.GetLoadedObject (modelName);
|
||||
return alreadyLoadedObject;
|
||||
}
|
||||
|
||||
Profiler.BeginSample ("ReadClump");
|
||||
|
@ -578,14 +581,14 @@ namespace SanAndreasUnity.Importing.Conversion
|
|||
var loaded = new GeometryParts(modelName, clump, txds);
|
||||
Profiler.EndSample ();
|
||||
|
||||
s_asyncLoader.AddToLoadedObjects (modelName, loaded);
|
||||
s_asyncLoader.OnObjectFinishedLoading(modelName, loaded, true);
|
||||
|
||||
return loaded;
|
||||
}
|
||||
|
||||
public static void LoadAsync(string modelName, TextureDictionary[] txds, System.Action<GeometryParts> onFinish)
|
||||
public static void LoadAsync(string modelName, TextureDictionary[] txds, float loadPriority, System.Action<GeometryParts> onFinish)
|
||||
{
|
||||
modelName = modelName.ToLower();
|
||||
modelName = modelName.ToLowerIfNotLower();
|
||||
|
||||
if (!s_asyncLoader.TryLoadObject (modelName, onFinish))
|
||||
{
|
||||
|
@ -597,6 +600,7 @@ namespace SanAndreasUnity.Importing.Conversion
|
|||
GeometryParts loadedGeoms = null;
|
||||
|
||||
LoadingThread.RegisterJob (new LoadingThread.Job<Clump> () {
|
||||
priority = loadPriority,
|
||||
action = () => {
|
||||
// read archive file in background thread
|
||||
var clump = ArchiveManager.ReadFile<Clump>(modelName + ".dff");
|
||||
|
@ -656,8 +660,8 @@ namespace SanAndreasUnity.Importing.Conversion
|
|||
matFlags |= MaterialFlags.NoBackCull;
|
||||
}
|
||||
|
||||
if ((flags & (ObjectFlag.Alpha1 | ObjectFlag.Alpha2)) != 0
|
||||
&& (flags & ObjectFlag.DisableShadowMesh) == ObjectFlag.DisableShadowMesh)
|
||||
if ((flags & (ObjectFlag.DrawLast | ObjectFlag.Additive)) != 0
|
||||
&& (flags & ObjectFlag.NoZBufferWrite) == ObjectFlag.NoZBufferWrite)
|
||||
{
|
||||
matFlags |= MaterialFlags.Alpha;
|
||||
}
|
||||
|
|
|
@ -255,20 +255,21 @@ namespace SanAndreasUnity.Importing.Conversion
|
|||
public static TextureDictionary Load(string name)
|
||||
{
|
||||
name = name.ToLower();
|
||||
if (s_asyncLoader.IsObjectLoaded (name))
|
||||
return s_asyncLoader.GetLoadedObject (name);
|
||||
|
||||
if (s_asyncLoader.TryGetLoadedObject(name, out var alreadyLoadedTxd))
|
||||
return alreadyLoadedTxd;
|
||||
|
||||
UnityEngine.Profiling.Profiler.BeginSample ("TextureDictionary.Load");
|
||||
|
||||
var txd = new TextureDictionary(DontLoadTextures ? null : ArchiveManager.ReadFile<RenderWareStream.TextureDictionary>(name + ".txd"));
|
||||
s_asyncLoader.AddToLoadedObjects(name, txd);
|
||||
s_asyncLoader.OnObjectFinishedLoading(name, txd, true);
|
||||
|
||||
UnityEngine.Profiling.Profiler.EndSample ();
|
||||
|
||||
return txd;
|
||||
}
|
||||
|
||||
public static void LoadAsync(string name, System.Action<TextureDictionary> onFinish)
|
||||
public static void LoadAsync(string name, float loadPriority, System.Action<TextureDictionary> onFinish)
|
||||
{
|
||||
name = name.ToLower();
|
||||
|
||||
|
@ -282,6 +283,7 @@ namespace SanAndreasUnity.Importing.Conversion
|
|||
bool bDontLoad = DontLoadTextures;
|
||||
|
||||
Behaviours.LoadingThread.RegisterJob (new Behaviours.LoadingThread.Job<RenderWareStream.TextureDictionary> () {
|
||||
priority = loadPriority,
|
||||
action = () => {
|
||||
return bDontLoad ? null : ArchiveManager.ReadFile<RenderWareStream.TextureDictionary>(name + ".txd");
|
||||
},
|
||||
|
|
|
@ -6,23 +6,26 @@ namespace SanAndreasUnity.Importing.Items.Definitions
|
|||
public enum ObjectFlag : uint
|
||||
{
|
||||
None = 0,
|
||||
WetEffect = 1,
|
||||
RenderAtNight = 2,
|
||||
Alpha1 = 4,
|
||||
Alpha2 = 8,
|
||||
RenderAtDay = 16,
|
||||
Interior = 32,
|
||||
DisableShadowMesh = 64,
|
||||
NoCull = 128,
|
||||
DisableDrawDist = 256,
|
||||
Breakable = 512,
|
||||
BreakableCrack = 1024,
|
||||
IsRoad = 1,
|
||||
RenderAtNight = 2, // ?
|
||||
DrawLast = 4,
|
||||
Additive = 8,
|
||||
RenderAtDay = 16, // ?
|
||||
Interior = 32, // ? doors
|
||||
NoZBufferWrite = 64,
|
||||
DontReceiveShadows = 128,
|
||||
DisableDrawDist = 256, // ?
|
||||
Breakable = 512, // IS_GLASS_TYPE_1
|
||||
BreakableCrack = 1024, // IS_GLASS_TYPE_2
|
||||
GarageDoor = 2048,
|
||||
MultiClumpCollide = 4096,
|
||||
WeatherBrightness = 32768,
|
||||
IsDamagable = 4096,
|
||||
IsTree = 8192,
|
||||
IsPalm = 16384,
|
||||
DoesNotCollideWithFlyer = 32768,
|
||||
ExplodeHit = 65536,
|
||||
MultiClumpSpray = 1048576,
|
||||
NoBackCull = 2097152
|
||||
IsTag = 1048576,
|
||||
NoBackCull = 2097152,
|
||||
IsBreakableStatue = 4194304,
|
||||
}
|
||||
|
||||
public interface ISimpleObjectDefinition : IObjectDefinition
|
||||
|
|
|
@ -12,7 +12,7 @@ namespace SanAndreasUnity.Importing.Items
|
|||
private static readonly List<Zone> _zones = new List<Zone>();
|
||||
|
||||
private static readonly List<EntranceExit> _enexes = new List<EntranceExit>();
|
||||
public static List<EntranceExit> Enexes => _enexes;
|
||||
public static IReadOnlyList<EntranceExit> Enexes => _enexes;
|
||||
|
||||
private static readonly Dictionary<int, IObjectDefinition> _definitions
|
||||
= new Dictionary<int, IObjectDefinition>();
|
||||
|
@ -119,7 +119,7 @@ namespace SanAndreasUnity.Importing.Items
|
|||
int lastCell = -1;
|
||||
foreach (var inst in list)
|
||||
{
|
||||
int cell = inst.CellId & 0xff;
|
||||
int cell = inst.InteriorLevel;
|
||||
if (lastCell != cell && !_placements.ContainsKey(lastCell = cell))
|
||||
{
|
||||
_placements.Add(cell, new List<Placement>());
|
||||
|
|
|
@ -11,12 +11,12 @@ namespace SanAndreasUnity.Importing.Items.Placements
|
|||
public class EntranceExit : Placement
|
||||
{
|
||||
|
||||
public readonly UnityEngine.Vector3 EntrancePos;
|
||||
public readonly float EntranceAngle;
|
||||
public readonly UnityEngine.Vector3 EntrancePos; // position where enex is located
|
||||
public readonly float EntranceAngle; // rotation of ped when entering enex
|
||||
public readonly UnityEngine.Vector3 Size;
|
||||
public readonly UnityEngine.Vector3 ExitPos;
|
||||
public readonly float ExitAngle;
|
||||
public readonly int TargetInterior;
|
||||
public readonly UnityEngine.Vector3 ExitPos; // position of ped after teleporting to this enex
|
||||
public readonly float ExitAngle; // rotation of ped after teleporting to this enex
|
||||
public readonly int TargetInterior; // interior level where enex is located
|
||||
public readonly int Flags;
|
||||
public readonly string Name;
|
||||
public readonly int SkyColorType;
|
||||
|
|
|
@ -24,6 +24,7 @@ namespace SanAndreasUnity.Importing.Items.Placements
|
|||
public readonly int ObjectId;
|
||||
public readonly string LodGeometry;
|
||||
public readonly int CellId;
|
||||
public int InteriorLevel => this.CellId & 0xff;
|
||||
public readonly UnityEngine.Vector3 Position;
|
||||
public readonly UnityEngine.Quaternion Rotation;
|
||||
public readonly int LodIndex;
|
||||
|
|
|
@ -6,18 +6,9 @@ using SanAndreasUnity.Behaviours.Vehicles;
|
|||
namespace SanAndreasUnity.Settings {
|
||||
|
||||
public class CameraSettings : MonoBehaviour {
|
||||
|
||||
static float s_farClipPlane = 1000;
|
||||
|
||||
static float s_fieldOfView = 60;
|
||||
|
||||
OptionsWindow.FloatInput m_farClipPlaneInput = new OptionsWindow.FloatInput() {
|
||||
description = "Far clip plane",
|
||||
minValue = 100,
|
||||
maxValue = 5000,
|
||||
getValue = () => Camera.main != null ? Camera.main.farClipPlane : s_farClipPlane,
|
||||
setValue = (value) => { s_farClipPlane = value; if (Camera.main != null) Camera.main.farClipPlane = value; },
|
||||
persistType = OptionsWindow.InputPersistType.OnStart,
|
||||
};
|
||||
OptionsWindow.FloatInput m_fieldOfViewInput = new OptionsWindow.FloatInput() {
|
||||
description = "Field of view",
|
||||
minValue = 20,
|
||||
|
@ -47,7 +38,7 @@ namespace SanAndreasUnity.Settings {
|
|||
|
||||
void Awake ()
|
||||
{
|
||||
OptionsWindow.RegisterInputs ("CAMERA", m_farClipPlaneInput, m_fieldOfViewInput, m_cameraDistanceFromPed, m_cameraDistanceFromVehicle);
|
||||
OptionsWindow.RegisterInputs ("CAMERA", m_fieldOfViewInput, m_cameraDistanceFromPed, m_cameraDistanceFromVehicle);
|
||||
UnityEngine.SceneManagement.SceneManager.activeSceneChanged += (s1, s2) => OnActiveSceneChanged();
|
||||
}
|
||||
|
||||
|
@ -65,7 +56,6 @@ namespace SanAndreasUnity.Settings {
|
|||
Camera cam = Utilities.F.FindMainCameraEvenIfDisabled();
|
||||
if (cam != null)
|
||||
{
|
||||
cam.farClipPlane = s_farClipPlane;
|
||||
cam.fieldOfView = s_fieldOfView;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,41 +1,44 @@
|
|||
using System.Collections.Generic;
|
||||
using SanAndreasUnity.Behaviours.World;
|
||||
using UnityEngine;
|
||||
using SanAndreasUnity.Behaviours.World;
|
||||
using SanAndreasUnity.UI;
|
||||
|
||||
namespace SanAndreasUnity.Settings {
|
||||
namespace SanAndreasUnity.Settings
|
||||
{
|
||||
public class WorldSettings : MonoBehaviour
|
||||
{
|
||||
private static WorldSettings Singleton { get; set; }
|
||||
|
||||
public class WorldSettings : MonoBehaviour {
|
||||
private float _drawDistanceToApply = 0f;
|
||||
|
||||
static float s_maxDrawDistance = 500f;
|
||||
|
||||
OptionsWindow.FloatInput m_maxDrawDistanceInput = new OptionsWindow.FloatInput() {
|
||||
private OptionsWindow.FloatInput m_maxDrawDistanceInput = new OptionsWindow.FloatInput
|
||||
{
|
||||
description = "Max draw distance",
|
||||
minValue = 50,
|
||||
maxValue = 1000,
|
||||
getValue = () => Cell.Instance != null ? Cell.Instance.maxDrawDistance : s_maxDrawDistance,
|
||||
setValue = (value) => { s_maxDrawDistance = value; if (Cell.Instance != null) Cell.Instance.maxDrawDistance = value; },
|
||||
persistType = OptionsWindow.InputPersistType.OnStart
|
||||
minValue = WorldManager.MinMaxDrawDistance,
|
||||
maxValue = WorldManager.MaxMaxDrawDistance,
|
||||
getValue = () => WorldManager.Singleton.MaxDrawDistance,
|
||||
setValue = value => { Singleton.OnDrawDistanceChanged(value); },
|
||||
persistType = OptionsWindow.InputPersistType.OnStart,
|
||||
};
|
||||
|
||||
|
||||
void Awake ()
|
||||
{
|
||||
Singleton = this;
|
||||
|
||||
OptionsWindow.RegisterInputs ("WORLD", m_maxDrawDistanceInput);
|
||||
UnityEngine.SceneManagement.SceneManager.activeSceneChanged += (s1, s2) => OnActiveSceneChanged();
|
||||
}
|
||||
|
||||
void OnActiveSceneChanged()
|
||||
void OnDrawDistanceChanged(float newValue)
|
||||
{
|
||||
// apply settings
|
||||
|
||||
// we need to find Cell with FindObjectOfType(), because it's Awake() method may have not been called yet
|
||||
Cell cell = Object.FindObjectOfType<Cell>();
|
||||
if (cell != null)
|
||||
cell.maxDrawDistance = s_maxDrawDistance;
|
||||
|
||||
this.CancelInvoke(nameof(ChangeDrawDistanceDelayed));
|
||||
_drawDistanceToApply = newValue;
|
||||
this.Invoke(nameof(ChangeDrawDistanceDelayed), 0.2f);
|
||||
}
|
||||
|
||||
void ChangeDrawDistanceDelayed()
|
||||
{
|
||||
WorldManager.Singleton.MaxDrawDistance = _drawDistanceToApply;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,10 +1,15 @@
|
|||
using System.Linq;
|
||||
using SanAndreasUnity.Behaviours;
|
||||
using SanAndreasUnity.Behaviours.World;
|
||||
using UnityEngine;
|
||||
|
||||
namespace SanAndreasUnity.Stats
|
||||
{
|
||||
public class WorldStats : MonoBehaviour
|
||||
{
|
||||
|
||||
private readonly System.Text.StringBuilder _stringBuilder = new System.Text.StringBuilder();
|
||||
|
||||
|
||||
void Start()
|
||||
{
|
||||
Utilities.Stats.RegisterStat(new Utilities.Stats.Entry(){category = "WORLD", onGUI = OnStatGUI});
|
||||
|
@ -12,16 +17,63 @@ namespace SanAndreasUnity.Stats
|
|||
|
||||
void OnStatGUI()
|
||||
{
|
||||
var sb = _stringBuilder;
|
||||
sb.Clear();
|
||||
|
||||
if (Behaviours.World.Cell.Instance != null)
|
||||
var cell = Cell.Instance;
|
||||
|
||||
if (cell != null)
|
||||
{
|
||||
Behaviours.World.Cell.Instance.showWindow (0);
|
||||
}
|
||||
sb.Append($"max draw distance {cell.MaxDrawDistance}\n");
|
||||
sb.Append($"num focus points {cell.FocusPoints.Count}\n");
|
||||
sb.Append($"num static objects {cell.NumStaticGeometries}\n");
|
||||
sb.Append($"num TOBJ objects {StaticGeometry.TimedObjects.Count}\n");
|
||||
sb.Append($"num active ENEX objects {EntranceExitMapObject.AllActiveObjects.Count}\n");
|
||||
sb.Append($"num active objects with lights {StaticGeometry.ActiveObjectsWithLights.Count}\n");
|
||||
sb.Append($"num active lights {StaticGeometry.ActiveObjectsWithLights.Sum(_ => _.NumLightSources)}\n");
|
||||
sb.Append($"geometry parts loaded {Importing.Conversion.Geometry.NumGeometryPartsLoaded}\n");
|
||||
|
||||
sb.Append($"world systems\n");
|
||||
for (int i = 0; i < cell.WorldSystem.WorldSystems.Count; i++)
|
||||
{
|
||||
sb.Append($"\tdistance level {cell.WorldSystem.DistanceLevels[i]}\n");
|
||||
var worldSystem = cell.WorldSystem.WorldSystems[i];
|
||||
sb.Append($"\tnum focus points {worldSystem.FocusPoints.Count}\n");
|
||||
sb.Append($"\tnum areas {worldSystem.GetNumAreas(0)} {worldSystem.GetNumAreas(1)} {worldSystem.GetNumAreas(2)}\n");
|
||||
if (Ped.Instance != null)
|
||||
{
|
||||
int numAreasVisible = 0,
|
||||
numObjectsInVisibleAreas = 0,
|
||||
maxNumFocusPointsThatSeeMe = 0,
|
||||
minNumFocusPointsThatSeeMe = int.MaxValue;
|
||||
|
||||
worldSystem.ForEachAreaInRadius(
|
||||
Ped.Instance.transform.position,
|
||||
Mathf.Min(cell.WorldSystem.DistanceLevels[i], cell.MaxDrawDistance),
|
||||
area =>
|
||||
{
|
||||
numAreasVisible++;
|
||||
numObjectsInVisibleAreas += area.ObjectsInside?.Count ?? 0;
|
||||
maxNumFocusPointsThatSeeMe = Mathf.Max(maxNumFocusPointsThatSeeMe, area.FocusPointsThatSeeMe?.Count ?? 0);
|
||||
minNumFocusPointsThatSeeMe = Mathf.Min(minNumFocusPointsThatSeeMe, area.FocusPointsThatSeeMe?.Count ?? 0);
|
||||
}
|
||||
);
|
||||
|
||||
sb.Append($"\tlocal ped visibility\n");
|
||||
sb.Append($"\t\tnum areas visible {numAreasVisible}\n");
|
||||
sb.Append($"\t\tnum objects in visible areas {numObjectsInVisibleAreas}\n");
|
||||
sb.Append($"\t\tmax num focus points that see an area {maxNumFocusPointsThatSeeMe}\n");
|
||||
sb.Append($"\t\tmin num focus points that see an area {minNumFocusPointsThatSeeMe}\n");
|
||||
}
|
||||
sb.Append($"\n");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
GUILayout.Label("World not loaded");
|
||||
sb.Append($"World not loaded\n");
|
||||
}
|
||||
|
||||
GUILayout.Label(sb.ToString());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -605,9 +605,12 @@ namespace SanAndreasUnity.UI {
|
|||
// draw enexes
|
||||
if (m_drawEnexes)
|
||||
{
|
||||
foreach (var enex in Importing.Items.Item.Enexes.Where(enex => enex.TargetInterior == 0))
|
||||
foreach (var enex in Importing.Items.Item.Enexes.Where(enex => Behaviours.World.Cell.IsExteriorLevel(enex.TargetInterior)))
|
||||
{
|
||||
this.DrawItemOnMap(MiniMap.Instance.GreenHouseTexture, enex.EntrancePos, 10);
|
||||
this.DrawItemOnMap(
|
||||
MiniMap.Instance.GreenHouseTexture,
|
||||
Behaviours.World.Cell.Instance.GetPositionBasedOnInteriorLevel(enex.EntrancePos, enex.TargetInterior),
|
||||
10);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -63,11 +63,11 @@ namespace SanAndreasUnity.UI {
|
|||
_spawnNames.Clear();
|
||||
|
||||
// if exterior is not loaded, then use enexes from loaded interiors
|
||||
if (Cell.Instance != null && ! Cell.Instance.HasExterior)
|
||||
if (Cell.Instance != null && ! Cell.Instance.HasMainExterior)
|
||||
{
|
||||
foreach(var enex in Cell.Instance.GetEnexesFromLoadedInteriors())
|
||||
{
|
||||
_spawns.Add(Cell.GetEnexExitTransform(enex));
|
||||
_spawns.Add(Cell.Instance.GetEnexExitTransform(enex));
|
||||
_spawnNames.Add(enex.Name);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@ namespace SanAndreasUnity.Utilities
|
|||
|
||||
public class AsyncLoader<TKey, TObj>
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// All successfully loaded objects.
|
||||
/// </summary>
|
||||
|
@ -29,103 +28,84 @@ namespace SanAndreasUnity.Utilities
|
|||
m_Loading = new Dictionary<TKey, List<System.Action<TObj>>> (comparer);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.Synchronized)]
|
||||
public int GetNumObjectsLoaded ()
|
||||
{
|
||||
return m_Loaded.Count;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.Synchronized)]
|
||||
public int GetNumObjectsLoading ()
|
||||
{
|
||||
return m_Loading.Count;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.Synchronized)]
|
||||
public bool IsObjectLoaded (TKey key)
|
||||
{
|
||||
return m_Loaded.ContainsKey (key);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.Synchronized)]
|
||||
public TObj GetLoadedObject (TKey key)
|
||||
{
|
||||
return m_Loaded [key];
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.Synchronized)]
|
||||
public bool CheckIsObjectLoaded (TKey key, System.Action<TObj> onFinish)
|
||||
public bool TryLoadObject (TKey key, System.Action<TObj> onFinish)
|
||||
{
|
||||
ThreadHelper.ThrowIfNotOnMainThread(); // not needed, but to make sure
|
||||
|
||||
if (m_Loaded.ContainsKey(key))
|
||||
{
|
||||
onFinish (m_Loaded [key]);
|
||||
return true;
|
||||
onFinish(m_Loaded[key]);
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.Synchronized)]
|
||||
public bool CheckIsObjectLoading (TKey key, System.Action<TObj> onFinish)
|
||||
{
|
||||
if (m_Loading.ContainsKey (key))
|
||||
if (m_Loading.ContainsKey(key))
|
||||
{
|
||||
// this object is loading
|
||||
// subscribe to finish event
|
||||
m_Loading[key].Add( onFinish );
|
||||
return true;
|
||||
m_Loading[key].Add(onFinish);
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.Synchronized)]
|
||||
public bool TryLoadObject (TKey key, System.Action<TObj> onFinish)
|
||||
{
|
||||
if (CheckIsObjectLoaded (key, onFinish))
|
||||
return false;
|
||||
|
||||
if (CheckIsObjectLoading (key, onFinish))
|
||||
return false;
|
||||
|
||||
// insert it into loading dict
|
||||
m_Loading [key] = new List<System.Action<TObj>>(){onFinish};
|
||||
m_Loading[key] = new List<System.Action<TObj>>() {onFinish};
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.Synchronized)]
|
||||
public bool TryGetLoadedObject(TKey key, out TObj loadedObject)
|
||||
{
|
||||
ThreadHelper.ThrowIfNotOnMainThread(); // not needed, but to make sure
|
||||
|
||||
return m_Loaded.TryGetValue(key, out loadedObject);
|
||||
}
|
||||
|
||||
public void OnObjectFinishedLoading (TKey key, TObj obj, bool bSuccess)
|
||||
{
|
||||
ThreadHelper.ThrowIfNotOnMainThread(); // not needed, but to make sure
|
||||
|
||||
if (bSuccess)
|
||||
{
|
||||
if (m_Loaded.ContainsKey (key))
|
||||
if (m_Loaded.ContainsKey(key))
|
||||
{
|
||||
// this object was loaded in the meantime
|
||||
// this can happen if someone else is loading objects synchronously
|
||||
Debug.LogErrorFormat ("Redundant load of object ({0}): {1}", typeof(TObj), key);
|
||||
Debug.LogErrorFormat("Redundant load of object ({0}): {1}", typeof(TObj), key);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_Loaded.Add (key, obj);
|
||||
m_Loaded.Add(key, obj);
|
||||
}
|
||||
}
|
||||
|
||||
if (m_Loading.TryGetValue(key, out var subscribersList))
|
||||
{
|
||||
// remove from loading dict
|
||||
m_Loading.Remove(key);
|
||||
|
||||
var list = m_Loading[key];
|
||||
|
||||
// remove from loading dict
|
||||
m_Loading.Remove( key );
|
||||
|
||||
// invoke subscribers
|
||||
foreach(var item in list)
|
||||
Utilities.F.RunExceptionSafe( () => item(obj));
|
||||
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.Synchronized)]
|
||||
public void AddToLoadedObjects (TKey key, TObj obj)
|
||||
{
|
||||
m_Loaded [key] = obj;
|
||||
// invoke subscribers
|
||||
foreach (var action in subscribersList)
|
||||
Utilities.F.RunExceptionSafe(() => action(obj));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace SanAndreasUnity.Utilities
|
||||
{
|
||||
public class ConcurrentProducerConsumerSortedSet<T> : IProducerConsumerCollection<T>
|
||||
{
|
||||
private SortedSet<T> _sortedSet;
|
||||
private object _lockObject = new object();
|
||||
|
||||
|
||||
public ConcurrentProducerConsumerSortedSet(IComparer<T> comparer)
|
||||
{
|
||||
_sortedSet = new SortedSet<T>(comparer);
|
||||
}
|
||||
|
||||
public IEnumerator<T> GetEnumerator()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
public void CopyTo(Array array, int index)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public int Count
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
return _sortedSet.Count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsSynchronized => throw new NotImplementedException();
|
||||
|
||||
public object SyncRoot => throw new NotImplementedException();
|
||||
|
||||
public void CopyTo(T[] array, int index)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public T[] ToArray()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public bool TryAdd(T item)
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
if (!_sortedSet.Add(item))
|
||||
throw new ArgumentException($"Item with this key already exists: {item}");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryTake(out T result)
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
if (_sortedSet.Count > 0)
|
||||
{
|
||||
T min = _sortedSet.Min;
|
||||
if (!_sortedSet.Remove(min))
|
||||
throw new Exception("Failed to remove min element");
|
||||
result = min;
|
||||
return true;
|
||||
}
|
||||
|
||||
result = default;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 9c1410970028b404588b4f41a7f34497
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -13,49 +13,63 @@ namespace SanAndreasUnity.Utilities
|
|||
/// </remarks>
|
||||
public class ConcurrentQueue<T>
|
||||
{
|
||||
private readonly System.Object queueLock = new System.Object();
|
||||
private readonly Queue<T> queue = new Queue<T>();
|
||||
private readonly System.Object _queueLock = new System.Object();
|
||||
private readonly Queue<T> _queue = new Queue<T>();
|
||||
|
||||
public void Enqueue(T item)
|
||||
{
|
||||
lock (queueLock)
|
||||
lock (_queueLock)
|
||||
{
|
||||
queue.Enqueue(item);
|
||||
_queue.Enqueue(item);
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryDequeue(out T result)
|
||||
{
|
||||
lock (queueLock)
|
||||
lock (_queueLock)
|
||||
{
|
||||
if (queue.Count == 0)
|
||||
if (_queue.Count == 0)
|
||||
{
|
||||
result = default(T);
|
||||
return false;
|
||||
}
|
||||
|
||||
result = queue.Dequeue();
|
||||
result = _queue.Dequeue();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public T[] DequeueAll()
|
||||
{
|
||||
lock (queueLock)
|
||||
lock (_queueLock)
|
||||
{
|
||||
T[] copy = queue.ToArray();
|
||||
queue.Clear();
|
||||
T[] copy = _queue.ToArray();
|
||||
_queue.Clear();
|
||||
return copy;
|
||||
}
|
||||
}
|
||||
|
||||
public int DequeueToQueue(Queue<T> collection, int maxNumItems)
|
||||
{
|
||||
lock (_queueLock)
|
||||
{
|
||||
int numAdded = 0;
|
||||
while (_queue.Count > 0 && numAdded < maxNumItems)
|
||||
{
|
||||
collection.Enqueue(_queue.Dequeue());
|
||||
numAdded++;
|
||||
}
|
||||
return numAdded;
|
||||
}
|
||||
}
|
||||
|
||||
public int Count
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (queueLock)
|
||||
lock (_queueLock)
|
||||
{
|
||||
return queue.Count;
|
||||
return _queue.Count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
22
Assets/Scripts/Utilities/DestroyWhenInHeadlessMode.cs
Normal file
22
Assets/Scripts/Utilities/DestroyWhenInHeadlessMode.cs
Normal file
|
@ -0,0 +1,22 @@
|
|||
using System;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace SanAndreasUnity.Utilities
|
||||
{
|
||||
public class DestroyWhenInHeadlessMode : MonoBehaviour
|
||||
{
|
||||
public Component[] componentsToDestroy = Array.Empty<Component>();
|
||||
|
||||
private void Start() // use Start() to avoid problems with script execution order
|
||||
{
|
||||
if (F.IsInHeadlessMode)
|
||||
{
|
||||
foreach (var component in this.componentsToDestroy)
|
||||
{
|
||||
Object.Destroy(component);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
11
Assets/Scripts/Utilities/DestroyWhenInHeadlessMode.cs.meta
Normal file
11
Assets/Scripts/Utilities/DestroyWhenInHeadlessMode.cs.meta
Normal file
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 4bb8f0fea23c3fe44828518a6ea9ad15
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
22
Assets/Scripts/Utilities/DestroyWhenNotOnServer.cs
Normal file
22
Assets/Scripts/Utilities/DestroyWhenNotOnServer.cs
Normal file
|
@ -0,0 +1,22 @@
|
|||
using System;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace SanAndreasUnity.Utilities
|
||||
{
|
||||
public class DestroyWhenNotOnServer : MonoBehaviour
|
||||
{
|
||||
public Component[] componentsToDestroy = Array.Empty<Component>();
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (!NetUtils.IsServer)
|
||||
{
|
||||
foreach (var component in this.componentsToDestroy)
|
||||
{
|
||||
Object.Destroy(component);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
11
Assets/Scripts/Utilities/DestroyWhenNotOnServer.cs.meta
Normal file
11
Assets/Scripts/Utilities/DestroyWhenNotOnServer.cs.meta
Normal file
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 6d68c88ae66cbc64c997da0c710b080c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -408,6 +408,16 @@ namespace SanAndreasUnity.Utilities
|
|||
return str[0].ToString().ToUpperInvariant() + str.Substring(1);
|
||||
}
|
||||
|
||||
public static string ToLowerIfNotLower(this string str)
|
||||
{
|
||||
for (int i = 0; i < str.Length; i++)
|
||||
{
|
||||
if (!char.IsLower(str[i]))
|
||||
return str.ToLower();
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
public static string GetGameObjectPath(this GameObject obj)
|
||||
{
|
||||
string path = obj.name;
|
||||
|
@ -576,6 +586,16 @@ namespace SanAndreasUnity.Utilities
|
|||
return enumerable.FindIndex (elem => elem.Equals (value));
|
||||
}
|
||||
|
||||
public static bool AnyInList<T>(this IList<T> list, System.Predicate<T> predicate)
|
||||
{
|
||||
for (int i = 0; i < list.Count; i++)
|
||||
{
|
||||
if (predicate(list[i]))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool AddIfNotPresent<T> (this List<T> list, T item)
|
||||
{
|
||||
if (!list.Contains (item)) {
|
||||
|
@ -931,7 +951,17 @@ namespace SanAndreasUnity.Utilities
|
|||
}
|
||||
|
||||
|
||||
public static bool IsInHeadlessMode => SystemInfo.graphicsDeviceType == UnityEngine.Rendering.GraphicsDeviceType.Null;
|
||||
private static bool? _isInHeadlessModeCached;
|
||||
public static bool IsInHeadlessMode
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_isInHeadlessModeCached.HasValue)
|
||||
return _isInHeadlessModeCached.Value;
|
||||
_isInHeadlessModeCached = SystemInfo.graphicsDeviceType == UnityEngine.Rendering.GraphicsDeviceType.Null;
|
||||
return _isInHeadlessModeCached.Value;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool ScreenHasHighDensity => Application.isMobilePlatform;
|
||||
|
||||
|
|
|
@ -11,10 +11,10 @@ namespace SanAndreasUnity.Utilities
|
|||
var cam = Camera.current;
|
||||
if (cam != null)
|
||||
{
|
||||
Vector3 f = -cam.transform.forward;
|
||||
Quaternion quaternion = Quaternion.LookRotation(-cam.transform.forward, cam.transform.up);
|
||||
for (int i = 0; i < this.transformsToFace.Length; i++)
|
||||
{
|
||||
this.transformsToFace[i].forward = f;
|
||||
this.transformsToFace[i].rotation = quaternion;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,46 +1,102 @@
|
|||
using UnityEngine;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using UnityEngine;
|
||||
using SanAndreasUnity.Utilities;
|
||||
using System.Threading;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Collections.Concurrent;
|
||||
using Debug = UnityEngine.Debug;
|
||||
|
||||
namespace SanAndreasUnity.Behaviours
|
||||
{
|
||||
|
||||
public class LoadingThread : MonoBehaviour {
|
||||
|
||||
|
||||
// TODO: maybe convert to class, because it takes 36 bytes - it's too much for red-black tree operations
|
||||
public struct Job<T>
|
||||
{
|
||||
public System.Func<T> action ;
|
||||
public System.Action<T> callbackSuccess ;
|
||||
public System.Action<System.Exception> callbackError ;
|
||||
public System.Action<T> callbackFinish;
|
||||
public float priority;
|
||||
internal object result ;
|
||||
internal System.Exception exception ;
|
||||
internal long id;
|
||||
}
|
||||
|
||||
static System.Collections.Concurrent.BlockingCollection<Job<object>> s_jobs = new System.Collections.Concurrent.BlockingCollection<Job<object>> ();
|
||||
static Thread s_thread;
|
||||
static System.Collections.Concurrent.BlockingCollection<Job<object>> s_processedJobs = new System.Collections.Concurrent.BlockingCollection<Job<object>> ();
|
||||
public class JobComparer : IComparer<Job<object>>
|
||||
{
|
||||
public int Compare(Job<object> a, Job<object> b)
|
||||
{
|
||||
if (a.id == b.id)
|
||||
return 0;
|
||||
|
||||
static bool s_shouldThreadExit = false;
|
||||
if (a.priority != b.priority)
|
||||
return a.priority <= b.priority ? -1 : 1;
|
||||
|
||||
// priorities are the same
|
||||
// the advantage has the job which was created earlier
|
||||
|
||||
return a.id <= b.id ? -1 : 1;
|
||||
}
|
||||
}
|
||||
|
||||
private class ThreadParameters
|
||||
{
|
||||
public readonly BlockingCollection<Job<object>> jobs =
|
||||
new BlockingCollection<Job<object>> (new System.Collections.Concurrent.ConcurrentQueue<Job<object>>());
|
||||
public readonly Utilities.ConcurrentQueue<Job<object>> processedJobs = new Utilities.ConcurrentQueue<Job<object>>();
|
||||
private bool _shouldThreadExit = false;
|
||||
private readonly object _shouldThreadExitLockObject = new object();
|
||||
|
||||
public bool ShouldThreadExit()
|
||||
{
|
||||
lock (_shouldThreadExitLockObject)
|
||||
return _shouldThreadExit;
|
||||
}
|
||||
|
||||
public void TellThreadToExit()
|
||||
{
|
||||
lock (_shouldThreadExitLockObject)
|
||||
_shouldThreadExit = true;
|
||||
}
|
||||
}
|
||||
|
||||
public static LoadingThread Singleton { get; private set; }
|
||||
|
||||
private Thread _thread;
|
||||
private readonly ThreadParameters _threadParameters = new ThreadParameters();
|
||||
private readonly Queue<Job<object>> _processedJobsBuffer = new Queue<Job<object>>(256);
|
||||
|
||||
private static long s_lastJobId = 1;
|
||||
private static readonly object s_lastJobIdLockObject = new object();
|
||||
|
||||
private readonly Stopwatch _stopwatch = new Stopwatch();
|
||||
|
||||
public ushort maxTimePerFrameMs = 0;
|
||||
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
Singleton = this;
|
||||
}
|
||||
|
||||
void Start () {
|
||||
|
||||
s_thread = new Thread (ThreadFunction);
|
||||
s_thread.Start ();
|
||||
_thread = new Thread (ThreadFunction);
|
||||
_thread.Start(_threadParameters);
|
||||
|
||||
}
|
||||
|
||||
void OnDisable ()
|
||||
{
|
||||
if (s_thread != null)
|
||||
if (_thread != null)
|
||||
{
|
||||
var sw = System.Diagnostics.Stopwatch.StartNew ();
|
||||
// s_thread.Interrupt ();
|
||||
TellThreadToExit ();
|
||||
if (s_thread.Join (7000))
|
||||
// _thread.Interrupt ();
|
||||
_threadParameters.TellThreadToExit ();
|
||||
if (_thread.Join (7000))
|
||||
Debug.LogFormat ("Stopped loading thread in {0} ms", sw.Elapsed.TotalMilliseconds);
|
||||
else
|
||||
Debug.LogError ("Failed to stop loading thread");
|
||||
|
@ -51,9 +107,26 @@ namespace SanAndreasUnity.Behaviours
|
|||
|
||||
// get all processed jobs
|
||||
|
||||
_stopwatch.Restart();
|
||||
|
||||
Job<object> job;
|
||||
while (s_processedJobs.TryTake (out job, 0))
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (this.maxTimePerFrameMs != 0 && _stopwatch.ElapsedMilliseconds >= this.maxTimePerFrameMs)
|
||||
break;
|
||||
|
||||
if (_processedJobsBuffer.Count > 0)
|
||||
job = _processedJobsBuffer.Dequeue();
|
||||
else
|
||||
{
|
||||
int numCopied = _threadParameters.processedJobs.DequeueToQueue(_processedJobsBuffer, 256);
|
||||
if (numCopied == 0)
|
||||
break;
|
||||
|
||||
job = _processedJobsBuffer.Dequeue();
|
||||
}
|
||||
|
||||
if (job.exception != null)
|
||||
{
|
||||
// error happened
|
||||
|
@ -80,12 +153,19 @@ namespace SanAndreasUnity.Behaviours
|
|||
|
||||
public static void RegisterJob<T> (Job<T> job)
|
||||
{
|
||||
// note: this function can be called from any thread
|
||||
|
||||
if (null == job.action)
|
||||
return;
|
||||
throw new ArgumentException("Job must have an action");
|
||||
|
||||
if (0f == job.priority)
|
||||
throw new ArgumentException("You forgot to assign job priority");
|
||||
|
||||
job.exception = null;
|
||||
|
||||
var j = new Job<object> () {
|
||||
id = GetNextJobId(),
|
||||
priority = job.priority,
|
||||
action = () => job.action(),
|
||||
callbackError = job.callbackError,
|
||||
};
|
||||
|
@ -94,28 +174,25 @@ namespace SanAndreasUnity.Behaviours
|
|||
if(job.callbackFinish != null)
|
||||
j.callbackFinish = (arg) => job.callbackFinish( (T) arg );
|
||||
|
||||
s_jobs.Add (j);
|
||||
Singleton._threadParameters.jobs.Add (j);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.Synchronized)]
|
||||
static bool ShouldThreadExit()
|
||||
static long GetNextJobId()
|
||||
{
|
||||
return s_shouldThreadExit;
|
||||
lock (s_lastJobIdLockObject)
|
||||
{
|
||||
return s_lastJobId++;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.Synchronized)]
|
||||
static void TellThreadToExit()
|
||||
static void ThreadFunction (object objectParameter)
|
||||
{
|
||||
s_shouldThreadExit = true;
|
||||
}
|
||||
ThreadParameters threadParameters = (ThreadParameters) objectParameter;
|
||||
|
||||
static void ThreadFunction ()
|
||||
{
|
||||
|
||||
while (!ShouldThreadExit ())
|
||||
while (!threadParameters.ShouldThreadExit())
|
||||
{
|
||||
Job<object> job;
|
||||
if (!s_jobs.TryTake (out job, 200))
|
||||
if (!threadParameters.jobs.TryTake (out job, 200))
|
||||
continue;
|
||||
|
||||
try
|
||||
|
@ -127,7 +204,7 @@ namespace SanAndreasUnity.Behaviours
|
|||
job.exception = ex;
|
||||
}
|
||||
|
||||
s_processedJobs.Add (job);
|
||||
threadParameters.processedJobs.Enqueue(job);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
14
Assets/Scripts/Utilities/ThreadHelper.cs
Normal file
14
Assets/Scripts/Utilities/ThreadHelper.cs
Normal file
|
@ -0,0 +1,14 @@
|
|||
using System;
|
||||
using System.Threading;
|
||||
|
||||
namespace SanAndreasUnity.Utilities
|
||||
{
|
||||
public static class ThreadHelper
|
||||
{
|
||||
public static void ThrowIfNotOnMainThread()
|
||||
{
|
||||
if (Thread.CurrentThread.ManagedThreadId != 1)
|
||||
throw new Exception("This can only be executed on the main thread");
|
||||
}
|
||||
}
|
||||
}
|
11
Assets/Scripts/Utilities/ThreadHelper.cs.meta
Normal file
11
Assets/Scripts/Utilities/ThreadHelper.cs.meta
Normal file
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 8ed180a98e16c5a4cbcc059ace2bb0ec
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -17,7 +17,7 @@ PhysicsManager:
|
|||
m_ClothInterCollisionDistance: 0
|
||||
m_ClothInterCollisionStiffness: 0
|
||||
m_ContactsGeneration: 1
|
||||
m_LayerCollisionMatrix: ffcfffffffcfffffffcfffffffffffffffcfffffffcfffffffffffffffffffffffcfffffff87ffffffcfffffff8dffffc8c0ffffc8c0ffffffb5ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
||||
m_LayerCollisionMatrix: ff4fffffff4fffffff4fffffffffffffff4fffffff4fffffffffffffffffffffff4fffffff87ffffff4fffffff8dffffc840ffffc840ffffff35ffffc80affffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
||||
m_AutoSimulation: 1
|
||||
m_AutoSyncTransforms: 1
|
||||
m_ReuseCollisionCallbacks: 0
|
||||
|
|
Loading…
Reference in a new issue