feature: Added psxsplash installer, added basic BSP implementation (non-functional)

This commit is contained in:
2025-09-04 18:01:23 +02:00
parent 0d1e363dbb
commit 53e993f58e
17 changed files with 2871 additions and 268 deletions

872
Debug.unity Normal file
View File

@@ -0,0 +1,872 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!29 &1
OcclusionCullingSettings:
m_ObjectHideFlags: 0
serializedVersion: 2
m_OcclusionBakeSettings:
smallestOccluder: 5
smallestHole: 0.25
backfaceThreshold: 100
m_SceneGUID: 00000000000000000000000000000000
m_OcclusionCullingData: {fileID: 0}
--- !u!104 &2
RenderSettings:
m_ObjectHideFlags: 0
serializedVersion: 10
m_Fog: 0
m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1}
m_FogMode: 3
m_FogDensity: 0.01
m_LinearFogStart: 0
m_LinearFogEnd: 300
m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1}
m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1}
m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1}
m_AmbientIntensity: 1
m_AmbientMode: 0
m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1}
m_SkyboxMaterial: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0}
m_HaloStrength: 0.5
m_FlareStrength: 1
m_FlareFadeSpeed: 3
m_HaloTexture: {fileID: 0}
m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0}
m_DefaultReflectionMode: 0
m_DefaultReflectionResolution: 128
m_ReflectionBounces: 1
m_ReflectionIntensity: 1
m_CustomReflection: {fileID: 0}
m_Sun: {fileID: 0}
m_UseRadianceAmbientProbe: 0
--- !u!157 &3
LightmapSettings:
m_ObjectHideFlags: 0
serializedVersion: 13
m_BakeOnSceneLoad: 0
m_GISettings:
serializedVersion: 2
m_BounceScale: 1
m_IndirectOutputScale: 1
m_AlbedoBoost: 1
m_EnvironmentLightingMode: 0
m_EnableBakedLightmaps: 1
m_EnableRealtimeLightmaps: 0
m_LightmapEditorSettings:
serializedVersion: 12
m_Resolution: 2
m_BakeResolution: 40
m_AtlasSize: 1024
m_AO: 0
m_AOMaxDistance: 1
m_CompAOExponent: 1
m_CompAOExponentDirect: 0
m_ExtractAmbientOcclusion: 0
m_Padding: 2
m_LightmapParameters: {fileID: 0}
m_LightmapsBakeMode: 1
m_TextureCompression: 1
m_ReflectionCompression: 2
m_MixedBakeMode: 2
m_BakeBackend: 1
m_PVRSampling: 1
m_PVRDirectSampleCount: 32
m_PVRSampleCount: 512
m_PVRBounces: 2
m_PVREnvironmentSampleCount: 256
m_PVREnvironmentReferencePointCount: 2048
m_PVRFilteringMode: 1
m_PVRDenoiserTypeDirect: 1
m_PVRDenoiserTypeIndirect: 1
m_PVRDenoiserTypeAO: 1
m_PVRFilterTypeDirect: 0
m_PVRFilterTypeIndirect: 0
m_PVRFilterTypeAO: 0
m_PVREnvironmentMIS: 1
m_PVRCulling: 1
m_PVRFilteringGaussRadiusDirect: 1
m_PVRFilteringGaussRadiusIndirect: 1
m_PVRFilteringGaussRadiusAO: 1
m_PVRFilteringAtrousPositionSigmaDirect: 0.5
m_PVRFilteringAtrousPositionSigmaIndirect: 2
m_PVRFilteringAtrousPositionSigmaAO: 1
m_ExportTrainingData: 0
m_TrainingDataDestination: TrainingData
m_LightProbeSampleCountMultiplier: 4
m_LightingDataAsset: {fileID: 20201, guid: 0000000000000000f000000000000000, type: 0}
m_LightingSettings: {fileID: 0}
--- !u!196 &4
NavMeshSettings:
serializedVersion: 2
m_ObjectHideFlags: 0
m_BuildSettings:
serializedVersion: 3
agentTypeID: 0
agentRadius: 0.5
agentHeight: 2
agentSlope: 45
agentClimb: 0.4
ledgeDropHeight: 0
maxJumpAcrossDistance: 0
minRegionArea: 2
manualCellSize: 0
cellSize: 0.16666667
manualTileSize: 0
tileSize: 256
buildHeightMesh: 0
maxJobWorkers: 0
preserveTilesOutsideBounds: 0
debug:
m_Flags: 0
m_NavMeshData: {fileID: 0}
--- !u!1 &283344192
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 283344197}
- component: {fileID: 283344196}
- component: {fileID: 283344195}
- component: {fileID: 283344194}
- component: {fileID: 283344193}
m_Layer: 0
m_Name: f (1)
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!114 &283344193
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 283344192}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: bea0f31a495202580ac77bd9fd6e99f2, type: 3}
m_Name:
m_EditorClassIdentifier:
IsActive: 1
bitDepth: 8
luaFile: {fileID: 0}
previewNormals: 0
normalPreviewLength: 0.5
--- !u!65 &283344194
BoxCollider:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 283344192}
m_Material: {fileID: 0}
m_IncludeLayers:
serializedVersion: 2
m_Bits: 0
m_ExcludeLayers:
serializedVersion: 2
m_Bits: 0
m_LayerOverridePriority: 0
m_IsTrigger: 0
m_ProvidesContacts: 0
m_Enabled: 1
serializedVersion: 3
m_Size: {x: 1, y: 1, z: 1}
m_Center: {x: 0, y: 0, z: 0}
--- !u!23 &283344195
MeshRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 283344192}
m_Enabled: 1
m_CastShadows: 1
m_ReceiveShadows: 1
m_DynamicOccludee: 1
m_StaticShadowCaster: 0
m_MotionVectors: 1
m_LightProbeUsage: 1
m_ReflectionProbeUsage: 1
m_RayTracingMode: 2
m_RayTraceProcedural: 0
m_RayTracingAccelStructBuildFlagsOverride: 0
m_RayTracingAccelStructBuildFlags: 1
m_SmallMeshCulling: 1
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
- {fileID: 2100000, guid: 31321ba15b8f8eb4c954353edc038b1d, type: 2}
m_StaticBatchInfo:
firstSubMesh: 0
subMeshCount: 0
m_StaticBatchRoot: {fileID: 0}
m_ProbeAnchor: {fileID: 0}
m_LightProbeVolumeOverride: {fileID: 0}
m_ScaleInLightmap: 1
m_ReceiveGI: 1
m_PreserveUVs: 0
m_IgnoreNormalsForChartDetection: 0
m_ImportantGI: 0
m_StitchLightmapSeams: 1
m_SelectedEditorRenderState: 3
m_MinimumChartSize: 4
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_SortingLayerID: 0
m_SortingLayer: 0
m_SortingOrder: 0
m_AdditionalVertexStreams: {fileID: 0}
--- !u!33 &283344196
MeshFilter:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 283344192}
m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0}
--- !u!4 &283344197
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 283344192}
serializedVersion: 2
m_LocalRotation: {x: -0.1479161, y: 0.37224695, z: -0.33835596, w: 0.85150945}
m_LocalPosition: {x: -30.19757, y: 0, z: 8.341}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 47.226, z: -43.342}
--- !u!1 &1293128684
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1293128689}
- component: {fileID: 1293128688}
- component: {fileID: 1293128687}
- component: {fileID: 1293128686}
- component: {fileID: 1293128685}
m_Layer: 0
m_Name: f
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!114 &1293128685
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1293128684}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: bea0f31a495202580ac77bd9fd6e99f2, type: 3}
m_Name:
m_EditorClassIdentifier:
IsActive: 1
bitDepth: 8
luaFile: {fileID: 0}
previewNormals: 0
normalPreviewLength: 0.5
--- !u!65 &1293128686
BoxCollider:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1293128684}
m_Material: {fileID: 0}
m_IncludeLayers:
serializedVersion: 2
m_Bits: 0
m_ExcludeLayers:
serializedVersion: 2
m_Bits: 0
m_LayerOverridePriority: 0
m_IsTrigger: 0
m_ProvidesContacts: 0
m_Enabled: 1
serializedVersion: 3
m_Size: {x: 1, y: 1, z: 1}
m_Center: {x: 0, y: 0, z: 0}
--- !u!23 &1293128687
MeshRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1293128684}
m_Enabled: 1
m_CastShadows: 1
m_ReceiveShadows: 1
m_DynamicOccludee: 1
m_StaticShadowCaster: 0
m_MotionVectors: 1
m_LightProbeUsage: 1
m_ReflectionProbeUsage: 1
m_RayTracingMode: 2
m_RayTraceProcedural: 0
m_RayTracingAccelStructBuildFlagsOverride: 0
m_RayTracingAccelStructBuildFlags: 1
m_SmallMeshCulling: 1
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
- {fileID: 2100000, guid: 31321ba15b8f8eb4c954353edc038b1d, type: 2}
m_StaticBatchInfo:
firstSubMesh: 0
subMeshCount: 0
m_StaticBatchRoot: {fileID: 0}
m_ProbeAnchor: {fileID: 0}
m_LightProbeVolumeOverride: {fileID: 0}
m_ScaleInLightmap: 1
m_ReceiveGI: 1
m_PreserveUVs: 0
m_IgnoreNormalsForChartDetection: 0
m_ImportantGI: 0
m_StitchLightmapSeams: 1
m_SelectedEditorRenderState: 3
m_MinimumChartSize: 4
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_SortingLayerID: 0
m_SortingLayer: 0
m_SortingOrder: 0
m_AdditionalVertexStreams: {fileID: 0}
--- !u!33 &1293128688
MeshFilter:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1293128684}
m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0}
--- !u!4 &1293128689
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1293128684}
serializedVersion: 2
m_LocalRotation: {x: -0.13355339, y: 0.35044792, z: -0.3301185, w: 0.8662399}
m_LocalPosition: {x: -30.19757, y: 0, z: 10.84006}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 44.053, z: -41.723}
--- !u!1 &1331845601
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1331845603}
- component: {fileID: 1331845602}
m_Layer: 0
m_Name: Exp
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!114 &1331845602
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1331845601}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: ab5195ad94fd173cfb6d48ee06eaf245, type: 3}
m_Name:
m_EditorClassIdentifier:
GTEScaling: 100
SceneLuaFile: {fileID: 0}
BSPPreviewDepth: 2
--- !u!4 &1331845603
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1331845601}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: -31.5414, y: 0, z: 10.89769}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &1337453585
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1337453587}
- component: {fileID: 1337453586}
m_Layer: 0
m_Name: Directional Light
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!108 &1337453586
Light:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1337453585}
m_Enabled: 1
serializedVersion: 11
m_Type: 1
m_Color: {r: 1, g: 0.95686275, b: 0.8392157, a: 1}
m_Intensity: 1
m_Range: 10
m_SpotAngle: 30
m_InnerSpotAngle: 21.80208
m_CookieSize: 10
m_Shadows:
m_Type: 2
m_Resolution: -1
m_CustomResolution: -1
m_Strength: 1
m_Bias: 0.05
m_NormalBias: 0.4
m_NearPlane: 0.2
m_CullingMatrixOverride:
e00: 1
e01: 0
e02: 0
e03: 0
e10: 0
e11: 1
e12: 0
e13: 0
e20: 0
e21: 0
e22: 1
e23: 0
e30: 0
e31: 0
e32: 0
e33: 1
m_UseCullingMatrixOverride: 0
m_Cookie: {fileID: 0}
m_DrawHalo: 0
m_Flare: {fileID: 0}
m_RenderMode: 0
m_CullingMask:
serializedVersion: 2
m_Bits: 4294967295
m_RenderingLayerMask: 1
m_Lightmapping: 4
m_LightShadowCasterMode: 0
m_AreaSize: {x: 1, y: 1}
m_BounceIntensity: 1
m_ColorTemperature: 6570
m_UseColorTemperature: 0
m_BoundingSphereOverride: {x: 0, y: 0, z: 0, w: 0}
m_UseBoundingSphereOverride: 0
m_UseViewFrustumForShadowCasterCull: 1
m_ForceVisible: 0
m_ShadowRadius: 0
m_ShadowAngle: 0
m_LightUnit: 1
m_LuxAtDistance: 1
m_EnableSpotReflector: 1
--- !u!4 &1337453587
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1337453585}
serializedVersion: 2
m_LocalRotation: {x: 0.40821788, y: -0.23456968, z: 0.10938163, w: 0.8754261}
m_LocalPosition: {x: 0, y: 3, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 50, y: -30, z: 0}
--- !u!1 &1388445967
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1388445970}
- component: {fileID: 1388445969}
- component: {fileID: 1388445968}
m_Layer: 0
m_Name: Main Camera
m_TagString: MainCamera
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!81 &1388445968
AudioListener:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1388445967}
m_Enabled: 1
--- !u!20 &1388445969
Camera:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1388445967}
m_Enabled: 1
serializedVersion: 2
m_ClearFlags: 1
m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0}
m_projectionMatrixMode: 1
m_GateFitMode: 2
m_FOVAxisMode: 0
m_Iso: 200
m_ShutterSpeed: 0.005
m_Aperture: 16
m_FocusDistance: 10
m_FocalLength: 50
m_BladeCount: 5
m_Curvature: {x: 2, y: 11}
m_BarrelClipping: 0.25
m_Anamorphism: 0
m_SensorSize: {x: 36, y: 24}
m_LensShift: {x: 0, y: 0}
m_NormalizedViewPortRect:
serializedVersion: 2
x: 0
y: 0
width: 1
height: 1
near clip plane: 0.3
far clip plane: 1000
field of view: 60
orthographic: 0
orthographic size: 5
m_Depth: -1
m_CullingMask:
serializedVersion: 2
m_Bits: 4294967295
m_RenderingPath: -1
m_TargetTexture: {fileID: 0}
m_TargetDisplay: 0
m_TargetEye: 3
m_HDR: 1
m_AllowMSAA: 1
m_AllowDynamicResolution: 0
m_ForceIntoRT: 0
m_OcclusionCulling: 1
m_StereoConvergence: 10
m_StereoSeparation: 0.022
--- !u!4 &1388445970
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1388445967}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 1, z: -10}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &1392840323
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1392840328}
- component: {fileID: 1392840327}
- component: {fileID: 1392840326}
- component: {fileID: 1392840325}
- component: {fileID: 1392840324}
m_Layer: 0
m_Name: f (2)
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!114 &1392840324
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1392840323}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: bea0f31a495202580ac77bd9fd6e99f2, type: 3}
m_Name:
m_EditorClassIdentifier:
IsActive: 1
bitDepth: 8
luaFile: {fileID: 0}
previewNormals: 0
normalPreviewLength: 0.5
--- !u!65 &1392840325
BoxCollider:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1392840323}
m_Material: {fileID: 0}
m_IncludeLayers:
serializedVersion: 2
m_Bits: 0
m_ExcludeLayers:
serializedVersion: 2
m_Bits: 0
m_LayerOverridePriority: 0
m_IsTrigger: 0
m_ProvidesContacts: 0
m_Enabled: 1
serializedVersion: 3
m_Size: {x: 1, y: 1, z: 1}
m_Center: {x: 0, y: 0, z: 0}
--- !u!23 &1392840326
MeshRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1392840323}
m_Enabled: 1
m_CastShadows: 1
m_ReceiveShadows: 1
m_DynamicOccludee: 1
m_StaticShadowCaster: 0
m_MotionVectors: 1
m_LightProbeUsage: 1
m_ReflectionProbeUsage: 1
m_RayTracingMode: 2
m_RayTraceProcedural: 0
m_RayTracingAccelStructBuildFlagsOverride: 0
m_RayTracingAccelStructBuildFlags: 1
m_SmallMeshCulling: 1
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
- {fileID: 2100000, guid: 31321ba15b8f8eb4c954353edc038b1d, type: 2}
m_StaticBatchInfo:
firstSubMesh: 0
subMeshCount: 0
m_StaticBatchRoot: {fileID: 0}
m_ProbeAnchor: {fileID: 0}
m_LightProbeVolumeOverride: {fileID: 0}
m_ScaleInLightmap: 1
m_ReceiveGI: 1
m_PreserveUVs: 0
m_IgnoreNormalsForChartDetection: 0
m_ImportantGI: 0
m_StitchLightmapSeams: 1
m_SelectedEditorRenderState: 3
m_MinimumChartSize: 4
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_SortingLayerID: 0
m_SortingLayer: 0
m_SortingOrder: 0
m_AdditionalVertexStreams: {fileID: 0}
--- !u!33 &1392840327
MeshFilter:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1392840323}
m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0}
--- !u!4 &1392840328
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1392840323}
serializedVersion: 2
m_LocalRotation: {x: -0.409346, y: -0.7736037, z: 0.22623056, w: 0.42754278}
m_LocalPosition: {x: -30.19757, y: 2.041, z: 10.84006}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: -122.144, z: 55.77}
--- !u!1 &1513869424
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1513869429}
- component: {fileID: 1513869428}
- component: {fileID: 1513869427}
- component: {fileID: 1513869426}
- component: {fileID: 1513869425}
m_Layer: 0
m_Name: f (3)
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!114 &1513869425
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1513869424}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: bea0f31a495202580ac77bd9fd6e99f2, type: 3}
m_Name:
m_EditorClassIdentifier:
IsActive: 1
bitDepth: 8
luaFile: {fileID: 0}
previewNormals: 0
normalPreviewLength: 0.5
--- !u!65 &1513869426
BoxCollider:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1513869424}
m_Material: {fileID: 0}
m_IncludeLayers:
serializedVersion: 2
m_Bits: 0
m_ExcludeLayers:
serializedVersion: 2
m_Bits: 0
m_LayerOverridePriority: 0
m_IsTrigger: 0
m_ProvidesContacts: 0
m_Enabled: 1
serializedVersion: 3
m_Size: {x: 1, y: 1, z: 1}
m_Center: {x: 0, y: 0, z: 0}
--- !u!23 &1513869427
MeshRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1513869424}
m_Enabled: 1
m_CastShadows: 1
m_ReceiveShadows: 1
m_DynamicOccludee: 1
m_StaticShadowCaster: 0
m_MotionVectors: 1
m_LightProbeUsage: 1
m_ReflectionProbeUsage: 1
m_RayTracingMode: 2
m_RayTraceProcedural: 0
m_RayTracingAccelStructBuildFlagsOverride: 0
m_RayTracingAccelStructBuildFlags: 1
m_SmallMeshCulling: 1
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
- {fileID: 2100000, guid: 31321ba15b8f8eb4c954353edc038b1d, type: 2}
m_StaticBatchInfo:
firstSubMesh: 0
subMeshCount: 0
m_StaticBatchRoot: {fileID: 0}
m_ProbeAnchor: {fileID: 0}
m_LightProbeVolumeOverride: {fileID: 0}
m_ScaleInLightmap: 1
m_ReceiveGI: 1
m_PreserveUVs: 0
m_IgnoreNormalsForChartDetection: 0
m_ImportantGI: 0
m_StitchLightmapSeams: 1
m_SelectedEditorRenderState: 3
m_MinimumChartSize: 4
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_SortingLayerID: 0
m_SortingLayer: 0
m_SortingOrder: 0
m_AdditionalVertexStreams: {fileID: 0}
--- !u!33 &1513869428
MeshFilter:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1513869424}
m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0}
--- !u!4 &1513869429
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1513869424}
serializedVersion: 2
m_LocalRotation: {x: -0.09615398, y: 0.2551484, z: 0.3392829, w: 0.9003004}
m_LocalPosition: {x: -27.156, y: 0, z: 9.065}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: -20.259, y: 24.845, z: 36.791}
--- !u!1660057539 &9223372036854775807
SceneRoots:
m_ObjectHideFlags: 0
m_Roots:
- {fileID: 1388445970}
- {fileID: 1337453587}
- {fileID: 1293128689}
- {fileID: 283344197}
- {fileID: 1392840328}
- {fileID: 1513869429}
- {fileID: 1331845603}

7
Debug.unity.meta Normal file
View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 14e1ffa687155c145bfed30e28f26bc1
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -2,7 +2,13 @@ using UnityEngine;
using UnityEditor; using UnityEditor;
using System.Collections.Generic; using System.Collections.Generic;
using SplashEdit.RuntimeCode; using SplashEdit.RuntimeCode;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
namespace SplashEdit.EditorCode
{
public class InstallerWindow : EditorWindow public class InstallerWindow : EditorWindow
{ {
// Cached status for MIPS toolchain binaries. // Cached status for MIPS toolchain binaries.
@@ -13,6 +19,18 @@ public class InstallerWindow : EditorWindow
private bool gdbInstalled; private bool gdbInstalled;
private string pcsxReduxPath; private string pcsxReduxPath;
// PSXSplash related variables
private bool psxsplashInstalled = false;
private bool psxsplashInstalling = false;
private bool psxsplashFetching = false;
private string selectedVersion = "main";
private Dictionary<string, string> availableBranches = new Dictionary<string, string>();
private List<string> availableReleases = new List<string>();
private bool showBranches = true;
private bool showReleases = false;
private Vector2 scrollPosition;
private Vector2 versionScrollPosition;
private bool isInstalling = false; private bool isInstalling = false;
[MenuItem("PSX/Toolchain & Build Tools Installer")] [MenuItem("PSX/Toolchain & Build Tools Installer")]
@@ -21,6 +39,7 @@ public class InstallerWindow : EditorWindow
InstallerWindow window = GetWindow<InstallerWindow>("Toolchain Installer"); InstallerWindow window = GetWindow<InstallerWindow>("Toolchain Installer");
window.RefreshToolStatus(); window.RefreshToolStatus();
window.pcsxReduxPath = DataStorage.LoadData().PCSXReduxPath; window.pcsxReduxPath = DataStorage.LoadData().PCSXReduxPath;
window.CheckPSXSplashInstallation();
} }
/// <summary> /// <summary>
@@ -38,26 +57,90 @@ public class InstallerWindow : EditorWindow
gdbInstalled = ToolchainChecker.IsToolAvailable("gdb-multiarch"); gdbInstalled = ToolchainChecker.IsToolAvailable("gdb-multiarch");
} }
private void CheckPSXSplashInstallation()
{
psxsplashInstalled = PSXSplashInstaller.IsInstalled();
if (psxsplashInstalled)
{
FetchPSXSplashVersions();
}
else
{
availableBranches = new Dictionary<string, string>();
availableReleases = new List<string>();
}
}
private async void FetchPSXSplashVersions()
{
if (psxsplashFetching) return;
psxsplashFetching = true;
try
{
// Fetch latest from remote
await PSXSplashInstaller.FetchLatestAsync();
// Get all available versions
var branchesTask = PSXSplashInstaller.GetBranchesWithLatestCommitsAsync();
var releasesTask = PSXSplashInstaller.GetReleasesAsync();
await Task.WhenAll(branchesTask, releasesTask);
availableBranches = branchesTask.Result;
availableReleases = releasesTask.Result;
// If no branches were found, add main as default
if (!availableBranches.Any())
{
availableBranches["main"] = "latest";
}
// Select the first branch by default
if (availableBranches.Any() && string.IsNullOrEmpty(selectedVersion))
{
selectedVersion = availableBranches.Keys.First();
}
Repaint();
}
catch (System.Exception e)
{
UnityEngine.Debug.LogError($"Failed to fetch PSXSplash versions: {e.Message}");
}
finally
{
psxsplashFetching = false;
}
}
private void OnGUI() private void OnGUI()
{ {
scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition);
GUILayout.Label("Toolchain & Build Tools Installer", EditorStyles.boldLabel); GUILayout.Label("Toolchain & Build Tools Installer", EditorStyles.boldLabel);
GUILayout.Space(5); GUILayout.Space(5);
if (GUILayout.Button("Refresh Status")) if (GUILayout.Button("Refresh Status"))
{ {
RefreshToolStatus(); RefreshToolStatus();
CheckPSXSplashInstallation();
} }
GUILayout.Space(10); GUILayout.Space(10);
EditorGUILayout.BeginHorizontal(); EditorGUILayout.BeginHorizontal();
DrawToolchainColumn(); DrawToolchainColumn();
DrawAdditionalToolsColumn(); DrawAdditionalToolsColumn();
DrawPSXSplashColumn();
EditorGUILayout.EndHorizontal(); EditorGUILayout.EndHorizontal();
EditorGUILayout.EndScrollView();
} }
private void DrawToolchainColumn() private void DrawToolchainColumn()
{ {
EditorGUILayout.BeginVertical("box", GUILayout.MaxWidth(position.width / 2 - 10)); EditorGUILayout.BeginVertical("box", GUILayout.MaxWidth(position.width / 3 - 10));
GUILayout.Label("MIPS Toolchain", EditorStyles.boldLabel); GUILayout.Label("MIPS Toolchain", EditorStyles.boldLabel);
GUILayout.Space(5); GUILayout.Space(5);
@@ -80,7 +163,7 @@ public class InstallerWindow : EditorWindow
private void DrawAdditionalToolsColumn() private void DrawAdditionalToolsColumn()
{ {
EditorGUILayout.BeginVertical("box", GUILayout.MaxWidth(position.width / 2 - 10)); EditorGUILayout.BeginVertical("box", GUILayout.MaxWidth(position.width / 3 - 10));
GUILayout.Label("Optional Tools", EditorStyles.boldLabel); GUILayout.Label("Optional Tools", EditorStyles.boldLabel);
GUILayout.Space(5); GUILayout.Space(5);
@@ -141,6 +224,229 @@ public class InstallerWindow : EditorWindow
EditorGUILayout.EndVertical(); EditorGUILayout.EndVertical();
} }
private void DrawPSXSplashColumn()
{
EditorGUILayout.BeginVertical("box", GUILayout.MaxWidth(position.width / 3 - 10));
GUILayout.Label("PSXSplash", EditorStyles.boldLabel);
GUILayout.Space(5);
// PSXSplash status
GUI.color = psxsplashInstalled ? Color.green : Color.red;
GUILayout.Label($"PSXSplash: {(psxsplashInstalled ? "Installed" : "Not Installed")}");
GUI.color = Color.white;
if (psxsplashFetching)
{
GUILayout.Label("Fetching versions...");
}
else if (!psxsplashInstalled)
{
GUILayout.Space(5);
EditorGUILayout.HelpBox("Git is required to install PSXSplash. Make sure it's installed and available in your PATH.", MessageType.Info);
// Show version selection even before installation
DrawVersionSelection();
if (GUILayout.Button("Install PSXSplash") && !psxsplashInstalling)
{
InstallPSXSplashAsync();
}
}
else
{
GUILayout.Space(10);
// Current version
EditorGUILayout.LabelField($"Current Version: {selectedVersion}", EditorStyles.boldLabel);
// Version selection
DrawVersionSelection();
GUILayout.Space(10);
// Refresh and update buttons
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("Refresh Versions"))
{
FetchPSXSplashVersions();
}
if (GUILayout.Button("Update PSXSplash"))
{
UpdatePSXSplashAsync();
}
EditorGUILayout.EndHorizontal();
}
EditorGUILayout.EndVertical();
}
private void DrawVersionSelection()
{
EditorGUILayout.LabelField("Available Versions:", EditorStyles.boldLabel);
versionScrollPosition = EditorGUILayout.BeginScrollView(versionScrollPosition, GUILayout.Height(200));
// Branches (with latest commits)
showBranches = EditorGUILayout.Foldout(showBranches, $"Branches ({availableBranches.Count})");
if (showBranches && availableBranches.Any())
{
foreach (var branch in availableBranches)
{
EditorGUILayout.BeginHorizontal();
bool isSelected = selectedVersion == branch.Key;
if (GUILayout.Toggle(isSelected, "", GUILayout.Width(20)) && !isSelected)
{
selectedVersion = branch.Key;
if (psxsplashInstalled)
{
CheckoutPSXSplashVersionAsync(branch.Key);
}
}
GUILayout.Label($"{branch.Key} (Latest: {branch.Value})", EditorStyles.label);
EditorGUILayout.EndHorizontal();
}
}
else if (showBranches)
{
GUILayout.Label("No branches available");
}
// Releases
showReleases = EditorGUILayout.Foldout(showReleases, $"Releases ({availableReleases.Count})");
if (showReleases && availableReleases.Any())
{
foreach (var release in availableReleases)
{
EditorGUILayout.BeginHorizontal();
bool isSelected = selectedVersion == release;
if (GUILayout.Toggle(isSelected, "", GUILayout.Width(20)) && !isSelected)
{
selectedVersion = release;
if (psxsplashInstalled)
{
CheckoutPSXSplashVersionAsync(release);
}
}
GUILayout.Label(release, EditorStyles.label);
EditorGUILayout.EndHorizontal();
}
}
else if (showReleases)
{
GUILayout.Label("No releases available");
}
EditorGUILayout.EndScrollView();
}
private async void InstallPSXSplashAsync()
{
try
{
psxsplashInstalling = true;
EditorUtility.DisplayProgressBar("Installing PSXSplash", "Cloning repository...", 0.3f);
bool success = await PSXSplashInstaller.Install();
EditorUtility.ClearProgressBar();
if (success)
{
EditorUtility.DisplayDialog("Installation Complete", "PSXSplash installed successfully.", "OK");
CheckPSXSplashInstallation();
// Checkout the selected version after installation
if (!string.IsNullOrEmpty(selectedVersion))
{
await CheckoutPSXSplashVersionAsync(selectedVersion);
}
}
else
{
EditorUtility.DisplayDialog("Installation Failed",
"Failed to install PSXSplash. Make sure Git is installed and available in your PATH.", "OK");
}
}
catch (System.Exception ex)
{
EditorUtility.ClearProgressBar();
EditorUtility.DisplayDialog("Installation Failed", $"Error: {ex.Message}", "OK");
}
finally
{
psxsplashInstalling = false;
}
}
private async Task<bool> CheckoutPSXSplashVersionAsync(string version)
{
try
{
psxsplashInstalling = true;
EditorUtility.DisplayProgressBar("Checking Out Version", $"Switching to {version}...", 0.3f);
bool success = await PSXSplashInstaller.CheckoutVersionAsync(version);
EditorUtility.ClearProgressBar();
if (success)
{
EditorUtility.DisplayDialog("Checkout Complete", $"Switched to {version} successfully.", "OK");
return true;
}
else
{
EditorUtility.DisplayDialog("Checkout Failed",
$"Failed to switch to {version}.", "OK");
return false;
}
}
catch (System.Exception ex)
{
EditorUtility.ClearProgressBar();
EditorUtility.DisplayDialog("Checkout Failed", $"Error: {ex.Message}", "OK");
return false;
}
finally
{
psxsplashInstalling = false;
}
}
private async void UpdatePSXSplashAsync()
{
try
{
psxsplashInstalling = true;
EditorUtility.DisplayProgressBar("Updating PSXSplash", "Pulling latest changes...", 0.3f);
// Pull the latest changes
bool success = await PSXSplashInstaller.CheckoutVersionAsync(selectedVersion);
EditorUtility.ClearProgressBar();
if (success)
{
EditorUtility.DisplayDialog("Update Complete", "PSXSplash updated successfully.", "OK");
}
else
{
EditorUtility.DisplayDialog("Update Failed",
"Failed to update PSXSplash.", "OK");
}
}
catch (System.Exception ex)
{
EditorUtility.ClearProgressBar();
EditorUtility.DisplayDialog("Update Failed", $"Error: {ex.Message}", "OK");
}
finally
{
psxsplashInstalling = false;
}
}
private async void InstallMipsToolchainAsync() private async void InstallMipsToolchainAsync()
{ {
try try
@@ -213,3 +519,4 @@ public class InstallerWindow : EditorWindow
} }
} }
} }
}

View File

@@ -0,0 +1,203 @@
using UnityEngine;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Collections.Generic;
using System.Threading.Tasks;
using System;
namespace SplashEdit.EditorCode
{
public static class PSXSplashInstaller
{
public static readonly string RepoUrl = "https://github.com/psxsplash/psxsplash.git";
public static readonly string InstallPath = "Assets/psxsplash";
public static readonly string FullInstallPath;
static PSXSplashInstaller()
{
FullInstallPath = Path.Combine(Application.dataPath, "psxsplash");
}
public static bool IsInstalled()
{
return Directory.Exists(FullInstallPath) &&
Directory.EnumerateFileSystemEntries(FullInstallPath).Any();
}
public static async Task<bool> Install()
{
if (IsInstalled()) return true;
try
{
// Create the parent directory if it doesn't exist
Directory.CreateDirectory(Application.dataPath);
// Clone the repository
var result = await RunGitCommandAsync($"clone --recursive {RepoUrl} \"{FullInstallPath}\"", Application.dataPath);
return !result.Contains("error");
}
catch (Exception e)
{
UnityEngine.Debug.LogError($"Failed to install PSXSplash: {e.Message}");
return false;
}
}
public static async Task<Dictionary<string, string>> GetBranchesWithLatestCommitsAsync()
{
if (!IsInstalled()) return new Dictionary<string, string>();
try
{
// Fetch all branches and tags
await RunGitCommandAsync("fetch --all", FullInstallPath);
// Get all remote branches
var branchesOutput = await RunGitCommandAsync("branch -r", FullInstallPath);
var branches = branchesOutput.Split('\n')
.Where(b => !string.IsNullOrEmpty(b.Trim()))
.Select(b => b.Trim().Replace("origin/", ""))
.Where(b => !b.Contains("HEAD"))
.ToList();
var branchesWithCommits = new Dictionary<string, string>();
// Get the latest commit for each branch
foreach (var branch in branches)
{
var commitOutput = await RunGitCommandAsync($"log origin/{branch} -1 --pretty=format:%h", FullInstallPath);
if (!string.IsNullOrEmpty(commitOutput))
{
branchesWithCommits[branch] = commitOutput.Trim();
}
}
return branchesWithCommits;
}
catch (Exception e)
{
UnityEngine.Debug.LogError($"Failed to get branches: {e.Message}");
return new Dictionary<string, string>();
}
}
public static async Task<List<string>> GetReleasesAsync()
{
if (!IsInstalled()) return new List<string>();
try
{
await RunGitCommandAsync("fetch --tags", FullInstallPath);
var output = await RunGitCommandAsync("tag -l", FullInstallPath);
return output.Split('\n')
.Where(t => !string.IsNullOrEmpty(t.Trim()))
.Select(t => t.Trim())
.ToList();
}
catch (Exception e)
{
UnityEngine.Debug.LogError($"Failed to get releases: {e.Message}");
return new List<string>();
}
}
public static async Task<bool> CheckoutVersionAsync(string version)
{
if (!IsInstalled()) return false;
try
{
// If it's a branch name, checkout the branch
// If it's a commit hash, checkout the commit
var result = await RunGitCommandAsync($"checkout {version}", FullInstallPath);
var result2 = await RunGitCommandAsync("submodule update --init --recursive", FullInstallPath);
return !result.Contains("error") && !result2.Contains("error");
}
catch (Exception e)
{
UnityEngine.Debug.LogError($"Failed to checkout version: {e.Message}");
return false;
}
}
public static async Task<bool> FetchLatestAsync()
{
if (!IsInstalled()) return false;
try
{
var result = await RunGitCommandAsync("fetch --all", FullInstallPath);
return !result.Contains("error");
}
catch (Exception e)
{
UnityEngine.Debug.LogError($"Failed to fetch latest: {e.Message}");
return false;
}
}
private static async Task<string> RunGitCommandAsync(string arguments, string workingDirectory)
{
var processInfo = new ProcessStartInfo
{
FileName = "git",
Arguments = arguments,
WorkingDirectory = workingDirectory,
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true
};
using (var process = new Process())
{
process.StartInfo = processInfo;
var outputBuilder = new System.Text.StringBuilder();
var errorBuilder = new System.Text.StringBuilder();
process.OutputDataReceived += (sender, e) =>
{
if (!string.IsNullOrEmpty(e.Data))
outputBuilder.AppendLine(e.Data);
};
process.ErrorDataReceived += (sender, e) =>
{
if (!string.IsNullOrEmpty(e.Data))
errorBuilder.AppendLine(e.Data);
};
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
// Wait for exit with timeout
var timeout = TimeSpan.FromSeconds(30);
if (await Task.Run(() => process.WaitForExit((int)timeout.TotalMilliseconds)))
{
process.WaitForExit(); // Ensure all output is processed
string output = outputBuilder.ToString();
string error = errorBuilder.ToString();
if (!string.IsNullOrEmpty(error))
{
UnityEngine.Debug.LogError($"Git error: {error}");
}
return output;
}
else
{
process.Kill();
throw new TimeoutException("Git command timed out");
}
}
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 72d1da27a16f0794cb1ad49c00799e74

View File

@@ -0,0 +1,39 @@
using System.IO.Ports;
namespace SplashEdit.EditorCode
{
public class SerialConnection
{
private static SerialPort serialPort;
public SerialConnection(string portName, int baudRate)
{
serialPort = new SerialPort(portName, baudRate);
serialPort.ReadTimeout = 50;
serialPort.WriteTimeout = 50;
}
public void Open()
{ serialPort.Open(); }
public void Close()
{ serialPort.Close(); }
public int ReadByte()
{ return serialPort.ReadByte(); }
public int ReadChar()
{ return serialPort.ReadChar(); }
public void Write(string text)
{ serialPort.Write(text); }
public void Write(char[] buffer, int offset, int count)
{ serialPort.Write(buffer, offset, count); }
public void Write(byte[] buffer, int offset, int count)
{ serialPort.Write(buffer, offset, count); }
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 714bd2374b7a9a14686078e5eb431795

View File

@@ -0,0 +1,22 @@
namespace SplashEdit.EditorCode
{
public class UniromConnection
{
private SerialConnection serialConnection;
public UniromConnection(int baudRate, string portName)
{
serialConnection = new SerialConnection(portName, baudRate);
}
public void Reset()
{
serialConnection.Open();
serialConnection.Write("REST");
serialConnection.Close();
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: d8fbc734f42ab9d42a843b6718127da7

View File

@@ -0,0 +1,145 @@
using UnityEngine;
using UnityEditor;
using System.IO.Ports;
using System.Collections;
using SplashEdit.RuntimeCode;
using System.Runtime.InteropServices;
namespace SplashEdit.EditorCode
{
public class PSXConnectionConfigWindow : EditorWindow
{
public PSXConnectionType connectionType = PSXConnectionType.REAL_HARDWARE;
// REAL HARDWARE (Unirom) SETTINGS
private string[] portNames;
private int selectedPortIndex = 1;
private int[] baudRates = { 9600, 115200 };
private int selectedBaudIndex = 0;
private string statusMessage = "";
private MessageType statusType;
private Vector2 scrollPosition;
[MenuItem("PSX/Console or Emulator Connection")]
public static void ShowWindow()
{
GetWindow<PSXConnectionConfigWindow>("Serial Config");
}
private void OnEnable()
{
RefreshPorts();
LoadSettings();
}
private void RefreshPorts()
{
portNames = SerialPort.GetPortNames();
if (portNames.Length == 0)
{
portNames = new[] { "No ports available" };
}
}
private void OnGUI()
{
using (var scrollView = new EditorGUILayout.ScrollViewScope(scrollPosition))
{
scrollPosition = scrollView.scrollPosition;
EditorGUILayout.LabelField("Pick connection type", EditorStyles.boldLabel);
connectionType = (PSXConnectionType)EditorGUILayout.EnumPopup("Connection Type", connectionType);
if (connectionType == PSXConnectionType.REAL_HARDWARE)
{
// Port selection
EditorGUILayout.LabelField("Select COM Port", EditorStyles.boldLabel);
selectedPortIndex = EditorGUILayout.Popup("Available Ports", selectedPortIndex, portNames);
// Baud rate selection
EditorGUILayout.Space();
EditorGUILayout.LabelField("Select Baud Rate", EditorStyles.boldLabel);
selectedBaudIndex = EditorGUILayout.Popup("Baud Rate", selectedBaudIndex, new[] { "9600", "115200" });
// Buttons
EditorGUILayout.Space();
using (new EditorGUILayout.HorizontalScope())
{
if (GUILayout.Button("Refresh Ports"))
{
RefreshPorts();
}
if (GUILayout.Button("Test Connection"))
{
TestConnection();
}
}
}
if (GUILayout.Button("Save settings"))
{
SaveSettings();
}
// Status message
EditorGUILayout.Space();
if (!string.IsNullOrEmpty(statusMessage))
{
EditorGUILayout.HelpBox(statusMessage, statusType);
}
}
}
private void LoadSettings()
{
PSXData _psxData = DataStorage.LoadData();
if (_psxData != null)
{
connectionType = _psxData.ConnectionType;
selectedBaudIndex = System.Array.IndexOf(baudRates, _psxData.BaudRate);
if (selectedBaudIndex == -1) selectedBaudIndex = 0;
RefreshPorts();
selectedPortIndex = System.Array.IndexOf(portNames, _psxData.PortName);
if (selectedPortIndex == -1) selectedPortIndex = 0;
}
}
private void TestConnection()
{
if (portNames.Length == 0 || portNames[0] == "No ports available")
{
statusMessage = "No serial ports available";
statusType = MessageType.Error;
return;
}
UniromConnection connection = new UniromConnection(baudRates[selectedBaudIndex], portNames[selectedPortIndex]);
connection.Reset();
statusMessage = "Connection tested. If your PlayStation reset, it worked!";
statusType = MessageType.Info;
Repaint();
}
private void SaveSettings()
{
PSXData _psxData = DataStorage.LoadData();
_psxData.ConnectionType = connectionType;
_psxData.BaudRate = baudRates[selectedBaudIndex];
_psxData.PortName = portNames[selectedPortIndex];
DataStorage.StoreData(_psxData);
statusMessage = "Settings saved";
statusType = MessageType.Info;
Repaint();
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: ade0bf0fd69f449458c5b43e0f48ddff

845
Runtime/BSP.cs Normal file
View File

@@ -0,0 +1,845 @@
using System.Collections.Generic;
using UnityEngine;
using System.Diagnostics;
using SplashEdit.RuntimeCode;
public class BSP
{
private List<PSXObjectExporter> _objects;
private Node root;
private const float EPSILON = 1e-6f;
private const int MAX_TRIANGLES_PER_LEAF = 256;
private const int MAX_TREE_DEPTH = 50;
private const int CANDIDATE_PLANE_COUNT = 15;
// Statistics
private int totalTrianglesProcessed;
private int totalSplits;
private int treeDepth;
private Stopwatch buildTimer;
public bool verboseLogging = false;
// Store the triangle that was used for the split plane for debugging
private Dictionary<Node, Triangle> splitPlaneTriangles = new Dictionary<Node, Triangle>();
private struct Triangle
{
public Vector3 v0;
public Vector3 v1;
public Vector3 v2;
public Vector3 n0;
public Vector3 n1;
public Vector3 n2;
public Vector2 uv0;
public Vector2 uv1;
public Vector2 uv2;
public Plane plane;
public Bounds bounds;
public PSXObjectExporter sourceExporter;
public int materialIndex; // Store material index instead of submesh index
public Triangle(Vector3 a, Vector3 b, Vector3 c, Vector3 na, Vector3 nb, Vector3 nc,
Vector2 uva, Vector2 uvb, Vector2 uvc, PSXObjectExporter exporter, int matIndex)
{
v0 = a;
v1 = b;
v2 = c;
n0 = na;
n1 = nb;
n2 = nc;
uv0 = uva;
uv1 = uvb;
uv2 = uvc;
sourceExporter = exporter;
materialIndex = matIndex;
// Calculate plane
Vector3 edge1 = v1 - v0;
Vector3 edge2 = v2 - v0;
Vector3 normal = Vector3.Cross(edge1, edge2);
if (normal.sqrMagnitude < 1e-4f)
{
plane = new Plane(Vector3.up, 0);
}
else
{
normal.Normalize();
plane = new Plane(normal, v0);
}
// Calculate bounds
bounds = new Bounds(v0, Vector3.zero);
bounds.Encapsulate(v1);
bounds.Encapsulate(v2);
}
public void Transform(Matrix4x4 matrix)
{
v0 = matrix.MultiplyPoint3x4(v0);
v1 = matrix.MultiplyPoint3x4(v1);
v2 = matrix.MultiplyPoint3x4(v2);
// Transform normals (using inverse transpose for correct scaling)
Matrix4x4 invTranspose = matrix.inverse.transpose;
n0 = invTranspose.MultiplyVector(n0).normalized;
n1 = invTranspose.MultiplyVector(n1).normalized;
n2 = invTranspose.MultiplyVector(n2).normalized;
// Recalculate plane and bounds after transformation
Vector3 edge1 = v1 - v0;
Vector3 edge2 = v2 - v0;
Vector3 normal = Vector3.Cross(edge1, edge2);
if (normal.sqrMagnitude < 1e-4f)
{
plane = new Plane(Vector3.up, 0);
}
else
{
normal.Normalize();
plane = new Plane(normal, v0);
}
bounds = new Bounds(v0, Vector3.zero);
bounds.Encapsulate(v1);
bounds.Encapsulate(v2);
}
}
private class Node
{
public Plane plane;
public Node front;
public Node back;
public List<Triangle> triangles;
public bool isLeaf = false;
public Bounds bounds;
public int depth;
public int triangleSourceIndex = -1;
}
public BSP(List<PSXObjectExporter> objects)
{
_objects = objects;
buildTimer = new Stopwatch();
}
public void Build()
{
buildTimer.Start();
List<Triangle> triangles = ExtractTrianglesFromMeshes();
totalTrianglesProcessed = triangles.Count;
if (verboseLogging)
UnityEngine.Debug.Log($"Starting BSP build with {totalTrianglesProcessed} triangles");
if (triangles.Count == 0)
{
root = null;
return;
}
// Calculate overall bounds
Bounds overallBounds = CalculateBounds(triangles);
// Build tree recursively with depth tracking
root = BuildNode(triangles, overallBounds, 0);
// Create modified meshes for all exporters
CreateModifiedMeshes();
buildTimer.Stop();
if (verboseLogging)
{
UnityEngine.Debug.Log($"BSP build completed in {buildTimer.Elapsed.TotalMilliseconds}ms");
UnityEngine.Debug.Log($"Total splits: {totalSplits}, Max depth: {treeDepth}");
}
}
private List<Triangle> ExtractTrianglesFromMeshes()
{
List<Triangle> triangles = new List<Triangle>();
foreach (var meshObj in _objects)
{
if (!meshObj.IsActive) continue;
MeshFilter mf = meshObj.GetComponent<MeshFilter>();
Renderer renderer = meshObj.GetComponent<Renderer>();
if (mf == null || mf.sharedMesh == null || renderer == null) continue;
Mesh mesh = mf.sharedMesh;
Vector3[] vertices = mesh.vertices;
Vector3[] normals = mesh.normals.Length > 0 ? mesh.normals : new Vector3[vertices.Length];
Vector2[] uvs = mesh.uv.Length > 0 ? mesh.uv : new Vector2[vertices.Length];
Matrix4x4 matrix = meshObj.transform.localToWorldMatrix;
// Handle case where normals are missing
if (mesh.normals.Length == 0)
{
for (int i = 0; i < normals.Length; i++)
{
normals[i] = Vector3.up;
}
}
// Handle case where UVs are missing
if (mesh.uv.Length == 0)
{
for (int i = 0; i < uvs.Length; i++)
{
uvs[i] = Vector2.zero;
}
}
// Process each submesh and track material index
for (int submesh = 0; submesh < mesh.subMeshCount; submesh++)
{
int materialIndex = Mathf.Min(submesh, renderer.sharedMaterials.Length - 1);
int[] indices = mesh.GetTriangles(submesh);
for (int i = 0; i < indices.Length; i += 3)
{
int idx0 = indices[i];
int idx1 = indices[i + 1];
int idx2 = indices[i + 2];
Vector3 v0 = vertices[idx0];
Vector3 v1 = vertices[idx1];
Vector3 v2 = vertices[idx2];
// Skip degenerate triangles
if (Vector3.Cross(v1 - v0, v2 - v0).sqrMagnitude < 1e-4f)
continue;
Vector3 n0 = normals[idx0];
Vector3 n1 = normals[idx1];
Vector3 n2 = normals[idx2];
Vector2 uv0 = uvs[idx0];
Vector2 uv1 = uvs[idx1];
Vector2 uv2 = uvs[idx2];
Triangle tri = new Triangle(v0, v1, v2, n0, n1, n2, uv0, uv1, uv2, meshObj, materialIndex);
tri.Transform(matrix);
triangles.Add(tri);
}
}
}
return triangles;
}
private Node BuildNode(List<Triangle> triangles, Bounds bounds, int depth)
{
if (triangles == null || triangles.Count == 0)
return null;
Node node = new Node
{
triangles = new List<Triangle>(),
bounds = bounds,
depth = depth
};
treeDepth = Mathf.Max(treeDepth, depth);
// Create leaf node if conditions are met
if (triangles.Count <= MAX_TRIANGLES_PER_LEAF || depth >= MAX_TREE_DEPTH)
{
node.isLeaf = true;
node.triangles = triangles;
if (verboseLogging && depth >= MAX_TREE_DEPTH)
UnityEngine.Debug.LogWarning($"Max tree depth reached at depth {depth} with {triangles.Count} triangles");
return node;
}
// Select the best splitting plane using multiple strategies
Triangle? splitTriangle = null;
if (!SelectBestSplittingPlane(triangles, bounds, out node.plane, out splitTriangle))
{
// Fallback: create leaf if no good split found
node.isLeaf = true;
node.triangles = triangles;
if (verboseLogging)
UnityEngine.Debug.Log($"Created leaf node with {triangles.Count} triangles (no good split found)");
return node;
}
// Store the triangle that provided the split plane for debugging
if (splitTriangle.HasValue)
{
splitPlaneTriangles[node] = splitTriangle.Value;
}
List<Triangle> frontList = new List<Triangle>();
List<Triangle> backList = new List<Triangle>();
List<Triangle> coplanarList = new List<Triangle>();
// Classify all triangles
foreach (var tri in triangles)
{
ClassifyTriangle(tri, node.plane, coplanarList, frontList, backList);
}
// Handle cases where splitting doesn't provide benefit
if (frontList.Count == 0 || backList.Count == 0)
{
// If split doesn't separate geometry, create a leaf
node.isLeaf = true;
node.triangles = triangles;
if (verboseLogging)
UnityEngine.Debug.Log($"Created leaf node with {triangles.Count} triangles (ineffective split)");
return node;
}
// Distribute coplanar triangles to the side with fewer triangles
if (coplanarList.Count > 0)
{
if (frontList.Count <= backList.Count)
{
frontList.AddRange(coplanarList);
}
else
{
backList.AddRange(coplanarList);
}
}
if (verboseLogging)
UnityEngine.Debug.Log($"Node at depth {depth}: {triangles.Count} triangles -> {frontList.Count} front, {backList.Count} back");
// Calculate bounds for children
Bounds frontBounds = CalculateBounds(frontList);
Bounds backBounds = CalculateBounds(backList);
// Recursively build child nodes
node.front = BuildNode(frontList, frontBounds, depth + 1);
node.back = BuildNode(backList, backBounds, depth + 1);
return node;
}
private bool SelectBestSplittingPlane(List<Triangle> triangles, Bounds bounds, out Plane bestPlane, out Triangle? splitTriangle)
{
bestPlane = new Plane();
splitTriangle = null;
int bestScore = int.MaxValue;
bool foundValidPlane = false;
// Strategy 1: Try planes from triangle centroids
int candidatesToTry = Mathf.Min(CANDIDATE_PLANE_COUNT, triangles.Count);
for (int i = 0; i < candidatesToTry; i++)
{
Triangle tri = triangles[i];
Plane candidate = tri.plane;
int score = EvaluateSplitPlane(triangles, candidate);
if (score < bestScore && score >= 0)
{
bestScore = score;
bestPlane = candidate;
splitTriangle = tri;
foundValidPlane = true;
}
}
// Strategy 2: Try axis-aligned planes through bounds center
if (!foundValidPlane || bestScore > triangles.Count * 3)
{
Vector3[] axes = { Vector3.right, Vector3.up, Vector3.forward };
for (int i = 0; i < 3; i++)
{
Plane candidate = new Plane(axes[i], bounds.center);
int score = EvaluateSplitPlane(triangles, candidate);
if (score < bestScore && score >= 0)
{
bestScore = score;
bestPlane = candidate;
splitTriangle = null;
foundValidPlane = true;
}
}
}
// Strategy 3: Try planes based on bounds extents
if (!foundValidPlane)
{
Vector3 extents = bounds.extents;
if (extents.x >= extents.y && extents.x >= extents.z)
bestPlane = new Plane(Vector3.right, bounds.center);
else if (extents.y >= extents.x && extents.y >= extents.z)
bestPlane = new Plane(Vector3.up, bounds.center);
else
bestPlane = new Plane(Vector3.forward, bounds.center);
splitTriangle = null;
foundValidPlane = true;
}
return foundValidPlane;
}
private int EvaluateSplitPlane(List<Triangle> triangles, Plane plane)
{
int frontCount = 0;
int backCount = 0;
int splitCount = 0;
int coplanarCount = 0;
foreach (var tri in triangles)
{
float d0 = plane.GetDistanceToPoint(tri.v0);
float d1 = plane.GetDistanceToPoint(tri.v1);
float d2 = plane.GetDistanceToPoint(tri.v2);
// Check for NaN/infinity
if (float.IsNaN(d0) || float.IsNaN(d1) || float.IsNaN(d2) ||
float.IsInfinity(d0) || float.IsInfinity(d1) || float.IsInfinity(d2))
{
return int.MaxValue;
}
bool front = d0 > EPSILON || d1 > EPSILON || d2 > EPSILON;
bool back = d0 < -EPSILON || d1 < -EPSILON || d2 < -EPSILON;
if (front && back)
splitCount++;
else if (front)
frontCount++;
else if (back)
backCount++;
else
coplanarCount++;
}
// Reject planes that would cause too many splits or imbalanced trees
if (splitCount > triangles.Count / 2)
return int.MaxValue;
// Score based on balance and split count
return Mathf.Abs(frontCount - backCount) + splitCount * 2;
}
private void ClassifyTriangle(Triangle tri, Plane plane, List<Triangle> coplanar, List<Triangle> front, List<Triangle> back)
{
float d0 = plane.GetDistanceToPoint(tri.v0);
float d1 = plane.GetDistanceToPoint(tri.v1);
float d2 = plane.GetDistanceToPoint(tri.v2);
// Check for numerical issues
if (float.IsNaN(d0) || float.IsNaN(d1) || float.IsNaN(d2) ||
float.IsInfinity(d0) || float.IsInfinity(d1) || float.IsInfinity(d2))
{
coplanar.Add(tri);
return;
}
bool front0 = d0 > EPSILON;
bool front1 = d1 > EPSILON;
bool front2 = d2 > EPSILON;
bool back0 = d0 < -EPSILON;
bool back1 = d1 < -EPSILON;
bool back2 = d2 < -EPSILON;
int fCount = (front0 ? 1 : 0) + (front1 ? 1 : 0) + (front2 ? 1 : 0);
int bCount = (back0 ? 1 : 0) + (back1 ? 1 : 0) + (back2 ? 1 : 0);
if (fCount == 3)
{
front.Add(tri);
}
else if (bCount == 3)
{
back.Add(tri);
}
else if (fCount == 0 && bCount == 0)
{
coplanar.Add(tri);
}
else
{
totalSplits++;
SplitTriangle(tri, plane, front, back);
}
}
private void SplitTriangle(Triangle tri, Plane plane, List<Triangle> front, List<Triangle> back)
{
// Get distances
float d0 = plane.GetDistanceToPoint(tri.v0);
float d1 = plane.GetDistanceToPoint(tri.v1);
float d2 = plane.GetDistanceToPoint(tri.v2);
// Classify points
bool[] frontSide = { d0 > EPSILON, d1 > EPSILON, d2 > EPSILON };
bool[] backSide = { d0 < -EPSILON, d1 < -EPSILON, d2 < -EPSILON };
// Count how many points are on each side
int frontCount = (frontSide[0] ? 1 : 0) + (frontSide[1] ? 1 : 0) + (frontSide[2] ? 1 : 0);
int backCount = (backSide[0] ? 1 : 0) + (backSide[1] ? 1 : 0) + (backSide[2] ? 1 : 0);
// 2 points on one side, 1 on the other
if (frontCount == 2 && backCount == 1)
{
int loneIndex = backSide[0] ? 0 : (backSide[1] ? 1 : 2);
SplitTriangle2To1(tri, plane, loneIndex, true, front, back);
}
else if (backCount == 2 && frontCount == 1)
{
int loneIndex = frontSide[0] ? 0 : (frontSide[1] ? 1 : 2);
SplitTriangle2To1(tri, plane, loneIndex, false, front, back);
}
else
{
// Complex case - add to both sides (should be rare)
front.Add(tri);
back.Add(tri);
}
}
private void SplitTriangle2To1(Triangle tri, Plane plane, int loneIndex, bool loneIsBack,
List<Triangle> front, List<Triangle> back)
{
Vector3[] v = { tri.v0, tri.v1, tri.v2 };
Vector3[] n = { tri.n0, tri.n1, tri.n2 };
Vector2[] uv = { tri.uv0, tri.uv1, tri.uv2 };
Vector3 loneVertex = v[loneIndex];
Vector3 loneNormal = n[loneIndex];
Vector2 loneUV = uv[loneIndex];
Vector3 v1 = v[(loneIndex + 1) % 3];
Vector3 v2 = v[(loneIndex + 2) % 3];
Vector3 n1 = n[(loneIndex + 1) % 3];
Vector3 n2 = n[(loneIndex + 2) % 3];
Vector2 uv1 = uv[(loneIndex + 1) % 3];
Vector2 uv2 = uv[(loneIndex + 2) % 3];
Vector3 i1 = PlaneIntersection(plane, loneVertex, v1);
float t1 = CalculateInterpolationFactor(plane, loneVertex, v1);
Vector3 n_i1 = Vector3.Lerp(loneNormal, n1, t1).normalized;
Vector2 uv_i1 = Vector2.Lerp(loneUV, uv1, t1);
Vector3 i2 = PlaneIntersection(plane, loneVertex, v2);
float t2 = CalculateInterpolationFactor(plane, loneVertex, v2);
Vector3 n_i2 = Vector3.Lerp(loneNormal, n2, t2).normalized;
Vector2 uv_i2 = Vector2.Lerp(loneUV, uv2, t2);
// Desired normal: prefer triangle's plane normal, fallback to geometric normal
Vector3 desired = tri.plane.normal;
if (desired.sqrMagnitude < 1e-4f)
desired = Vector3.Cross(tri.v1 - tri.v0, tri.v2 - tri.v0).normalized;
if (desired.sqrMagnitude < 1e-4f)
desired = Vector3.up;
// Helper: decide and swap b/c if necessary, then add triangle
void AddTriClockwise(List<Triangle> list,
Vector3 a, Vector3 b, Vector3 c,
Vector3 na, Vector3 nb, Vector3 nc,
Vector2 ua, Vector2 ub, Vector2 uc)
{
Vector3 cross = Vector3.Cross(b - a, c - a);
if (cross.z > 0f) // <-- assumes you're working in PS1 screen space (z forward)
{
// swap b <-> c
var tmpV = b; b = c; c = tmpV;
var tmpN = nb; nb = nc; nc = tmpN;
var tmpUv = ub; ub = uc; uc = tmpUv;
}
list.Add(new Triangle(a, b, c, na, nb, nc, ua, ub, uc, tri.sourceExporter, tri.materialIndex));
}
if (loneIsBack)
{
// back: (lone, i1, i2)
AddTriClockwise(back, loneVertex, i1, i2, loneNormal, n_i1, n_i2, loneUV, uv_i1, uv_i2);
// front: (v1, i1, i2) and (v1, i2, v2)
AddTriClockwise(front, v1, i1, i2, n1, n_i1, n_i2, uv1, uv_i1, uv_i2);
AddTriClockwise(front, v1, i2, v2, n1, n_i2, n2, uv1, uv_i2, uv2);
}
else
{
// front: (lone, i1, i2)
AddTriClockwise(front, loneVertex, i1, i2, loneNormal, n_i1, n_i2, loneUV, uv_i1, uv_i2);
// back: (v1, i1, i2) and (v1, i2, v2)
AddTriClockwise(back, v1, i1, i2, n1, n_i1, n_i2, uv1, uv_i1, uv_i2);
AddTriClockwise(back, v1, i2, v2, n1, n_i2, n2, uv1, uv_i2, uv2);
}
}
private Vector3 PlaneIntersection(Plane plane, Vector3 a, Vector3 b)
{
Vector3 ba = b - a;
float denominator = Vector3.Dot(plane.normal, ba);
// Check for parallel line (shouldn't happen in our case)
if (Mathf.Abs(denominator) < 1e-4f)
return a;
float t = (-plane.distance - Vector3.Dot(plane.normal, a)) / denominator;
return a + ba * Mathf.Clamp01(t);
}
private float CalculateInterpolationFactor(Plane plane, Vector3 a, Vector3 b)
{
Vector3 ba = b - a;
float denominator = Vector3.Dot(plane.normal, ba);
if (Mathf.Abs(denominator) < 1e-4f)
return 0.5f;
float t = (-plane.distance - Vector3.Dot(plane.normal, a)) / denominator;
return Mathf.Clamp01(t);
}
private Bounds CalculateBounds(List<Triangle> triangles)
{
if (triangles == null || triangles.Count == 0)
return new Bounds();
Bounds bounds = triangles[0].bounds;
for (int i = 1; i < triangles.Count; i++)
{
bounds.Encapsulate(triangles[i].bounds);
}
return bounds;
}
// Add a method to create modified meshes after BSP construction
// Add a method to create modified meshes after BSP construction
private void CreateModifiedMeshes()
{
if (root == null) return;
// Collect all triangles from the BSP tree
List<Triangle> allTriangles = new List<Triangle>();
CollectTrianglesFromNode(root, allTriangles);
// Group triangles by their source exporter and material index
Dictionary<PSXObjectExporter, Dictionary<int, List<Triangle>>> exporterTriangles =
new Dictionary<PSXObjectExporter, Dictionary<int, List<Triangle>>>();
foreach (var tri in allTriangles)
{
if (!exporterTriangles.ContainsKey(tri.sourceExporter))
{
exporterTriangles[tri.sourceExporter] = new Dictionary<int, List<Triangle>>();
}
var materialDict = exporterTriangles[tri.sourceExporter];
if (!materialDict.ContainsKey(tri.materialIndex))
{
materialDict[tri.materialIndex] = new List<Triangle>();
}
materialDict[tri.materialIndex].Add(tri);
}
// Create modified meshes for each exporter
foreach (var kvp in exporterTriangles)
{
PSXObjectExporter exporter = kvp.Key;
Dictionary<int, List<Triangle>> materialTriangles = kvp.Value;
Mesh originalMesh = exporter.GetComponent<MeshFilter>().sharedMesh;
Renderer renderer = exporter.GetComponent<Renderer>();
Mesh modifiedMesh = new Mesh();
modifiedMesh.name = originalMesh.name + "_BSP";
List<Vector3> vertices = new List<Vector3>();
List<Vector3> normals = new List<Vector3>();
List<Vector2> uvs = new List<Vector2>();
List<Vector4> tangents = new List<Vector4>();
List<Color> colors = new List<Color>();
// Create a list for each material's triangles
List<List<int>> materialIndices = new List<List<int>>();
for (int i = 0; i < renderer.sharedMaterials.Length; i++)
{
materialIndices.Add(new List<int>());
}
// Get the inverse transform to convert from world space back to object space
Matrix4x4 worldToLocal = exporter.transform.worldToLocalMatrix;
// Process each material
foreach (var materialKvp in materialTriangles)
{
int materialIndex = materialKvp.Key;
List<Triangle> triangles = materialKvp.Value;
// Add vertices, normals, and uvs for this material
for (int i = 0; i < triangles.Count; i++)
{
Triangle tri = triangles[i];
// Transform vertices from world space back to object space
Vector3 v0 = worldToLocal.MultiplyPoint3x4(tri.v0);
Vector3 v1 = worldToLocal.MultiplyPoint3x4(tri.v1);
Vector3 v2 = worldToLocal.MultiplyPoint3x4(tri.v2);
int vertexIndex = vertices.Count;
vertices.Add(v0);
vertices.Add(v1);
vertices.Add(v2);
// Transform normals from world space back to object space
Vector3 n0 = worldToLocal.MultiplyVector(tri.n0).normalized;
Vector3 n1 = worldToLocal.MultiplyVector(tri.n1).normalized;
Vector3 n2 = worldToLocal.MultiplyVector(tri.n2).normalized;
normals.Add(n0);
normals.Add(n1);
normals.Add(n2);
uvs.Add(tri.uv0);
uvs.Add(tri.uv1);
uvs.Add(tri.uv2);
// Add default tangents and colors (will be recalculated later)
tangents.Add(new Vector4(1, 0, 0, 1));
tangents.Add(new Vector4(1, 0, 0, 1));
tangents.Add(new Vector4(1, 0, 0, 1));
colors.Add(Color.white);
colors.Add(Color.white);
colors.Add(Color.white);
// Add indices for this material
materialIndices[materialIndex].Add(vertexIndex);
materialIndices[materialIndex].Add(vertexIndex + 1);
materialIndices[materialIndex].Add(vertexIndex + 2);
}
}
// Assign data to the mesh
modifiedMesh.vertices = vertices.ToArray();
modifiedMesh.normals = normals.ToArray();
modifiedMesh.uv = uvs.ToArray();
modifiedMesh.tangents = tangents.ToArray();
modifiedMesh.colors = colors.ToArray();
// Set up submeshes based on materials
modifiedMesh.subMeshCount = materialIndices.Count;
for (int i = 0; i < materialIndices.Count; i++)
{
modifiedMesh.SetTriangles(materialIndices[i].ToArray(), i);
}
// Recalculate important mesh properties
modifiedMesh.RecalculateBounds();
modifiedMesh.RecalculateTangents();
// Assign the modified mesh to the exporter
exporter.ModifiedMesh = modifiedMesh;
}
}
// Helper method to collect all triangles from the BSP tree
private void CollectTrianglesFromNode(Node node, List<Triangle> triangles)
{
if (node == null) return;
if (node.isLeaf)
{
triangles.AddRange(node.triangles);
}
else
{
CollectTrianglesFromNode(node.front, triangles);
CollectTrianglesFromNode(node.back, triangles);
}
}
public void DrawGizmos(int maxDepth)
{
if (root == null) return;
DrawNodeGizmos(root, 0, maxDepth);
}
private void DrawNodeGizmos(Node node, int depth, int maxDepth)
{
if (node == null) return;
if (depth > maxDepth) return;
Color nodeColor = Color.HSVToRGB((depth * 0.1f) % 1f, 0.8f, 0.8f);
Gizmos.color = nodeColor;
if (node.isLeaf)
{
foreach (var tri in node.triangles)
{
DrawTriangleGizmo(tri);
}
}
else
{
DrawPlaneGizmo(node.plane, node.bounds);
// Draw the triangle that was used for the split plane if available
if (splitPlaneTriangles.ContainsKey(node))
{
Gizmos.color = Color.magenta;
DrawTriangleGizmo(splitPlaneTriangles[node]);
Gizmos.color = nodeColor;
}
DrawNodeGizmos(node.front, depth + 1, maxDepth);
DrawNodeGizmos(node.back, depth + 1, maxDepth);
}
}
private void DrawTriangleGizmo(Triangle tri)
{
Gizmos.DrawLine(tri.v0, tri.v1);
Gizmos.DrawLine(tri.v1, tri.v2);
Gizmos.DrawLine(tri.v2, tri.v0);
}
private void DrawPlaneGizmo(Plane plane, Bounds bounds)
{
Vector3 center = bounds.center;
Vector3 normal = plane.normal;
Vector3 tangent = Vector3.Cross(normal, Vector3.up);
if (tangent.magnitude < 0.1f) tangent = Vector3.Cross(normal, Vector3.right);
tangent = tangent.normalized;
Vector3 bitangent = Vector3.Cross(normal, tangent).normalized;
float size = Mathf.Max(bounds.size.x, bounds.size.y, bounds.size.z) * 0.5f;
tangent *= size;
bitangent *= size;
Vector3 p0 = center - tangent - bitangent;
Vector3 p1 = center + tangent - bitangent;
Vector3 p2 = center + tangent + bitangent;
Vector3 p3 = center - tangent + bitangent;
Gizmos.DrawLine(p0, p1);
Gizmos.DrawLine(p1, p2);
Gizmos.DrawLine(p2, p3);
Gizmos.DrawLine(p3, p0);
Gizmos.color = Color.red;
Gizmos.DrawLine(center, center + normal * size * 0.5f);
}
}

2
Runtime/BSP.cs.meta Normal file
View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 15144e67b42b92447a546346e594155b

View File

@@ -3,14 +3,36 @@ using UnityEngine;
namespace SplashEdit.RuntimeCode namespace SplashEdit.RuntimeCode
{ {
public enum PSXConnectionType
{
REAL_HARDWARE, // Unirom
EMULATOR // PCSX-Redux
}
[CreateAssetMenu(fileName = "PSXData", menuName = "Scriptable Objects/PSXData")] [CreateAssetMenu(fileName = "PSXData", menuName = "Scriptable Objects/PSXData")]
public class PSXData : ScriptableObject public class PSXData : ScriptableObject
{ {
// Texture packing settings
public Vector2 OutputResolution = new Vector2(320, 240); public Vector2 OutputResolution = new Vector2(320, 240);
public bool DualBuffering = true; public bool DualBuffering = true;
public bool VerticalBuffering = true; public bool VerticalBuffering = true;
public List<ProhibitedArea> ProhibitedAreas = new List<ProhibitedArea>(); public List<ProhibitedArea> ProhibitedAreas = new List<ProhibitedArea>();
// Connection settings
public PSXConnectionType ConnectionType = PSXConnectionType.REAL_HARDWARE;
// Real hardware settings
public string PortName = "COM3";
public int BaudRate = 0;
// Emulator settings
public string PCSXReduxPath = ""; public string PCSXReduxPath = "";
} }
} }

View File

@@ -142,7 +142,106 @@ namespace SplashEdit.RuntimeCode
(vid1, vid2) = (vid2, vid1); (vid1, vid2) = (vid2, vid1);
} }
psxMesh.Triangles.Add(new Tri { psxMesh.Triangles.Add(new Tri
{
v0 = convertData(vid0),
v1 = convertData(vid1),
v2 = convertData(vid2),
TextureIndex = textureIndex
});
}
}
return psxMesh;
}
public static PSXMesh CreateFromUnityMesh(Mesh mesh, Renderer renderer, float GTEScaling, Transform transform, List<PSXTexture2D> textures)
{
PSXMesh psxMesh = new PSXMesh { Triangles = new List<Tri>() };
Material[] materials = renderer.sharedMaterials;
// Ensure mesh has required data
if (mesh.normals == null || mesh.normals.Length == 0)
{
mesh.RecalculateNormals();
}
if (mesh.uv == null || mesh.uv.Length == 0)
{
Vector2[] uvs = new Vector2[mesh.vertices.Length];
mesh.uv = uvs;
}
// Precompute smooth normals for the entire mesh
Vector3[] smoothNormals = RecalculateSmoothNormals(mesh);
// Precompute world positions and normals for all vertices
Vector3[] worldVertices = new Vector3[mesh.vertices.Length];
Vector3[] worldNormals = new Vector3[mesh.normals.Length];
for (int i = 0; i < mesh.vertices.Length; i++)
{
worldVertices[i] = transform.TransformPoint(mesh.vertices[i]);
worldNormals[i] = transform.TransformDirection(mesh.normals[i]).normalized;
}
for (int submeshIndex = 0; submeshIndex < mesh.subMeshCount; submeshIndex++)
{
int materialIndex = Mathf.Min(submeshIndex, materials.Length - 1);
Material material = materials[materialIndex];
Texture2D texture = material.mainTexture as Texture2D;
// Find texture index
int textureIndex = -1;
if (texture != null)
{
for (int i = 0; i < textures.Count; i++)
{
if (textures[i].OriginalTexture == texture)
{
textureIndex = i;
break;
}
}
}
int[] submeshTriangles = mesh.GetTriangles(submeshIndex);
// Get mesh data arrays
Vector3[] vertices = mesh.vertices;
Vector3[] normals = mesh.normals;
Vector2[] uv = mesh.uv;
PSXVertex convertData(int index)
{
Vector3 v = Vector3.Scale(vertices[index], transform.lossyScale);
// Use precomputed world position and normal for consistent lighting
Vector3 wv = worldVertices[index];
Vector3 wn = worldNormals[index];
// For split triangles, use the original vertex's lighting if possible
Color c = PSXLightingBaker.ComputeLighting(wv, wn);
return ConvertToPSXVertex(v, GTEScaling, normals[index], uv[index],
textures[textureIndex]?.Width, textures[textureIndex]?.Height, c);
}
for (int i = 0; i < submeshTriangles.Length; i += 3)
{
int vid0 = submeshTriangles[i];
int vid1 = submeshTriangles[i + 1];
int vid2 = submeshTriangles[i + 2];
Vector3 faceNormal = Vector3.Cross(vertices[vid1] - vertices[vid0], vertices[vid2] - vertices[vid0]).normalized;
if (Vector3.Dot(faceNormal, normals[vid0]) < 0)
{
(vid1, vid2) = (vid2, vid1);
}
psxMesh.Triangles.Add(new Tri
{
v0 = convertData(vid0), v0 = convertData(vid0),
v1 = convertData(vid1), v1 = convertData(vid1),
v2 = convertData(vid2), v2 = convertData(vid2),

View File

@@ -12,19 +12,30 @@ namespace SplashEdit.RuntimeCode
public bool IsActive = true; public bool IsActive = true;
public List<PSXTexture2D> Textures { get; set; } = new List<PSXTexture2D>(); // Stores the converted PlayStation-style texture public List<PSXTexture2D> Textures { get; set; } = new List<PSXTexture2D>();
public PSXMesh Mesh { get; protected set; } // Stores the converted PlayStation-style mesh public PSXMesh Mesh { get; protected set; }
[Header("Export Settings")] [Header("Export Settings")]
[FormerlySerializedAs("BitDepth")] [FormerlySerializedAs("BitDepth")]
[SerializeField] private PSXBPP bitDepth = PSXBPP.TEX_8BIT; // Defines the bit depth of the texture (e.g., 4BPP, 8BPP) [SerializeField] private PSXBPP bitDepth = PSXBPP.TEX_8BIT;
[SerializeField] private LuaFile luaFile; [SerializeField] private LuaFile luaFile;
[Header("BSP Settings")]
[SerializeField] private Mesh _modifiedMesh; // Mesh after BSP processing
[Header("Gizmo Settings")] [Header("Gizmo Settings")]
[FormerlySerializedAs("PreviewNormals")] [FormerlySerializedAs("PreviewNormals")]
[SerializeField] private bool previewNormals = false; [SerializeField] private bool previewNormals = false;
[SerializeField] private float normalPreviewLength = 0.5f; // Length of the normal lines [SerializeField] private float normalPreviewLength = 0.5f;
private readonly Dictionary<(int, PSXBPP), PSXTexture2D> cache = new(); private readonly Dictionary<(int, PSXBPP), PSXTexture2D> cache = new();
public Mesh ModifiedMesh
{
get => _modifiedMesh;
set => _modifiedMesh = value;
}
private void OnDrawGizmos() private void OnDrawGizmos()
{ {
if (previewNormals) if (previewNormals)
@@ -37,25 +48,19 @@ namespace SplashEdit.RuntimeCode
Vector3[] vertices = mesh.vertices; Vector3[] vertices = mesh.vertices;
Vector3[] normals = mesh.normals; Vector3[] normals = mesh.normals;
Gizmos.color = Color.green; // Normal color Gizmos.color = Color.green;
for (int i = 0; i < vertices.Length; i++) for (int i = 0; i < vertices.Length; i++)
{ {
Vector3 worldVertex = transform.TransformPoint(vertices[i]); // Convert to world space Vector3 worldVertex = transform.TransformPoint(vertices[i]);
Vector3 worldNormal = transform.TransformDirection(normals[i]); // Transform normal to world space Vector3 worldNormal = transform.TransformDirection(normals[i]);
Gizmos.DrawLine(worldVertex, worldVertex + worldNormal * normalPreviewLength); Gizmos.DrawLine(worldVertex, worldVertex + worldNormal * normalPreviewLength);
} }
} }
} }
} }
/// <summary>
/// Converts the object's material texture into a PlayStation-compatible texture.
/// </summary>
///
public void CreatePSXTextures2D() public void CreatePSXTextures2D()
{ {
Renderer renderer = GetComponent<Renderer>(); Renderer renderer = GetComponent<Renderer>();
@@ -71,14 +76,12 @@ namespace SplashEdit.RuntimeCode
Texture mainTexture = mat.mainTexture; Texture mainTexture = mat.mainTexture;
Texture2D tex2D = null; Texture2D tex2D = null;
// Check if it's already a Texture2D
if (mainTexture is Texture2D existingTex2D) if (mainTexture is Texture2D existingTex2D)
{ {
tex2D = existingTex2D; tex2D = existingTex2D;
} }
else else
{ {
// If not a Texture2D, try to convert
tex2D = ConvertToTexture2D(mainTexture); tex2D = ConvertToTexture2D(mainTexture);
} }
@@ -92,7 +95,7 @@ namespace SplashEdit.RuntimeCode
else else
{ {
tex = PSXTexture2D.CreateFromTexture2D(tex2D, bitDepth); tex = PSXTexture2D.CreateFromTexture2D(tex2D, bitDepth);
tex.OriginalTexture = tex2D; // Store reference to the original texture tex.OriginalTexture = tex2D;
cache.Add((tex2D.GetInstanceID(), bitDepth), tex); cache.Add((tex2D.GetInstanceID(), bitDepth), tex);
} }
Textures.Add(tex); Textures.Add(tex);
@@ -104,10 +107,8 @@ namespace SplashEdit.RuntimeCode
private Texture2D ConvertToTexture2D(Texture texture) private Texture2D ConvertToTexture2D(Texture texture)
{ {
// Create a new Texture2D with the same dimensions and format
Texture2D texture2D = new Texture2D(texture.width, texture.height, TextureFormat.RGBA32, false); Texture2D texture2D = new Texture2D(texture.width, texture.height, TextureFormat.RGBA32, false);
// Read the texture pixels
RenderTexture currentActiveRT = RenderTexture.active; RenderTexture currentActiveRT = RenderTexture.active;
RenderTexture.active = texture as RenderTexture; RenderTexture.active = texture as RenderTexture;
@@ -128,16 +129,35 @@ namespace SplashEdit.RuntimeCode
return null; return null;
} }
/// <summary> public void CreatePSXMesh(float GTEScaling, bool useBSP = false)
/// Converts the object's mesh into a PlayStation-compatible mesh.
/// </summary>
public void CreatePSXMesh(float GTEScaling)
{ {
Renderer renderer = GetComponent<Renderer>(); Renderer renderer = GetComponent<Renderer>();
if (renderer != null) if (renderer != null)
{
if (useBSP && _modifiedMesh != null)
{
// Create a temporary GameObject with the modified mesh but same materials
GameObject tempGO = new GameObject("TempBSPMesh");
tempGO.transform.position = transform.position;
tempGO.transform.rotation = transform.rotation;
tempGO.transform.localScale = transform.localScale;
MeshFilter tempMF = tempGO.AddComponent<MeshFilter>();
tempMF.sharedMesh = _modifiedMesh;
MeshRenderer tempMR = tempGO.AddComponent<MeshRenderer>();
tempMR.sharedMaterials = renderer.sharedMaterials;
Mesh = PSXMesh.CreateFromUnityRenderer(tempMR, GTEScaling, transform, Textures);
// Clean up
GameObject.DestroyImmediate(tempGO);
}
else
{ {
Mesh = PSXMesh.CreateFromUnityRenderer(renderer, GTEScaling, transform, Textures); Mesh = PSXMesh.CreateFromUnityRenderer(renderer, GTEScaling, transform, Textures);
} }
} }
} }
} }
}

View File

@@ -32,6 +32,11 @@ namespace SplashEdit.RuntimeCode
private Quaternion _playerRot; private Quaternion _playerRot;
private float _playerHeight; private float _playerHeight;
private BSP _bsp;
public bool PreviewBSP = true;
public int BSPPreviewDepth = 9999;
public void Export() public void Export()
{ {
_psxData = DataStorage.LoadData(out selectedResolution, out dualBuffering, out verticalLayout, out prohibitedAreas); _psxData = DataStorage.LoadData(out selectedResolution, out dualBuffering, out verticalLayout, out prohibitedAreas);
@@ -42,7 +47,7 @@ namespace SplashEdit.RuntimeCode
PSXObjectExporter exp = _exporters[i]; PSXObjectExporter exp = _exporters[i];
EditorUtility.DisplayProgressBar($"{nameof(PSXSceneExporter)}", $"Export {nameof(PSXObjectExporter)}", ((float)i) / _exporters.Length); EditorUtility.DisplayProgressBar($"{nameof(PSXSceneExporter)}", $"Export {nameof(PSXObjectExporter)}", ((float)i) / _exporters.Length);
exp.CreatePSXTextures2D(); exp.CreatePSXTextures2D();
exp.CreatePSXMesh(GTEScaling); exp.CreatePSXMesh(GTEScaling, true);
} }
_navmeshes = FindObjectsByType<PSXNavMesh>(FindObjectsSortMode.None); _navmeshes = FindObjectsByType<PSXNavMesh>(FindObjectsSortMode.None);
@@ -66,6 +71,9 @@ namespace SplashEdit.RuntimeCode
_playerRot = player.transform.rotation; _playerRot = player.transform.rotation;
} }
_bsp = new BSP(_exporters.ToList());
_bsp.Build();
ExportFile(); ExportFile();
} }
@@ -450,6 +458,10 @@ namespace SplashEdit.RuntimeCode
Vector3 cubeSize = new Vector3(8.0f * GTEScaling, 8.0f * GTEScaling, 8.0f * GTEScaling); Vector3 cubeSize = new Vector3(8.0f * GTEScaling, 8.0f * GTEScaling, 8.0f * GTEScaling);
Gizmos.color = Color.red; Gizmos.color = Color.red;
Gizmos.DrawWireCube(sceneOrigin, cubeSize); Gizmos.DrawWireCube(sceneOrigin, cubeSize);
if (_bsp == null || !PreviewBSP) return;
_bsp.DrawGizmos(BSPPreviewDepth);
} }
} }