From 66e8c2ebcb247e6f6e05d44694ec3c7594e55236 Mon Sep 17 00:00:00 2001 From: parax0 Date: Sun, 26 Jul 2015 17:39:49 -0400 Subject: [PATCH] Initial commit of current work on Prime World Editor --- .gitignore | 34 + Common/AnimUtil.cpp | 10 + Common/AnimUtil.h | 9 + Common/CAABox.cpp | 158 ++ Common/CAABox.h | 51 + Common/CColor.cpp | 183 ++ Common/CColor.h | 60 + Common/CFourCC.cpp | 111 + Common/CFourCC.h | 34 + Common/CHashFNV1A.cpp | 72 + Common/CHashFNV1A.h | 36 + Common/CMatrix4f.cpp | 241 ++ Common/CMatrix4f.h | 50 + Common/CQuaternion.cpp | 86 + Common/CQuaternion.h | 25 + Common/CRay.cpp | 43 + Common/CRay.h | 35 + Common/CRayCollisionTester.cpp | 56 + Common/CRayCollisionTester.h | 32 + Common/CTimer.cpp | 89 + Common/CTimer.h | 28 + Common/CTransform4f.cpp | 309 +++ Common/CTransform4f.h | 70 + Common/CUniqueID.cpp | 299 +++ Common/CUniqueID.h | 56 + Common/CVector2f.cpp | 156 ++ Common/CVector2f.h | 46 + Common/CVector2i.cpp | 139 ++ Common/CVector2i.h | 39 + Common/CVector3f.cpp | 300 +++ Common/CVector3f.h | 83 + Common/CVector4f.cpp | 273 +++ Common/CVector4f.h | 68 + Common/CompressionUtil.cpp | 39 + Common/CompressionUtil.h | 11 + Common/EKeyInputs.h | 21 + Common/EMouseInputs.h | 15 + Common/EnumUtil.h | 19 + Common/Math.cpp | 244 ++ Common/Math.h | 25 + Common/SRayIntersection.h | 20 + Common/StringUtil.cpp | 321 +++ Common/StringUtil.h | 35 + Common/types.h | 15 + Core/CAreaAttributes.cpp | 55 + Core/CAreaAttributes.h | 20 + Core/CCamera.cpp | 286 +++ Core/CCamera.h | 76 + Core/CDrawUtil.cpp | 436 ++++ Core/CDrawUtil.h | 91 + Core/CGraphics.cpp | 159 ++ Core/CGraphics.h | 97 + Core/CRenderBucket.cpp | 89 + Core/CRenderBucket.h | 33 + Core/CRenderer.cpp | 328 +++ Core/CRenderer.h | 83 + Core/CResCache.cpp | 236 ++ Core/CResCache.h | 40 + Core/CSceneManager.cpp | 380 +++ Core/CSceneManager.h | 87 + Core/CToken.cpp | 55 + Core/CToken.h | 21 + Core/ERenderCommand.h | 13 + Core/ERenderOptions.h | 18 + Core/Log.cpp | 78 + Core/Log.h | 22 + Core/main.cpp | 52 + EditorAssets/Create.png | Bin 0 -> 3249 bytes EditorAssets/Display.png | Bin 0 -> 3025 bytes EditorAssets/Down.png | Bin 0 -> 3369 bytes EditorAssets/Free Camera.png | Bin 0 -> 3142 bytes EditorAssets/GridLight.png | Bin 0 -> 2874 bytes EditorAssets/Hide.png | Bin 0 -> 3193 bytes EditorAssets/Highlight.png | Bin 0 -> 3257 bytes EditorAssets/Instances.png | Bin 0 -> 2932 bytes EditorAssets/Link.png | Bin 0 -> 3186 bytes EditorAssets/Material Highlight.png | Bin 0 -> 3121 bytes EditorAssets/Minus v2.png | Bin 0 -> 2938 bytes EditorAssets/Minus.png | Bin 0 -> 2902 bytes EditorAssets/Model Preview.png | Bin 0 -> 4082 bytes EditorAssets/Modify.png | Bin 0 -> 3497 bytes EditorAssets/Orbit Camera v2.png | Bin 0 -> 3301 bytes EditorAssets/Orbit Camera.png | Bin 0 -> 3370 bytes EditorAssets/Plus.png | Bin 0 -> 3227 bytes EditorAssets/Rotate.png | Bin 0 -> 3355 bytes EditorAssets/Samus Silhouette Gradient.png | Bin 0 -> 3858 bytes EditorAssets/Scale.png | Bin 0 -> 3032 bytes EditorAssets/Show.png | Bin 0 -> 3442 bytes EditorAssets/Sphere Preview.png | Bin 0 -> 3761 bytes EditorAssets/Square Preview.png | Bin 0 -> 3127 bytes EditorAssets/Translate.png | Bin 0 -> 3250 bytes EditorAssets/Unlink.png | Bin 0 -> 3251 bytes EditorAssets/Up.png | Bin 0 -> 3352 bytes EditorAssets/World.png | Bin 0 -> 3617 bytes Icons.qrc | 31 + OpenGL/CDynamicVertexBuffer.cpp | 136 ++ OpenGL/CDynamicVertexBuffer.h | 32 + OpenGL/CFramebuffer.cpp | 111 + OpenGL/CFramebuffer.h | 32 + OpenGL/CGL.cpp | 41 + OpenGL/CGL.h | 26 + OpenGL/CIndexBuffer.cpp | 156 ++ OpenGL/CIndexBuffer.h | 39 + OpenGL/CRenderbuffer.cpp | 52 + OpenGL/CRenderbuffer.h | 31 + OpenGL/CShader.cpp | 263 ++ OpenGL/CShader.h | 45 + OpenGL/CShaderGenerator.cpp | 445 ++++ OpenGL/CShaderGenerator.cpp.bW5112 | 445 ++++ OpenGL/CShaderGenerator.h | 22 + OpenGL/CUniformBuffer.cpp | 60 + OpenGL/CUniformBuffer.h | 28 + OpenGL/CVertexArrayManager.cpp | 105 + OpenGL/CVertexArrayManager.h | 34 + OpenGL/CVertexBuffer.cpp | 230 ++ OpenGL/CVertexBuffer.h | 37 + OpenGL/GLCommon.cpp | 37 + OpenGL/GLCommon.h | 34 + OpenGL/SMeshPointer.h | 18 + PrimeWorldEditor.pro | 339 +++ Resource/CAnimSet.cpp | 36 + Resource/CAnimSet.h | 37 + Resource/CCollisionMesh.cpp | 119 + Resource/CCollisionMesh.h | 89 + Resource/CFont.cpp | 182 ++ Resource/CFont.h | 73 + Resource/CGameArea.cpp | 184 ++ Resource/CGameArea.h | 66 + Resource/CLight.cpp | 273 +++ Resource/CLight.h | 80 + Resource/CMaterial.cpp | 240 ++ Resource/CMaterial.h | 107 + Resource/CMaterialPass.cpp | 296 +++ Resource/CMaterialPass.h | 127 + Resource/CMaterialSet.cpp | 11 + Resource/CMaterialSet.h | 19 + Resource/CPakFile.cpp | 174 ++ Resource/CPakFile.h | 31 + Resource/CResource.cpp | 96 + Resource/CResource.h | 33 + Resource/CScan.cpp | 39 + Resource/CScan.h | 42 + Resource/CStringTable.cpp | 57 + Resource/CStringTable.h | 39 + Resource/CTexture.cpp | 337 +++ Resource/CTexture.h | 83 + Resource/CWorld.cpp | 101 + Resource/CWorld.h | 107 + Resource/EFormatVersion.h | 18 + Resource/EResType.h | 50 + Resource/ETevEnums.h | 106 + Resource/ETexelFormat.h | 38 + Resource/SDependency.h | 14 + Resource/SNamedResource.h | 14 + Resource/SResInfo.h | 16 + Resource/cooker/CMaterialCooker.cpp | 364 +++ Resource/cooker/CMaterialCooker.h | 27 + Resource/cooker/CModelCooker.cpp | 275 +++ Resource/cooker/CModelCooker.h | 30 + Resource/cooker/CSectionMgrOut.cpp | 33 + Resource/cooker/CSectionMgrOut.h | 24 + Resource/cooker/CTextureEncoder.cpp | 106 + Resource/cooker/CTextureEncoder.h | 27 + Resource/factory/CAnimSetLoader.cpp | 199 ++ Resource/factory/CAnimSetLoader.h | 24 + Resource/factory/CAreaLoader.cpp | 538 +++++ Resource/factory/CAreaLoader.h | 82 + Resource/factory/CBlockMgr.cpp | 58 + Resource/factory/CBlockMgrIn.h | 28 + Resource/factory/CCollisionLoader.cpp | 136 ++ Resource/factory/CCollisionLoader.h | 31 + Resource/factory/CFontLoader.cpp | 119 + Resource/factory/CFontLoader.h | 21 + Resource/factory/CMaterialLoader.cpp | 581 +++++ Resource/factory/CMaterialLoader.h | 38 + Resource/factory/CModelLoader.cpp | 445 ++++ Resource/factory/CModelLoader.h | 63 + Resource/factory/CScanLoader.cpp | 150 ++ Resource/factory/CScanLoader.h | 21 + Resource/factory/CScriptLoader.cpp | 464 ++++ Resource/factory/CScriptLoader.h | 34 + Resource/factory/CStringLoader.cpp | 221 ++ Resource/factory/CStringLoader.h | 24 + Resource/factory/CTemplateLoader.cpp | 644 +++++ Resource/factory/CTemplateLoader.h | 36 + Resource/factory/CTextureDecoder.cpp | 875 +++++++ Resource/factory/CTextureDecoder.h | 93 + Resource/factory/CWorldLoader.cpp | 341 +++ Resource/factory/CWorldLoader.h | 23 + Resource/model/CBasicModel.cpp | 57 + Resource/model/CBasicModel.h | 37 + Resource/model/CModel.cpp | 207 ++ Resource/model/CModel.h | 46 + Resource/model/CStaticModel.cpp | 177 ++ Resource/model/CStaticModel.h | 39 + Resource/model/CVertex.h | 42 + Resource/model/EVertexDescription.h | 33 + Resource/model/SModelData.h | 13 + Resource/model/SSurface.cpp | 89 + Resource/model/SSurface.h | 39 + Resource/script/CMasterTemplate.cpp | 112 + Resource/script/CMasterTemplate.h | 66 + Resource/script/CProperty.h | 227 ++ Resource/script/CScriptLayer.cpp | 95 + Resource/script/CScriptLayer.h | 48 + Resource/script/CScriptObject.cpp | 287 +++ Resource/script/CScriptObject.h | 93 + Resource/script/CScriptTemplate.cpp | 316 +++ Resource/script/CScriptTemplate.h | 141 ++ Resource/script/CTemplateCategory.h | 46 + Resource/script/EAttribType.h | 26 + Resource/script/EObjectType.h | 130 + Resource/script/EPropertyType.h | 28 + Resource/script/SConnection.h | 13 + Scene/CBoundingBoxNode.cpp | 12 + Scene/CBoundingBoxNode.h | 16 + Scene/CCollisionNode.cpp | 58 + Scene/CCollisionNode.h | 21 + Scene/CLightNode.cpp | 78 + Scene/CLightNode.h | 20 + Scene/CModelNode.cpp | 148 ++ Scene/CModelNode.h | 61 + Scene/CRootNode.h | 30 + Scene/CSceneNode.cpp | 372 +++ Scene/CSceneNode.h | 113 + Scene/CScriptNode.cpp | 320 +++ Scene/CScriptNode.h | 35 + Scene/CStaticNode.cpp | 113 + Scene/CStaticNode.h | 21 + Scene/ENodeType.h | 15 + UI/CDarkStyle.cpp | 5 + UI/CDarkStyle.h | 14 + UI/CEditorGLWidget.cpp | 247 ++ UI/CEditorGLWidget.h | 63 + UI/CMaterialEditor.cpp | 14 + UI/CMaterialEditor.h | 22 + UI/CMaterialEditor.ui | 18 + UI/CModelEditorWindow.cpp | 795 +++++++ UI/CModelEditorWindow.h | 122 + UI/CModelEditorWindow.ui | 2502 ++++++++++++++++++++ UI/CNodeSelection.cpp | 58 + UI/CNodeSelection.h | 24 + UI/CSimpleDelegate.cpp | 12 + UI/CSimpleDelegate.h | 12 + UI/CStartWindow.cpp | 181 ++ UI/CStartWindow.h | 46 + UI/CStartWindow.ui | 260 ++ UI/CWorldEditor.cpp | 491 ++++ UI/CWorldEditor.h | 94 + UI/CWorldEditor.ui | 608 +++++ UI/CWorldEditorWindow.cpp | 300 +++ UI/CWorldEditorWindow.h | 89 + UI/CWorldEditorWindow.ui | 460 ++++ UI/IPreviewPanel.cpp | 24 + UI/IPreviewPanel.h | 17 + UI/PWEMaterialEditor.h | 22 + UI/TestDialog.cpp | 22 + UI/TestDialog.h | 22 + UI/TestDialog.ui | 125 + UI/UICommon.cpp | 46 + UI/UICommon.h | 9 + UI/WCollapsibleGroupBox.cpp | 12 + UI/WCollapsibleGroupBox.h | 12 + UI/WColorPicker.cpp | 92 + UI/WColorPicker.h | 28 + UI/WDraggableSpinBox.cpp | 55 + UI/WDraggableSpinBox.h | 23 + UI/WPropertyEditor.cpp | 353 +++ UI/WPropertyEditor.h | 36 + UI/WResourceSelector.cpp | 335 +++ UI/WResourceSelector.h | 91 + UI/WRollout.cpp | 64 + UI/WRollout.h | 29 + UI/WScanPreviewPanel.cpp | 75 + UI/WScanPreviewPanel.h | 24 + UI/WScanPreviewPanel.ui | 154 ++ UI/WStringPreviewPanel.cpp | 52 + UI/WStringPreviewPanel.h | 24 + UI/WTextureGLWidget.cpp | 156 ++ UI/WTextureGLWidget.h | 39 + UI/WTexturePreviewPanel.cpp | 63 + UI/WTexturePreviewPanel.h | 25 + UI/WTexturePreviewPanel.ui | 77 + UI/WVectorEditor.cpp | 117 + UI/WVectorEditor.h | 43 + UI/WorldEditor/CLayerEditor.cpp | 54 + UI/WorldEditor/CLayerEditor.h | 32 + UI/WorldEditor/CLayerEditor.ui | 60 + UI/WorldEditor/CLayerModel.cpp | 46 + UI/WorldEditor/CLayerModel.h | 21 + UI/WorldEditor/CLayersInstanceModel.cpp | 119 + UI/WorldEditor/CLayersInstanceModel.h | 49 + UI/WorldEditor/CLinkModel.cpp | 101 + UI/WorldEditor/CLinkModel.h | 30 + UI/WorldEditor/CTypesInstanceModel.cpp | 451 ++++ UI/WorldEditor/CTypesInstanceModel.h | 67 + UI/WorldEditor/WCreateTab.cpp | 14 + UI/WorldEditor/WCreateTab.h | 22 + UI/WorldEditor/WCreateTab.ui | 19 + UI/WorldEditor/WInstancesTab.cpp | 163 ++ UI/WorldEditor/WInstancesTab.h | 51 + UI/WorldEditor/WInstancesTab.ui | 74 + UI/WorldEditor/WModifyTab.cpp | 145 ++ UI/WorldEditor/WModifyTab.h | 47 + UI/WorldEditor/WModifyTab.ui | 222 ++ 305 files changed, 33469 insertions(+) create mode 100644 .gitignore create mode 100644 Common/AnimUtil.cpp create mode 100644 Common/AnimUtil.h create mode 100644 Common/CAABox.cpp create mode 100644 Common/CAABox.h create mode 100644 Common/CColor.cpp create mode 100644 Common/CColor.h create mode 100644 Common/CFourCC.cpp create mode 100644 Common/CFourCC.h create mode 100644 Common/CHashFNV1A.cpp create mode 100644 Common/CHashFNV1A.h create mode 100644 Common/CMatrix4f.cpp create mode 100644 Common/CMatrix4f.h create mode 100644 Common/CQuaternion.cpp create mode 100644 Common/CQuaternion.h create mode 100644 Common/CRay.cpp create mode 100644 Common/CRay.h create mode 100644 Common/CRayCollisionTester.cpp create mode 100644 Common/CRayCollisionTester.h create mode 100644 Common/CTimer.cpp create mode 100644 Common/CTimer.h create mode 100644 Common/CTransform4f.cpp create mode 100644 Common/CTransform4f.h create mode 100644 Common/CUniqueID.cpp create mode 100644 Common/CUniqueID.h create mode 100644 Common/CVector2f.cpp create mode 100644 Common/CVector2f.h create mode 100644 Common/CVector2i.cpp create mode 100644 Common/CVector2i.h create mode 100644 Common/CVector3f.cpp create mode 100644 Common/CVector3f.h create mode 100644 Common/CVector4f.cpp create mode 100644 Common/CVector4f.h create mode 100644 Common/CompressionUtil.cpp create mode 100644 Common/CompressionUtil.h create mode 100644 Common/EKeyInputs.h create mode 100644 Common/EMouseInputs.h create mode 100644 Common/EnumUtil.h create mode 100644 Common/Math.cpp create mode 100644 Common/Math.h create mode 100644 Common/SRayIntersection.h create mode 100644 Common/StringUtil.cpp create mode 100644 Common/StringUtil.h create mode 100644 Common/types.h create mode 100644 Core/CAreaAttributes.cpp create mode 100644 Core/CAreaAttributes.h create mode 100644 Core/CCamera.cpp create mode 100644 Core/CCamera.h create mode 100644 Core/CDrawUtil.cpp create mode 100644 Core/CDrawUtil.h create mode 100644 Core/CGraphics.cpp create mode 100644 Core/CGraphics.h create mode 100644 Core/CRenderBucket.cpp create mode 100644 Core/CRenderBucket.h create mode 100644 Core/CRenderer.cpp create mode 100644 Core/CRenderer.h create mode 100644 Core/CResCache.cpp create mode 100644 Core/CResCache.h create mode 100644 Core/CSceneManager.cpp create mode 100644 Core/CSceneManager.h create mode 100644 Core/CToken.cpp create mode 100644 Core/CToken.h create mode 100644 Core/ERenderCommand.h create mode 100644 Core/ERenderOptions.h create mode 100644 Core/Log.cpp create mode 100644 Core/Log.h create mode 100644 Core/main.cpp create mode 100644 EditorAssets/Create.png create mode 100644 EditorAssets/Display.png create mode 100644 EditorAssets/Down.png create mode 100644 EditorAssets/Free Camera.png create mode 100644 EditorAssets/GridLight.png create mode 100644 EditorAssets/Hide.png create mode 100644 EditorAssets/Highlight.png create mode 100644 EditorAssets/Instances.png create mode 100644 EditorAssets/Link.png create mode 100644 EditorAssets/Material Highlight.png create mode 100644 EditorAssets/Minus v2.png create mode 100644 EditorAssets/Minus.png create mode 100644 EditorAssets/Model Preview.png create mode 100644 EditorAssets/Modify.png create mode 100644 EditorAssets/Orbit Camera v2.png create mode 100644 EditorAssets/Orbit Camera.png create mode 100644 EditorAssets/Plus.png create mode 100644 EditorAssets/Rotate.png create mode 100644 EditorAssets/Samus Silhouette Gradient.png create mode 100644 EditorAssets/Scale.png create mode 100644 EditorAssets/Show.png create mode 100644 EditorAssets/Sphere Preview.png create mode 100644 EditorAssets/Square Preview.png create mode 100644 EditorAssets/Translate.png create mode 100644 EditorAssets/Unlink.png create mode 100644 EditorAssets/Up.png create mode 100644 EditorAssets/World.png create mode 100644 Icons.qrc create mode 100644 OpenGL/CDynamicVertexBuffer.cpp create mode 100644 OpenGL/CDynamicVertexBuffer.h create mode 100644 OpenGL/CFramebuffer.cpp create mode 100644 OpenGL/CFramebuffer.h create mode 100644 OpenGL/CGL.cpp create mode 100644 OpenGL/CGL.h create mode 100644 OpenGL/CIndexBuffer.cpp create mode 100644 OpenGL/CIndexBuffer.h create mode 100644 OpenGL/CRenderbuffer.cpp create mode 100644 OpenGL/CRenderbuffer.h create mode 100644 OpenGL/CShader.cpp create mode 100644 OpenGL/CShader.h create mode 100644 OpenGL/CShaderGenerator.cpp create mode 100644 OpenGL/CShaderGenerator.cpp.bW5112 create mode 100644 OpenGL/CShaderGenerator.h create mode 100644 OpenGL/CUniformBuffer.cpp create mode 100644 OpenGL/CUniformBuffer.h create mode 100644 OpenGL/CVertexArrayManager.cpp create mode 100644 OpenGL/CVertexArrayManager.h create mode 100644 OpenGL/CVertexBuffer.cpp create mode 100644 OpenGL/CVertexBuffer.h create mode 100644 OpenGL/GLCommon.cpp create mode 100644 OpenGL/GLCommon.h create mode 100644 OpenGL/SMeshPointer.h create mode 100644 PrimeWorldEditor.pro create mode 100644 Resource/CAnimSet.cpp create mode 100644 Resource/CAnimSet.h create mode 100644 Resource/CCollisionMesh.cpp create mode 100644 Resource/CCollisionMesh.h create mode 100644 Resource/CFont.cpp create mode 100644 Resource/CFont.h create mode 100644 Resource/CGameArea.cpp create mode 100644 Resource/CGameArea.h create mode 100644 Resource/CLight.cpp create mode 100644 Resource/CLight.h create mode 100644 Resource/CMaterial.cpp create mode 100644 Resource/CMaterial.h create mode 100644 Resource/CMaterialPass.cpp create mode 100644 Resource/CMaterialPass.h create mode 100644 Resource/CMaterialSet.cpp create mode 100644 Resource/CMaterialSet.h create mode 100644 Resource/CPakFile.cpp create mode 100644 Resource/CPakFile.h create mode 100644 Resource/CResource.cpp create mode 100644 Resource/CResource.h create mode 100644 Resource/CScan.cpp create mode 100644 Resource/CScan.h create mode 100644 Resource/CStringTable.cpp create mode 100644 Resource/CStringTable.h create mode 100644 Resource/CTexture.cpp create mode 100644 Resource/CTexture.h create mode 100644 Resource/CWorld.cpp create mode 100644 Resource/CWorld.h create mode 100644 Resource/EFormatVersion.h create mode 100644 Resource/EResType.h create mode 100644 Resource/ETevEnums.h create mode 100644 Resource/ETexelFormat.h create mode 100644 Resource/SDependency.h create mode 100644 Resource/SNamedResource.h create mode 100644 Resource/SResInfo.h create mode 100644 Resource/cooker/CMaterialCooker.cpp create mode 100644 Resource/cooker/CMaterialCooker.h create mode 100644 Resource/cooker/CModelCooker.cpp create mode 100644 Resource/cooker/CModelCooker.h create mode 100644 Resource/cooker/CSectionMgrOut.cpp create mode 100644 Resource/cooker/CSectionMgrOut.h create mode 100644 Resource/cooker/CTextureEncoder.cpp create mode 100644 Resource/cooker/CTextureEncoder.h create mode 100644 Resource/factory/CAnimSetLoader.cpp create mode 100644 Resource/factory/CAnimSetLoader.h create mode 100644 Resource/factory/CAreaLoader.cpp create mode 100644 Resource/factory/CAreaLoader.h create mode 100644 Resource/factory/CBlockMgr.cpp create mode 100644 Resource/factory/CBlockMgrIn.h create mode 100644 Resource/factory/CCollisionLoader.cpp create mode 100644 Resource/factory/CCollisionLoader.h create mode 100644 Resource/factory/CFontLoader.cpp create mode 100644 Resource/factory/CFontLoader.h create mode 100644 Resource/factory/CMaterialLoader.cpp create mode 100644 Resource/factory/CMaterialLoader.h create mode 100644 Resource/factory/CModelLoader.cpp create mode 100644 Resource/factory/CModelLoader.h create mode 100644 Resource/factory/CScanLoader.cpp create mode 100644 Resource/factory/CScanLoader.h create mode 100644 Resource/factory/CScriptLoader.cpp create mode 100644 Resource/factory/CScriptLoader.h create mode 100644 Resource/factory/CStringLoader.cpp create mode 100644 Resource/factory/CStringLoader.h create mode 100644 Resource/factory/CTemplateLoader.cpp create mode 100644 Resource/factory/CTemplateLoader.h create mode 100644 Resource/factory/CTextureDecoder.cpp create mode 100644 Resource/factory/CTextureDecoder.h create mode 100644 Resource/factory/CWorldLoader.cpp create mode 100644 Resource/factory/CWorldLoader.h create mode 100644 Resource/model/CBasicModel.cpp create mode 100644 Resource/model/CBasicModel.h create mode 100644 Resource/model/CModel.cpp create mode 100644 Resource/model/CModel.h create mode 100644 Resource/model/CStaticModel.cpp create mode 100644 Resource/model/CStaticModel.h create mode 100644 Resource/model/CVertex.h create mode 100644 Resource/model/EVertexDescription.h create mode 100644 Resource/model/SModelData.h create mode 100644 Resource/model/SSurface.cpp create mode 100644 Resource/model/SSurface.h create mode 100644 Resource/script/CMasterTemplate.cpp create mode 100644 Resource/script/CMasterTemplate.h create mode 100644 Resource/script/CProperty.h create mode 100644 Resource/script/CScriptLayer.cpp create mode 100644 Resource/script/CScriptLayer.h create mode 100644 Resource/script/CScriptObject.cpp create mode 100644 Resource/script/CScriptObject.h create mode 100644 Resource/script/CScriptTemplate.cpp create mode 100644 Resource/script/CScriptTemplate.h create mode 100644 Resource/script/CTemplateCategory.h create mode 100644 Resource/script/EAttribType.h create mode 100644 Resource/script/EObjectType.h create mode 100644 Resource/script/EPropertyType.h create mode 100644 Resource/script/SConnection.h create mode 100644 Scene/CBoundingBoxNode.cpp create mode 100644 Scene/CBoundingBoxNode.h create mode 100644 Scene/CCollisionNode.cpp create mode 100644 Scene/CCollisionNode.h create mode 100644 Scene/CLightNode.cpp create mode 100644 Scene/CLightNode.h create mode 100644 Scene/CModelNode.cpp create mode 100644 Scene/CModelNode.h create mode 100644 Scene/CRootNode.h create mode 100644 Scene/CSceneNode.cpp create mode 100644 Scene/CSceneNode.h create mode 100644 Scene/CScriptNode.cpp create mode 100644 Scene/CScriptNode.h create mode 100644 Scene/CStaticNode.cpp create mode 100644 Scene/CStaticNode.h create mode 100644 Scene/ENodeType.h create mode 100644 UI/CDarkStyle.cpp create mode 100644 UI/CDarkStyle.h create mode 100644 UI/CEditorGLWidget.cpp create mode 100644 UI/CEditorGLWidget.h create mode 100644 UI/CMaterialEditor.cpp create mode 100644 UI/CMaterialEditor.h create mode 100644 UI/CMaterialEditor.ui create mode 100644 UI/CModelEditorWindow.cpp create mode 100644 UI/CModelEditorWindow.h create mode 100644 UI/CModelEditorWindow.ui create mode 100644 UI/CNodeSelection.cpp create mode 100644 UI/CNodeSelection.h create mode 100644 UI/CSimpleDelegate.cpp create mode 100644 UI/CSimpleDelegate.h create mode 100644 UI/CStartWindow.cpp create mode 100644 UI/CStartWindow.h create mode 100644 UI/CStartWindow.ui create mode 100644 UI/CWorldEditor.cpp create mode 100644 UI/CWorldEditor.h create mode 100644 UI/CWorldEditor.ui create mode 100644 UI/CWorldEditorWindow.cpp create mode 100644 UI/CWorldEditorWindow.h create mode 100644 UI/CWorldEditorWindow.ui create mode 100644 UI/IPreviewPanel.cpp create mode 100644 UI/IPreviewPanel.h create mode 100644 UI/PWEMaterialEditor.h create mode 100644 UI/TestDialog.cpp create mode 100644 UI/TestDialog.h create mode 100644 UI/TestDialog.ui create mode 100644 UI/UICommon.cpp create mode 100644 UI/UICommon.h create mode 100644 UI/WCollapsibleGroupBox.cpp create mode 100644 UI/WCollapsibleGroupBox.h create mode 100644 UI/WColorPicker.cpp create mode 100644 UI/WColorPicker.h create mode 100644 UI/WDraggableSpinBox.cpp create mode 100644 UI/WDraggableSpinBox.h create mode 100644 UI/WPropertyEditor.cpp create mode 100644 UI/WPropertyEditor.h create mode 100644 UI/WResourceSelector.cpp create mode 100644 UI/WResourceSelector.h create mode 100644 UI/WRollout.cpp create mode 100644 UI/WRollout.h create mode 100644 UI/WScanPreviewPanel.cpp create mode 100644 UI/WScanPreviewPanel.h create mode 100644 UI/WScanPreviewPanel.ui create mode 100644 UI/WStringPreviewPanel.cpp create mode 100644 UI/WStringPreviewPanel.h create mode 100644 UI/WTextureGLWidget.cpp create mode 100644 UI/WTextureGLWidget.h create mode 100644 UI/WTexturePreviewPanel.cpp create mode 100644 UI/WTexturePreviewPanel.h create mode 100644 UI/WTexturePreviewPanel.ui create mode 100644 UI/WVectorEditor.cpp create mode 100644 UI/WVectorEditor.h create mode 100644 UI/WorldEditor/CLayerEditor.cpp create mode 100644 UI/WorldEditor/CLayerEditor.h create mode 100644 UI/WorldEditor/CLayerEditor.ui create mode 100644 UI/WorldEditor/CLayerModel.cpp create mode 100644 UI/WorldEditor/CLayerModel.h create mode 100644 UI/WorldEditor/CLayersInstanceModel.cpp create mode 100644 UI/WorldEditor/CLayersInstanceModel.h create mode 100644 UI/WorldEditor/CLinkModel.cpp create mode 100644 UI/WorldEditor/CLinkModel.h create mode 100644 UI/WorldEditor/CTypesInstanceModel.cpp create mode 100644 UI/WorldEditor/CTypesInstanceModel.h create mode 100644 UI/WorldEditor/WCreateTab.cpp create mode 100644 UI/WorldEditor/WCreateTab.h create mode 100644 UI/WorldEditor/WCreateTab.ui create mode 100644 UI/WorldEditor/WInstancesTab.cpp create mode 100644 UI/WorldEditor/WInstancesTab.h create mode 100644 UI/WorldEditor/WInstancesTab.ui create mode 100644 UI/WorldEditor/WModifyTab.cpp create mode 100644 UI/WorldEditor/WModifyTab.h create mode 100644 UI/WorldEditor/WModifyTab.ui diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..12c4b9f7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,34 @@ +# C++ objects and libs + +*.slo +*.lo +*.o +*.a +*.la +*.lai +*.so +*.dll +*.dylib + +# Qt-es + +/.qmake.cache +/.qmake.stash +*.pro.user +*.pro.user.* +*.qbs.user +*.qbs.user.* +*.moc +moc_*.cpp +qrc_*.cpp +ui_*.h +Makefile* +*-build-* + +# QtCreator + +*.autosave + +#QtCtreator Qml +*.qmlproject.user +*.qmlproject.user.* \ No newline at end of file diff --git a/Common/AnimUtil.cpp b/Common/AnimUtil.cpp new file mode 100644 index 00000000..8617687f --- /dev/null +++ b/Common/AnimUtil.cpp @@ -0,0 +1,10 @@ +#include "CTimer.h" +#include + +namespace AnimUtil +{ + float SecondsMod900() + { + return fmod((float) CTimer::GlobalTime(), 900.f); + } +} diff --git a/Common/AnimUtil.h b/Common/AnimUtil.h new file mode 100644 index 00000000..b86d4f9f --- /dev/null +++ b/Common/AnimUtil.h @@ -0,0 +1,9 @@ +#ifndef ANIMUTIL_H +#define ANIMUTIL_H + +namespace AnimUtil +{ + float SecondsMod900(); +} + +#endif // ANIMUTIL_H diff --git a/Common/CAABox.cpp b/Common/CAABox.cpp new file mode 100644 index 00000000..54438ba3 --- /dev/null +++ b/Common/CAABox.cpp @@ -0,0 +1,158 @@ +#include "CAABox.h" +#include +#include +#include "CRay.h" +#include "SRayIntersection.h" +#include + +CAABox::CAABox() +{ + mMin = CVector3f::skInfinite; + mMax = -CVector3f::skInfinite; +} + +CAABox::CAABox(const CVector3f& Min, const CVector3f& Max) +{ + mMin = Min; + mMax = Max; +} + +CAABox::CAABox(CInputStream& input) +{ + mMin = CVector3f(input); + mMax = CVector3f(input); +} + +void CAABox::Write(COutputStream& Output) +{ + mMin.Write(Output); + mMax.Write(Output); +} + +CVector3f CAABox::Center() const +{ + return (mMax - ((mMax - mMin) * 0.5f)); +} + +CVector3f CAABox::GetSize() const +{ + return (mMax - mMin); +} + +CVector3f CAABox::Min() const +{ + return mMin; +} + +CVector3f CAABox::Max() const +{ + return mMax; +} + +bool CAABox::IsNull() const +{ + return (GetSize() == CVector3f::skZero); +} + +bool CAABox::IsInfinite() const +{ + return (GetSize() == CVector3f::skInfinite); +} + +void CAABox::ExpandBounds(const CVector3f& vtx) +{ + // take an input vertex coordinate and expand the bounding box to fit it, if necessary + if (vtx.x < mMin.x) mMin.x = vtx.x; + if (vtx.x > mMax.x) mMax.x = vtx.x; + if (vtx.y < mMin.y) mMin.y = vtx.y; + if (vtx.y > mMax.y) mMax.y = vtx.y; + if (vtx.z < mMin.z) mMin.z = vtx.z; + if (vtx.z > mMax.z) mMax.z = vtx.z; +} + +void CAABox::ExpandBounds(const CAABox& AABox) +{ + ExpandBounds(AABox.mMin); + ExpandBounds(AABox.mMax); +} + +CAABox CAABox::Transformed(const CTransform4f& transform) const +{ + CAABox AABox; + + AABox.ExpandBounds(transform * CVector3f(mMin.x, mMin.y, mMin.z)); + AABox.ExpandBounds(transform * CVector3f(mMin.x, mMin.y, mMax.z)); + AABox.ExpandBounds(transform * CVector3f(mMin.x, mMax.y, mMax.z)); + AABox.ExpandBounds(transform * CVector3f(mMin.x, mMax.y, mMin.z)); + AABox.ExpandBounds(transform * CVector3f(mMax.x, mMin.y, mMin.z)); + AABox.ExpandBounds(transform * CVector3f(mMax.x, mMin.y, mMax.z)); + AABox.ExpandBounds(transform * CVector3f(mMax.x, mMax.y, mMax.z)); + AABox.ExpandBounds(transform * CVector3f(mMax.x, mMax.y, mMin.z)); + + return AABox; +} + +bool CAABox::IsPointInBox(const CVector3f& Point) const +{ + return ( ((Point.x >= mMin.x) && (Point.x <= mMax.x)) && + ((Point.y >= mMin.y) && (Point.y <= mMax.y)) && + ((Point.z >= mMin.z) && (Point.z <= mMax.z)) ); +} + +CVector3f CAABox::ClosestPointAlongVector(const CVector3f& dir) const +{ + CVector3f out; + out.x = (dir.x >= 0.f) ? mMin.x : mMax.x; + out.y = (dir.y >= 0.f) ? mMin.y : mMax.y; + out.z = (dir.z >= 0.f) ? mMin.z : mMax.z; + return out; +} + +CVector3f CAABox::FurthestPointAlongVector(const CVector3f& dir) const +{ + CVector3f out; + out.x = (dir.x >= 0.f) ? mMax.x : mMin.x; + out.y = (dir.y >= 0.f) ? mMax.y : mMin.y; + out.z = (dir.z >= 0.f) ? mMax.z : mMin.z; + return out; +} + +// ************ INTERSECTION TESTS ************ +// These tests are kinda bad and probably inaccurate, they need rewrites +bool CAABox::IntersectsAABox(const CAABox& AABox) +{ + return ((mMax > AABox.mMin) && (mMin < AABox.mMax)); +} + +bool CAABox::IntersectsSphere(const CVector3f& SphereCenter, const float SphereRadius) +{ + // Placeholder for proper sphere intersection test + // Generate an AABox for the sphere and do an AABox/AABox intersection test instead + return IntersectsAABox(CAABox(SphereCenter - SphereRadius, SphereCenter + SphereRadius)); +} + +std::pair CAABox::IntersectsRay(const CRay &Ray) const +{ + return Math::RayBoxIntersection(Ray, *this); +} + +bool CAABox::operator==(const CAABox& Other) +{ + return ((mMin == Other.mMin) && (mMax == Other.mMax)); +} + +bool CAABox::operator!=(const CAABox& Other) +{ + return (!(*this == Other)); +} + +// ************ CONSTANTS ************ + +// min set to float maximum, max set to float minimum; ideal for creating an AABox from scratch +const CAABox CAABox::skInfinite = CAABox(CVector3f(FLT_MAX), CVector3f(-FLT_MAX)); + +// a 1x1x1 bounding box +const CAABox CAABox::skOne = CAABox( CVector3f(-0.5f, -0.5f, -0.5f), CVector3f(0.5f, 0.5f, 0.5f) ); + +// a 0x0x0 bounding box +const CAABox CAABox::skZero = CAABox(CVector3f(0), CVector3f(0)); diff --git a/Common/CAABox.h b/Common/CAABox.h new file mode 100644 index 00000000..72d7c2d2 --- /dev/null +++ b/Common/CAABox.h @@ -0,0 +1,51 @@ +#ifndef CAABOX_H +#define CAABOX_H + +#include +#include +#include "CVector3f.h" +#include + +class CRay; +class CRayIntersection; + +class CAABox +{ + CVector3f mMin, mMax; + +public: + CAABox(); + CAABox(const CVector3f& Min, const CVector3f& Max); + CAABox(CInputStream& input); + void Write(COutputStream& Output); + CVector3f Center() const; + CVector3f GetSize() const; + CVector3f Min() const; + CVector3f Max() const; + bool IsNull() const; + bool IsInfinite() const; + + void ExpandBounds(const CVector3f& vtx); + void ExpandBounds(const CAABox& AABox); + CAABox Transformed(const CTransform4f& transform) const; + + bool IsPointInBox(const CVector3f& Point) const; + CVector3f ClosestPointAlongVector(const CVector3f& dir) const; + CVector3f FurthestPointAlongVector(const CVector3f& dir) const; + + // Intersection Tests + bool IntersectsAABox(const CAABox& AABox); + bool IntersectsSphere(const CVector3f& SphereCenter, const float SphereRadius); + std::pair IntersectsRay(const CRay& Ray) const; + + // Operators + bool operator==(const CAABox& Other); + bool operator!=(const CAABox& Other); + + // Constants + static const CAABox skInfinite; + static const CAABox skOne; + static const CAABox skZero; +}; + +#endif // CAABOX_H diff --git a/Common/CColor.cpp b/Common/CColor.cpp new file mode 100644 index 00000000..728d25c1 --- /dev/null +++ b/Common/CColor.cpp @@ -0,0 +1,183 @@ +#include "CColor.h" + +CColor::CColor() +{ + r = g = b = a = 0; +} + +CColor::CColor(CInputStream& src) +{ + src.ReadBytes(&r, 4); +} + +CColor::CColor(u32 rgba) +{ + r = (rgba >> 24) & 0xFF; + g = (rgba >> 16) & 0xFF; + b = (rgba >> 8) & 0xFF; + a = rgba & 0xFF; +} + +CColor::CColor(u8 rgba) +{ + r = g = b = a = rgba; +} + +CColor::CColor(u8 _r, u8 _g, u8 _b, u8 _a) +{ + r = _r; + g = _g; + b = _b; + a = _a; +} + +CColor::CColor(float rgba) +{ + r = g = b = a = u8(rgba * 255.f); +} + +CColor::CColor(float _r, float _g, float _b, float _a) +{ + r = u8(_r * 255.f); + g = u8(_g * 255.f); + b = u8(_b * 255.f); + a = u8(_a * 255.f); +} + +void CColor::Write(COutputStream &Output) +{ + Output.WriteBytes(&r, 4); +} + +long CColor::AsLongRGBA() const +{ + return (r << 24) | (g << 16) | (b << 8) | a; +} + +long CColor::ToLongARGB() const +{ + return (a << 24) | (r << 16) | (g << 8) | b; +} + +CVector4f CColor::ToVector4f() const +{ + return CVector4f(float(r) / 255.f, + float(g) / 255.f, + float(b) / 255.f, + float(a) / 255.f); +} + +bool CColor::operator==(const CColor& other) const +{ + return ((r == other.r) && + (g == other.g) && + (b == other.b) && + (a == other.a)); +} + +bool CColor::operator!=(const CColor& other) const +{ + return (!(*this == other)); +} + +CColor CColor::operator+(const CColor& other) const +{ + u16 NewR = r + other.r; + if (NewR > 255) NewR = 255; + u16 NewG = g + other.g; + if (NewG > 255) NewG = 255; + u16 NewB = b + other.b; + if (NewB > 255) NewB = 255; + u16 NewA = a + other.a; + if (NewA > 255) NewA = 255; + return CColor((u8) NewR, (u8) NewG, (u8) NewB, (u8) NewA); +} + +void CColor::operator+=(const CColor& other) +{ + *this = (*this + other); +} + +CColor CColor::operator-(const CColor& other) const +{ + s16 NewR = r - other.r; + if (NewR < 0) NewR = 0; + s16 NewG = g - other.g; + if (NewG < 0) NewG = 0; + s16 NewB = b - other.b; + if (NewB < 0) NewB = 0; + s16 NewA = a - other.a; + if (NewA < 0) NewA = 0; + return CColor((u8) NewR, (u8) NewG, (u8) NewB, (u8) NewA); +} + +void CColor::operator-=(const CColor& other) +{ + *this = (*this - other); +} + +CColor CColor::operator*(const CColor& other) const +{ + u16 NewR = r * other.r; + if (NewR > 255) NewR = 255; + u16 NewG = g * other.g; + if (NewG > 255) NewG = 255; + u16 NewB = b * other.b; + if (NewB > 255) NewB = 255; + u16 NewA = a * other.a; + if (NewA > 255) NewA = 255; + return CColor((u8) NewR, (u8) NewG, (u8) NewB, (u8) NewA); +} + +void CColor::operator*=(const CColor& other) +{ + *this = (*this - other); +} + +CColor CColor::operator*(const float other) const +{ + u8 NewR = (u8) (r * other); + u8 NewG = (u8) (g * other); + u8 NewB = (u8) (b * other); + u8 NewA = (u8) (a * other); + return CColor(NewR, NewG, NewB, NewA); +} + +void CColor::operator*=(const float other) +{ + *this = (*this * other); +} + +CColor CColor::operator/(const CColor& other) const +{ + u16 NewR = (other.r == 0) ? 0 : r / other.r; + u16 NewG = (other.g == 0) ? 0 : g / other.g; + u16 NewB = (other.b == 0) ? 0 : b / other.b; + u16 NewA = (other.a == 0) ? 0 : a / other.a; + return CColor((u8) NewR, (u8) NewG, (u8) NewB, (u8) NewA); +} + +void CColor::operator/=(const CColor& other) +{ + *this = (*this / other); +} + +// defining predefined colors +const CColor CColor::skRed (u32(0xFF0000FF)); +const CColor CColor::skGreen (u32(0x00FF00FF)); +const CColor CColor::skBlue (u32(0x0000FFFF)); +const CColor CColor::skYellow(u32(0xFFFF00FF)); +const CColor CColor::skPurple(u32(0xFF00FFFF)); +const CColor CColor::skCyan (u32(0x00FFFFFF)); +const CColor CColor::skWhite (u32(0xFFFFFFFF)); +const CColor CColor::skBlack (u32(0x000000FF)); +const CColor CColor::skGray (u32(0x808080FF)); +const CColor CColor::skTransparentRed (u32(0xFF000000)); +const CColor CColor::skTransparentGreen (u32(0x00FF0000)); +const CColor CColor::skTransparentBlue (u32(0x0000FF00)); +const CColor CColor::skTransparentYellow(u32(0xFFFF0000)); +const CColor CColor::skTransparentPurple(u32(0xFF00FF00)); +const CColor CColor::skTransparentCyan (u32(0x00FFFF00)); +const CColor CColor::skTransparentWhite (u32(0xFFFFFF00)); +const CColor CColor::skTransparentBlack (u32(0x00000000)); +const CColor CColor::skTransparentGray (u32(0x80808000)); diff --git a/Common/CColor.h b/Common/CColor.h new file mode 100644 index 00000000..ead6611c --- /dev/null +++ b/Common/CColor.h @@ -0,0 +1,60 @@ +#ifndef CCOLOR_H +#define CCOLOR_H + +#include +#include +#include "CVector4f.h" +#include "types.h" + +class CColor +{ +public: + u8 r, g, b, a; + + CColor(); + CColor(CInputStream& src); + CColor(u32 rgba); + CColor(u8 rgba); + CColor(u8 _r, u8 _g, u8 _b, u8 _a); + CColor(float rgba); + CColor(float _r, float _g, float _b, float _a); + void Write(COutputStream& Output); + + long AsLongRGBA() const; + long ToLongARGB() const; + CVector4f ToVector4f() const; + bool operator==(const CColor& other) const; + bool operator!=(const CColor& other) const; + CColor operator+(const CColor& other) const; + void operator+=(const CColor& other); + CColor operator-(const CColor& other) const; + void operator-=(const CColor& other); + CColor operator*(const CColor& other) const; + void operator*=(const CColor& other); + CColor operator*(const float other) const; + void operator*=(const float other); + CColor operator/(const CColor& other) const; + void operator/=(const CColor& other); + + // some predefined colors below for ease of use + static const CColor skRed; + static const CColor skGreen; + static const CColor skBlue; + static const CColor skYellow; + static const CColor skPurple; + static const CColor skCyan; + static const CColor skWhite; + static const CColor skBlack; + static const CColor skGray; + static const CColor skTransparentRed; + static const CColor skTransparentGreen; + static const CColor skTransparentBlue; + static const CColor skTransparentYellow; + static const CColor skTransparentPurple; + static const CColor skTransparentCyan; + static const CColor skTransparentWhite; + static const CColor skTransparentBlack; + static const CColor skTransparentGray; +}; + +#endif // CCOLOR_H diff --git a/Common/CFourCC.cpp b/Common/CFourCC.cpp new file mode 100644 index 00000000..c94b1070 --- /dev/null +++ b/Common/CFourCC.cpp @@ -0,0 +1,111 @@ +#include "CFourCC.h" + +CFourCC::CFourCC() +{ + FourCC[0] = 0; + FourCC[1] = 0; + FourCC[2] = 0; + FourCC[3] = 0; +} + +CFourCC::CFourCC(const char *src) +{ + *this = src; +} + +CFourCC::CFourCC(const std::string& src) +{ + *this = src; +} + +CFourCC::CFourCC(long src) +{ + *this = src; +} + +CFourCC::CFourCC(CInputStream& src) +{ + src.ReadBytes(&FourCC[0], 4); +} + +void CFourCC::Write(COutputStream &Output) +{ + Output.WriteBytes(FourCC, 4); +} + +CFourCC& CFourCC::operator=(const char *src) +{ + + memcpy(&FourCC[0], src, 4); + return *this; +} + +CFourCC& CFourCC::operator=(const std::string& src) +{ + memcpy(&FourCC[0], src.c_str(), 4); + return *this; +} + +CFourCC& CFourCC::operator=(long src) +{ + FourCC[0] = (src >> 24) & 0xFF; + FourCC[1] = (src >> 16) & 0xFF; + FourCC[2] = (src >> 8) & 0xFF; + FourCC[3] = (src >> 0) & 0xFF; + return *this; +} + +bool CFourCC::operator==(const CFourCC& other) const +{ + return ((FourCC[0] == other.FourCC[0]) && (FourCC[1] == other.FourCC[1]) && (FourCC[2] == other.FourCC[2]) && (FourCC[3] == other.FourCC[3])); +} + +bool CFourCC::operator!=(const CFourCC& other) const +{ + return (!(*this == other)); +} + +bool CFourCC::operator==(const char *other) const +{ + return (*this == CFourCC(other)); +} + +bool CFourCC::operator!=(const char *other) const +{ + return (!(*this == other)); +} + +bool CFourCC::operator==(const long other) const +{ + return (*this == CFourCC(other)); +} + +bool CFourCC::operator!=(const long other) const +{ + return (!(*this == other)); +} + +long CFourCC::ToLong() const +{ + return FourCC[0] << 24 | FourCC[1] << 16 | FourCC[2] << 8 | FourCC[3]; +} + +std::string CFourCC::ToString() const +{ + return std::string(FourCC, 4); +} + +CFourCC CFourCC::ToUpper() const +{ + CFourCC Out; + + for (int c = 0; c < 4; c++) + { + if ((FourCC[c] >= 0x61) && (FourCC[c] <= 0x7A)) + Out.FourCC[c] = FourCC[c] - 0x20; + else + Out.FourCC[c] = FourCC[c]; + } + + return Out; +} diff --git a/Common/CFourCC.h b/Common/CFourCC.h new file mode 100644 index 00000000..95e04319 --- /dev/null +++ b/Common/CFourCC.h @@ -0,0 +1,34 @@ +#ifndef CFOURCC_H +#define CFOURCC_H + +#include +#include +#include + +class CFourCC +{ + char FourCC[4]; +public: + CFourCC(); + CFourCC(const char *src); + CFourCC(const std::string& src); + CFourCC(long src); + CFourCC(CInputStream& src); + void Write(COutputStream& Output); + + CFourCC& operator=(const char *src); + CFourCC& operator=(const std::string& src); + CFourCC& operator=(long src); + bool operator==(const CFourCC& other) const; + bool operator!=(const CFourCC& other) const; + bool operator==(const char *other) const; + bool operator!=(const char *other) const; + bool operator==(const long other) const; + bool operator!=(const long other) const; + + long ToLong() const; + std::string ToString() const; + CFourCC ToUpper() const; +}; + +#endif // CFOURCC_H diff --git a/Common/CHashFNV1A.cpp b/Common/CHashFNV1A.cpp new file mode 100644 index 00000000..9bb9e729 --- /dev/null +++ b/Common/CHashFNV1A.cpp @@ -0,0 +1,72 @@ +#include "CHashFNV1A.h" + +const u64 CHashFNV1A::skFNVOffsetBasis32 = 0x811C9DC5; +const u64 CHashFNV1A::skFNVOffsetBasis64 = 0xCBF29CE484222325; +const u64 CHashFNV1A::skFNVPrime32 = 0x1000193; +const u64 CHashFNV1A::skFNVPrime64 = 0x100000001B3; + +CHashFNV1A::CHashFNV1A() +{ + Init32(); +} + +void CHashFNV1A::Init32() +{ + mHashLength = e32Bit; + mHash = skFNVOffsetBasis32; +} + +void CHashFNV1A::Init64() +{ + mHashLength = e64Bit; + mHash = skFNVOffsetBasis64; +} + +void CHashFNV1A::HashData(const void *pData, u32 Size) +{ + const char *pCharData = (const char*) pData; + u64 FNVPrime = (mHashLength == e32Bit) ? skFNVPrime32 : skFNVPrime64; + + for (u32 i = 0; i < Size; i++) + { + mHash ^= *pCharData; + mHash *= FNVPrime; + pCharData++; + } +} + +u32 CHashFNV1A::GetHash32() +{ + return (u32) mHash; +} + +u64 CHashFNV1A::GetHash64() +{ + return mHash; +} + +// ************ CONVENIENCE FUNCTIONS ************ +void CHashFNV1A::HashByte(const u8& v) +{ + HashData(&v, 1); +} + +void CHashFNV1A::HashShort(const u16& v) +{ + HashData(&v, 2); +} + +void CHashFNV1A::HashLong(const u32& v) +{ + HashData(&v, 4); +} + +void CHashFNV1A::HashFloat(const float& v) +{ + HashData(&v, 4); +} + +void CHashFNV1A::HashString(const std::string& v) +{ + HashData(v.data(), v.size()); +} diff --git a/Common/CHashFNV1A.h b/Common/CHashFNV1A.h new file mode 100644 index 00000000..0b76e0cc --- /dev/null +++ b/Common/CHashFNV1A.h @@ -0,0 +1,36 @@ +#ifndef CHASHFNV1A_H +#define CHASHFNV1A_H + +#include "types.h" +#include + +class CHashFNV1A +{ + u64 mHash; + + enum EHashLength { + e32Bit, e64Bit + } mHashLength; + + static const u64 skFNVOffsetBasis32; + static const u64 skFNVOffsetBasis64; + static const u64 skFNVPrime32; + static const u64 skFNVPrime64; + +public: + CHashFNV1A(); + void Init32(); + void Init64(); + void HashData(const void *pData, u32 Size); + u32 GetHash32(); + u64 GetHash64(); + + // Convenience functions + void HashByte(const u8& v); + void HashShort(const u16& v); + void HashLong(const u32& v); + void HashFloat(const float& v); + void HashString(const std::string& v); +}; + +#endif // CHASHFNV1A_H diff --git a/Common/CMatrix4f.cpp b/Common/CMatrix4f.cpp new file mode 100644 index 00000000..46d7750d --- /dev/null +++ b/Common/CMatrix4f.cpp @@ -0,0 +1,241 @@ +#include "CMatrix4f.h" +#include "CVector3f.h" +#include "CVector4f.h" +#include "CTransform4f.h" + +CMatrix4f::CMatrix4f() +{ +} + +CMatrix4f::CMatrix4f(float v) +{ + *this = skZero; + m[0][0] = v; + m[1][1] = v; + m[2][2] = v; + m[3][3] = v; +} + +CMatrix4f::CMatrix4f(float m00, float m01, float m02, float m03, + float m10, float m11, float m12, float m13, + float m20, float m21, float m22, float m23, + float m30, float m31, float m32, float m33) +{ + m[0][0] = m00; + m[0][1] = m01; + m[0][2] = m02; + m[0][3] = m03; + m[1][0] = m10; + m[1][1] = m11; + m[1][2] = m12; + m[1][3] = m13; + m[2][0] = m20; + m[2][1] = m21; + m[2][2] = m22; + m[2][3] = m23; + m[3][0] = m30; + m[3][1] = m31; + m[3][2] = m32; + m[3][3] = m33; +} + +// ************ MATH ************ +CMatrix4f CMatrix4f::Transpose() const +{ + return CMatrix4f(m[0][0], m[1][0], m[2][0], m[3][0], + m[0][1], m[1][1], m[2][1], m[3][1], + m[0][2], m[1][2], m[2][2], m[3][2], + m[0][3], m[1][3], m[2][3], m[3][3]); +} + +CMatrix4f CMatrix4f::Inverse() const +{ + // Copied from Ogre. + // todo after developing a better understanding of the math - rewrite + float m00 = m[0][0], m01 = m[0][1], m02 = m[0][2], m03 = m[0][3]; + float m10 = m[1][0], m11 = m[1][1], m12 = m[1][2], m13 = m[1][3]; + float m20 = m[2][0], m21 = m[2][1], m22 = m[2][2], m23 = m[2][3]; + float m30 = m[3][0], m31 = m[3][1], m32 = m[3][2], m33 = m[3][3]; + + float v0 = m20 * m31 - m21 * m30; + float v1 = m20 * m32 - m22 * m30; + float v2 = m20 * m33 - m23 * m30; + float v3 = m21 * m32 - m22 * m31; + float v4 = m21 * m33 - m23 * m31; + float v5 = m22 * m33 - m23 * m32; + + float t00 = + (v5 * m11 - v4 * m12 + v3 * m13); + float t10 = - (v5 * m10 - v2 * m12 + v1 * m13); + float t20 = + (v4 * m10 - v2 * m11 + v0 * m13); + float t30 = - (v3 * m10 - v1 * m11 + v0 * m12); + + float invDet = 1 / (t00 * m00 + t10 * m01 + t20 * m02 + t30 * m03); + + float d00 = t00 * invDet; + float d10 = t10 * invDet; + float d20 = t20 * invDet; + float d30 = t30 * invDet; + + float d01 = - (v5 * m01 - v4 * m02 + v3 * m03) * invDet; + float d11 = + (v5 * m00 - v2 * m02 + v1 * m03) * invDet; + float d21 = - (v4 * m00 - v2 * m01 + v0 * m03) * invDet; + float d31 = + (v3 * m00 - v1 * m01 + v0 * m02) * invDet; + + v0 = m10 * m31 - m11 * m30; + v1 = m10 * m32 - m12 * m30; + v2 = m10 * m33 - m13 * m30; + v3 = m11 * m32 - m12 * m31; + v4 = m11 * m33 - m13 * m31; + v5 = m12 * m33 - m13 * m32; + + float d02 = + (v5 * m01 - v4 * m02 + v3 * m03) * invDet; + float d12 = - (v5 * m00 - v2 * m02 + v1 * m03) * invDet; + float d22 = + (v4 * m00 - v2 * m01 + v0 * m03) * invDet; + float d32 = - (v3 * m00 - v1 * m01 + v0 * m02) * invDet; + + v0 = m21 * m10 - m20 * m11; + v1 = m22 * m10 - m20 * m12; + v2 = m23 * m10 - m20 * m13; + v3 = m22 * m11 - m21 * m12; + v4 = m23 * m11 - m21 * m13; + v5 = m23 * m12 - m22 * m13; + + float d03 = - (v5 * m01 - v4 * m02 + v3 * m03) * invDet; + float d13 = + (v5 * m00 - v2 * m02 + v1 * m03) * invDet; + float d23 = - (v4 * m00 - v2 * m01 + v0 * m03) * invDet; + float d33 = + (v3 * m00 - v1 * m01 + v0 * m02) * invDet; + + return CMatrix4f( + d00, d01, d02, d03, + d10, d11, d12, d13, + d20, d21, d22, d23, + d30, d31, d32, d33); +} + +float CMatrix4f::Determinant() const +{ + float AA = m[1][1] * ((m[2][2] * m[3][3]) - (m[2][3] * m[3][2])); + float AB = m[1][2] * ((m[2][1] * m[3][3]) - (m[2][3] * m[3][1])); + float AC = m[1][3] * ((m[2][1] * m[3][2]) - (m[2][2] * m[3][1])); + float A = m[0][0] * (AA - AB + AC); + + float BA = m[1][0] * ((m[2][2] * m[3][3]) - (m[2][3] * m[3][2])); + float BB = m[1][2] * ((m[2][0] * m[3][3]) - (m[2][3] * m[3][0])); + float BC = m[1][3] * ((m[2][0] * m[3][2]) - (m[2][2] * m[3][0])); + float B = m[0][1] * (BA - BB + BC); + + float CA = m[1][0] * ((m[2][1] * m[3][3]) - (m[2][3] * m[3][1])); + float CB = m[1][1] * ((m[2][0] * m[3][3]) - (m[2][3] * m[3][0])); + float CC = m[1][3] * ((m[2][0] * m[3][1]) - (m[2][1] * m[3][0])); + float C = m[0][2] * (CA - CB + CC); + + float DA = m[1][0] * ((m[2][1] * m[3][2]) - (m[2][2] * m[3][1])); + float DB = m[1][1] * ((m[2][0] * m[3][2]) - (m[2][2] * m[3][0])); + float DC = m[1][2] * ((m[2][0] * m[3][1]) - (m[2][1] * m[3][0])); + float D = m[0][3] * (DA - DB + DC); + + return (A - B + C - D); +} + +glm::mat4 CMatrix4f::ToGlmMat4() const +{ + glm::mat4 out = glm::mat4(1); + memcpy(&out[0][0], &m[0][0], sizeof(glm::mat4)); + return out; +} + +// ************ STATIC ************ +CMatrix4f CMatrix4f::FromGlmMat4(glm::mat4 src) +{ + CMatrix4f out; + memcpy(&out[0][0], &src[0][0], sizeof(CMatrix4f)); + return out; +} + +// ************ OPERATORS ************ +inline float* CMatrix4f::operator[](long index) +{ + return m[index]; +} + +inline const float* CMatrix4f::operator[](long index) const +{ + return m[index]; +} + +CVector3f CMatrix4f::operator*(const CVector3f& vec) const +{ + // For vec3 multiplication, the vector w component is considered to be 1.0 + CVector3f out; + float w = (m[3][0] * vec.x) + (m[3][1] * vec.y) + (m[3][2] * vec.z) + m[3][3]; + out.x = ((m[0][0] * vec.x) + (m[0][1] * vec.y) + (m[0][2] * vec.z) + m[0][3]) / w; + out.y = ((m[1][0] * vec.x) + (m[1][1] * vec.y) + (m[1][2] * vec.z) + m[1][3]) / w; + out.z = ((m[2][0] * vec.x) + (m[2][1] * vec.y) + (m[2][2] * vec.z) + m[1][3]) / w; + return out; +} + +CVector4f CMatrix4f::operator*(const CVector4f& vec) const +{ + CVector4f out; + out.x = (m[0][0] * vec.x) + (m[0][1] * vec.y) + (m[0][2] * vec.z) + (m[0][3] * vec.w); + out.y = (m[1][0] * vec.x) + (m[1][1] * vec.y) + (m[1][2] * vec.z) + (m[1][3] * vec.w); + out.z = (m[2][0] * vec.x) + (m[2][1] * vec.y) + (m[2][2] * vec.z) + (m[2][3] * vec.w); + out.w = (m[3][0] * vec.x) + (m[3][1] * vec.y) + (m[3][2] * vec.z) + (m[3][3] * vec.w); + return out; +} + +CMatrix4f CMatrix4f::operator*(const CTransform4f& mtx) const +{ + // CTransform4f is a 3x4 matrix with an implicit fourth row of {0, 0, 0, 1} + CMatrix4f out; + out[0][0] = (m[0][0] * mtx[0][0]) + (m[0][1] * mtx[1][0]) + (m[0][2] * mtx[2][0]); + out[0][1] = (m[0][0] * mtx[0][1]) + (m[0][1] * mtx[1][1]) + (m[0][2] * mtx[2][1]); + out[0][2] = (m[0][0] * mtx[0][2]) + (m[0][1] * mtx[1][2]) + (m[0][2] * mtx[2][2]); + out[0][3] = (m[0][0] * mtx[0][3]) + (m[0][1] * mtx[1][3]) + (m[0][2] * mtx[2][3]) + m[0][3]; + out[1][0] = (m[1][0] * mtx[0][0]) + (m[1][1] * mtx[1][0]) + (m[1][2] * mtx[2][0]); + out[1][1] = (m[1][0] * mtx[0][1]) + (m[1][1] * mtx[1][1]) + (m[1][2] * mtx[2][1]); + out[1][2] = (m[1][0] * mtx[0][2]) + (m[1][1] * mtx[1][2]) + (m[1][2] * mtx[2][2]); + out[1][3] = (m[1][0] * mtx[0][3]) + (m[1][1] * mtx[1][3]) + (m[1][2] * mtx[2][3]) + m[1][3]; + out[2][0] = (m[2][0] * mtx[0][0]) + (m[2][1] * mtx[1][0]) + (m[2][2] * mtx[2][0]); + out[2][1] = (m[2][0] * mtx[0][1]) + (m[2][1] * mtx[1][1]) + (m[2][2] * mtx[2][1]); + out[2][2] = (m[2][0] * mtx[0][2]) + (m[2][1] * mtx[1][2]) + (m[2][2] * mtx[2][2]); + out[2][3] = (m[2][0] * mtx[0][3]) + (m[2][1] * mtx[1][3]) + (m[2][2] * mtx[2][3]) + m[2][3]; + out[3][0] = (m[3][0] * mtx[0][0]) + (m[3][1] * mtx[1][0]) + (m[3][2] * mtx[2][0]); + out[3][1] = (m[3][0] * mtx[0][1]) + (m[3][1] * mtx[1][1]) + (m[3][2] * mtx[2][1]); + out[3][2] = (m[3][0] * mtx[0][2]) + (m[3][1] * mtx[1][2]) + (m[3][2] * mtx[2][2]); + out[3][3] = (m[3][0] * mtx[0][3]) + (m[3][1] * mtx[1][3]) + (m[3][2] * mtx[2][3]) + m[3][3]; + return out; +} + +CMatrix4f CMatrix4f::operator*(const CMatrix4f& mtx) const +{ + CMatrix4f out; + out[0][0] = (m[0][0] * mtx[0][0]) + (m[0][1] * mtx[1][0]) + (m[0][2] * mtx[2][0]) + (m[0][3] * mtx[3][0]); + out[0][1] = (m[0][0] * mtx[0][1]) + (m[0][1] * mtx[1][1]) + (m[0][2] * mtx[2][1]) + (m[0][3] * mtx[3][1]); + out[0][2] = (m[0][0] * mtx[0][2]) + (m[0][1] * mtx[1][2]) + (m[0][2] * mtx[2][2]) + (m[0][3] * mtx[3][2]); + out[0][3] = (m[0][0] * mtx[0][3]) + (m[0][1] * mtx[1][3]) + (m[0][2] * mtx[2][3]) + (m[0][3] * mtx[3][3]); + out[1][0] = (m[1][0] * mtx[0][0]) + (m[1][1] * mtx[1][0]) + (m[1][2] * mtx[2][0]) + (m[1][3] * mtx[3][0]); + out[1][1] = (m[1][0] * mtx[0][1]) + (m[1][1] * mtx[1][1]) + (m[1][2] * mtx[2][1]) + (m[1][3] * mtx[3][1]); + out[1][2] = (m[1][0] * mtx[0][2]) + (m[1][1] * mtx[1][2]) + (m[1][2] * mtx[2][2]) + (m[1][3] * mtx[3][2]); + out[1][3] = (m[1][0] * mtx[0][3]) + (m[1][1] * mtx[1][3]) + (m[1][2] * mtx[2][3]) + (m[1][3] * mtx[3][3]); + out[2][0] = (m[2][0] * mtx[0][0]) + (m[2][1] * mtx[1][0]) + (m[2][2] * mtx[2][0]) + (m[2][3] * mtx[3][0]); + out[2][1] = (m[2][0] * mtx[0][1]) + (m[2][1] * mtx[1][1]) + (m[2][2] * mtx[2][1]) + (m[2][3] * mtx[3][1]); + out[2][2] = (m[2][0] * mtx[0][2]) + (m[2][1] * mtx[1][2]) + (m[2][2] * mtx[2][2]) + (m[2][3] * mtx[3][2]); + out[2][3] = (m[2][0] * mtx[0][3]) + (m[2][1] * mtx[1][3]) + (m[2][2] * mtx[2][3]) + (m[2][3] * mtx[3][3]); + out[3][0] = (m[3][0] * mtx[0][0]) + (m[3][1] * mtx[1][0]) + (m[3][2] * mtx[2][0]) + (m[3][3] * mtx[3][0]); + out[3][1] = (m[3][0] * mtx[0][1]) + (m[3][1] * mtx[1][1]) + (m[3][2] * mtx[2][1]) + (m[3][3] * mtx[3][1]); + out[3][2] = (m[3][0] * mtx[0][2]) + (m[3][1] * mtx[1][2]) + (m[3][2] * mtx[2][2]) + (m[3][3] * mtx[3][2]); + out[3][3] = (m[3][0] * mtx[0][3]) + (m[3][1] * mtx[1][3]) + (m[3][2] * mtx[2][3]) + (m[3][3] * mtx[3][3]); + return out; +} + +// ************ CONSTANT ************ +const CMatrix4f CMatrix4f::skZero(0.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 0.0f); + +const CMatrix4f CMatrix4f::skIdentity(1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f); diff --git a/Common/CMatrix4f.h b/Common/CMatrix4f.h new file mode 100644 index 00000000..c6af8269 --- /dev/null +++ b/Common/CMatrix4f.h @@ -0,0 +1,50 @@ +#ifndef CMATRIX4_H +#define CMATRIX4_H + +#include + +class CVector3f; +class CVector4f; +class CTransform4f; + +class CMatrix4f +{ + union + { + float m[4][4]; + float _m[16]; + }; + +public: + CMatrix4f(); + CMatrix4f(float v); + CMatrix4f(float m00, float m01, float m02, float m03, + float m10, float m11, float m12, float m13, + float m20, float m21, float m22, float m23, + float m30, float m31, float m32, float m33); + + // Math + CMatrix4f Transpose() const; + CMatrix4f Inverse() const; + float Determinant() const; + + // Conversion + glm::mat4 ToGlmMat4() const; + + // Static + static CMatrix4f FromGlmMat4(glm::mat4 src); + + // Operators + inline float* operator[](long index); + inline const float* operator[](long index) const; + CVector3f operator*(const CVector3f& vec) const; + CVector4f operator*(const CVector4f& vec) const; + CMatrix4f operator*(const CTransform4f& mtx) const; + CMatrix4f operator*(const CMatrix4f& mtx) const; + + // Constants + static const CMatrix4f skZero; + static const CMatrix4f skIdentity; +}; + +#endif // CMATRIX4_H diff --git a/Common/CQuaternion.cpp b/Common/CQuaternion.cpp new file mode 100644 index 00000000..3208743e --- /dev/null +++ b/Common/CQuaternion.cpp @@ -0,0 +1,86 @@ + #include "CQuaternion.h" +#include +#include + +CQuaternion::CQuaternion() +{ + x = 0; + y = 0; + z = 0; + w = 0; +} + +CQuaternion::CQuaternion(float _x, float _y, float _z, float _w) +{ + x = _x; + y = _y; + z = _z; + w = _w; +} + +CQuaternion CQuaternion::operator*(const CQuaternion& other) const +{ + CQuaternion out; + out.x = ( x * other.w) + (y * other.z) - (z * other.y) + (w * other.x); + out.y = (-x * other.z) + (y * other.w) + (z * other.x) + (w * other.y); + out.z = ( x * other.y) - (y * other.x) + (z * other.w) + (w * other.z); + out.w = (-x * other.x) - (y * other.y) - (z * other.z) + (w * other.w); + return out; +} + +void CQuaternion::operator *= (const CQuaternion& other) +{ + *this = *this * other; +} + +// ************ STATIC ************ +CQuaternion CQuaternion::FromEuler(CVector3f euler) +{ + /** + * The commented-out code below might be faster but the conversion isn't completely correct + * So in lieu of fixing it I'm using axis angles to convert from Eulers instead + * I'm not sure what the difference is performance-wise but the result is 100% accurate + */ + /*CQuaternion quat; + + // Convert from degrees to radians + float pi = 3.14159265358979323846f; + euler = euler * pi / 180; + + // Convert to quaternion + float c1 = cos(euler.x / 2); + float c2 = cos(euler.y / 2); + float c3 = cos(euler.z / 2); + float s1 = sin(euler.x / 2); + float s2 = sin(euler.y / 2); + float s3 = sin(euler.z / 2); + + quat.w = (c1 * c2 * c3) - (s1 * s2 * s3); + quat.x = -((s1 * c2 * c3) + (c1 * s2 * s3)); + quat.y = ((c1 * s2 * c3) - (s1 * c2 * s3)); + quat.z = ((s1 * s2 * c3) + (c1 * c2 * s3));*/ + + CQuaternion x = CQuaternion::FromAxisAngle(euler.x, CVector3f(1,0,0)); + CQuaternion y = CQuaternion::FromAxisAngle(euler.y, CVector3f(0,1,0)); + CQuaternion z = CQuaternion::FromAxisAngle(euler.z, CVector3f(0,0,1)); + CQuaternion quat = z * y * x; + + return quat; +} + +CQuaternion CQuaternion::FromAxisAngle(float angle, CVector3f axis) +{ + CQuaternion quat; + axis = axis.Normalized(); + angle = angle * 3.14159265358979323846f / 180; + + float sa = sin(angle / 2); + quat.x = axis.x * sa; + quat.y = axis.y * sa; + quat.z = axis.z * sa; + quat.w = cos(angle / 2); + return quat; + +} + +CQuaternion CQuaternion::skIdentity = CQuaternion(0.f, 0.f, 0.f, 1.f); diff --git a/Common/CQuaternion.h b/Common/CQuaternion.h new file mode 100644 index 00000000..a22528c1 --- /dev/null +++ b/Common/CQuaternion.h @@ -0,0 +1,25 @@ +#ifndef CQUATERNION_H +#define CQUATERNION_H + +#include "CVector3f.h" + +class CQuaternion +{ +public: + float x, y, z, w; + + CQuaternion(); + CQuaternion(float _x, float _y, float _z, float _w); + + // Operators + CQuaternion operator*(const CQuaternion& other) const; + void operator *= (const CQuaternion& other); + + // Static + static CQuaternion FromEuler(CVector3f euler); + static CQuaternion FromAxisAngle(float angle, CVector3f axis); + + static CQuaternion skIdentity; +}; + +#endif // CQUATERNION_H diff --git a/Common/CRay.cpp b/Common/CRay.cpp new file mode 100644 index 00000000..cf3df7e5 --- /dev/null +++ b/Common/CRay.cpp @@ -0,0 +1,43 @@ +#include "CRay.h" +#include +#include +#include + +CRay::CRay() +{ +} + +CRay::CRay(const CVector3f& Origin, const CVector3f& Direction) + : mOrigin(Origin), mDirection(Direction) +{ +} + +CRay::~CRay() +{ +} + +void CRay::SetOrigin(const CVector3f& Origin) +{ + mOrigin = Origin; +} + +void CRay::SetDirection(const CVector3f& Direction) +{ + mDirection = Direction; +} + +CRay CRay::Transformed(const CTransform4f &Matrix) const +{ + CRay out; + out.mOrigin = Matrix * mOrigin; + + CVector3f Point = Matrix * (mOrigin + mDirection); + out.mDirection = (Point - out.mOrigin).Normalized(); + + return out; +} + +CVector3f CRay::PointOnRay(float Distance) const +{ + return mOrigin + (mDirection * Distance); +} diff --git a/Common/CRay.h b/Common/CRay.h new file mode 100644 index 00000000..4bef54bd --- /dev/null +++ b/Common/CRay.h @@ -0,0 +1,35 @@ +#ifndef CRAY_H +#define CRAY_H + +#include "CVector3f.h" + +class CRay +{ + CVector3f mOrigin; + CVector3f mDirection; + +public: + CRay(); + CRay(const CVector3f& Origin, const CVector3f& Direction); + ~CRay(); + const CVector3f& Origin() const; + const CVector3f& Direction() const; + void SetOrigin(const CVector3f& Origin); + void SetDirection(const CVector3f& Direction); + + CRay Transformed(const CTransform4f& Matrix) const; + CVector3f PointOnRay(float Distance) const; +}; + +// ************ INLINE FUNCTIONS ************ +inline const CVector3f& CRay::Origin() const +{ + return mOrigin; +} + +inline const CVector3f& CRay::Direction() const +{ + return mDirection; +} + +#endif // CRAY_H diff --git a/Common/CRayCollisionTester.cpp b/Common/CRayCollisionTester.cpp new file mode 100644 index 00000000..d252fd12 --- /dev/null +++ b/Common/CRayCollisionTester.cpp @@ -0,0 +1,56 @@ +#include "CRayCollisionTester.h" +#include + +CRayCollisionTester::CRayCollisionTester(const CRay& Ray) + : mRay(Ray) +{ +} + +CRayCollisionTester::~CRayCollisionTester() +{ +} + +void CRayCollisionTester::AddNode(CSceneNode *pNode, u32 AssetIndex, float Distance) +{ + mBoxIntersectList.emplace_back(SRayIntersection()); + SRayIntersection& Intersection = mBoxIntersectList.back(); + Intersection.pNode = pNode; + Intersection.AssetIndex = AssetIndex; + Intersection.Distance = Distance; +} + +SRayIntersection CRayCollisionTester::TestNodes() +{ + // Sort nodes by distance from ray + mBoxIntersectList.sort( + [](const SRayIntersection& A, SRayIntersection& B) -> bool + { + return (A.Distance < B.Distance); + }); + + // Now do more precise intersection tests on geometry + SRayIntersection Result; + Result.Hit = false; + + for (auto iNode = mBoxIntersectList.begin(); iNode != mBoxIntersectList.end(); iNode++) + { + SRayIntersection& Intersection = *iNode; + + // If we have a result, and the distance for the bounding box hit is further than the current result distance + // then we know that every remaining node is further away and there is no chance of finding a closer hit. + if ((Result.Hit) && (Result.Distance < Intersection.Distance)) + break; + + // Otherwise, more intersection tests... + CSceneNode *pNode = Intersection.pNode; + SRayIntersection MidResult = pNode->RayNodeIntersectTest(mRay, Intersection.AssetIndex); + + if (MidResult.Hit) + { + if ((!Result.Hit) || (MidResult.Distance < Result.Distance)) + Result = MidResult; + } + } + + return Result; +} diff --git a/Common/CRayCollisionTester.h b/Common/CRayCollisionTester.h new file mode 100644 index 00000000..573211f7 --- /dev/null +++ b/Common/CRayCollisionTester.h @@ -0,0 +1,32 @@ +#ifndef CRAYCOLLISIONHELPER_H +#define CRAYCOLLISIONHELPER_H + +#include "CAABox.h" +#include "CRay.h" +#include "CVector3f.h" +#include "SRayIntersection.h" +#include "types.h" + +#include + +class CSceneNode; + +class CRayCollisionTester +{ + CRay mRay; + std::list mBoxIntersectList; + +public: + CRayCollisionTester(const CRay& Ray); + ~CRayCollisionTester(); + const CRay& Ray() const; + void AddNode(CSceneNode *pNode, u32 AssetIndex, float Distance); + SRayIntersection TestNodes(); +}; + +inline const CRay& CRayCollisionTester::Ray() const +{ + return mRay; +} + +#endif // CRAYCOLLISIONHELPER_H diff --git a/Common/CTimer.cpp b/Common/CTimer.cpp new file mode 100644 index 00000000..2a3c8a6f --- /dev/null +++ b/Common/CTimer.cpp @@ -0,0 +1,89 @@ +#include "CTimer.h" +#include + +CTimer::CTimer() +{ + mStartTime = 0; + mStopTime = 0; + mStarted = false; + mPaused = false; +} + +void CTimer::Start() +{ + if (!mStarted) + { + mStartTime = GlobalTime(); + mStarted = true; + mPaused = false; + mPauseStartTime = 0; + mTotalPauseTime = 0; + mStopTime = 0; + } +} + +void CTimer::Start(double StartTime) +{ + if (!mStarted) + { + mStartTime = GlobalTime() - StartTime; + mStarted = true; + mPaused = false; + mPauseStartTime = 0; + mTotalPauseTime = 0; + mStopTime = 0; + } +} + +void CTimer::Restart() +{ + mStarted = false; + Start(); +} + +double CTimer::Stop() +{ + mStopTime = Time(); + mStarted = false; + mPaused = false; + return mStopTime; +} + +double CTimer::Pause() +{ + mPauseStartTime = GlobalTime(); + mPaused = true; + return Time(); +} + +bool CTimer::IsPaused() +{ + return mPaused; +} + +void CTimer::Resume() +{ + if (mPaused) + { + mTotalPauseTime += GlobalTime() - mPauseStartTime; + mPaused = false; + } +} + +double CTimer::Time() +{ + if (mStarted) + { + double CurrentPauseTime = 0; + if (mPaused) CurrentPauseTime = GlobalTime() - mPauseStartTime; + return GlobalTime() - mStartTime - mTotalPauseTime - CurrentPauseTime; + } + + else + return mStopTime; +} + +double CTimer::GlobalTime() +{ + return (double) clock() / CLOCKS_PER_SEC; +} diff --git a/Common/CTimer.h b/Common/CTimer.h new file mode 100644 index 00000000..0aa508f8 --- /dev/null +++ b/Common/CTimer.h @@ -0,0 +1,28 @@ +#ifndef CTIMER_H +#define CTIMER_H + +class CTimer +{ + double mStartTime; + double mPauseStartTime; + double mTotalPauseTime; + double mStopTime; + bool mStarted; + bool mPaused; + +public: + CTimer(); + void Start(); + void Start(double StartTime); + void Restart(); + double Stop(); + double Pause(); + bool IsPaused(); + void Resume(); + double Time(); + + // Static + static double GlobalTime(); +}; + +#endif // CTIMER_H diff --git a/Common/CTransform4f.cpp b/Common/CTransform4f.cpp new file mode 100644 index 00000000..5569c808 --- /dev/null +++ b/Common/CTransform4f.cpp @@ -0,0 +1,309 @@ +#include "CTransform4f.h" +#include "CVector3f.h" +#include "CVector4f.h" +#include "CQuaternion.h" +#include "CMatrix4f.h" + +// ************ CONSTRUCTRS ************ +CTransform4f::CTransform4f() +{ + *this = skIdentity; +} + +CTransform4f::CTransform4f(CInputStream& input) +{ + for (int v = 0; v < 12; v++) + _m[v] = input.ReadFloat(); +} + +CTransform4f::CTransform4f(float v) +{ + *this = skZero; + m[0][0] = v; + m[1][1] = v; + m[2][2] = v; +} + +CTransform4f::CTransform4f(float m00, float m01, float m02, float m03, + float m10, float m11, float m12, float m13, + float m20, float m21, float m22, float m23) +{ + m[0][0] = m00; + m[0][1] = m01; + m[0][2] = m02; + m[0][3] = m03; + m[1][0] = m10; + m[1][1] = m11; + m[1][2] = m12; + m[1][3] = m13; + m[2][0] = m20; + m[2][1] = m21; + m[2][2] = m22; + m[2][3] = m23; +} + +CTransform4f::CTransform4f(CVector3f Position, CQuaternion Rotation, CVector3f Scale) +{ + *this = skIdentity; + Translate(Position); + Rotate(Rotation); + this->Scale(Scale); +} + +CTransform4f::CTransform4f(CVector3f, CVector3f, CVector3f) +{ +} + +// ************ MATH ************ +void CTransform4f::Translate(CVector3f Translation) +{ + CTransform4f TranslateMtx = CTransform4f::TranslationMatrix(Translation); + *this = TranslateMtx * *this; +} + +void CTransform4f::Translate(float XTrans, float YTrans, float ZTrans) +{ + Translate(CVector3f(XTrans, YTrans, ZTrans)); +} + +void CTransform4f::Rotate(CQuaternion Rotation) +{ + CTransform4f RotateMtx = CTransform4f::RotationMatrix(Rotation); + *this = RotateMtx * *this; +} + +void CTransform4f::Rotate(CVector3f Rotation) +{ + CQuaternion quat = CQuaternion::FromEuler(Rotation); + Rotate(quat); +} + +void CTransform4f::Rotate(float XRot, float YRot, float ZRot) +{ + Rotate(CVector3f(XRot, YRot, ZRot)); +} + +void CTransform4f::Scale(CVector3f Scale) +{ + CTransform4f ScaleMtx = CTransform4f::ScaleMatrix(Scale); + *this = ScaleMtx * *this; +} + +void CTransform4f::Scale(float XScale, float YScale, float ZScale) +{ + Scale(CVector3f(XScale, YScale, ZScale)); +} + +CTransform4f CTransform4f::MultiplyIgnoreTranslation(const CTransform4f& mtx) const +{ + CTransform4f out; + out[0][0] = (m[0][0] * mtx[0][0]) + (m[0][1] * mtx[1][0]) + (m[0][2] * mtx[2][0]); + out[0][1] = (m[0][0] * mtx[0][1]) + (m[0][1] * mtx[1][1]) + (m[0][2] * mtx[2][1]); + out[0][2] = (m[0][0] * mtx[0][2]) + (m[0][1] * mtx[1][2]) + (m[0][2] * mtx[2][2]); + out[1][0] = (m[1][0] * mtx[0][0]) + (m[1][1] * mtx[1][0]) + (m[1][2] * mtx[2][0]); + out[1][1] = (m[1][0] * mtx[0][1]) + (m[1][1] * mtx[1][1]) + (m[1][2] * mtx[2][1]); + out[1][2] = (m[1][0] * mtx[0][2]) + (m[1][1] * mtx[1][2]) + (m[1][2] * mtx[2][2]); + out[2][0] = (m[2][0] * mtx[0][0]) + (m[2][1] * mtx[1][0]) + (m[2][2] * mtx[2][0]); + out[2][1] = (m[2][0] * mtx[0][1]) + (m[2][1] * mtx[1][1]) + (m[2][2] * mtx[2][1]); + out[2][2] = (m[2][0] * mtx[0][2]) + (m[2][1] * mtx[1][2]) + (m[2][2] * mtx[2][2]); + out[0][3] = 0.f; + out[1][3] = 0.f; + out[2][3] = 0.f; + return out; +} + +CTransform4f CTransform4f::Inverse() const +{ + // This uses CMatrix4f because I suck at math + // todo - rewrite this without using CMatrix4f + CMatrix4f Mat4 = ToMatrix4f().Inverse(); + CTransform4f Out; + memcpy(&Out[0][0], &Mat4[0][0], sizeof(CTransform4f)); + return Out; +} + +CTransform4f CTransform4f::QuickInverse() const +{ + CTransform4f out; + out[0][0] = m[0][0]; + out[0][1] = m[1][0]; + out[0][2] = m[2][0]; + out[0][3] = -((m[0][0] * m[0][3]) + (m[1][0] * m[1][3]) + (m[2][0] * m[2][3])); + out[1][0] = m[0][1]; + out[1][1] = m[1][1]; + out[1][2] = m[2][1]; + out[1][3] = -((m[0][1] * m[0][3]) + (m[1][1] * m[1][3]) + (m[2][1] * m[2][3])); + out[2][0] = m[0][2]; + out[2][1] = m[1][2]; + out[2][2] = m[2][2]; + out[2][3] = -((m[0][2] * m[0][3]) + (m[1][2] * m[1][3]) + (m[2][2] * m[2][3])); + return out; +} + +CTransform4f CTransform4f::NoTranslation() const +{ + return CTransform4f(m[0][0], m[0][1], m[0][2], 0.f, + m[1][0], m[1][1], m[1][2], 0.f, + m[2][0], m[2][1], m[2][2], 0.f); +} + +CTransform4f CTransform4f::RotationOnly() const +{ + return CTransform4f::FromMatrix4f(Inverse().ToMatrix4f().Transpose()); +} + +// ************ OPERATORS ************ +float* CTransform4f::operator[](long index) +{ + return m[index]; +} + +const float* CTransform4f::operator[](long index) const +{ + return m[index]; +} + +CVector3f CTransform4f::operator*(const CVector3f& vec) const +{ + CVector3f out; + out.x = (m[0][0] * vec.x) + (m[0][1] * vec.y) + (m[0][2] * vec.z) + (m[0][3]); + out.y = (m[1][0] * vec.x) + (m[1][1] * vec.y) + (m[1][2] * vec.z) + (m[1][3]); + out.z = (m[2][0] * vec.x) + (m[2][1] * vec.y) + (m[2][2] * vec.z) + (m[2][3]); + return out; +} + +CVector4f CTransform4f::operator*(const CVector4f& vec) const +{ + CVector4f out; + out.x = (m[0][0] * vec.x) + (m[0][1] * vec.y) + (m[0][2] * vec.z) + (m[0][3] * vec.w); + out.y = (m[1][0] * vec.x) + (m[1][1] * vec.y) + (m[1][2] * vec.z) + (m[1][3] * vec.w); + out.z = (m[2][0] * vec.x) + (m[2][1] * vec.y) + (m[2][2] * vec.z) + (m[2][3] * vec.w); + out.w = vec.w; + return out; +} + +CTransform4f CTransform4f::operator*(const CTransform4f& mtx) const +{ + CTransform4f out; + out[0][0] = (m[0][0] * mtx[0][0]) + (m[0][1] * mtx[1][0]) + (m[0][2] * mtx[2][0]); + out[0][1] = (m[0][0] * mtx[0][1]) + (m[0][1] * mtx[1][1]) + (m[0][2] * mtx[2][1]); + out[0][2] = (m[0][0] * mtx[0][2]) + (m[0][1] * mtx[1][2]) + (m[0][2] * mtx[2][2]); + out[0][3] = (m[0][0] * mtx[0][3]) + (m[0][1] * mtx[1][3]) + (m[0][2] * mtx[2][3]) + m[0][3]; + out[1][0] = (m[1][0] * mtx[0][0]) + (m[1][1] * mtx[1][0]) + (m[1][2] * mtx[2][0]); + out[1][1] = (m[1][0] * mtx[0][1]) + (m[1][1] * mtx[1][1]) + (m[1][2] * mtx[2][1]); + out[1][2] = (m[1][0] * mtx[0][2]) + (m[1][1] * mtx[1][2]) + (m[1][2] * mtx[2][2]); + out[1][3] = (m[1][0] * mtx[0][3]) + (m[1][1] * mtx[1][3]) + (m[1][2] * mtx[2][3]) + m[1][3]; + out[2][0] = (m[2][0] * mtx[0][0]) + (m[2][1] * mtx[1][0]) + (m[2][2] * mtx[2][0]); + out[2][1] = (m[2][0] * mtx[0][1]) + (m[2][1] * mtx[1][1]) + (m[2][2] * mtx[2][1]); + out[2][2] = (m[2][0] * mtx[0][2]) + (m[2][1] * mtx[1][2]) + (m[2][2] * mtx[2][2]); + out[2][3] = (m[2][0] * mtx[0][3]) + (m[2][1] * mtx[1][3]) + (m[2][2] * mtx[2][3]) + m[2][3]; + return out; +} + +void CTransform4f::operator*=(const CTransform4f& mtx) +{ + *this = *this * mtx; +} + +bool CTransform4f::operator==(const CTransform4f& mtx) const +{ + return ((m[0][0] == mtx[0][0]) && + (m[0][1] == mtx[0][1]) && + (m[0][2] == mtx[0][2]) && + (m[0][3] == mtx[0][3]) && + (m[1][0] == mtx[1][0]) && + (m[1][1] == mtx[1][1]) && + (m[1][2] == mtx[1][2]) && + (m[1][3] == mtx[1][3]) && + (m[2][0] == mtx[2][0]) && + (m[2][1] == mtx[2][1]) && + (m[2][2] == mtx[2][2]) && + (m[2][3] == mtx[2][3])); +} + +bool CTransform4f::operator!=(const CTransform4f& mtx) const +{ + return (!(*this == mtx)); +} + +// ************ CONVERSION ************ +CMatrix4f CTransform4f::ToMatrix4f() const +{ + return CMatrix4f(m[0][0], m[0][1], m[0][2], m[0][3], + m[1][0], m[1][1], m[1][2], m[1][3], + m[2][0], m[2][1], m[2][2], m[2][3], + 0.f, 0.f, 0.f, 1.f); +} + +// ************ STATIC ************ +CTransform4f CTransform4f::TranslationMatrix(CVector3f Translation) +{ + CTransform4f out = skIdentity; + out[0][3] = Translation.x; + out[1][3] = Translation.y; + out[2][3] = Translation.z; + return out; +} + +CTransform4f CTransform4f::RotationMatrix(CQuaternion Rotation) +{ + CTransform4f out = skIdentity; + float x = Rotation.x; + float y = Rotation.y; + float z = Rotation.z; + float w = Rotation.w; + float x2 = x * x; + float y2 = y * y; + float z2 = z * z; + + out[0][0] = 1.0f - (2 * y2) - (2 * z2); + out[0][1] = (2 * x * y) - (2 * z * w); + out[0][2] = (2 * x * z) + (2 * y * w); + out[1][0] = (2 * x * y) + (2 * z * w); + out[1][1] = 1.0f - (2 * x2) - (2 * z2); + out[1][2] = (2 * y * z) - (2 * x * w); + out[2][0] = (2 * x * z) - (2 * y * w); + out[2][1] = (2 * y * z) + (2 * x * w); + out[2][2] = 1.0f - (2 * x2) - (2 * y2); + return out; +} + + CTransform4f CTransform4f::ScaleMatrix(CVector3f Scale) + { + CTransform4f out = skIdentity; + out[0][0] = Scale.x; + out[1][1] = Scale.y; + out[2][2] = Scale.z; + return out; + } + +CTransform4f CTransform4f::FromMatrix4f(const CMatrix4f& mtx) +{ + CTransform4f out; + for (int r = 0; r < 3; r++) + for (int c = 0; c < 4; c++) + out[r][c] = mtx[r][c]; + return out; +} + +CTransform4f CTransform4f::FromGlmMat4(const glm::mat4& mtx) +{ + CTransform4f out; + for (int r = 0; r < 3; r++) + for (int c = 0; c < 4; c++) + out[r][c] = mtx[r][c]; + return out; +} + +static CTransform4f FromGlmMat4(const glm::mat4&) +{ +} + +// ************ CONSTANTS ************ +const CTransform4f CTransform4f::skIdentity(1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f); + +const CTransform4f CTransform4f::skZero(0.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 0.0f); diff --git a/Common/CTransform4f.h b/Common/CTransform4f.h new file mode 100644 index 00000000..b8edfe41 --- /dev/null +++ b/Common/CTransform4f.h @@ -0,0 +1,70 @@ +#ifndef CTRANSFORM4F_H +#define CTRANSFORM4F_H + +#include +#include "CMatrix4f.h" +#include + +class CVector3f; +class CVector4f; +class CQuaternion; +class CMatrix4f; + +class CTransform4f +{ + union + { + float m[3][4]; + float _m[12]; + }; + +public: + CTransform4f(); + CTransform4f(CInputStream& input); + CTransform4f(float v); + CTransform4f(float m00, float m01, float m02, float m03, + float m10, float m11, float m12, float m13, + float m20, float m21, float m22, float m23); + CTransform4f(CVector3f Position, CQuaternion Rotation, CVector3f Scale); + CTransform4f(CVector3f Position, CVector3f Rotation, CVector3f Scale); + + // Math + void Translate(CVector3f Translation); + void Translate(float XTrans, float YTrans, float ZTrans); + void Rotate(CQuaternion Rotation); + void Rotate(CVector3f Rotation); + void Rotate(float XRot, float YRot, float ZRot); + void Scale(CVector3f Scale); + void Scale(float XScale, float YScale, float ZScale); + CTransform4f MultiplyIgnoreTranslation(const CTransform4f& mtx) const; + CTransform4f Inverse() const; + CTransform4f QuickInverse() const; + CTransform4f NoTranslation() const; + CTransform4f RotationOnly() const; + + // Conversion + CMatrix4f ToMatrix4f() const; + + // Static + static CTransform4f TranslationMatrix(CVector3f Translation); + static CTransform4f RotationMatrix(CQuaternion Rotation); + static CTransform4f ScaleMatrix(CVector3f Scale); + static CTransform4f FromMatrix4f(const CMatrix4f& mtx); + static CTransform4f FromGlmMat4(const glm::mat4& mtx); + + // Operators + float* operator[](long index); + const float* operator[](long index) const; + CVector3f operator*(const CVector3f& vec) const; + CVector4f operator*(const CVector4f& vec) const; + CTransform4f operator*(const CTransform4f& mtx) const; + void operator*=(const CTransform4f& mtx); + bool operator==(const CTransform4f& mtx) const; + bool operator!=(const CTransform4f& mtx) const; + + // Constant + static const CTransform4f skIdentity; + static const CTransform4f skZero; +}; + +#endif // CTRANSFORM4F_H diff --git a/Common/CUniqueID.cpp b/Common/CUniqueID.cpp new file mode 100644 index 00000000..77e0fa4c --- /dev/null +++ b/Common/CUniqueID.cpp @@ -0,0 +1,299 @@ +#include "CUniqueID.h" +#include + +#include +#include +#include + +// this class probably isn't optimized! this may not be the best way to do things +using IOUtil::SystemEndianness; +using IOUtil::LittleEndian; +using IOUtil::BigEndian; + +CUniqueID::CUniqueID() +{ + memset(mID, 0xFF, 16); + mLength = eInvalidUIDLength; +} + +CUniqueID::CUniqueID(u64 ID) +{ + // This constructor is intended to be used with both 32-bit and 64-bit input values + memset(mID, 0xFF, 16); + + // 64-bit - check for valid content in upper 32 bits (at least one bit set + one bit unset) + if ((ID & 0xFFFFFFFF00000000) && (~ID & 0xFFFFFFFF00000000)) + { + memcpy(&mID, &ID, 8); + mLength = e64Bit; + } + + // 32-bit + else + { + memcpy(mID, &ID, 4); + mLength = e32Bit; + } + + // Reverse for Big Endian + if (SystemEndianness == BigEndian) + Reverse(); +} + +CUniqueID::CUniqueID(u64 Part1, u64 Part2) +{ + // Constructor for 128-bit IDs + memcpy(&mID[0], &Part1, 8); + memcpy(&mID[8], &Part2, 8); + mLength = e128Bit; +} + +CUniqueID::CUniqueID(const char* ID) +{ + *this = CUniqueID::FromString(ID); +} + +CUniqueID::CUniqueID(CInputStream& Input, EUIDLength Length) +{ + memset(mID, 0, 16); + Input.ReadBytes(&mID[16 - Length], Length); + + if (Length != e128Bit) + if (SystemEndianness == LittleEndian) + Reverse(); + + mLength = Length; +} + +u32 CUniqueID::ToLong() const +{ + if (SystemEndianness == LittleEndian) + return *((u32*) mID); + else + return *((u32*) &mID[12]); +} + +u64 CUniqueID::ToLongLong() const +{ + if (SystemEndianness == LittleEndian) + return *((u64*) mID); + else + return *((u64*) &mID[8]); +} + +std::string CUniqueID::ToString() const +{ + std::stringstream Ret; + Ret << std::hex << std::setfill('0'); + + switch (mLength) + { + case e32Bit: + Ret << std::setw(8) << ToLong(); + break; + case e64Bit: + Ret << std::setw(16) << ToLongLong(); + break; + case e128Bit: + for (u32 i = 0; i < 16; i++) + Ret << std::setw(2) << (u32) mID[i]; + break; + } + + return Ret.str(); +} + +void CUniqueID::Reverse() +{ + std::reverse(mID, &mID[16]); +} + +EUIDLength CUniqueID::Length() const +{ + return mLength; +} + +void CUniqueID::SetLength(EUIDLength Length) +{ + mLength = Length; +} + +bool CUniqueID::IsValid() const +{ + if (mLength == e32Bit) + return (*this != skInvalidID32); + + else if (mLength == e64Bit) + return (*this != skInvalidID64); + + else if (mLength == e128Bit) + return (*this != skInvalidID128); + + else return false; +} + +// ************ OPERATORS ************ +void CUniqueID::operator=(const u64& Input) +{ + *this = CUniqueID(Input); +} + +void CUniqueID::operator=(const char* Input) +{ + *this = CUniqueID(Input); +} + +bool CUniqueID::operator==(const CUniqueID& Other) const +{ + return ((mLength == Other.mLength) && + (memcmp(mID, Other.mID, 16) == 0)); +} + +bool CUniqueID::operator!=(const CUniqueID& Other) const +{ + return (!(*this == Other)); +} + +bool CUniqueID::operator>(const CUniqueID& Other) const +{ + if (mLength != Other.mLength) + return mLength > Other.mLength; + + switch (mLength) + { + case e32Bit: + return (ToLong() > Other.ToLong()); + + case e64Bit: + return (ToLongLong() > Other.ToLongLong()); + + case e128Bit: + for (u32 i = 0; i < 16; i++) + if (mID[i] != Other.mID[i]) + return (mID[i] > Other.mID[i]); + return false; + + default: + return false; + } +} + +bool CUniqueID::operator>=(const CUniqueID& Other) const +{ + return ((*this == Other) || (*this > Other)); +} + +bool CUniqueID::operator<(const CUniqueID& Other) const +{ + if (mLength != Other.mLength) + return mLength < Other.mLength; + + switch (mLength) + { + case e32Bit: + return (ToLong() < Other.ToLong()); + + case e64Bit: + return (ToLongLong() < Other.ToLongLong()); + + case e128Bit: + for (u32 i = 0; i < 16; i++) + if (mID[i] != Other.mID[i]) + return (mID[i] < Other.mID[i]); + return false; + + default: + return false; + } +} + +bool CUniqueID::operator<=(const CUniqueID& Other) const +{ + return ((*this == Other) || (*this < Other)); +} + +bool CUniqueID::operator==(u64 Other) const +{ + return (*this == CUniqueID(Other)); +} + +bool CUniqueID::operator!=(u64 Other) const +{ + return (!(*this == Other)); +} + +// ************ STATIC ************ +CUniqueID CUniqueID::FromString(std::string String) +{ + // If the input is a hex ID in string form, then preserve it... otherwise, generate an ID by hashing the string + std::string Name = StringUtil::GetFileName(String); + if (Name.back() == '\0') Name.pop_back(); + u32 NameLength = Name.length(); + + if (StringUtil::IsHexString(Name)) + { + if (NameLength == 8) + { + CUniqueID ID; + ID.mLength = e32Bit; + + u32 LongID = StringUtil::StrToRes32(Name); + + if (SystemEndianness == LittleEndian) + memcpy(ID.mID, &LongID, 4); + else + memcpy(&ID.mID[12], &LongID, 4); + + return ID; + } + + else if (NameLength == 16) + { + CUniqueID ID; + ID.mLength = e64Bit; + + u64 LongID = StringUtil::StrToRes64(Name); + + if (SystemEndianness == LittleEndian) + memcpy(ID.mID, &LongID, 8); + else + memcpy(&ID.mID[8], &LongID, 8); + + return ID; + } + + else if (NameLength == 32) + { + CUniqueID ID; + ID.mLength = e128Bit; + StringUtil::StrToRes128(Name, (char*) ID.mID); + return ID; + } + } + + return CUniqueID( (u64) StringUtil::Hash64(String) ); +} + +CUniqueID CUniqueID::FromData(void *pData, EUIDLength Length) +{ + CUniqueID ID; + ID.mLength = Length; + memcpy(ID.mID, pData, Length); + return ID; +} + +CUniqueID CUniqueID::RandomID() +{ + CUniqueID ID; + ID.mLength = e128Bit; + + for (u32 i = 0; i < 16; i++) + ID.mID[i] = rand() & 0xFF; + + return ID; +} + +// ************ STATIC MEMBER INITIALIZATION ************ +CUniqueID CUniqueID::skInvalidID32 = CUniqueID((u32) -1); +CUniqueID CUniqueID::skInvalidID64 = CUniqueID((u64) -1); +CUniqueID CUniqueID::skInvalidID128 = CUniqueID((u64) -1, (u64) -1); diff --git a/Common/CUniqueID.h b/Common/CUniqueID.h new file mode 100644 index 00000000..61e6c6c5 --- /dev/null +++ b/Common/CUniqueID.h @@ -0,0 +1,56 @@ +#ifndef CUNIQUEID_H +#define CUNIQUEID_H + +#include "types.h" +#include + +enum EUIDLength +{ + e32Bit = 4, + e64Bit = 8, + e128Bit = 16, + eInvalidUIDLength = 0 +}; + +class CUniqueID +{ + EUIDLength mLength; + u8 mID[16]; + +public: + CUniqueID(); + CUniqueID(u64 ID); + CUniqueID(u64 Part1, u64 Part2); + CUniqueID(const char* ID); + CUniqueID(CInputStream& Input, EUIDLength Length); + u32 ToLong() const; + u64 ToLongLong() const; + std::string ToString() const; + void Reverse(); + EUIDLength Length() const; + void SetLength(EUIDLength Length); + bool IsValid() const; + + // Operators + void operator=(const u64& Input); + void operator=(const char *Input); + bool operator==(const CUniqueID& Other) const; + bool operator!=(const CUniqueID& Other) const; + bool operator>(const CUniqueID& Other) const; + bool operator>=(const CUniqueID& Other) const; + bool operator<(const CUniqueID& Other) const; + bool operator<=(const CUniqueID& Other) const; + bool operator==(u64 Other) const; + bool operator!=(u64 Other) const; + + // Static + static CUniqueID FromString(std::string String); + static CUniqueID FromData(void *pData, EUIDLength Length); + static CUniqueID RandomID(); + + static CUniqueID skInvalidID32; + static CUniqueID skInvalidID64; + static CUniqueID skInvalidID128; +}; + +#endif // CUNIQUEID_H diff --git a/Common/CVector2f.cpp b/Common/CVector2f.cpp new file mode 100644 index 00000000..ba4a526b --- /dev/null +++ b/Common/CVector2f.cpp @@ -0,0 +1,156 @@ +#include "CVector2f.h" + +CVector2f::CVector2f() +{ + x = y = 0.f; +} + +CVector2f::CVector2f(float xy) +{ + x = y = xy; +} + +CVector2f::CVector2f(float _x, float _y) +{ + x = _x; + y = _y; +} + +CVector2f::CVector2f(CInputStream& Input) +{ + x = Input.ReadFloat(); + y = Input.ReadFloat(); +} + +void CVector2f::Write(COutputStream &Output) +{ + Output.WriteFloat(x); + Output.WriteFloat(y); +} + +CVector2f CVector2f::operator+(const CVector2f& src) const +{ + CVector2f out; + out.x = this->x + src.x; + out.y = this->y + src.y; + return out; +} + +CVector2f CVector2f::operator-(const CVector2f& src) const +{ + CVector2f out; + out.x = this->x - src.x; + out.y = this->y - src.y; + return out; +} + +CVector2f CVector2f::operator*(const CVector2f& src) const +{ + CVector2f out; + out.x = this->x * src.x; + out.y = this->y * src.y; + return out; +} + +CVector2f CVector2f::operator/(const CVector2f& src) const +{ + CVector2f out; + out.x = this->x / src.x; + out.y = this->y / src.y; + return out; +} + +void CVector2f::operator+=(const CVector2f& other) +{ + *this = *this + other; +} + +void CVector2f::operator-=(const CVector2f& other) +{ + *this = *this - other; +} + +void CVector2f::operator*=(const CVector2f& other) +{ + *this = *this * other; +} + +void CVector2f::operator/=(const CVector2f& other) +{ + *this = *this / other; +} + +CVector2f CVector2f::operator+(const float src) const +{ + CVector2f out; + out.x = this->x + src; + out.y = this->y + src; + return out; +} + +CVector2f CVector2f::operator-(const float src) const +{ + CVector2f out; + out.x = this->x - src; + out.y = this->y - src; + return out; +} + +CVector2f CVector2f::operator*(const float src) const +{ + CVector2f out; + out.x = this->x * src; + out.y = this->y * src; + return out; +} + +CVector2f CVector2f::operator/(const float src) const +{ + CVector2f out; + out.x = this->x / src; + out.y = this->y / src; + return out; +} + +void CVector2f::operator+=(const float other) +{ + *this = *this + other; +} + +void CVector2f::operator-=(const float other) +{ + *this = *this - other; +} + +void CVector2f::operator*=(const float other) +{ + *this = *this * other; +} + +void CVector2f::operator/=(const float other) +{ + *this = *this / other; +} + +bool CVector2f::operator==(const CVector2f& other) const +{ + return ((x == other.x) && (y == other.y)); +} + +bool CVector2f::operator!=(const CVector2f& other) const +{ + return (!(*this == other)); +} + +float& CVector2f::operator[](long index) +{ + return (&x)[index]; +} + +// ************ STATIC MEMBER INITIALIZATION ************ +const CVector2f CVector2f::skZero = CVector2f(0, 0); +const CVector2f CVector2f::skOne = CVector2f(1, 1); +const CVector2f CVector2f::skUp = CVector2f(0, 1); +const CVector2f CVector2f::skRight = CVector2f(1, 0); +const CVector2f CVector2f::skDown = CVector2f(0,-1); +const CVector2f CVector2f::skLeft = CVector2f(-1,0); diff --git a/Common/CVector2f.h b/Common/CVector2f.h new file mode 100644 index 00000000..dc7c931a --- /dev/null +++ b/Common/CVector2f.h @@ -0,0 +1,46 @@ +#ifndef CVECTOR2F +#define CVECTOR2F + +#include +#include + +class CVector2f +{ +public: + float x, y; + CVector2f(); + CVector2f(float xy); + CVector2f(float _x, float _y); + CVector2f(CInputStream& Input); + void Write(COutputStream& Output); + + CVector2f operator+(const CVector2f& other) const; + CVector2f operator-(const CVector2f& other) const; + CVector2f operator*(const CVector2f& other) const; + CVector2f operator/(const CVector2f& other) const; + void operator+=(const CVector2f& other); + void operator-=(const CVector2f& other); + void operator*=(const CVector2f& other); + void operator/=(const CVector2f& other); + CVector2f operator+(const float other) const; + CVector2f operator-(const float other) const; + CVector2f operator*(const float other) const; + CVector2f operator/(const float other) const; + void operator+=(const float other); + void operator-=(const float other); + void operator*=(const float other); + void operator/=(const float other); + bool operator==(const CVector2f& other) const; + bool operator!=(const CVector2f& other) const; + float& operator[](long index); + + // Static Members + static const CVector2f skZero; + static const CVector2f skOne; + static const CVector2f skUp; + static const CVector2f skRight; + static const CVector2f skDown; + static const CVector2f skLeft; +}; + +#endif // CVECTOR2F diff --git a/Common/CVector2i.cpp b/Common/CVector2i.cpp new file mode 100644 index 00000000..88922bc5 --- /dev/null +++ b/Common/CVector2i.cpp @@ -0,0 +1,139 @@ +#include "CVector2i.h" + +CVector2i::CVector2i() +{ + x = y = 0; +} + +CVector2i::CVector2i(int xy) +{ + x = y = xy; +} + +CVector2i::CVector2i(int _x, int _y) +{ + x = _x; + y = _y; +} + +CVector2i CVector2i::operator+(const CVector2i& other) const +{ + CVector2i out; + out.x = this->x + other.x; + out.y = this->y + other.y; + return out; +} + +CVector2i CVector2i::operator-(const CVector2i& other) const +{ + CVector2i out; + out.x = this->x - other.x; + out.y = this->y - other.y; + return out; +} + +CVector2i CVector2i::operator*(const CVector2i& other) const +{ + CVector2i out; + out.x = this->x * other.x; + out.y = this->y * other.y; + return out; +} + +CVector2i CVector2i::operator/(const CVector2i& other) const +{ + CVector2i out; + out.x = this->x / other.x; + out.y = this->y / other.y; + return out; +} + +void CVector2i::operator+=(const CVector2i& other) +{ + *this = *this + other; +} + +void CVector2i::operator-=(const CVector2i& other) +{ + *this = *this - other; +} + +void CVector2i::operator*=(const CVector2i& other) +{ + *this = *this * other; +} + +void CVector2i::operator/=(const CVector2i& other) +{ + *this = *this / other; +} + +CVector2i CVector2i::operator+(const int other) const +{ + CVector2i out; + out.x = this->x + other; + out.y = this->y + other; + return out; +} + +CVector2i CVector2i::operator-(const int other) const +{ + CVector2i out; + out.x = this->x - other; + out.y = this->y - other; + return out; +} + +CVector2i CVector2i::operator*(const int other) const +{ + CVector2i out; + out.x = this->x * other; + out.y = this->y * other; + return out; +} + +CVector2i CVector2i::operator/(const int other) const +{ + CVector2i out; + out.x = this->x / other; + out.y = this->y / other; + return out; +} + +void CVector2i::operator+=(const int other) +{ + *this = *this + other; +} + +void CVector2i::operator-=(const int other) +{ + *this = *this - other; +} + +void CVector2i::operator*=(const int other) +{ + *this = *this * other; +} + +void CVector2i::operator/=(const int other) +{ + *this = *this / other; +} + +bool CVector2i::operator==(const CVector2i& other) const +{ + return ((this->x == other.x) && (this->y == other.y)); +} + +bool CVector2i::operator!=(const CVector2i& other) const +{ + return (!(*this == other)); +} + +int& CVector2i::operator[](int index) +{ + return (&x)[index]; +} + +// ************ STATIC MEMBER INTIALIZATION ************ +const CVector2i CVector2i::skZero = CVector2i(0,0); diff --git a/Common/CVector2i.h b/Common/CVector2i.h new file mode 100644 index 00000000..de540f89 --- /dev/null +++ b/Common/CVector2i.h @@ -0,0 +1,39 @@ +#ifndef CVECTOR2I_H +#define CVECTOR2I_H + +#include +#include + +class CVector2i +{ +public: + int x, y; + CVector2i(); + CVector2i(int xy); + CVector2i(int _x, int _y); + + CVector2i operator+(const CVector2i& other) const; + CVector2i operator-(const CVector2i& other) const; + CVector2i operator*(const CVector2i& other) const; + CVector2i operator/(const CVector2i& other) const; + void operator+=(const CVector2i& other); + void operator-=(const CVector2i& other); + void operator*=(const CVector2i& other); + void operator/=(const CVector2i& other); + CVector2i operator+(const int other) const; + CVector2i operator-(const int other) const; + CVector2i operator*(const int other) const; + CVector2i operator/(const int other) const; + void operator+=(const int other); + void operator-=(const int other); + void operator*=(const int other); + void operator/=(const int other); + bool operator==(const CVector2i& other) const; + bool operator!=(const CVector2i& other) const; + int& operator[](int index); + + // Static Members + static const CVector2i skZero; +}; + +#endif // CVECTOR2I_H diff --git a/Common/CVector3f.cpp b/Common/CVector3f.cpp new file mode 100644 index 00000000..381aa7f0 --- /dev/null +++ b/Common/CVector3f.cpp @@ -0,0 +1,300 @@ +#include "CVector3f.h" +#include "CVector2f.h" +#include "CVector4f.h" +#include "CTransform4f.h" +#include +#include + +CVector3f::CVector3f() +{ + x = y = z = 0.f; +} + +CVector3f::CVector3f(float xyz) +{ + x = y = z = xyz; +} + +CVector3f::CVector3f(float _x, float _y, float _z) +{ + x = _x; + y = _y; + z = _z; +} + +CVector3f::CVector3f(CInputStream& Input) +{ + x = Input.ReadFloat(); + y = Input.ReadFloat(); + z = Input.ReadFloat(); +} + +void CVector3f::Write(COutputStream &Output) +{ + Output.WriteFloat(x); + Output.WriteFloat(y); + Output.WriteFloat(z); +} + +// ************ SWIZZLE ************ +CVector2f CVector3f::xy() +{ + return CVector2f(x, y); +} + +CVector2f CVector3f::xz() +{ + return CVector2f(x, z); +} + +CVector2f CVector3f::yz() +{ + return CVector2f(y, z); +} + +// ************ MATH ************ +float CVector3f::Magnitude() const +{ + return sqrtf(SquaredMagnitude()); +} + +float CVector3f::SquaredMagnitude() const +{ + return (powf(x,2) + powf(y,2) + powf(z,2)); +} + +CVector3f CVector3f::Normalized() const +{ + return *this / Magnitude();; +} + +float CVector3f::Dot(const CVector3f& other) const +{ + return (x * other.x) + (y * other.y) + (z * other.z); +} + +CVector3f CVector3f::Cross(const CVector3f& other) const +{ + return CVector3f((y * other.z) - (z * other.y), (z * other.x) - (x * other.z), (x * other.y) - (y * other.x)); +} + +float CVector3f::Distance(const CVector3f& point) const +{ + return (*this - point).Magnitude(); +} + +float CVector3f::SquaredDistance(const CVector3f& point) const +{ + return (*this - point).SquaredMagnitude(); +} + +// ************ OPERATORS ************ +// VECTOR/VECTOR +CVector3f CVector3f::operator+(const CVector3f& src) const +{ + CVector3f out; + out.x = this->x + src.x; + out.y = this->y + src.y; + out.z = this->z + src.z; + return out; +} + +CVector3f CVector3f::operator-(const CVector3f& src) const +{ + CVector3f out; + out.x = this->x - src.x; + out.y = this->y - src.y; + out.z = this->z - src.z; + return out; +} + +CVector3f CVector3f::operator*(const CVector3f& src) const +{ + CVector3f out; + out.x = this->x * src.x; + out.y = this->y * src.y; + out.z = this->z * src.z; + return out; +} + +CVector3f CVector3f::operator/(const CVector3f& src) const +{ + CVector3f out; + out.x = this->x / src.x; + out.y = this->y / src.y; + out.z = this->z / src.z; + return out; +} + +void CVector3f::operator+=(const CVector3f& other) +{ + *this = *this + other; +} + +void CVector3f::operator-=(const CVector3f& other) +{ + *this = *this - other; +} + +void CVector3f::operator*=(const CVector3f& other) +{ + *this = *this * other; +} + +void CVector3f::operator/=(const CVector3f& other) +{ + *this = *this / other; +} + +bool CVector3f::operator> (const CVector3f& other) const +{ + return ((x > other.x) && (y > other.y) && (z > other.z)); +} + +bool CVector3f::operator>=(const CVector3f& other) const +{ + return ((x >= other.x) && (y >= other.y) && (z >= other.z)); +} + +bool CVector3f::operator< (const CVector3f& other) const +{ + return ((x < other.x) && (y < other.y) && (z < other.z)); +} + +bool CVector3f::operator<=(const CVector3f& other) const +{ + return ((x <= other.x) && (y <= other.y) && (z <= other.z)); +} + +bool CVector3f::operator==(const CVector3f& other) const +{ + return ((x == other.x) && (y == other.y) && (z == other.z)); +} + +bool CVector3f::operator!=(const CVector3f& other) const +{ + return (!(*this == other)); +} + +// VECTOR/FLOAT +CVector3f CVector3f::operator+(const float src) const +{ + CVector3f out; + out.x = this->x + src; + out.y = this->y + src; + out.z = this->z + src; + return out; +} + +CVector3f CVector3f::operator-(const float src) const +{ + CVector3f out; + out.x = this->x - src; + out.y = this->y - src; + out.z = this->z - src; + return out; +} + +CVector3f CVector3f::operator*(const float src) const +{ + CVector3f out; + out.x = this->x * src; + out.y = this->y * src; + out.z = this->z * src; + return out; +} + +CVector3f CVector3f::operator/(const float src) const +{ + CVector3f out; + out.x = this->x / src; + out.y = this->y / src; + out.z = this->z / src; + return out; +} + +void CVector3f::operator+=(const float other) +{ + *this = *this + other; +} + +void CVector3f::operator-=(const float other) +{ + *this = *this - other; +} + +void CVector3f::operator*=(const float other) +{ + *this = *this * other; +} + +void CVector3f::operator/=(const float other) +{ + *this = *this / other; +} + +// VECTOR/MATRIX +CVector3f CVector3f::operator*(const CTransform4f& mtx) const +{ + CVector3f out; + out.x = (x * mtx[0][0]) + (y * mtx[1][0]) + (z * mtx[2][0]); + out.y = (x * mtx[0][1]) + (y * mtx[1][1]) + (z * mtx[2][1]); + out.z = (x * mtx[0][2]) + (y * mtx[1][2]) + (z * mtx[2][2]); + return out; +} + +void CVector3f::operator*=(const CTransform4f& mtx) +{ + *this = *this * mtx; +} + +CVector3f CVector3f::operator*(const CMatrix4f& mtx) const +{ + // To multiply by a Matrix4f, we consider the vector w component to be 1 + CVector3f out; + float w = (x * mtx[0][3]) + (y * mtx[1][3]) + (z * mtx[2][3]) + mtx[3][3]; + out.x = ((x * mtx[0][0]) + (y * mtx[1][0]) + (z * mtx[2][0]) + mtx[3][0]) / w; + out.y = ((x * mtx[0][1]) + (y * mtx[1][1]) + (z * mtx[2][1]) + mtx[3][1]) / w; + out.z = ((x * mtx[0][2]) + (y * mtx[1][2]) + (z * mtx[2][2]) + mtx[3][2]) / w; + return out; +} + +// UNARY +CVector3f CVector3f::operator-() const +{ + return CVector3f(-x, -y, -z); +} + +float& CVector3f::operator[](long index) +{ + return (&x)[index]; +} + +const float& CVector3f::operator[](long index) const +{ + return (&x)[index]; +} + +// ************ CONSTANTS ************ +const CVector3f CVector3f::skZero = CVector3f(0.f); +const CVector3f CVector3f::skOne = CVector3f(1.f); +const CVector3f CVector3f::skInfinite = CVector3f(FLT_MAX); + +// ************ OTHER ************ +std::ostream& operator<<(std::ostream& o, const CVector3f& Vector) +{ + o << std::setprecision(6) + << std::fixed + << "[" + << ((Vector.x >= 0) ? " " : "") + << Vector.x + << ", " + << ((Vector.y >= 0) ? " " : "") + << Vector.y + << ", " + << ((Vector.z >= 0) ? " " : "") + << Vector.z + << "]"; + + return o; +} diff --git a/Common/CVector3f.h b/Common/CVector3f.h new file mode 100644 index 00000000..9b81c511 --- /dev/null +++ b/Common/CVector3f.h @@ -0,0 +1,83 @@ +#ifndef CVECTOR3F +#define CVECTOR3F + +#include +#include +#include + +class CMatrix4f; +class CVector2f; +class CVector4f; +class CTransform4f; + +class CVector3f +{ +public: + float x, y, z; + + CVector3f(); + CVector3f(float xyz); + CVector3f(float _x, float _y, float _z); + CVector3f(CInputStream& Input); + void Write(COutputStream& Output); + + // Swizzle + CVector2f xy(); + CVector2f xz(); + CVector2f yz(); + + // Math + float Magnitude() const; + float SquaredMagnitude() const; + CVector3f Normalized() const; + float Dot(const CVector3f& other) const; + CVector3f Cross(const CVector3f& other) const; + float Distance(const CVector3f& point) const; + float SquaredDistance(const CVector3f& point) const; + + // Vector/Vector + CVector3f operator+(const CVector3f& other) const; + CVector3f operator-(const CVector3f& other) const; + CVector3f operator*(const CVector3f& other) const; + CVector3f operator/(const CVector3f& other) const; + void operator+=(const CVector3f& other); + void operator-=(const CVector3f& other); + void operator*=(const CVector3f& other); + void operator/=(const CVector3f& other); + bool operator> (const CVector3f& other) const; + bool operator>=(const CVector3f& other) const; + bool operator< (const CVector3f& other) const; + bool operator<=(const CVector3f& other) const; + bool operator==(const CVector3f& other) const; + bool operator!=(const CVector3f& other) const; + + // Vector/Float + CVector3f operator+(const float other) const; + CVector3f operator-(const float other) const; + CVector3f operator*(const float other) const; + CVector3f operator/(const float other) const; + void operator+=(const float other); + void operator-=(const float other); + void operator*=(const float other); + void operator/=(const float other); + + // Vector/Matrix + CVector3f operator*(const CTransform4f& mtx) const; + void operator*=(const CTransform4f& mtx); + CVector3f operator*(const CMatrix4f& mtx) const; + + // Unary + CVector3f operator-() const; + float& operator[](long index); + const float& operator[](long index) const; + + // Constants + static const CVector3f skZero; + static const CVector3f skOne; + static const CVector3f skInfinite; + + // Other + friend std::ostream& operator<<(std::ostream& o, const CVector3f& Vector); +}; + +#endif // CVECTOR3F diff --git a/Common/CVector4f.cpp b/Common/CVector4f.cpp new file mode 100644 index 00000000..8b4521a2 --- /dev/null +++ b/Common/CVector4f.cpp @@ -0,0 +1,273 @@ +#include "CVector4f.h" +#include "CVector2f.h" +#include "CVector3f.h" +#include "CTransform4f.h" + +CVector4f::CVector4f() +{ + x = y = z = w = 0.f; +} + +CVector4f::CVector4f(float xyzw) +{ + x = y = z = w = xyzw; +} + +CVector4f::CVector4f(float _x, float _y, float _z, float _w) +{ + x = _x; + y = _y; + z = _z; + w = _w; +} + +CVector4f::CVector4f(const CVector2f& xy, float _z, float _w) +{ + x = xy.x; + y = xy.y; + z = _z; + w = _w; +} + +CVector4f::CVector4f(const CVector3f& xyz) +{ + x = xyz.x; + y = xyz.y; + z = xyz.z; + w = 1.f; +} + +CVector4f::CVector4f(const CVector3f& xyz, float _w) +{ + x = xyz.x; + y = xyz.y; + z = xyz.z; + w = _w; +} + +CVector4f::CVector4f(CInputStream& Input) +{ + x = Input.ReadFloat(); + y = Input.ReadFloat(); + z = Input.ReadFloat(); + w = Input.ReadFloat(); +} + +void CVector4f::Write(COutputStream &Output) +{ + Output.WriteFloat(x); + Output.WriteFloat(y); + Output.WriteFloat(z); + Output.WriteFloat(w); +} + +// ************ SWIZZLE ************ +CVector3f CVector4f::xyz() +{ + return CVector3f(x, y, z); +} + +CVector3f CVector4f::xzw() +{ + return CVector3f(x, z, w); +} + +CVector3f CVector4f::yzw() +{ + return CVector3f(y, z, w); +} + +CVector2f CVector4f::xy() +{ + return CVector2f(x, y); +} + +CVector2f CVector4f::xz() +{ + return CVector2f(x, z); +} + +CVector2f CVector4f::xw() +{ + return CVector2f(x, w); +} + +CVector2f CVector4f::yz() +{ + return CVector2f(y, z); +} + +CVector2f CVector4f::yw() +{ + return CVector2f(y, w); +} + +CVector2f CVector4f::zw() +{ + return CVector2f(z, w); +} + +// ************ MATH ************ +// ************ VECTOR/VECTOR ************ +CVector4f CVector4f::operator+(const CVector4f& src) const +{ + CVector4f out; + out.x = this->x + src.x; + out.y = this->y + src.y; + out.z = this->z + src.z; + out.w = this->w + src.w; + return out; +} + +CVector4f CVector4f::operator-(const CVector4f& src) const +{ + CVector4f out; + out.x = this->x - src.x; + out.y = this->y - src.y; + out.z = this->z - src.z; + out.w = this->w - src.w; + return out; +} + +CVector4f CVector4f::operator*(const CVector4f& src) const +{ + CVector4f out; + out.x = this->x * src.x; + out.y = this->y * src.y; + out.z = this->z * src.z; + out.w = this->w * src.w; + return out; +} + +CVector4f CVector4f::operator/(const CVector4f& src) const +{ + CVector4f out; + out.x = this->x / src.x; + out.y = this->y / src.y; + out.z = this->z / src.z; + out.w = this->w / src.w; + return out; +} + +void CVector4f::operator+=(const CVector4f& other) +{ + *this = *this + other; +} + +void CVector4f::operator-=(const CVector4f& other) +{ + *this = *this - other; +} + +void CVector4f::operator*=(const CVector4f& other) +{ + *this = *this * other; +} + +void CVector4f::operator/=(const CVector4f& other) +{ + *this = *this / other; +} + +bool CVector4f::operator==(const CVector4f& other) const +{ + return ((x == other.x) && (y == other.y) && (z == other.z) && (w == other.w)); +} + +// ************ VECTOR/FLOAT ************ +CVector4f CVector4f::operator+(const float src) const +{ + CVector4f out; + out.x = this->x + src; + out.y = this->y + src; + out.z = this->z + src; + out.w = this->w + src; + return out; +} + +CVector4f CVector4f::operator-(const float src) const +{ + CVector4f out; + out.x = this->x - src; + out.y = this->y - src; + out.z = this->z - src; + out.w = this->w - src; + return out; +} + +CVector4f CVector4f::operator*(const float src) const +{ + CVector4f out; + out.x = this->x * src; + out.y = this->y * src; + out.z = this->z * src; + out.w = this->w * src; + return out; +} + +CVector4f CVector4f::operator/(const float src) const +{ + CVector4f out; + out.x = this->x / src; + out.y = this->y / src; + out.z = this->z / src; + out.w = this->w / src; + return out; +} + +void CVector4f::operator+=(const float other) +{ + *this = *this + other; +} + +void CVector4f::operator-=(const float other) +{ + *this = *this - other; +} + +void CVector4f::operator*=(const float other) +{ + *this = *this * other; +} + +void CVector4f::operator/=(const float other) +{ + *this = *this / other; +} + +// ************ VECTOR/MATRIX ************ +CVector4f CVector4f::operator*(const CTransform4f& mtx) const +{ + CVector4f out; + out.x = (x * mtx[0][0]) + (y * mtx[1][0]) + (z * mtx[2][0]); + out.y = (x * mtx[0][1]) + (y * mtx[1][1]) + (z * mtx[2][1]); + out.z = (x * mtx[0][2]) + (y * mtx[1][2]) + (z * mtx[2][2]); + out.w = (x * mtx[0][3]) + (y * mtx[1][3]) + (z * mtx[2][3]) + w; + return out; +} + +void CVector4f::operator*=(const CTransform4f& mtx) +{ + *this = *this * mtx; +} + +CVector4f CVector4f::operator*(const CMatrix4f& mtx) const +{ + CVector4f out; + out.x = (x * mtx[0][0]) + (y * mtx[1][0]) + (z * mtx[2][0]) + (w * mtx[3][0]); + out.y = (x * mtx[0][1]) + (y * mtx[1][1]) + (z * mtx[2][1]) + (w * mtx[3][1]); + out.z = (x * mtx[0][2]) + (y * mtx[1][2]) + (z * mtx[2][2]) + (w * mtx[3][2]); + out.w = (x * mtx[0][3]) + (y * mtx[1][3]) + (z * mtx[2][3]) + (w * mtx[3][3]); + return out; +} + +void CVector4f::operator*=(const CMatrix4f& mtx) +{ + *this = *this * mtx; +} + +// ************ UNARY ************ +float& CVector4f::operator[](long index) +{ + return (&x)[index]; +} diff --git a/Common/CVector4f.h b/Common/CVector4f.h new file mode 100644 index 00000000..cab802e5 --- /dev/null +++ b/Common/CVector4f.h @@ -0,0 +1,68 @@ +#ifndef CVECTOR4F +#define CVECTOR4F + +#include +#include + +class CMatrix4f; +class CTransform4f; +class CVector2f; +class CVector3f; + +class CVector4f +{ +public: + float x, y, z, w; + + CVector4f(); + CVector4f(float xyzw); + CVector4f(float _x, float _y, float _z, float _w); + CVector4f(const CVector2f& xy, float _z, float _w); + CVector4f(const CVector3f& xyz); + CVector4f(const CVector3f& xyz, float _w); + CVector4f(CInputStream& Input); + void Write(COutputStream& Output); + + // Swizzle + CVector3f xyz(); + CVector3f xzw(); + CVector3f yzw(); + CVector2f xy(); + CVector2f xz(); + CVector2f xw(); + CVector2f yz(); + CVector2f yw(); + CVector2f zw(); + + // Vector/Vector + CVector4f operator+(const CVector4f& other) const; + CVector4f operator-(const CVector4f& other) const; + CVector4f operator*(const CVector4f& other) const; + CVector4f operator/(const CVector4f& other) const; + void operator+=(const CVector4f& other); + void operator-=(const CVector4f& other); + void operator*=(const CVector4f& other); + void operator/=(const CVector4f& other); + bool operator==(const CVector4f& other) const; + + // Vector/Float + CVector4f operator+(const float other) const; + CVector4f operator-(const float other) const; + CVector4f operator*(const float other) const; + CVector4f operator/(const float other) const; + void operator+=(const float other); + void operator-=(const float other); + void operator*=(const float other); + void operator/=(const float other); + + // Vector/Matrix + CVector4f operator*(const CTransform4f& mtx) const; + void operator*=(const CTransform4f& mtx); + CVector4f operator*(const CMatrix4f& mtx) const; + void operator*=(const CMatrix4f& mtx); + + // Unary + float& operator[](long index); +}; + +#endif // CVECTOR4F diff --git a/Common/CompressionUtil.cpp b/Common/CompressionUtil.cpp new file mode 100644 index 00000000..9ef7206b --- /dev/null +++ b/Common/CompressionUtil.cpp @@ -0,0 +1,39 @@ +#include "types.h" +#include +#include + +namespace CompressionUtil +{ + bool DecompressAreaLZO(u8 *src, u32 src_len, u8 *dst, u32 dst_len) + { + u8 *src_end = src + src_len; + u8 *dst_end = dst + dst_len; + + lzo_init(); + lzo_uint decmp; + + while ((src < src_end) && (dst < dst_end)) + { + u8 a = *src++; + u8 b = *src++; + u16 size = (a << 8) | b; + + if (size >= 0xC000) + { + size = 0x10000 - size; + memcpy(dst, src, size); + dst += size; + src += size; + } + + else + { + lzo1x_decompress(src, size, dst, &decmp, LZO1X_MEM_DECOMPRESS); + src += size; + dst += decmp; + } + } + + return ((src == src_end) && (dst == dst_end)); + } +} diff --git a/Common/CompressionUtil.h b/Common/CompressionUtil.h new file mode 100644 index 00000000..559de81d --- /dev/null +++ b/Common/CompressionUtil.h @@ -0,0 +1,11 @@ +#ifndef COMPRESSIONUTIL_H +#define COMPRESSIONUTIL_H + +#include "types.h" + +namespace CompressionUtil +{ + bool DecompressAreaLZO(u8 *src, u32 src_len, u8 *dst, u32 dst_len); +} + +#endif // COMPRESSIONUTIL_H diff --git a/Common/EKeyInputs.h b/Common/EKeyInputs.h new file mode 100644 index 00000000..dae53081 --- /dev/null +++ b/Common/EKeyInputs.h @@ -0,0 +1,21 @@ +#ifndef EKEYINPUTS +#define EKEYINPUTS + +#include "EnumUtil.h" + +enum EKeyInputs +{ + eNoKeys = 0, + eCtrlKey = 0x1, + eAltKey = 0x2, + eQKey = 0x4, + eWKey = 0x8, + eEKey = 0x10, + eAKey = 0x20, + eSKey = 0x40, + eDKey = 0x80 +}; +DEFINE_ENUM_FLAGS(EKeyInputs) + +#endif // EKEYINPUTS + diff --git a/Common/EMouseInputs.h b/Common/EMouseInputs.h new file mode 100644 index 00000000..d865b553 --- /dev/null +++ b/Common/EMouseInputs.h @@ -0,0 +1,15 @@ +#ifndef EMOUSEINPUTS +#define EMOUSEINPUTS + +#include "EnumUtil.h" + +enum EMouseInputs +{ + eLeftButton = 0x1, + eMiddleButton = 0x2, + eRightButton = 0x4 +}; +DEFINE_ENUM_FLAGS(EMouseInputs) + +#endif // EMOUSEINPUTS + diff --git a/Common/EnumUtil.h b/Common/EnumUtil.h new file mode 100644 index 00000000..eaf51ae3 --- /dev/null +++ b/Common/EnumUtil.h @@ -0,0 +1,19 @@ +#ifndef ENUMUTIL +#define ENUMUTIL + +#define DEFINE_ENUM_FLAGS(X) \ +inline X operator|(const X& A, const X& B) { \ + return (X) ((int) A | (int) B); \ +} \ +inline void operator|= (X& A, X& B) { \ + A = A | B; \ +} \ +inline X operator|(const X& A, const int B) { \ + return (X) ((int) A | B); \ +} \ +inline void operator|= (X& A, int B) { \ + A = A | B; \ +} + +#endif // ENUMUTIL + diff --git a/Common/Math.cpp b/Common/Math.cpp new file mode 100644 index 00000000..a1ceeafc --- /dev/null +++ b/Common/Math.cpp @@ -0,0 +1,244 @@ +#include "Math.h" + +namespace Math +{ + +float Pow(float Base, float Exponent) +{ + return pow(Base, Exponent); +} + +float Distance(const CVector3f& A, const CVector3f& B) +{ + return sqrtf( Pow(B.x - A.x, 2.f) + + Pow(B.y - A.y, 2.f) + + Pow(B.z - A.z, 2.f) ); +} + +std::pair RayBoxIntersection(const CRay& Ray, const CAABox& Box) +{ + // Code slightly modified from Ogre + // https://github.com/ehsan/ogre/blob/master/OgreMain/src/OgreMath.cpp + if (Box.IsNull()) return std::pair(false, 0); + if (Box.IsInfinite()) return std::pair(true, 0); + + float lowt = 0.0f; + float t; + bool Hit = false; + CVector3f HitPoint; + const CVector3f& RayOrig = Ray.Origin(); + const CVector3f& RayDir = Ray.Direction(); + const CVector3f Min = Box.Min(); + const CVector3f Max = Box.Max(); + + // Check origin inside first + if ( RayOrig > Min && RayOrig < Max ) + { + return std::pair(true, 0); + } + + // Check each face in turn, only check closest 3 + // Min x + if (RayOrig.x <= Min.x && RayDir.x > 0) + { + t = (Min.x - RayOrig.x) / RayDir.x; + if (t >= 0) + { + // Substitute t back into ray and check bounds and dist + HitPoint = RayOrig + RayDir * t; + if (HitPoint.y >= Min.y && HitPoint.y <= Max.y && + HitPoint.z >= Min.z && HitPoint.z <= Max.z && + (!Hit || t < lowt)) + { + Hit = true; + lowt = t; + } + } + } + // Max x + if (RayOrig.x >= Max.x && RayDir.x < 0) + { + t = (Max.x - RayOrig.x) / RayDir.x; + if (t >= 0) + { + // Substitute t back into ray and check bounds and dist + HitPoint = RayOrig + RayDir * t; + if (HitPoint.y >= Min.y && HitPoint.y <= Max.y && + HitPoint.z >= Min.z && HitPoint.z <= Max.z && + (!Hit || t < lowt)) + { + Hit = true; + lowt = t; + } + } + } + // Min y + if (RayOrig.y <= Min.y && RayDir.y > 0) + { + t = (Min.y - RayOrig.y) / RayDir.y; + if (t >= 0) + { + // Substitute t back into ray and check bounds and dist + HitPoint = RayOrig + RayDir * t; + if (HitPoint.x >= Min.x && HitPoint.x <= Max.x && + HitPoint.z >= Min.z && HitPoint.z <= Max.z && + (!Hit || t < lowt)) + { + Hit = true; + lowt = t; + } + } + } + // Max y + if (RayOrig.y >= Max.y && RayDir.y < 0) + { + t = (Max.y - RayOrig.y) / RayDir.y; + if (t >= 0) + { + // Substitute t back into ray and check bounds and dist + HitPoint = RayOrig + RayDir * t; + if (HitPoint.x >= Min.x && HitPoint.x <= Max.x && + HitPoint.z >= Min.z && HitPoint.z <= Max.z && + (!Hit || t < lowt)) + { + Hit = true; + lowt = t; + } + } + } + // Min z + if (RayOrig.z <= Min.z && RayDir.z > 0) + { + t = (Min.z - RayOrig.z) / RayDir.z; + if (t >= 0) + { + // Substitute t back into ray and check bounds and dist + HitPoint = RayOrig + RayDir * t; + if (HitPoint.x >= Min.x && HitPoint.x <= Max.x && + HitPoint.y >= Min.y && HitPoint.y <= Max.y && + (!Hit || t < lowt)) + { + Hit = true; + lowt = t; + } + } + } + // Max z + if (RayOrig.z >= Max.z && RayDir.z < 0) + { + t = (Max.z - RayOrig.z) / RayDir.z; + if (t >= 0) + { + // Substitute t back into ray and check bounds and dist + HitPoint = RayOrig + RayDir * t; + if (HitPoint.x >= Min.x && HitPoint.x <= Max.x && + HitPoint.y >= Min.y && HitPoint.y <= Max.y && + (!Hit || t < lowt)) + { + Hit = true; + lowt = t; + } + } + } + + return std::pair(Hit, lowt); +} + +std::pair RayTriangleIntersection(const CRay& Ray, + const CVector3f& vtxA, const CVector3f& vtxB, + const CVector3f& vtxC, bool AllowBackfaces) +{ + // Ogre code cuz I'm lazy and bad at math + // https://github.com/ehsan/ogre/blob/master/OgreMain/src/OgreMath.cpp#L709 + CVector3f FaceNormal = (vtxB - vtxA).Cross(vtxC - vtxA); + + // + // Calculate intersection with plane. + // + float t; + { + float denom = FaceNormal.Dot(Ray.Direction()); + + // Check intersect side + if (denom > + std::numeric_limits::epsilon()) + { + if (!AllowBackfaces) + return std::pair(false, 0); + } + else if (denom < - std::numeric_limits::epsilon()) + { + if (false) + return std::pair(false, 0); + } + else + { + // Parallel or triangle area is close to zero when + // the plane normal not normalised. + return std::pair(false, 0); + } + + t = FaceNormal.Dot(vtxA - Ray.Origin()) / denom; + + if (t < 0) + { + // Intersection is behind origin + return std::pair(false, 0); + } + } + + // + // Calculate the largest area projection plane in X, Y or Z. + // + size_t i0, i1; + { + float n0 = fabs(FaceNormal[0]); + float n1 = fabs(FaceNormal[1]); + float n2 = fabs(FaceNormal[2]); + + i0 = 1; i1 = 2; + if (n1 > n2) + { + if (n1 > n0) i0 = 0; + } + else + { + if (n2 > n0) i1 = 0; + } + } + + // + // Check the intersection point is inside the triangle. + // + { + float u1 = vtxB[i0] - vtxA[i0]; + float v1 = vtxB[i1] - vtxA[i1]; + float u2 = vtxC[i0] - vtxA[i0]; + float v2 = vtxC[i1] - vtxA[i1]; + float u0 = t * Ray.Direction()[i0] + Ray.Origin()[i0] - vtxA[i0]; + float v0 = t * Ray.Direction()[i1] + Ray.Origin()[i1] - vtxA[i1]; + + float alpha = u0 * v2 - u2 * v0; + float beta = u1 * v0 - u0 * v1; + float area = u1 * v2 - u2 * v1; + + // epsilon to avoid float precision error + const float EPSILON = 1e-6f; + + float tolerance = - EPSILON * area; + + if (area > 0) + { + if (alpha < tolerance || beta < tolerance || alpha+beta > area-tolerance) + return std::pair(false, 0); + } + else + { + if (alpha > tolerance || beta > tolerance || alpha+beta < area-tolerance) + return std::pair(false, 0); + } + } + + return std::pair(true, t); +} + +} // End namespace diff --git a/Common/Math.h b/Common/Math.h new file mode 100644 index 00000000..98ccd478 --- /dev/null +++ b/Common/Math.h @@ -0,0 +1,25 @@ +#ifndef MATH +#define MATH + +#include "CAABox.h" +#include "CRay.h" +#include "CVector3f.h" +#include "SRayIntersection.h" +#include + +namespace Math +{ + +float Pow(float Base, float Exponent); + +float Distance(const CVector3f& A, const CVector3f& B); + +std::pair RayBoxIntersection(const CRay& Ray, const CAABox& Box); + +std::pair RayTriangleIntersection(const CRay& Ray, const CVector3f& PointA, + const CVector3f& PointB, const CVector3f& PointC, + bool AllowBackfaces = false); +} + +#endif // MATH + diff --git a/Common/SRayIntersection.h b/Common/SRayIntersection.h new file mode 100644 index 00000000..23baeab4 --- /dev/null +++ b/Common/SRayIntersection.h @@ -0,0 +1,20 @@ +#ifndef SRAYINTERSECTION +#define SRAYINTERSECTION + +#include "types.h" +class CSceneNode; + +struct SRayIntersection +{ + bool Hit; + float Distance; + CSceneNode *pNode; + u32 AssetIndex; + + SRayIntersection() {} + SRayIntersection(bool _Hit, float _Distance, CSceneNode *_pNode, u32 _AssetIndex) + : Hit(_Hit), Distance(_Distance), pNode(_pNode), AssetIndex(_AssetIndex) {} +}; + +#endif // SRAYINTERSECTION + diff --git a/Common/StringUtil.cpp b/Common/StringUtil.cpp new file mode 100644 index 00000000..b8b97543 --- /dev/null +++ b/Common/StringUtil.cpp @@ -0,0 +1,321 @@ +#include +#include +#include +#include +#include "StringUtil.h" + +#include // For SwapBytes + +namespace StringUtil +{ + std::string GetFileDirectory(std::string path) + { + size_t endpath = path.find_last_of("\\/"); + return path.substr(0, endpath + 1); + } + + std::string GetFileName(std::string path) + { + size_t endpath = path.find_last_of("\\/") + 1; + size_t endname = path.find_last_of("."); + return path.substr(endpath, endname - endpath); + } + + std::string GetFileNameWithExtension(std::string path) + { + size_t endpath = path.find_last_of("\\/"); + return path.substr(endpath + 1, path.size() - endpath); + } + + std::string GetPathWithoutExtension(std::string path) + { + size_t endname = path.find_last_of("."); + return path.substr(0, endname); + } + + std::string GetExtension(std::string path) + { + size_t endname = path.find_last_of("."); + return path.substr(endname + 1, path.size() - endname); + } + + + // Not convinced stringstream is the best way to do string conversions of asset IDs - don't know of a better way tho + std::string ResToStr(unsigned long assetID) + { + std::stringstream sstream; + sstream << std::hex << std::setw(8) << std::setfill('0') << assetID << std::dec; + return sstream.str(); + } + + std::string ResToStr(unsigned long long assetID) + { + std::stringstream sstream; + sstream << std::hex << std::setw(16) << std::setfill('0') << assetID << std::dec; + return sstream.str(); + } + + std::string ToUpper(std::string str) + { + for (unsigned int i = 0; i < str.length(); i++) + { + if ((str[i] >= 0x61) && (str[i] <= 0x7A)) + str[i] -= 0x20; + } + + return str; + } + + std::string ToLower(std::string str) + { + for (unsigned int i = 0; i < str.length(); i++) + { + if ((str[i] >= 0x41) && (str[i] <= 0x5A)) + str[i] += 0x20; + } + + return str; + } + + std::string ToHexString(unsigned char num, bool addPrefix, int width) + { + return ToHexString((unsigned long) num, addPrefix, width); + } + + std::string ToHexString(unsigned short num, bool addPrefix, int width) + { + return ToHexString((unsigned long) num, addPrefix, width); + } + + std::string ToHexString(unsigned long num, bool addPrefix, int width) + { + std::stringstream str; + if (addPrefix) str << "0x"; + str << std::hex << std::setw(width) << std::setfill('0') << num; + return str.str(); + } + + long Hash32(std::string str) + { + unsigned long hash = 0; + + for (unsigned int c = 0; c < str.size(); c++) { + hash += str[c]; + hash *= 101; + } + + return hash; + } + + long long Hash64(std::string str) + { + unsigned long long hash = 0; + + for (unsigned int c = 0; c < str.size(); c++) { + hash += str[c]; + hash *= 101; + } + + return hash; + } + + long StrToRes32(std::string str) { + return std::stoul(str, nullptr, 16); + } + + long long StrToRes64(std::string str) { + return std::stoull(str, nullptr, 16); + } + + void StrToRes128(std::string str, char *out) { + long long Part1 = std::stoull(str.substr(0, 16), nullptr, 16); + long long Part2 = std::stoull(str.substr(16, 16), nullptr, 16); + + if (IOUtil::SystemEndianness == IOUtil::LittleEndian) + { + IOUtil::SwapBytes(Part1); + IOUtil::SwapBytes(Part2); + } + + memcpy(out, &Part1, 8); + memcpy(out + 8, &Part2, 8); + } + + long GetResID32(std::string str) + { + long resID; + if (IsHexString(str, false, 8)) + resID = StrToRes32(str); + else + resID = Hash32(GetFileName(str)); + return resID; + } + + bool IsHexString(std::string str, bool requirePrefix, long width) + { + str = GetFileName(str); + + if (requirePrefix && (str.substr(0, 2) != "0x")) + return false; + + if ((width == -1) && (str.substr(0, 2) == "0x")) + str = str.substr(2, str.size() - 2); + + if (width == -1) + width = str.size(); + + if ((str.size() == width + 2) && (str.substr(0, 2) == "0x")) + str = str.substr(2, width); + + if (str.size() != width) return false; + + for (int c = 0; c < width; c++) + { + char chr = str[c]; + if (!((chr >= '0') && (chr <= '9')) && + !((chr >= 'a') && (chr <= 'f')) && + !((chr >= 'A') && (chr <= 'F'))) + return false; + } + return true; + } + + std::string AppendSlash(std::string str) + { + char a = str.back(); + char b = str[str.length() - 1]; + + if (a == 0) + { + if ((b != '/') && (b != '\\')) + { + str.back() = '/'; + str.push_back(0); + } + } + + else if ((a != '/') && (b != '\\')) + str.push_back('/'); + + return str; + } + + std::wstring UTF8to16(std::string str) + { + const char *cstr = str.c_str(); + std::vector CodePoints; + + // Step 1: decode UTF-8 code points + while (cstr[0]) + { + int CodePoint; + + // One byte + if ((cstr[0] & 0x80000000) == 0) + { + CodePoint = cstr[0] & 0x7FFFFFFF; + cstr++; + } + + // Two bytes + else if ((cstr[0] & 0xE0) == 0xC0) + { + CodePoint = (((cstr[0] & 0x1F) << 6) | + (cstr[1] & 0x3F)); + cstr += 2; + } + + // Three bytes + else if ((cstr[0] & 0xF0) == 0xE0) + { + CodePoint = (((cstr[0] & 0xF) << 12) | + ((cstr[1] & 0x3F) << 6) | + (cstr[2] & 0x3F)); + cstr += 3; + } + + // Four bytes + else if ((cstr[0] & 0xF8) == 0xF0) + { + CodePoint = (((cstr[0] & 0x7) << 18) | + ((cstr[1] & 0x3F) << 12) | + ((cstr[2] & 0x3F) << 6) | + (cstr[3] & 0x3F)); + cstr += 4; + } + + // Five bytes + else if ((cstr[0] & 0xFC) == 0xF8) + { + CodePoint = (((cstr[0] & 0x3) << 24) | + ((cstr[1] & 0x3F) << 18) | + ((cstr[2] & 0x3F) << 12) | + ((cstr[3] & 0x3F) << 6) | + (cstr[4] & 0x3F)); + cstr += 5; + } + + // Six bytes + else if ((cstr[0] & 0xFE) == 0xFC) + { + CodePoint = (((cstr[0] & 0x1) << 30) | + ((cstr[1] & 0x3F) << 24) | + ((cstr[2] & 0x3F) << 18) | + ((cstr[3] & 0x3F) << 12) | + ((cstr[4] & 0x3F) << 6) | + (cstr[5] & 0x3F)); + cstr += 6; + } + + CodePoints.push_back(CodePoint); + } + + // Step 2: encode as UTF-16 + std::wstring out; + out.reserve(CodePoints.size()); + + for (int c = 0; c < CodePoints.size(); c++) + { + // todo: support all code points + if (((CodePoints[c] >= 0) && (CodePoints[c] <= 0xD7FF)) || + ((CodePoints[c] >= 0xE000) && (CodePoints[c] <= 0xFFFF))) + { + out.push_back(CodePoints[c] & 0xFFFF); + } + } + + return out; + } + + CStringList Tokenize(const std::string& str, const char *pTokens) + { + CStringList out; + int lastSplit = 0; + + // Iterate over all characters in the input string + for (int iChr = 0; iChr < str.length(); iChr++) + { + // Check whether this character is one of the user-provided tokens + for (int iTok = 0; true; iTok++) + { + if (!pTokens[iTok]) break; + + if (str[iChr] == pTokens[iTok]) + { + // Token found - split string + if (iChr > lastSplit) + out.push_back(str.substr(lastSplit, iChr - lastSplit)); + + lastSplit = iChr + 1; + break; + } + } + } + + // Add final string + if (lastSplit != str.length()) + out.push_back(str.substr(lastSplit, str.length() - lastSplit)); + + return out; + } +} diff --git a/Common/StringUtil.h b/Common/StringUtil.h new file mode 100644 index 00000000..a756b3ff --- /dev/null +++ b/Common/StringUtil.h @@ -0,0 +1,35 @@ +#ifndef STRINGUTIL_H +#define STRINGUTIL_H + +#include +#include +typedef std::list CStringList; + +namespace StringUtil +{ + std::string GetFileDirectory(std::string path); + std::string GetFileName(std::string path); + std::string GetFileNameWithExtension(std::string path); + std::string GetPathWithoutExtension(std::string path); + std::string GetExtension(std::string path); + std::string ResToStr(unsigned long ID); + std::string ResToStr(unsigned long long ID); + std::string ToUpper(std::string str); + std::string ToLower(std::string str); + std::string ToHexString(unsigned char num, bool addPrefix = true, int width = 0); + std::string ToHexString(unsigned short num, bool addPrefix = true, int width = 0); + std::string ToHexString(unsigned long num, bool addPrefix = true, int width = 0); + long Hash32(std::string str); + long long Hash64(std::string str); + long StrToRes32(std::string str); + long long StrToRes64(std::string str); + void StrToRes128(std::string str, char *out); + long GetResID32(std::string str); + bool IsHexString(std::string str, bool requirePrefix = false, long width = -1); + std::string AppendSlash(std::string str); + CStringList Tokenize(const std::string& str, const char *pTokens); + + std::wstring UTF8to16(std::string str); +} + +#endif // STRINGUTIL_H diff --git a/Common/types.h b/Common/types.h new file mode 100644 index 00000000..ead9c39d --- /dev/null +++ b/Common/types.h @@ -0,0 +1,15 @@ +#ifndef TYPES_H +#define TYPES_H + +typedef unsigned char u8; +typedef unsigned short u16; +typedef unsigned long u32; +typedef unsigned long long u64; +typedef signed char s8; +typedef signed short s16; +typedef signed long s32; +typedef signed long long s64; + + + +#endif // TYPES_H diff --git a/Core/CAreaAttributes.cpp b/Core/CAreaAttributes.cpp new file mode 100644 index 00000000..ae4a10d3 --- /dev/null +++ b/Core/CAreaAttributes.cpp @@ -0,0 +1,55 @@ +#include "CAreaAttributes.h" +#include +#include + +CAreaAttributes::CAreaAttributes(CScriptObject *pObj) +{ + SetObject(pObj); +} + +CAreaAttributes::~CAreaAttributes() +{ +} + +void CAreaAttributes::SetObject(CScriptObject *pObj) +{ + mpObj = pObj; + mGame = pObj->Template()->MasterTemplate()->GetGame(); +} + +bool CAreaAttributes::IsLayerEnabled() +{ + return mpObj->Layer()->IsActive(); +} + +bool CAreaAttributes::IsSkyEnabled() +{ + CPropertyStruct *pBaseStruct = mpObj->Properties(); + + switch (mGame) + { + case ePrime: + case eEchoes: + return static_cast(pBaseStruct->PropertyByIndex(1))->Get(); + case eCorruption: + return static_cast(pBaseStruct->PropertyByIndex(9))->Get(); + default: + return false; + } +} + +CModel* CAreaAttributes::SkyModel() +{ + CPropertyStruct *pBaseStruct = mpObj->Properties(); + + switch (mGame) + { + case ePrime: + case eEchoes: + return (CModel*) static_cast(pBaseStruct->PropertyByIndex(7))->Get(); + case eCorruption: + return (CModel*) static_cast(pBaseStruct->PropertyByIndex(8))->Get(); + default: + return nullptr; + } +} diff --git a/Core/CAreaAttributes.h b/Core/CAreaAttributes.h new file mode 100644 index 00000000..b108cf7b --- /dev/null +++ b/Core/CAreaAttributes.h @@ -0,0 +1,20 @@ +#ifndef CAREAATTRIBUTES_H +#define CAREAATTRIBUTES_H + +#include + +class CAreaAttributes +{ + EGame mGame; + CScriptObject *mpObj; + +public: + CAreaAttributes(CScriptObject *pObj); + ~CAreaAttributes(); + void SetObject(CScriptObject *pObj); + bool IsLayerEnabled(); + bool IsSkyEnabled(); + CModel* SkyModel(); +}; + +#endif // CAREAATTRIBUTES_H diff --git a/Core/CCamera.cpp b/Core/CCamera.cpp new file mode 100644 index 00000000..321d1a37 --- /dev/null +++ b/Core/CCamera.cpp @@ -0,0 +1,286 @@ +#include "CCamera.h" +#include "CGraphics.h" +#include + +#include + +#define HALF_PI 1.570796371f + +CCamera::CCamera() +{ + mMode = eFreeCamera; + mPosition = CVector3f(0); + mAspectRatio = 1.7777777f; + + mYaw = -HALF_PI; + mPitch = 0.0f; + CalculateDirection(); + + mMoveSpeed = 1.f; // Old: 0.01f + mLookSpeed = 1.f; // Old: 0.003f + mViewOutdated = true; + mProjectionOutdated = true; +} + +CCamera::CCamera(CVector3f Position, CVector3f) +{ + // todo: make it actually look at the target! + // Not using parameter 2 (CVector3f - Target) + mMode = eFreeCamera; + mMoveSpeed = 1.f; // Old: 0.01f + mLookSpeed = 1.f; // Old: 0.003f + mPosition = Position; + mYaw = -HALF_PI; + mPitch = 0.0f; + CalculateDirection(); +} + +void CCamera::Pan(float XAmount, float YAmount) +{ + switch (mMode) + { + + case eFreeCamera: + { + CVector3f Right( + cos(mYaw - HALF_PI), + sin(mYaw - HALF_PI), + 0 + ); + CVector3f Up = Right.Cross(mDirection); + + mPosition += Right * (XAmount * mMoveSpeed); + mPosition += Up * (YAmount * mMoveSpeed); + mViewOutdated = true; + break; + } + + // Unfinished + case eOrbitCamera: + { + CVector3f Right( + cos(mYaw - HALF_PI), + sin(mYaw - HALF_PI), + 0 + ); + CVector3f Up = Right.Cross(mDirection); + + CVector3f TargetDirection = mPosition - mOrbitTarget; + //CMatrix4f YawRotation = CQuaternion::F + } + } +} + +void CCamera::Rotate(float XAmount, float YAmount) +{ + switch (mMode) + { + case eFreeCamera: + mYaw -= (XAmount * mLookSpeed * 0.3f); + mPitch -= (YAmount * mLookSpeed * 0.3f); + mViewOutdated = true; + break; + } +} + +void CCamera::Zoom(float Amount) +{ + mPosition += (mDirection * Amount) * (mMoveSpeed * 25.f); + mViewOutdated = true; +} + +void CCamera::Snap(CVector3f Position) +{ + mPosition = Position; + mYaw = -1.570796371f; + mPitch = 0.0f; + mViewOutdated = true; +} + +void CCamera::ProcessKeyInput(EKeyInputs KeyFlags, double DeltaTime) +{ + float FDeltaTime = (float) DeltaTime; + if (mMode == eFreeCamera) + { + if (KeyFlags & eWKey) Zoom(FDeltaTime); + if (KeyFlags & eSKey) Zoom(-FDeltaTime); + if (KeyFlags & eQKey) Pan(0, -FDeltaTime * 25.f); + if (KeyFlags & eEKey) Pan(0, FDeltaTime * 25.f); + if (KeyFlags & eAKey) Pan(-FDeltaTime * 25.f, 0); + if (KeyFlags & eDKey) Pan(FDeltaTime * 25.f, 0); + } +} + +void CCamera::ProcessMouseInput(EKeyInputs KeyFlags, EMouseInputs MouseFlags, float XMovement, float YMovement) +{ + // Free Camera + if (mMode == eFreeCamera) + { + if (MouseFlags & eMiddleButton) + { + if (KeyFlags & eCtrlKey) Zoom(-YMovement * 0.2f); + else Pan(-XMovement, YMovement); + } + + else if (MouseFlags & eRightButton) Rotate(XMovement, YMovement); + } + + // Orbit Camera + else if (mMode == eOrbitCamera) + { + if ((MouseFlags & eMiddleButton) && (KeyFlags & eCtrlKey)) + Zoom(-YMovement * 0.2f); + + else if ((MouseFlags & eMiddleButton) || (MouseFlags & eRightButton)) + Pan(XMovement, YMovement); + } +} + +CRay CCamera::CastRay(CVector2f DeviceCoords) +{ + CMatrix4f InverseVP = (ViewMatrix().Transpose() * ProjectionMatrix().Transpose()).Inverse(); + + CVector3f RayOrigin = CVector3f(DeviceCoords.x, DeviceCoords.y, -1.f) * InverseVP; + CVector3f RayTarget = CVector3f(DeviceCoords.x, DeviceCoords.y, 0.f) * InverseVP; + CVector3f RayDir = (RayTarget - RayOrigin).Normalized(); + + CRay Ray; + Ray.SetOrigin(RayOrigin); + Ray.SetDirection(RayDir); + return Ray; +} + +void CCamera::LoadMatrices() +{ + CGraphics::sMVPBlock.ViewMatrix = ViewMatrix(); + CGraphics::sMVPBlock.ProjectionMatrix = ProjectionMatrix(); + CGraphics::UpdateMVPBlock(); +} + +// ************ GETTERS ************ +CVector3f CCamera::Position() const +{ + return mPosition; +} + +CVector3f CCamera::GetDirection() const +{ + return mDirection; +} + +float CCamera::GetYaw() const +{ + return mYaw; +} + +float CCamera::GetPitch() const +{ + return mPitch; +} + + +const CMatrix4f& CCamera::ViewMatrix() +{ + if (mViewOutdated) + CalculateView(); + + return mCachedViewMatrix; +} + +const CMatrix4f& CCamera::ProjectionMatrix() +{ + if (mProjectionOutdated) + CalculateProjection(); + + return mCachedProjectionMatrix; +} + +// ************ SETTERS ************ +void CCamera::SetPosition(CVector3f Position) +{ + mPosition = Position; + mViewOutdated = true; +} + +void CCamera::SetDirection(CVector3f Direction) +{ + mDirection = Direction; + mViewOutdated = true; +} + +void CCamera::SetYaw(float Yaw) +{ + mYaw = Yaw; +} + +void CCamera::SetPitch(float Pitch) +{ + mPitch = Pitch; +} + +void CCamera::SetMoveSpeed(float MoveSpeed) +{ + mMoveSpeed = MoveSpeed; +} + +void CCamera::SetLookSpeed(float LookSpeed) +{ + mLookSpeed = LookSpeed; +} + +void CCamera::SetFree() +{ + mMode = eFreeCamera; +} + +void CCamera::SetOrbit(const CVector3f& OrbitTarget) +{ + mMode = eOrbitCamera; + mOrbitTarget = OrbitTarget; +} + +void CCamera::SetAspectRatio(float AspectRatio) +{ + mAspectRatio = AspectRatio; + mProjectionOutdated = true; +} + +// ************ PRIVATE ************ +void CCamera::CalculateDirection() +{ + if (mPitch > HALF_PI) mPitch = HALF_PI; + if (mPitch < -HALF_PI) mPitch = -HALF_PI; + + mDirection = CVector3f( + cos(mPitch) * cos(mYaw), + cos(mPitch) * sin(mYaw), + sin(mPitch) + ); +} + +void CCamera::CalculateView() +{ + // todo: don't use glm + CalculateDirection(); + + CVector3f Right( + cos(mYaw - HALF_PI), + sin(mYaw - HALF_PI), + 0 + ); + + CVector3f Up = Right.Cross(mDirection); + + glm::vec3 glmpos(mPosition.x, mPosition.y, mPosition.z); + glm::vec3 glmdir(mDirection.x, mDirection.y, mDirection.z); + glm::vec3 glmup(Up.x, Up.y, Up.z); + mCachedViewMatrix = CMatrix4f::FromGlmMat4(glm::lookAt(glmpos, glmpos + glmdir, glmup)).Transpose(); + mViewOutdated = false; +} + +void CCamera::CalculateProjection() +{ + // todo: don't use glm + mCachedProjectionMatrix = CMatrix4f::FromGlmMat4(glm::perspective(55.f, mAspectRatio, 0.1f, 4096.f)).Transpose(); + mProjectionOutdated = false; +} diff --git a/Core/CCamera.h b/Core/CCamera.h new file mode 100644 index 00000000..2d2a2ead --- /dev/null +++ b/Core/CCamera.h @@ -0,0 +1,76 @@ +#ifndef CCAMERA_H +#define CCAMERA_H + +#include +#include +#include +#include +#include +#include +#include +#include + +enum ECameraMoveMode +{ + eFreeCamera, eOrbitCamera +}; + +class CCamera +{ + ECameraMoveMode mMode; + CVector3f mPosition; + CVector3f mDirection; + float mAspectRatio; + + float mYaw; + float mPitch; + CVector3f mOrbitTarget; + float mMoveSpeed; + float mLookSpeed; + + CMatrix4f mCachedViewMatrix; + CMatrix4f mCachedProjectionMatrix; + bool mViewOutdated; + bool mProjectionOutdated; + +public: + CCamera(); + CCamera(CVector3f Position, CVector3f Target); + + void Pan(float XAmount, float YAmount); + void Rotate(float XAmount, float YAmount); + void Zoom(float Amount); + void Snap(CVector3f Position); + void ProcessKeyInput(EKeyInputs KeyFlags, double DeltaTime); + void ProcessMouseInput(EKeyInputs KeyFlags, EMouseInputs MouseFlags, float XMovement, float YMovement); + CRay CastRay(CVector2f DeviceCoords); + void LoadMatrices(); + + // Getters + CVector3f Position() const; + CVector3f GetDirection() const; + float GetYaw() const; + float GetPitch() const; + const CMatrix4f& ViewMatrix(); + const CMatrix4f& ProjectionMatrix(); + + // Setters + void SetPosition(CVector3f Position); + void SetDirection(CVector3f Direction); + void SetYaw(float Yaw); + void SetPitch(float Pitch); + void SetMoveSpeed(float MoveSpeed); + void SetLookSpeed(float LookSpeed); + void SetFree(); + void SetOrbit(const CVector3f& OrbitTarget); + void SetOrbit(const CAABox& OrbitTarget); + void SetAspectRatio(float AspectRatio); + + // Private +private: + void CalculateDirection(); + void CalculateView(); + void CalculateProjection(); +}; + +#endif // CCAMERA_H diff --git a/Core/CDrawUtil.cpp b/Core/CDrawUtil.cpp new file mode 100644 index 00000000..fb902448 --- /dev/null +++ b/Core/CDrawUtil.cpp @@ -0,0 +1,436 @@ +#include "CDrawUtil.h" +#include "CGraphics.h" +#include "CResCache.h" +#include +#include +#include "Log.h" + +// ************ MEMBER INITIALIZATION ************ +CVertexBuffer CDrawUtil::mGridVertices; +CIndexBuffer CDrawUtil::mGridIndices; + +CDynamicVertexBuffer CDrawUtil::mSquareVertices; +CIndexBuffer CDrawUtil::mSquareIndices; + +CDynamicVertexBuffer CDrawUtil::mLineVertices; +CIndexBuffer CDrawUtil::mLineIndices; + +CModel *CDrawUtil::mpCubeModel; +CToken CDrawUtil::mCubeToken; + +CVertexBuffer CDrawUtil::mWireCubeVertices; +CIndexBuffer CDrawUtil::mWireCubeIndices; + +CModel *CDrawUtil::mpSphereModel; +CModel *CDrawUtil::mpDoubleSidedSphereModel; +CToken CDrawUtil::mSphereToken; +CToken CDrawUtil::mDoubleSidedSphereToken; + +CShader *CDrawUtil::mpColorShader; +CShader *CDrawUtil::mpColorShaderLighting; +CShader *CDrawUtil::mpTextureShader; +CShader *CDrawUtil::mpCollisionShader; +CShader *CDrawUtil::mpTextShader; + +CTexture *CDrawUtil::mpCheckerTexture; +CToken CDrawUtil::mCheckerTextureToken; + +bool CDrawUtil::mDrawUtilInitialized = false; + +// ************ PUBLIC ************ +void CDrawUtil::DrawGrid() +{ + Init(); + + mGridVertices.Bind(); + + CGraphics::sMVPBlock.ModelMatrix = CMatrix4f::skIdentity; + CGraphics::UpdateMVPBlock(); + + glBlendFunc(GL_ONE, GL_ZERO); + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + glDepthMask(GL_TRUE); + + glLineWidth(1.0f); + UseColorShader(CColor(0.6f, 0.6f, 0.6f, 0.f)); + mGridIndices.DrawElements(0, mGridIndices.GetSize() - 4); + + glLineWidth(1.5f); + UseColorShader(CColor::skTransparentBlack); + mGridIndices.DrawElements(mGridIndices.GetSize() - 4, 4); +} + +void CDrawUtil::DrawSquare() +{ + // Overload with default tex coords + CVector2f TexCoords[4] = { CVector2f(0.f, 1.f), CVector2f(1.f, 1.f), CVector2f(1.f, 0.f), CVector2f(0.f, 0.f) }; + DrawSquare(&TexCoords[0].x); +} + +void CDrawUtil::DrawSquare(const CVector2f& TexUL, const CVector2f& TexUR, const CVector2f& TexBR, const CVector2f& TexBL) +{ + // Overload with tex coords specified via parameters + // I don't think that parameters are guaranteed to be contiguous in memory, so: + CVector2f TexCoords[4] = { TexUL, TexUR, TexBR, TexBL }; + DrawSquare(&TexCoords[0].x); +} + +void CDrawUtil::DrawSquare(const float *pTexCoords) +{ + Init(); + + // Set tex coords + for (u32 iTex = 0; iTex < 8; iTex++) + { + EVertexDescription TexAttrib = (EVertexDescription) (eTex0 << (iTex *2)); + mSquareVertices.BufferAttrib(TexAttrib, pTexCoords); + } + + // Draw + mSquareVertices.Bind(); + mSquareIndices.DrawElements(); + mSquareVertices.Unbind(); +} + +void CDrawUtil::DrawLine(const CVector3f& PointA, const CVector3f& PointB) +{ + DrawLine(PointA, PointB, CColor::skWhite); +} + +void CDrawUtil::DrawLine(const CVector3f& PointA, const CVector3f& PointB, const CColor& LineColor) +{ + Init(); + + glLineWidth(1.f); + CGraphics::sMVPBlock.ModelMatrix = CMatrix4f::skIdentity; + CGraphics::UpdateMVPBlock(); + + // Copy vec3s into an array to ensure they are adjacent in memory + CVector3f Points[2] = { PointA, PointB }; + mLineVertices.BufferAttrib(ePosition, Points); + + // Draw + UseColorShader(LineColor); + mLineVertices.Bind(); + mLineIndices.DrawElements(); + mLineVertices.Unbind(); +} + +void CDrawUtil::DrawCube() +{ + Init(); + mpCubeModel->Draw(eNoMaterialSetup, 0); +} + +void CDrawUtil::DrawCube(const CColor& Color) +{ + Init(); + UseColorShader(Color); + DrawCube(); +} + +void CDrawUtil::DrawShadedCube(const CColor& Color) +{ + Init(); + UseColorShaderLighting(Color); + DrawCube(); +} + +void CDrawUtil::DrawWireCube() +{ + Init(); + glLineWidth(1.f); + mWireCubeVertices.Bind(); + mWireCubeIndices.DrawElements(); + mWireCubeVertices.Unbind(); +} + +void CDrawUtil::DrawWireCube(const CAABox& kAABox, const CColor& kColor) +{ + Init(); + + // Calculate model matrix + CTransform4f Transform; + Transform.Scale(kAABox.GetSize()); + Transform.Translate(kAABox.Center()); + CGraphics::sMVPBlock.ModelMatrix = Transform.ToMatrix4f(); + CGraphics::UpdateMVPBlock(); + + UseColorShader(kColor); + DrawWireCube(); +} + +void CDrawUtil::DrawSphere(bool DoubleSided) +{ + Init(); + + if (!DoubleSided) + mpSphereModel->Draw(eNoMaterialSetup, 0); + else + mpDoubleSidedSphereModel->Draw(eNoMaterialSetup, 0); +} + +void CDrawUtil::DrawSphere(const CColor &kColor) +{ + Init(); + UseColorShader(kColor); + DrawSphere(false); +} + +void CDrawUtil::UseColorShader(const CColor& kColor) +{ + Init(); + mpColorShader->SetCurrent(); + + GLuint ColorLoc = mpColorShader->GetUniformLocation("ColorIn"); + CVector4f ColorVec4 = kColor.ToVector4f(); + glUniform4f(ColorLoc, ColorVec4.x, ColorVec4.y, ColorVec4.z, ColorVec4.w); + + CMaterial::KillCachedMaterial(); +} + +void CDrawUtil::UseColorShaderLighting(const CColor& kColor) +{ + Init(); + mpColorShaderLighting->SetCurrent(); + + GLuint NumLightsLoc = mpColorShaderLighting->GetUniformLocation("NumLights"); + glUniform1i(NumLightsLoc, CGraphics::sNumLights); + + GLuint ColorLoc = mpColorShaderLighting->GetUniformLocation("ColorIn"); + CVector4f ColorVec4 = kColor.ToVector4f(); + glUniform4f(ColorLoc, ColorVec4.x, ColorVec4.y, ColorVec4.z, ColorVec4.w); + + CMaterial::KillCachedMaterial(); +} + +void CDrawUtil::UseTextureShader() +{ + UseTextureShader(CColor::skWhite); +} + +void CDrawUtil::UseTextureShader(const CColor& TintColor) +{ + Init(); + mpTextureShader->SetCurrent(); + + GLuint TintColorLoc = mpTextureShader->GetUniformLocation("TintColor"); + CVector4f TintVec4 = TintColor.ToVector4f(); + glUniform4f(TintColorLoc, TintVec4.x, TintVec4.y, TintVec4.z, TintVec4.w); + + CMaterial::KillCachedMaterial(); +} + +void CDrawUtil::UseCollisionShader() +{ + Init(); + mpCollisionShader->SetCurrent(); + LoadCheckerboardTexture(0); + + CMaterial::KillCachedMaterial(); +} + +CShader* CDrawUtil::GetTextShader() +{ + Init(); + return mpTextShader; +} + +void CDrawUtil::LoadCheckerboardTexture(u32 GLTextureUnit) +{ + Init(); + mpCheckerTexture->Bind(GLTextureUnit); +} + +CModel* CDrawUtil::GetCubeModel() +{ + Init(); + return mpCubeModel; +} + +// ************ PRIVATE ************ +CDrawUtil::CDrawUtil() +{ +} + +void CDrawUtil::Init() +{ + if (!mDrawUtilInitialized) + { + Log::Write("Initializing CDrawUtil"); + InitGrid(); + InitSquare(); + InitLine(); + InitCube(); + InitWireCube(); + InitSphere(); + InitShaders(); + InitTextures(); + mDrawUtilInitialized = true; + } +} + +void CDrawUtil::InitGrid() +{ + Log::Write("Creating grid"); + mGridVertices.SetVertexDesc(ePosition); + mGridVertices.Reserve(64); + + for (s32 i = -7; i < 8; i++) + { + if (i == 0) continue; + mGridVertices.AddVertex(CVector3f(-7.0f, float(i), 0.0f)); + mGridVertices.AddVertex(CVector3f( 7.0f, float(i), 0.0f)); + mGridVertices.AddVertex(CVector3f(float(i), -7.0f, 0.0f)); + mGridVertices.AddVertex(CVector3f(float(i), 7.0f, 0.0f)); + } + + mGridVertices.AddVertex(CVector3f(-7.0f, 0, 0.0f)); + mGridVertices.AddVertex(CVector3f( 7.0f, 0, 0.0f)); + mGridVertices.AddVertex(CVector3f(0, -7.0f, 0.0f)); + mGridVertices.AddVertex(CVector3f(0, 7.0f, 0.0f)); + + mGridIndices.Reserve(60); + for (u16 i = 0; i < 60; i++) mGridIndices.AddIndex(i); + mGridIndices.SetPrimitiveType(GL_LINES); +} + +void CDrawUtil::InitSquare() +{ + Log::Write("Creating square"); + mSquareVertices.SetActiveAttribs(ePosition | eNormal | + eTex0 | eTex1 | eTex2 | eTex3 | + eTex4 | eTex5 | eTex6 | eTex7); + mSquareVertices.SetVertexCount(4); + + CVector3f SquareVertices[] = { + CVector3f(-1.f, 1.f, 0.f), + CVector3f( 1.f, 1.f, 0.f), + CVector3f( 1.f, -1.f, 0.f), + CVector3f(-1.f, -1.f, 0.f) + }; + + CVector3f SquareNormals[] = { + CVector3f(0.f, 0.f, 1.f), + CVector3f(0.f, 0.f, 1.f), + CVector3f(0.f, 0.f, 1.f), + CVector3f(0.f, 0.f, 1.f) + }; + + CVector2f SquareTexCoords[] = { + CVector2f(0.f, 1.f), + CVector2f(1.f, 1.f), + CVector2f(1.f, 0.f), + CVector2f(0.f, 0.f) + }; + + mSquareVertices.BufferAttrib(ePosition, SquareVertices); + mSquareVertices.BufferAttrib(eNormal, SquareNormals); + + for (u32 iTex = 0; iTex < 8; iTex++) + { + EVertexDescription Attrib = (EVertexDescription) (eTex0 << (iTex *2)); + mSquareVertices.BufferAttrib(Attrib, SquareTexCoords); + } + + mSquareIndices.Reserve(4); + mSquareIndices.SetPrimitiveType(GL_TRIANGLE_STRIP); + mSquareIndices.AddIndex(3); + mSquareIndices.AddIndex(2); + mSquareIndices.AddIndex(0); + mSquareIndices.AddIndex(1); +} + +void CDrawUtil::InitLine() +{ + Log::Write("Creating line"); + mLineVertices.SetActiveAttribs(ePosition); + mLineVertices.SetVertexCount(2); + + mLineIndices.Reserve(2); + mLineIndices.SetPrimitiveType(GL_LINES); + mLineIndices.AddIndex(0); + mLineIndices.AddIndex(1); +} + +void CDrawUtil::InitCube() +{ + Log::Write("Creating cube"); + mpCubeModel = (CModel*) gResCache.GetResource("../resources/Cube.cmdl"); + mCubeToken = CToken(mpCubeModel); +} + +void CDrawUtil::InitWireCube() +{ + Log::Write("Creating wire cube"); + mWireCubeVertices.SetVertexDesc(ePosition); + mWireCubeVertices.Reserve(8); + mWireCubeVertices.AddVertex(CVector3f(-0.5f, -0.5f, -0.5f)); + mWireCubeVertices.AddVertex(CVector3f(-0.5f, 0.5f, -0.5f)); + mWireCubeVertices.AddVertex(CVector3f( 0.5f, 0.5f, -0.5f)); + mWireCubeVertices.AddVertex(CVector3f( 0.5f, -0.5f, -0.5f)); + mWireCubeVertices.AddVertex(CVector3f(-0.5f, -0.5f, 0.5f)); + mWireCubeVertices.AddVertex(CVector3f( 0.5f, -0.5f, 0.5f)); + mWireCubeVertices.AddVertex(CVector3f( 0.5f, 0.5f, 0.5f)); + mWireCubeVertices.AddVertex(CVector3f(-0.5f, 0.5f, 0.5f)); + + u16 Indices[] = { + 0, 1, + 1, 2, + 2, 3, + 3, 0, + 4, 5, + 5, 6, + 6, 7, + 7, 4, + 0, 4, + 1, 7, + 2, 6, + 3, 5 + }; + mWireCubeIndices.AddIndices(Indices, sizeof(Indices) / sizeof(u16)); + mWireCubeIndices.SetPrimitiveType(GL_LINES); +} + +void CDrawUtil::InitSphere() +{ + Log::Write("Creating sphere"); + mpSphereModel = (CModel*) gResCache.GetResource("../resources/Sphere.cmdl"); + mpDoubleSidedSphereModel = (CModel*) gResCache.GetResource("../resources/SphereDoubleSided.cmdl"); + mSphereToken = CToken(mpSphereModel); + mDoubleSidedSphereToken = CToken(mpDoubleSidedSphereModel); +} + +void CDrawUtil::InitShaders() +{ + Log::Write("Creating shaders"); + mpColorShader = CShader::FromResourceFile("ColorShader"); + mpColorShaderLighting = CShader::FromResourceFile("ColorShaderLighting"); + mpTextureShader = CShader::FromResourceFile("TextureShader"); + mpCollisionShader = CShader::FromResourceFile("CollisionShader"); + mpTextShader = CShader::FromResourceFile("TextShader"); +} + +void CDrawUtil::InitTextures() +{ + Log::Write("Loading checkerboard texture"); + mpCheckerTexture = (CTexture*) gResCache.GetResource("../resources/Checkerboard.txtr"); + mCheckerTextureToken = CToken(mpCheckerTexture); +} + +void CDrawUtil::Shutdown() +{ + if (mDrawUtilInitialized) + { + Log::Write("Shutting down"); + mCubeToken = CToken(); + mSphereToken = CToken(); + mDoubleSidedSphereToken = CToken(); + delete mpColorShader; + delete mpColorShaderLighting; + delete mpTextureShader; + delete mpCollisionShader; + delete mpTextShader; + mDrawUtilInitialized = false; + } +} diff --git a/Core/CDrawUtil.h b/Core/CDrawUtil.h new file mode 100644 index 00000000..6b8548e5 --- /dev/null +++ b/Core/CDrawUtil.h @@ -0,0 +1,91 @@ +#ifndef CDRAWUTIL +#define CDRAWUTIL + +#include +#include +#include +#include + +class CDrawUtil +{ + // 7x7 Grid + static CVertexBuffer mGridVertices; + static CIndexBuffer mGridIndices; + + // Square + static CDynamicVertexBuffer mSquareVertices; + static CIndexBuffer mSquareIndices; + + // Line + static CDynamicVertexBuffer mLineVertices; + static CIndexBuffer mLineIndices; + + // Cube + static CModel *mpCubeModel; + static CToken mCubeToken; + + // Wire Cube + static CVertexBuffer mWireCubeVertices; + static CIndexBuffer mWireCubeIndices; + + // Sphere + static CModel *mpSphereModel; + static CModel *mpDoubleSidedSphereModel; + static CToken mSphereToken; + static CToken mDoubleSidedSphereToken; + + // Shaders + static CShader *mpColorShader; + static CShader *mpColorShaderLighting; + static CShader *mpTextureShader; + static CShader *mpCollisionShader; + static CShader *mpTextShader; + + // Textures + static CTexture *mpCheckerTexture; + static CToken mCheckerTextureToken; + + // Have all the above members been initialized? + static bool mDrawUtilInitialized; + +public: + static void DrawGrid(); + static void DrawSquare(); + static void DrawSquare(const CVector2f& TexUL, const CVector2f& TexUR, const CVector2f& TexBR, const CVector2f& TexBL); + static void DrawSquare(const float *pTexCoords); + static void DrawLine(const CVector3f& PointA, const CVector3f& PointB); + static void DrawLine(const CVector3f& PointA, const CVector3f& PointB, const CColor& LineColor); + static void DrawCube(); + static void DrawCube(const CColor& Color); + static void DrawShadedCube(const CColor& Color); + static void DrawWireCube(); + static void DrawWireCube(const CAABox& AABox, const CColor& Color); + static void DrawSphere(bool DoubleSided = false); + static void DrawSphere(const CColor& Color); + static void UseColorShader(const CColor& Color); + static void UseColorShaderLighting(const CColor& Color); + static void UseTextureShader(); + static void UseTextureShader(const CColor& TintColor); + static void UseCollisionShader(); + static CShader* GetTextShader(); + static void LoadCheckerboardTexture(u32 GLTextureUnit); + static CModel* GetCubeModel(); + +private: + CDrawUtil(); // Private constructor to prevent class from being instantiated + static void Init(); + static void InitGrid(); + static void InitSquare(); + static void InitLine(); + static void InitCube(); + static void InitWireCube(); + static void InitSphere(); + static void InitShaders(); + static void InitTextures(); + +public: + static void Shutdown(); +}; + +#endif // CDRAWUTIL + diff --git a/Core/CGraphics.cpp b/Core/CGraphics.cpp new file mode 100644 index 00000000..a5faaea4 --- /dev/null +++ b/Core/CGraphics.cpp @@ -0,0 +1,159 @@ +#include "CGraphics.h" +#include +#include +#include "Log.h" + +// ************ MEMBER INITIALIZATION ************ +CUniformBuffer* CGraphics::mpMVPBlockBuffer; +CUniformBuffer* CGraphics::mpVertexBlockBuffer; +CUniformBuffer* CGraphics::mpPixelBlockBuffer; +CUniformBuffer* CGraphics::mpLightBlockBuffer; +u32 CGraphics::mContextIndices = 0; +u32 CGraphics::mActiveContext = -1; +bool CGraphics::mInitialized = false; +std::vector CGraphics::mVAMs; + +CGraphics::SMVPBlock CGraphics::sMVPBlock; +CGraphics::SVertexBlock CGraphics::sVertexBlock; +CGraphics::SPixelBlock CGraphics::sPixelBlock; +CGraphics::SLightBlock CGraphics::sLightBlock; + +CGraphics::ELightingMode CGraphics::sLightMode; +u32 CGraphics::sNumLights; +const CColor CGraphics::skDefaultAmbientColor = CColor(0.5f, 0.5f, 0.5f, 1.f); +CColor CGraphics::sAreaAmbientColor = CColor::skBlack; +float CGraphics::sWorldLightMultiplier; +CLight CGraphics::sDefaultDirectionalLights[3] = { + *CLight::BuildDirectional(CVector3f(0), CVector3f (0.f, -0.866025f, -0.5f), CColor(0.3f, 0.3f, 0.3f, 1.f)), + *CLight::BuildDirectional(CVector3f(0), CVector3f(-0.75f, 0.433013f, -0.5f), CColor(0.3f, 0.3f, 0.3f, 1.f)), + *CLight::BuildDirectional(CVector3f(0), CVector3f( 0.75f, 0.433013f, -0.5f), CColor(0.3f, 0.3f, 0.3f, 1.f)) +}; + +// ************ FUNCTIONS ************ +void CGraphics::Initialize() +{ + if (!mInitialized) + { + Log::Write("Initializing GLEW"); + glewExperimental = true; + glewInit(); + glGetError(); // This is to work around a glew bug - error is always set after initializing + + Log::Write("Creating uniform buffers"); + mpMVPBlockBuffer = new CUniformBuffer(sizeof(sMVPBlock)); + mpVertexBlockBuffer = new CUniformBuffer(sizeof(sVertexBlock)); + mpPixelBlockBuffer = new CUniformBuffer(sizeof(sPixelBlock)); + mpLightBlockBuffer = new CUniformBuffer(sizeof(sLightBlock)); + + sLightMode = WorldLighting; + sNumLights = 0; + sWorldLightMultiplier = 1.f; + + mInitialized = true; + } + mpMVPBlockBuffer->BindBase(0); + mpVertexBlockBuffer->BindBase(1); + mpPixelBlockBuffer->BindBase(2); + mpLightBlockBuffer->BindBase(3); +} + +void CGraphics::Shutdown() +{ + if (mInitialized) + { + Log::Write("Shutting down CGraphics"); + delete mpMVPBlockBuffer; + delete mpVertexBlockBuffer; + delete mpPixelBlockBuffer; + delete mpLightBlockBuffer; + mInitialized = false; + } +} + +void CGraphics::UpdateMVPBlock() +{ + mpMVPBlockBuffer->Buffer(&sMVPBlock); +} + +void CGraphics::UpdateVertexBlock() +{ + mpVertexBlockBuffer->Buffer(&sVertexBlock); +} + +void CGraphics::UpdatePixelBlock() +{ + mpPixelBlockBuffer->Buffer(&sPixelBlock); +} + +void CGraphics::UpdateLightBlock() +{ + mpLightBlockBuffer->Buffer(&sLightBlock); +} + +GLuint CGraphics::MVPBlockBindingPoint() +{ + return 0; +} + +GLuint CGraphics::VertexBlockBindingPoint() +{ + return 1; +} + +GLuint CGraphics::PixelBlockBindingPoint() +{ + return 2; +} + +GLuint CGraphics::LightBlockBindingPoint() +{ + return 3; +} + +u32 CGraphics::GetContextIndex() +{ + for (u32 iCon = 0; iCon < 32; iCon++) + { + u32 Mask = (1 << iCon); + if ((mContextIndices & Mask) == 0) + { + mContextIndices |= Mask; + + CVertexArrayManager *pVAM = new CVertexArrayManager; + if (mVAMs.size() >= iCon) mVAMs.resize(iCon + 1); + mVAMs[iCon] = pVAM; + + return iCon; + } + } + + return -1; +} + +u32 CGraphics::GetActiveContext() +{ + return mActiveContext; +} + +void CGraphics::ReleaseContext(u32 Index) +{ + if (Index < 32) mContextIndices &= ~(1 << Index); + if (mActiveContext == Index) mActiveContext = -1; + delete mVAMs[Index]; +} + +void CGraphics::SetActiveContext(u32 Index) +{ + mActiveContext = Index; + mVAMs[Index]->SetCurrent(); + CMaterial::KillCachedMaterial(); + CShader::KillCachedShader(); +} + +void CGraphics::SetDefaultLighting() +{ + sNumLights = 0; // CLight load function increments the light count by 1, which is why I set it to 0 + sDefaultDirectionalLights[0].Load(); + sDefaultDirectionalLights[1].Load(); + sDefaultDirectionalLights[2].Load(); +} diff --git a/Core/CGraphics.h b/Core/CGraphics.h new file mode 100644 index 00000000..20e58a81 --- /dev/null +++ b/Core/CGraphics.h @@ -0,0 +1,97 @@ +#ifndef CGRAPHICS_H +#define CGRAPHICS_H + +#include +#include +#include +#include +#include +#include +#include +#include + +class CGraphics +{ + static CUniformBuffer *mpMVPBlockBuffer; + static CUniformBuffer *mpVertexBlockBuffer; + static CUniformBuffer *mpPixelBlockBuffer; + static CUniformBuffer *mpLightBlockBuffer; + static u32 mContextIndices; + static u32 mActiveContext; + static bool mInitialized; + static std::vector mVAMs; + +public: + // SMVPBlock + struct SMVPBlock + { + CMatrix4f ModelMatrix; + CMatrix4f ViewMatrix; + CMatrix4f ProjectionMatrix; + }; + static SMVPBlock sMVPBlock; + + // SVertexBlock + struct SVertexBlock + { + CMatrix4f TexMatrices[10]; + CMatrix4f PostMatrices[20]; + CVector4f COLOR0_Amb; + CVector4f COLOR0_Mat; + CVector4f COLOR1_Amb; + CVector4f COLOR1_Mat; + }; + static SVertexBlock sVertexBlock; + + // SPixelBlock + struct SPixelBlock + { + CVector4f Konst[4]; + CVector4f TevColor; + CVector4f TintColor; + }; + static SPixelBlock sPixelBlock; + + // SLightBlock + struct SLightBlock + { + struct SGXLight + { + CVector4f Position; + CVector4f Direction; + CVector4f Color; + CVector4f DistAtten; + CVector4f AngleAtten; + }; + SGXLight Lights[8]; + }; + static SLightBlock sLightBlock; + + // Lighting-related + enum ELightingMode { NoLighting, BasicLighting, WorldLighting }; + static ELightingMode sLightMode; + static u32 sNumLights; + static const CColor skDefaultAmbientColor; + static CColor sAreaAmbientColor; + static float sWorldLightMultiplier; + static CLight sDefaultDirectionalLights[3]; + + // Functions + static void Initialize(); + static void Shutdown(); + static void UpdateMVPBlock(); + static void UpdateVertexBlock(); + static void UpdatePixelBlock(); + static void UpdateLightBlock(); + static GLuint MVPBlockBindingPoint(); + static GLuint VertexBlockBindingPoint(); + static GLuint PixelBlockBindingPoint(); + static GLuint LightBlockBindingPoint(); + static u32 GetContextIndex(); + static u32 GetActiveContext(); + static void ReleaseContext(u32 Index); + static void SetActiveContext(u32 Index); + static void SetDefaultLighting(); +}; + +#endif // CGRAPHICS_H diff --git a/Core/CRenderBucket.cpp b/Core/CRenderBucket.cpp new file mode 100644 index 00000000..733664a1 --- /dev/null +++ b/Core/CRenderBucket.cpp @@ -0,0 +1,89 @@ +#include "CRenderBucket.h" +#include +#include "CDrawUtil.h" +#include "CGraphics.h" + +CRenderBucket::CRenderBucket() +{ + mEstSize = 0; + mSize = 0; +} + +void CRenderBucket::SetSortType(ESortType Type) +{ + mSortType = Type; +} + +void CRenderBucket::Add(const SMeshPointer& Mesh) +{ + if (mSize >= mEstSize) + mNodes.push_back(Mesh); + else + mNodes[mSize] = Mesh; + + mSize++; +} + +void CRenderBucket::Sort(CCamera& Camera) +{ + struct { + CCamera *pCamera; + bool operator()(SMeshPointer left, SMeshPointer right) { + CVector3f cPos = pCamera->Position(); + CVector3f cDir = pCamera->GetDirection(); + + CVector3f distL = left.AABox.ClosestPointAlongVector(cDir) - cPos; + float dotL = distL.Dot(cDir); + CVector3f distR = right.AABox.ClosestPointAlongVector(cDir) - cPos; + float dotR = distR.Dot(cDir); + return (dotL > dotR); + } + } backToFront; + backToFront.pCamera = &Camera; + + if (mSortType == BackToFront) + std::stable_sort(mNodes.begin(), mNodes.begin() + mSize, backToFront); + + // Test: draw node bounding boxes + vertices used for sorting + /*for (u32 iNode = 0; iNode < mNodes.size(); iNode++) + { + SMeshPointer *pNode = &mNodes[iNode]; + CVector3f Vert = pNode->AABox.ClosestPointAlongVector(Camera.GetDirection()); + CDrawUtil::DrawWireCube(pNode->AABox, CColor::skWhite); + + CVector3f Dist = Vert - Camera.GetPosition(); + float Dot = Dist.Dot(Camera.GetDirection()); + if (Dot < 0.f) Dot = -Dot; + if (Dot > 50.f) Dot = 50.f; + float Intensity = 1.f - (Dot / 50.f); + CColor CubeColor(Intensity, Intensity, Intensity, 1.f); + + CGraphics::sMVPBlock.ModelMatrix = CTransform4f::TranslationMatrix(Vert).ToMatrix4f(); + CGraphics::UpdateMVPBlock(); + CDrawUtil::DrawCube(CubeColor); + }*/ +} + +void CRenderBucket::Clear() +{ + mEstSize = mSize; + if (mNodes.size() > mSize) mNodes.resize(mSize); + mSize = 0; +} + +void CRenderBucket::Draw(ERenderOptions Options) +{ + for (u32 n = 0; n < mSize; n++) + { + if (mNodes[n].Command == eDrawMesh) + mNodes[n].pNode->Draw(Options); + + else if (mNodes[n].Command == eDrawAsset) + mNodes[n].pNode->DrawAsset(Options, mNodes[n].Asset); + + else if (mNodes[n].Command == eDrawSelection) + mNodes[n].pNode->DrawSelection(); + + // todo: implementation for eDrawExtras + } +} diff --git a/Core/CRenderBucket.h b/Core/CRenderBucket.h new file mode 100644 index 00000000..3483e9b8 --- /dev/null +++ b/Core/CRenderBucket.h @@ -0,0 +1,33 @@ +#ifndef CRENDERBUCKET_H +#define CRENDERBUCKET_H + +#include "CCamera.h" +#include "ERenderOptions.h" +#include +#include +#include + +class CRenderBucket +{ +public: + enum ESortType + { + BackToFront, + FrontToBack + }; +private: + ESortType mSortType; + std::vector mNodes; + u32 mEstSize; + u32 mSize; + +public: + CRenderBucket(); + void SetSortType(ESortType Type); + void Add(const SMeshPointer& Mesh); + void Sort(CCamera& Camera); + void Clear(); + void Draw(ERenderOptions Options); +}; + +#endif // CRENDERBUCKET_H diff --git a/Core/CRenderer.cpp b/Core/CRenderer.cpp new file mode 100644 index 00000000..b2a917b5 --- /dev/null +++ b/Core/CRenderer.cpp @@ -0,0 +1,328 @@ +#include "CRenderer.h" + +#include "CDrawUtil.h" +#include "CGraphics.h" +#include "CResCache.h" +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +// ************ STATIC MEMBER INITIALIZATION ************ +u32 CRenderer::sNumRenderers = 0; + +// ************ INITIALIZATION ************ +CRenderer::CRenderer() +{ + mOptions = eEnableUVScroll | eEnableBackfaceCull; + mBloomMode = eNoBloom; + mDrawGrid = true; + mInitialized = false; + mContextIndex = -1; + mOpaqueBucket.SetSortType(CRenderBucket::FrontToBack); + mTransparentBucket.SetSortType(CRenderBucket::BackToFront); + sNumRenderers++; +} + +CRenderer::~CRenderer() +{ + sNumRenderers--; + + if (sNumRenderers == 0) + { + CGraphics::Shutdown(); + CDrawUtil::Shutdown(); + } +} + +void CRenderer::Init() +{ + if (!mInitialized) + { + CVector4f ClearVec = mClearColor.ToVector4f(); + glClearColor(ClearVec.x, ClearVec.y, ClearVec.z, ClearVec.w); + mContextIndex = CGraphics::GetContextIndex(); + mInitialized = true; + } +} + +// ************ GETTERS/SETTERS ************ +bool CRenderer::IsUVAnimationOn() +{ + return ((mOptions & eUVScroll) != 0); +} + +void CRenderer::ToggleBackfaceCull(bool b) +{ + mOptions = (ERenderOptions) ((mOptions & ~eEnableBackfaceCull) | (b << 1)); +} + +void CRenderer::ToggleUVAnimation(bool b) +{ + mOptions = (ERenderOptions) ((mOptions & ~eEnableUVScroll) | b); +} + +void CRenderer::ToggleGrid(bool b) +{ + mDrawGrid = b; +} + +void CRenderer::ToggleOccluders(bool b) +{ + mOptions = (ERenderOptions) ((mOptions & ~eEnableOccluders) | (b << 2)); +} + +void CRenderer::ToggleAlphaDisabled(bool b) +{ + mOptions = (ERenderOptions) ((mOptions & ~eNoAlpha) | (b << 5)); +} + +void CRenderer::SetBloom(EBloomMode BloomMode) +{ + mBloomMode = BloomMode; + + if (BloomMode != eNoBloom) + mOptions = (ERenderOptions) (mOptions | eEnableBloom); + else + mOptions = (ERenderOptions) (mOptions & ~eEnableBloom); +} + +void CRenderer::SetFont(CFont *pFont) +{ +} + +void CRenderer::SetClearColor(CColor Clear) +{ + mClearColor = Clear; + CVector4f ClearVec = Clear.ToVector4f(); + ClearVec.w = 0.f; + glClearColor(ClearVec.x, ClearVec.y, ClearVec.z, ClearVec.w); +} + +void CRenderer::SetViewportSize(u32 Width, u32 Height) +{ + mViewportWidth = Width; + mViewportHeight = Height; + mBloomHScale = ((float) Width / 640); + mBloomVScale = ((float) Height / 528); + mBloomWidth = (u32) (320 * mBloomHScale); + mBloomHeight = (u32) (224 * mBloomVScale); + mBloomHScale = 1.f / mBloomHScale; + mBloomVScale = 1.f / mBloomVScale; +} + +// ************ RENDER ************ +void CRenderer::RenderScene(CCamera& Camera) +{ + if (!mInitialized) Init(); + + // Render scene to texture + glDepthRange(0.f, 1.f); + + mOpaqueBucket.Draw(mOptions); + mOpaqueBucket.Clear(); + mTransparentBucket.Sort(Camera); + mTransparentBucket.Draw(mOptions); + mTransparentBucket.Clear(); +} + +void CRenderer::RenderBloom() +{ + // Setup + static const float skHOffset[6] = { -0.008595f, -0.005470f, -0.002345f, + 0.002345f, 0.005470f, 0.008595f }; + + static const float skVOffset[6] = { -0.012275f, -0.007815f, -0.003350f, + 0.003350f, 0.007815f, 0.012275f }; + + static const CColor skTintColors[6] = { CColor((u8) 17, 17, 17, 255), + CColor((u8) 53, 53, 53, 255), + CColor((u8) 89, 89, 89, 255), + CColor((u8) 89, 89, 89, 255), + CColor((u8) 53, 53, 53, 255), + CColor((u8) 17, 17, 17, 255) }; + + glDisable(GL_DEPTH_TEST); + glViewport(0, 0, mBloomWidth, mBloomHeight); + glClearColor(0.f, 0.f, 0.f, 0.f); + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + + CGraphics::sMVPBlock.ModelMatrix = CMatrix4f::skIdentity; + CGraphics::sMVPBlock.ViewMatrix = CMatrix4f::skIdentity; + CGraphics::sMVPBlock.ProjectionMatrix = CMatrix4f::skIdentity; + CGraphics::UpdateMVPBlock(); + + // Pass 1: Alpha-blend the scene texture on a black background + mBloomFramebuffers[0].Resize(mBloomWidth, mBloomHeight); + mBloomFramebuffers[0].Bind(); + glClear(GL_COLOR_BUFFER_BIT); + + CDrawUtil::UseTextureShader(); + glBlendFunc(GL_SRC_ALPHA, GL_ZERO); + mSceneFramebuffer.Texture()->Bind(0); + CDrawUtil::DrawSquare(); + + // Pass 2: Horizontal blur + mBloomFramebuffers[1].Resize(mBloomWidth, mBloomHeight); + mBloomFramebuffers[1].Bind(); + + CDrawUtil::UseTextureShader(CColor::skGray); + glBlendFunc(GL_ONE, GL_ZERO); + mBloomFramebuffers[0].Texture()->Bind(0); + CDrawUtil::DrawSquare(); + + for (u32 iPass = 0; iPass < 6; iPass++) + { + CDrawUtil::UseTextureShader(skTintColors[iPass]); + CVector3f Translate(skHOffset[iPass] * mBloomHScale, 0.f, 0.f); + CGraphics::sMVPBlock.ModelMatrix = CTransform4f::TranslationMatrix(Translate).ToMatrix4f(); + CGraphics::UpdateMVPBlock(); + glBlendFunc(GL_ONE, GL_ONE); + CDrawUtil::DrawSquare(); + } + + // Pass 3: Vertical blur + mBloomFramebuffers[2].Resize(mBloomWidth, mBloomHeight); + mBloomFramebuffers[2].Bind(); + glClear(GL_COLOR_BUFFER_BIT); + + CDrawUtil::UseTextureShader(CColor::skGray); + glBlendFunc(GL_ONE, GL_ZERO); + mBloomFramebuffers[1].Texture()->Bind(0); + CDrawUtil::DrawSquare(); + + for (u32 iPass = 0; iPass < 6; iPass++) + { + CDrawUtil::UseTextureShader(skTintColors[iPass]); + CVector3f Translate(0.f, skVOffset[iPass] * mBloomVScale, 0.f); + CGraphics::sMVPBlock.ModelMatrix = CTransform4f::TranslationMatrix(Translate).ToMatrix4f(); + CGraphics::UpdateMVPBlock(); + glBlendFunc(GL_ONE, GL_ONE); + CDrawUtil::DrawSquare(); + } +} + +void CRenderer::RenderSky(CModel *pSkyboxModel, CVector3f CameraPosition) +{ + if (!mInitialized) Init(); + + if (!pSkyboxModel) return; + glEnable(GL_CULL_FACE); + + CTransform4f ModelMtx; + ModelMtx.Translate(CameraPosition); + + CGraphics::sMVPBlock.ModelMatrix = ModelMtx.ToMatrix4f(); + CGraphics::sVertexBlock.COLOR0_Amb = CVector4f(1.f, 1.f, 1.f, 1.f); + CGraphics::sPixelBlock.TevColor = CVector4f(1.f, 1.f, 1.f, 1.f); + CGraphics::sNumLights = 0; + CGraphics::UpdateMVPBlock(); + CGraphics::UpdateVertexBlock(); + CGraphics::UpdatePixelBlock(); + CGraphics::UpdateLightBlock(); + + glDepthRange(1.f, 1.f); + pSkyboxModel->Draw(mOptions, 0); +} + +void CRenderer::AddOpaqueMesh(CSceneNode *pNode, u32 AssetID, CAABox& AABox, ERenderCommand Command) +{ + SMeshPointer mesh; + mesh.pNode = pNode; + mesh.Asset = AssetID; + mesh.AABox = AABox; + mesh.Command = Command; + mOpaqueBucket.Add(mesh); +} + +void CRenderer::AddTransparentMesh(CSceneNode *pNode, u32 AssetID, CAABox& AABox, ERenderCommand Command) +{ + SMeshPointer mesh; + mesh.pNode = pNode; + mesh.Asset = AssetID; + mesh.AABox = AABox; + mesh.Command = Command; + mTransparentBucket.Add(mesh); +} + +void CRenderer::BeginFrame() +{ + if (!mInitialized) Init(); + + CGraphics::SetActiveContext(mContextIndex); + glGetIntegerv(GL_FRAMEBUFFER_BINDING, &mDefaultFramebuffer); + + mSceneFramebuffer.Resize(mViewportWidth, mViewportHeight); + mSceneFramebuffer.Bind(); + + glViewport(0, 0, mViewportWidth, mViewportHeight); + + InitFramebuffer(); + + if (mOptions & eEnableBackfaceCull) glEnable(GL_CULL_FACE); + else glDisable(GL_CULL_FACE); +} + +void CRenderer::EndFrame() +{ + // Post-processing + if (mBloomMode == eBloom) + RenderBloom(); + + // Render result to screen + glBindFramebuffer(GL_FRAMEBUFFER, mDefaultFramebuffer); + InitFramebuffer(); + glViewport(0, 0, mViewportWidth, mViewportHeight); + + CGraphics::sMVPBlock.ModelMatrix = CMatrix4f::skIdentity; + CGraphics::sMVPBlock.ViewMatrix = CMatrix4f::skIdentity; + CGraphics::sMVPBlock.ProjectionMatrix = CMatrix4f::skIdentity; + CGraphics::UpdateMVPBlock(); + + glDisable(GL_DEPTH_TEST); + + CDrawUtil::UseTextureShader(); + glBlendFunc(GL_ONE, GL_ZERO); + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + mSceneFramebuffer.Texture()->Bind(0); + CDrawUtil::DrawSquare(); + + if (mBloomMode == eBloom) + { + CDrawUtil::UseTextureShader(); + glBlendFunc(GL_ONE, GL_ONE); + mBloomFramebuffers[2].Texture()->Bind(0); + CDrawUtil::DrawSquare(); + } + + else if (mBloomMode == eBloomMaps) + { + // Bloom maps are in the framebuffer alpha channel. + // White * dst alpha = bloom map colors + CDrawUtil::UseColorShader(CColor::skWhite); + glBlendFunc(GL_DST_ALPHA, GL_ZERO); + CDrawUtil::DrawSquare(); + } + + glEnable(GL_DEPTH_TEST); + gDrawCount = 0; +} + +// ************ PRIVATE ************ +void CRenderer::InitFramebuffer() +{ + CVector4f Clear = mClearColor.ToVector4f(); + Clear.w = 0.f; + glClearColor(Clear.x, Clear.y, Clear.z, Clear.w); + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + glDepthMask(GL_TRUE); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); +} + +u32 gDrawCount; diff --git a/Core/CRenderer.h b/Core/CRenderer.h new file mode 100644 index 00000000..438f6a39 --- /dev/null +++ b/Core/CRenderer.h @@ -0,0 +1,83 @@ +#ifndef CRENDERER_H +#define CRENDERER_H + +#include + +#include "CCamera.h" +#include "CGraphics.h" +#include "CRenderBucket.h" +#include "ERenderOptions.h" +#include "ERenderCommand.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class CRenderer +{ +public: + enum EBloomMode { + eNoBloom, eBloom, eBloomMaps + }; + +private: + ERenderOptions mOptions; + EBloomMode mBloomMode; + bool mDrawGrid; + CColor mClearColor; + u32 mContextIndex; + CRenderBucket mOpaqueBucket; + CRenderBucket mTransparentBucket; + bool mInitialized; + u32 mViewportWidth, mViewportHeight; + u32 mBloomWidth, mBloomHeight; + float mBloomHScale, mBloomVScale; + + CFramebuffer mSceneFramebuffer; + CFramebuffer mBloomFramebuffers[3]; + GLint mDefaultFramebuffer; + + // Static Members + static u32 sNumRenderers; + +public: + // Initialization + CRenderer(); + ~CRenderer(); + void Init(); + + // Getters/Setters + bool IsUVAnimationOn(); + void ToggleBackfaceCull(bool b); + void ToggleUVAnimation(bool b); + void ToggleGrid(bool b); + void ToggleOccluders(bool b); + void ToggleAlphaDisabled(bool b); + void SetBloom(EBloomMode BloomMode); + void SetFont(CFont *pFont); + void SetClearColor(CColor Clear); + void SetViewportSize(u32 Width, u32 Height); + + // Render + void RenderScene(CCamera& Camera); + void RenderBloom(); + void RenderSky(CModel *pSkyboxModel, CVector3f CameraPosition); + void AddOpaqueMesh(CSceneNode *pNode, u32 AssetID, CAABox& AABox, ERenderCommand Command); + void AddTransparentMesh(CSceneNode *pNode, u32 AssetID, CAABox& AABox, ERenderCommand Command); + void BeginFrame(); + void EndFrame(); + + // Private +private: + void InitFramebuffer(); +}; + +extern u32 gDrawCount; + +#endif // RENDERMANAGER_H diff --git a/Core/CResCache.cpp b/Core/CResCache.cpp new file mode 100644 index 00000000..3f6866ca --- /dev/null +++ b/Core/CResCache.cpp @@ -0,0 +1,236 @@ +#include "CResCache.h" +#include "Log.h" +#include +#include +#include +#include + +// Loaders +#include +#include +#include +#include +#include +#include +#include +#include +#include + +CResCache::CResCache() +{ + mpPak = nullptr; +} + +CResCache::~CResCache() +{ + Clean(); +} + +void CResCache::Clean() +{ + if (mResourceCache.empty()) return; + Log::Write("Cleaning unused resources"); + + // I couldn't get this to work properly using reverse iterators, lol. + // Resources get cached after their dependencies, which is why I go backwards + // while loop is to ensure -all- unused resources are cleaned. Not sure of a better way to do it. + int numResourcesCleaned = 1; + + while (numResourcesCleaned) + { + numResourcesCleaned = 0; + + for (auto it = mResourceCache.end(); it != mResourceCache.begin();) + { + it--; + if (it->second->mRefCount <= 0) + { + delete it->second; + it = mResourceCache.erase(it); + numResourcesCleaned++; + } + } + } + Log::Write(std::to_string(mResourceCache.size()) + " resources loaded"); +} + +void CResCache::SetFolder(std::string path) +{ + StringUtil::AppendSlash(path); + mResSource.Path = path; + mResSource.Source = SResSource::Folder; + Log::Write("Set resource folder: " + path); +} + +void CResCache::SetPak(std::string path) +{ + CFileInStream *pakfile = new CFileInStream(path, IOUtil::BigEndian); + if (!pakfile->IsValid()) + { + Log::Error("Couldn't load pak file: " + path); + delete pakfile; + return; + } + + if (mpPak) delete mpPak; + mpPak = new CPakFile(pakfile); + mResSource.Path = path; + mResSource.Source = SResSource::PakFile; + Log::Write("Loaded pak file: " + path); +} + +void CResCache::SetResSource(SResSource& ResSource) +{ + mResSource = ResSource; +} + +SResSource CResCache::GetResSource() +{ + return mResSource; +} + +std::string CResCache::GetSourcePath() +{ + return mResSource.Path; +} + +CResource* CResCache::GetResource(CUniqueID ResID, CFourCC type) +{ + if (!ResID.IsValid()) return nullptr; + + auto got = mResourceCache.find(ResID.ToLongLong()); + + if (got != mResourceCache.end()) + return got->second; + + std::vector *pBuffer = nullptr; + std::string Source; + + // Load from pak + if (mResSource.Source == SResSource::PakFile) + { + pBuffer = mpPak->getResource(ResID.ToLongLong(), type); + Source = ResID.ToString() + "." + type.ToString(); + } + + // Load from folder + else + { + Source = mResSource.Path + StringUtil::ResToStr(ResID.ToLong()) + "." + type.ToString(); + CFileInStream file(Source, IOUtil::BigEndian); + if (!file.IsValid()) + { + Source = mResSource.Path + StringUtil::ResToStr(ResID.ToLongLong()) + "." + type.ToString(); + file.Open(Source, IOUtil::BigEndian); + if (!file.IsValid()) + { + Log::Error("Couldn't open resource: " + ResID.ToString() + "." + type.ToString()); + return nullptr; + } + } + + pBuffer = new std::vector; + pBuffer->resize(file.Size()); + file.ReadBytes(pBuffer->data(), pBuffer->size()); + } + if (!pBuffer) return nullptr; + + // Load resource + CMemoryInStream mem(pBuffer->data(), pBuffer->size(), IOUtil::BigEndian); + mem.SetSourceString(StringUtil::GetFileNameWithExtension(Source)); + CResource *Res = nullptr; + bool SupportedFormat = true; + + if (type == "CMDL") Res = CModelLoader::LoadCMDL(mem); + else if (type == "TXTR") Res = CTextureDecoder::LoadTXTR(mem); + else if (type == "ANCS") Res = CAnimSetLoader::LoadANCS(mem); + else if (type == "CHAR") Res = CAnimSetLoader::LoadCHAR(mem); + else if (type == "MREA") Res = CAreaLoader::LoadMREA(mem); + else if (type == "MLVL") Res = CWorldLoader::LoadMLVL(mem); + else if (type == "STRG") Res = CStringLoader::LoadSTRG(mem); + else if (type == "FONT") Res = CFontLoader::LoadFONT(mem); + else if (type == "SCAN") Res = CScanLoader::LoadSCAN(mem); + else SupportedFormat = false; + + // Log errors + if (!SupportedFormat) + Log::Write("Unsupported format; unable to load " + type.ToString() + " " + ResID.ToString()); + + if (!Res) Res = new CResource(); // Default for invalid resource or unsupported format + + // Add to cache and cleanup + Res->mID = ResID; + Res->mResSource = Source; + mResourceCache[ResID.ToLongLong()] = Res; + delete pBuffer; + return Res; +} + +CResource* CResCache::GetResource(std::string ResPath) +{ + // Since this function takes a string argument it always loads directly from a file - no pak + CUniqueID ResID = StringUtil::Hash64(ResPath); + + auto got = mResourceCache.find(ResID.ToLongLong()); + + if (got != mResourceCache.end()) + return got->second; + + CFileInStream file(ResPath, IOUtil::BigEndian); + if (!file.IsValid()) + { + Log::Error("Couldn't open resource: " + ResPath); + return nullptr; + } + + // Save old ResSource to restore later + const SResSource OldSource = mResSource; + mResSource.Source = SResSource::Folder; + mResSource.Path = StringUtil::GetFileDirectory(ResPath); + + // Load resource + CResource *Res = nullptr; + CFourCC type = StringUtil::ToUpper( StringUtil::GetExtension(ResPath) ).c_str(); + bool SupportedFormat = true; + + if (type == "CMDL") Res = CModelLoader::LoadCMDL(file); + else if (type == "TXTR") Res = CTextureDecoder::LoadTXTR(file); + else if (type == "ANCS") Res = CAnimSetLoader::LoadANCS(file); + else if (type == "CHAR") Res = CAnimSetLoader::LoadCHAR(file); + else if (type == "MREA") Res = CAreaLoader::LoadMREA(file); + else if (type == "MLVL") Res = CWorldLoader::LoadMLVL(file); + else if (type == "FONT") Res = CFontLoader::LoadFONT(file); + else if (type == "SCAN") Res = CScanLoader::LoadSCAN(file); + else SupportedFormat = false; + + if (!Res) Res = new CResource(); // Default for unsupported formats + + // Add to cache and cleanup + Res->mID = ResPath.c_str(); + Res->mResSource = ResPath; + mResourceCache[ResID.ToLongLong()] = Res; + mResSource = OldSource; + return Res; +} + +void CResCache::CacheResource(CResource *pRes) +{ + u64 ID = pRes->ResID().ToLongLong(); + auto got = mResourceCache.find(ID); + + if (got != mResourceCache.end()) + mResourceCache[ID] = pRes; +} + +void CResCache::DeleteResource(CUniqueID ResID) +{ + auto got = mResourceCache.find(ResID.ToLongLong()); + + if (got != mResourceCache.end()) + { + delete got->second; + mResourceCache.erase(got, got); + } +} + +CResCache gResCache; diff --git a/Core/CResCache.h b/Core/CResCache.h new file mode 100644 index 00000000..cc86f7be --- /dev/null +++ b/Core/CResCache.h @@ -0,0 +1,40 @@ +#ifndef CRESCACHE_H +#define CRESCACHE_H + +#include +#include +#include +#include + +struct SResSource +{ + std::string Path; + enum { + Folder, PakFile + } Source; +}; + +class CResCache +{ + std::unordered_map mResourceCache; + CPakFile *mpPak; + SResSource mResSource; + +public: + CResCache(); + ~CResCache(); + void Clean(); + void SetFolder(std::string path); + void SetPak(std::string path); + void SetResSource(SResSource& ResSource); + SResSource GetResSource(); + std::string GetSourcePath(); + CResource* GetResource(CUniqueID ResID, CFourCC type); + CResource* GetResource(std::string res); + void CacheResource(CResource *pRes); + void DeleteResource(CUniqueID ResID); +}; + +extern CResCache gResCache; + +#endif // CRESCACHE_H diff --git a/Core/CSceneManager.cpp b/Core/CSceneManager.cpp new file mode 100644 index 00000000..70e7bdb0 --- /dev/null +++ b/Core/CSceneManager.cpp @@ -0,0 +1,380 @@ +#include "CSceneManager.h" + +#include +#include + +#include +#include +#include +#include +#include +#include + +/** + * This class direly needs a rewrite + * Future plan is to integrate a "scene layer" system, where nodes are grouped into layers + * We would have terrain layer, lights layer, collision layer, multiple script layers, etc + * Advantage of this is that I don't need to write separate functions for every single node type + * They can all be tracked together and the code could be streamlined a lot. + */ +CSceneManager::CSceneManager() +{ + mShowTerrain = true; + mShowCollision = false; + mShowObjects = true; + mShowLights = true; + mSplitTerrain = true; + mNodeCount = 0; + mpSceneRootNode = new CRootNode(this, nullptr); + mpArea = nullptr; + mpWorld = nullptr; + mpAreaRootNode = nullptr; +} + +CSceneManager::~CSceneManager() +{ + ClearScene(); +} + +CModelNode* CSceneManager::AddModel(CModel *m) +{ + if (m == nullptr) return nullptr; + + CModelNode *node = new CModelNode(this, mpSceneRootNode, m); + mModelNodes.push_back(node); + mNodeCount++; + return node; +} + +CStaticNode* CSceneManager::AddStaticModel(CStaticModel *mdl) +{ + if (mdl == nullptr) return nullptr; + + CStaticNode *node = new CStaticNode(this, mpAreaRootNode, mdl); + mStaticNodes.push_back(node); + mNodeCount++; + return node; +} + +CCollisionNode* CSceneManager::AddCollision(CCollisionMesh *mesh) +{ + if (mesh == nullptr) return nullptr; + + CCollisionNode *node = new CCollisionNode(this, mpAreaRootNode, mesh); + mCollisionNodes.push_back(node); + mNodeCount++; + return node; +} + +CScriptNode* CSceneManager::AddScriptObject(CScriptObject *obj) +{ + if (obj == nullptr) return nullptr; + + CScriptNode *node = new CScriptNode(this, mpAreaRootNode, obj); + mScriptNodes.push_back(node); + mNodeCount++; + return node; +} + +CLightNode* CSceneManager::AddLight(CLight *Light) +{ + if (Light == nullptr) return nullptr; + + CLightNode *node = new CLightNode(this, mpAreaRootNode, Light); + mLightNodes.push_back(node); + mNodeCount++; + return node; +} + +void CSceneManager::SetActiveArea(CGameArea* _area) +{ + // Clear existing area + delete mpAreaRootNode; + mModelNodes.clear(); + mStaticNodes.clear(); + mCollisionNodes.clear(); + mScriptNodes.clear(); + mLightNodes.clear(); + mAreaAttributesObjects.clear(); + mpActiveAreaAttributes = nullptr; + mScriptNodeMap.clear(); + + // Create nodes for new area + mpArea = _area; + mAreaToken = CToken(mpArea); + mpAreaRootNode = new CRootNode(this, mpSceneRootNode); + + if (mSplitTerrain) + { + u32 count = mpArea->GetStaticModelCount(); + for (u32 m = 0; m < count; m++) + AddStaticModel(mpArea->GetStaticModel(m)); + } + else + { + u32 count = mpArea->GetTerrainModelCount(); + for (u32 m = 0; m < count; m++) + { + CModel *mdl = mpArea->GetTerrainModel(m); + CModelNode *node = AddModel(mdl); + node->SetDynamicLighting(false); + } + } + + AddCollision(mpArea->GetCollision()); + + u32 NumLayers = mpArea->GetScriptLayerCount(); + for (u32 l = 0; l < NumLayers; l++) + { + CScriptLayer *layer = mpArea->GetScriptLayer(l); + u32 NumObjects = layer->GetNumObjects(); + mScriptNodes.reserve(mScriptNodes.size() + NumObjects); + + for (u32 o = 0; o < NumObjects; o++) + { + CScriptObject *pObj = layer->ObjectByIndex(o); + CScriptNode *Node = AddScriptObject( pObj ); + Node->BuildLightList(mpArea); + + // Add to map + mScriptNodeMap[pObj->InstanceID()] = Node; + + // AreaAttributes check + switch (pObj->ObjectTypeID()) + { + case 0x4E: // MP1 AreaAttributes ID + case 0x52454141: // MP2 AreaAttributes ID ("REAA") + mAreaAttributesObjects.emplace_back( CAreaAttributes(pObj) ); + break; + } + } + } + + CScriptLayer *pGenLayer = mpArea->GetGeneratorLayer(); + if (pGenLayer) + { + for (u32 o = 0; o < pGenLayer->GetNumObjects(); o++) + { + CScriptObject *pObj = pGenLayer->ObjectByIndex(o); + CScriptNode *Node = AddScriptObject(pObj); + Node->BuildLightList(mpArea); + + // Add to map + mScriptNodeMap[pObj->InstanceID()] = Node; + } + } + PickEnvironmentObjects(); + + // Ensure script nodes have valid positions + for (auto it = mScriptNodeMap.begin(); it != mScriptNodeMap.end(); it++) + it->second->GeneratePosition(); + + u32 NumLightLayers = mpArea->GetLightLayerCount(); + CGraphics::sAreaAmbientColor = CColor::skBlack; + + for (u32 ly = 0; ly < NumLightLayers; ly++) + { + u32 NumLights = mpArea->GetLightCount(ly); + + for (u32 l = 0; l < NumLights; l++) + { + CLight *Light = mpArea->GetLight(ly, l); + + if (Light->GetType() == eLocalAmbient) + CGraphics::sAreaAmbientColor += Light->GetColor(); + + AddLight(Light); + } + } + + std::cout << CSceneNode::NumNodes() << " nodes\n"; +} + +void CSceneManager::SetActiveWorld(CWorld* _world) +{ + mpWorld = _world; + mWorldToken = CToken(mpWorld); +} + +void CSceneManager::ClearScene() +{ + mpAreaRootNode->Unparent(); + delete mpAreaRootNode; + + mModelNodes.clear(); + mStaticNodes.clear(); + mCollisionNodes.clear(); + mScriptNodes.clear(); + mLightNodes.clear(); + + mpArea = nullptr; + mAreaToken.Unlock(); + mpWorld = nullptr; + mWorldToken.Unlock(); + mNodeCount = 0; +} + +void CSceneManager::AddSceneToRenderer(CRenderer *pRenderer) +{ + if (mShowTerrain) + { + for (u32 n = 0; n < mModelNodes.size(); n++) + if (mModelNodes[n]->IsVisible()) + mModelNodes[n]->AddToRenderer(pRenderer); + + for (u32 n = 0; n < mStaticNodes.size(); n++) + if (mStaticNodes[n]->IsVisible()) + mStaticNodes[n]->AddToRenderer(pRenderer); + } + + if (mShowCollision) + { + for (u32 n = 0; n < mCollisionNodes.size(); n++) + if (mCollisionNodes[n]->IsVisible()) + mCollisionNodes[n]->AddToRenderer(pRenderer); + } + + if (mShowObjects) + { + for (u32 n = 0; n < mScriptNodes.size(); n++) + if (mScriptNodes[n]->IsVisible()) + mScriptNodes[n]->AddToRenderer(pRenderer); + } + + if (mShowLights) + { + for (u32 n = 0; n < mLightNodes.size(); n++) + if (mLightNodes[n]->IsVisible()) + mLightNodes[n]->AddToRenderer(pRenderer); + } +} + +SRayIntersection CSceneManager::SceneRayCast(const CRay& Ray) +{ + // Terribly hacky stuff to avoid having tons of redundant code + // because I'm too lazy to rewrite CSceneManager right now and fix it + // (I'm probably going to do it soon...) + std::vector *pNodeVectors[5] = { + reinterpret_cast*>(&mModelNodes), + reinterpret_cast*>(&mStaticNodes), + reinterpret_cast*>(&mCollisionNodes), + reinterpret_cast*>(&mScriptNodes), + reinterpret_cast*>(&mLightNodes), + }; + bool NodesVisible[5] = { + true, mShowTerrain, mShowCollision, mShowObjects, mShowLights + }; + + // Less hacky stuff + CRayCollisionTester Tester(Ray); + + for (u32 iVec = 0; iVec < 5; iVec++) + { + if (!NodesVisible[iVec]) continue; + + std::vector& vec = *pNodeVectors[iVec]; + + for (u32 iNode = 0; iNode < vec.size(); iNode++) + if (vec[iNode]->IsVisible()) + vec[iNode]->RayAABoxIntersectTest(Tester); + } + + return Tester.TestNodes(); +} + +void CSceneManager::PickEnvironmentObjects() +{ + // Pick AreaAttributes + for (auto it = mAreaAttributesObjects.begin(); it != mAreaAttributesObjects.end(); it++) + { + if ((*it).IsLayerEnabled()) + { + mpActiveAreaAttributes = &(*it); + break; + } + } +} + +CScriptNode* CSceneManager::ScriptNodeByID(u32 InstanceID) +{ + auto it = mScriptNodeMap.find(InstanceID); + + if (it != mScriptNodeMap.end()) return it->second; + else return nullptr; +} + +CScriptNode* CSceneManager::NodeForObject(CScriptObject *pObj) +{ + return ScriptNodeByID(pObj->InstanceID()); +} + +CLightNode* CSceneManager::NodeForLight(CLight *pLight) +{ + // Slow. Is there a better way to do this? + for (auto it = mLightNodes.begin(); it != mLightNodes.end(); it++) + if ((*it)->Light() == pLight) return *it; + + return nullptr; +} + +CModel* CSceneManager::GetActiveSkybox() +{ + if (mpActiveAreaAttributes) + { + if (mpActiveAreaAttributes->IsSkyEnabled()) + { + CModel *pSky = mpActiveAreaAttributes->SkyModel(); + if (pSky) return pSky; + else return mpWorld->GetDefaultSkybox(); + } + else + return nullptr; + } + + else return nullptr; +} + +CGameArea* CSceneManager::GetActiveArea() +{ + return mpArea; +} + +void CSceneManager::SetWorld(bool on) +{ + mShowTerrain = on; +} + +void CSceneManager::SetCollision(bool on) +{ + mShowCollision = on; +} + +void CSceneManager::SetLights(bool on) +{ + mShowLights = on; +} + +void CSceneManager::SetObjects(bool on) +{ + mShowObjects = on; +} + +bool CSceneManager::IsTerrainEnabled() +{ + return mShowTerrain; +} + +bool CSceneManager::IsCollisionEnabled() +{ + return mShowCollision; +} + +bool CSceneManager::AreLightsEnabled() +{ + return mShowLights; +} + +bool CSceneManager::AreScriptObjectsEnabled() +{ + return mShowObjects; +} diff --git a/Core/CSceneManager.h b/Core/CSceneManager.h new file mode 100644 index 00000000..2124c15b --- /dev/null +++ b/Core/CSceneManager.h @@ -0,0 +1,87 @@ +#ifndef CSCENEMANAGER_h +#define CSCENEMANAGER_h + +#include +#include +#include + +#include "CAreaAttributes.h" +#include "CRenderer.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class CSceneManager +{ + bool mShowTerrain; + bool mShowCollision; + bool mShowObjects; + bool mShowLights; + bool mSplitTerrain; + + u32 mNodeCount; + std::vector mModelNodes; + std::vector mStaticNodes; + std::vector mCollisionNodes; + std::vector mScriptNodes; + std::vector mLightNodes; + CRootNode *mpSceneRootNode; + + CGameArea *mpArea; + CWorld *mpWorld; + CToken mAreaToken; + CToken mWorldToken; + CRootNode *mpAreaRootNode; + + // Environment + std::vector mAreaAttributesObjects; + CAreaAttributes *mpActiveAreaAttributes; + + // Objects + std::unordered_map mScriptNodeMap; + +public: + CSceneManager(); + ~CSceneManager(); + + // Scene Management + CModelNode* AddModel(CModel *mdl); + CStaticNode* AddStaticModel(CStaticModel *mdl); + CCollisionNode* AddCollision(CCollisionMesh *mesh); + CScriptNode* AddScriptObject(CScriptObject *obj); + CLightNode* AddLight(CLight *Light); + void SetActiveArea(CGameArea *_area); + void SetActiveWorld(CWorld *_world); + void ClearScene(); + void AddSceneToRenderer(CRenderer *pRenderer); + SRayIntersection SceneRayCast(const CRay& Ray); + void PickEnvironmentObjects(); + CScriptNode* ScriptNodeByID(u32 InstanceID); + CScriptNode* NodeForObject(CScriptObject *pObj); + CLightNode* NodeForLight(CLight *pLight); + + // Setters/Getters + CModel* GetActiveSkybox(); + CGameArea* GetActiveArea(); + + void SetBackfaceCulling(bool on); + void SetWorld(bool on); + void SetCollision(bool on); + void SetObjects(bool on); + void SetLights(bool on); + bool IsBackfaceCullEnabled(); + bool IsTerrainEnabled(); + bool IsCollisionEnabled(); + bool AreLightsEnabled(); + bool AreScriptObjectsEnabled(); +}; + +#endif // CSCENEMANAGER_H diff --git a/Core/CToken.cpp b/Core/CToken.cpp new file mode 100644 index 00000000..1302f88c --- /dev/null +++ b/Core/CToken.cpp @@ -0,0 +1,55 @@ +#include "CToken.h" + +CToken::CToken() +{ + mpRes = nullptr; + mLocked = false; +} + +CToken::CToken(CResource *pRes) +{ + mpRes = pRes; + mLocked = false; + Lock(); +} + +CToken::CToken(const CToken& Source) +{ + mLocked = false; + *this = Source; +} + +CToken::~CToken() +{ + Unlock(); +} + +void CToken::Lock() +{ + if (!mLocked) + { + if (mpRes) + { + mpRes->Lock(); + mLocked = true; + } + } +} + +void CToken::Unlock() +{ + if (mLocked) + { + mpRes->Release(); + mLocked = false; + } +} + +CToken& CToken::operator=(const CToken& Source) +{ + if (mLocked) Unlock(); + + mpRes = Source.mpRes; + Lock(); + return *this; +} diff --git a/Core/CToken.h b/Core/CToken.h new file mode 100644 index 00000000..5ea9ae40 --- /dev/null +++ b/Core/CToken.h @@ -0,0 +1,21 @@ +#ifndef CTOKEN_H +#define CTOKEN_H + +#include + +class CToken +{ + CResource *mpRes; + bool mLocked; + +public: + CToken(); + CToken(CResource *pRes); + CToken(const CToken& Source); + ~CToken(); + void Lock(); + void Unlock(); + CToken& operator=(const CToken& Source); +}; + +#endif // CTOKEN_H diff --git a/Core/ERenderCommand.h b/Core/ERenderCommand.h new file mode 100644 index 00000000..ffd82b2a --- /dev/null +++ b/Core/ERenderCommand.h @@ -0,0 +1,13 @@ +#ifndef ERENDERCOMMAND +#define ERENDERCOMMAND + +enum ERenderCommand +{ + eDrawMesh, + eDrawAsset, + eDrawSelection, + eDrawExtras +}; + +#endif // ERENDERCOMMAND + diff --git a/Core/ERenderOptions.h b/Core/ERenderOptions.h new file mode 100644 index 00000000..9b63d73e --- /dev/null +++ b/Core/ERenderOptions.h @@ -0,0 +1,18 @@ +#ifndef ERENDEROPTIONS +#define ERENDEROPTIONS + +#include + +enum ERenderOptions +{ + eEnableUVScroll = 0x1, + eEnableBackfaceCull = 0x2, + eEnableOccluders = 0x4, + eNoMaterialSetup = 0x8, + eEnableBloom = 0x10, + eNoAlpha = 0x20 +}; +DEFINE_ENUM_FLAGS(ERenderOptions) + +#endif // ERENDEROPTIONS + diff --git a/Core/Log.cpp b/Core/Log.cpp new file mode 100644 index 00000000..1ae823ea --- /dev/null +++ b/Core/Log.cpp @@ -0,0 +1,78 @@ +#include +#include +#include +#include +#include + +namespace Log +{ + +static const std::string gskLogFilename = "primeworldeditor.log"; +FILE *gpLogFile = fopen(gskLogFilename.c_str(), "w"); + +void Write(const std::string& message) +{ + + if (gpLogFile) + { + time_t RawTime; + time(&RawTime); + + tm *pTimeInfo = localtime(&RawTime); + char Buffer[80]; + strftime(Buffer, 80, "[%H:%M:%S]", pTimeInfo); + + fprintf(gpLogFile, "%s %s\n", Buffer, message.c_str()); + fflush(gpLogFile); + } +} + +void Error(const std::string& message) +{ + Write("ERROR: " + message); + +#ifdef _DEBUG + std::cout << "ERROR: " << message << "\n"; +#endif +} + +void Warning(const std::string& message) +{ + Write("Warning: " + message); + +#ifdef _DEBUG + std::cout << "Warning: " << message << "\n"; +#endif +} + +void FileWrite(const std::string& filename, const std::string& message) +{ + Write(filename + " : " + message); +} + +void FileWrite(const std::string& filename, unsigned long offset, const std::string& message) +{ + Write(filename + " : " + StringUtil::ToHexString(offset) + " - " + message); +} + +void FileError(const std::string& filename, const std::string& message) +{ + Error(filename + " : " + message); +} + +void FileError(const std::string &filename, unsigned long offset, const std::string &message) +{ + Error(filename + " : " + StringUtil::ToHexString(offset) + " - " + message); +} + +void FileWarning(const std::string& filename, const std::string& message) +{ + Warning(filename + " : " + message); +} + +void FileWarning(const std::string& filename, unsigned long offset, const std::string& message) +{ + Warning(filename + " : " + StringUtil::ToHexString(offset) + " - " + message); +} + +} diff --git a/Core/Log.h b/Core/Log.h new file mode 100644 index 00000000..35e34c75 --- /dev/null +++ b/Core/Log.h @@ -0,0 +1,22 @@ +#ifndef INFO +#define INFO + +#include + +namespace Log +{ + +void Write(const std::string& message); +void Error(const std::string& message); +void Warning(const std::string& message); +void FileWrite(const std::string& filename, const std::string& message); +void FileWrite(const std::string& filename, unsigned long offset, const std::string& message); +void FileError(const std::string& filename, const std::string& message); +void FileError(const std::string& filename, unsigned long offset, const std::string& message); +void FileWarning(const std::string& filename, const std::string& message); +void FileWarning(const std::string& filename, unsigned long offset, const std::string& message); + +} + +#endif // INFO + diff --git a/Core/main.cpp b/Core/main.cpp new file mode 100644 index 00000000..b564c15b --- /dev/null +++ b/Core/main.cpp @@ -0,0 +1,52 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +int main(int argc, char *argv[]) +{ + QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts); + QApplication app(argc, argv); + CStartWindow w; + w.show(); + + CTemplateLoader::LoadGameList(); + //CTexture *pTex = CTextureDecoder::LoadDDS(CFileInStream("E:/34f7c12211777ce8.dds", IOUtil::LittleEndian)); + //CTextureEncoder::EncodeTXTR(CFileOutStream("E:/Unpacked/Metroid Prime 3 Dolphin/Metroid4-pak/34f7c12211777ce8.TXTR", IOUtil::BigEndian), pTex); + + app.setStyle(new CDarkStyle); + qApp->setStyle(QStyleFactory::create("Fusion")); + + QPalette darkPalette; + darkPalette.setColor(QPalette::Window, QColor(53,53,53)); + darkPalette.setColor(QPalette::WindowText, Qt::white); + darkPalette.setColor(QPalette::Base, QColor(25,25,25)); + darkPalette.setColor(QPalette::AlternateBase, QColor(53,53,53)); + darkPalette.setColor(QPalette::ToolTipBase, Qt::white); + darkPalette.setColor(QPalette::ToolTipText, Qt::white); + darkPalette.setColor(QPalette::Text, Qt::white); + darkPalette.setColor(QPalette::Button, QColor(53,53,53)); + darkPalette.setColor(QPalette::ButtonText, Qt::white); + darkPalette.setColor(QPalette::BrightText, Qt::red); + darkPalette.setColor(QPalette::Link, QColor(42, 130, 218)); + + darkPalette.setColor(QPalette::Highlight, QColor(42, 130, 218)); + darkPalette.setColor(QPalette::HighlightedText, Qt::white); + + qApp->setPalette(darkPalette); + + return app.exec(); +} diff --git a/EditorAssets/Create.png b/EditorAssets/Create.png new file mode 100644 index 0000000000000000000000000000000000000000..57449b93dd06c394f7663ad1c3542525e0dfe95b GIT binary patch literal 3249 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 literal 0 HcmV?d00001 diff --git a/EditorAssets/Display.png b/EditorAssets/Display.png new file mode 100644 index 0000000000000000000000000000000000000000..e2c860d5994da9342927c62a7646819167a4fff3 GIT binary patch literal 3025 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 literal 0 HcmV?d00001 diff --git a/EditorAssets/Free Camera.png b/EditorAssets/Free Camera.png new file mode 100644 index 0000000000000000000000000000000000000000..4d9ff98be39c7302ab33e633934ad39c9f891a3b GIT binary patch literal 3142 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 literal 0 HcmV?d00001 diff --git a/EditorAssets/GridLight.png b/EditorAssets/GridLight.png new file mode 100644 index 0000000000000000000000000000000000000000..9539fc76aadd6977f04abe7dc903f9ce70f37072 GIT binary patch literal 2874 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 A&Hw-a literal 0 HcmV?d00001 diff --git a/EditorAssets/Model Preview.png b/EditorAssets/Model Preview.png new file mode 100644 index 0000000000000000000000000000000000000000..eaabc505d551a55a181f2d813f1d60649bed4963 GIT binary patch literal 4082 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 A)c^nh literal 0 HcmV?d00001 diff --git a/EditorAssets/Plus.png b/EditorAssets/Plus.png new file mode 100644 index 0000000000000000000000000000000000000000..d0fac51a3a1779e207d68f2a98f1bced5791b458 GIT binary patch literal 3227 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 literal 0 HcmV?d00001 diff --git a/EditorAssets/Samus Silhouette Gradient.png b/EditorAssets/Samus Silhouette Gradient.png new file mode 100644 index 0000000000000000000000000000000000000000..14c32c825cf0e31289615a17607a4833358da37f GIT binary patch literal 3858 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 UE^O({fdBvi07*qoM6N<$f-oUKIRF3v literal 0 HcmV?d00001 diff --git a/EditorAssets/Scale.png b/EditorAssets/Scale.png new file mode 100644 index 0000000000000000000000000000000000000000..3ee9a24f900a091a0a9357ce5069f451c943f57a GIT binary patch literal 3032 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 literal 0 HcmV?d00001 diff --git a/EditorAssets/Sphere Preview.png b/EditorAssets/Sphere Preview.png new file mode 100644 index 0000000000000000000000000000000000000000..22003eb019ff2f96234c24688a49a6f9351844fd GIT binary patch literal 3761 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 btiJ~UWiY|GJTPGM00000NkvXXu0mjfEt34P literal 0 HcmV?d00001 diff --git a/EditorAssets/Square Preview.png b/EditorAssets/Square Preview.png new file mode 100644 index 0000000000000000000000000000000000000000..e0bb1f87f24c32e3503f734cb4b0e2a32a990fba GIT binary patch literal 3127 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 RxN86a002ovPDHLkV1kEG$MygK literal 0 HcmV?d00001 diff --git a/EditorAssets/Translate.png b/EditorAssets/Translate.png new file mode 100644 index 0000000000000000000000000000000000000000..162abf603456984fb11d17da477acfc0854248b0 GIT binary patch literal 3250 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 literal 0 HcmV?d00001 diff --git a/EditorAssets/Up.png b/EditorAssets/Up.png new file mode 100644 index 0000000000000000000000000000000000000000..9e1bd11d2dd926019edf76c152e6556e81616743 GIT binary patch literal 3352 zcmV+z4d?QSP)KLZ*U+IBfRsybQWXdwQbLP>6pAqfylh#{fb6;Z(vMMVS~$e@S=j*ftg6;Uhf59&ghTmgWD0l;*T zI709Y^p6lP1rIRMx#05C~cW=H_Aw*bJ-5DT&Z2n+x)QHX^p z00esgV8|mQcmRZ%02D^@S3L16t`O%c004NIvOKvYIYoh62rY33S640`D9%Y2D-rV&neh&#Q1i z007~1e$oCcFS8neI|hJl{-P!B1ZZ9hpmq0)X0i`JwE&>$+E?>%_LC6RbVIkUx0b+_+BaR3cnT7Zv!AJxW zizFb)h!jyGOOZ85F;a?DAXP{m@;!0_IfqH8(HlgRxt7s3}k3K`kFu>>-2Q$QMFfPW!La{h336o>X zu_CMttHv6zR;&ZNiS=X8v3CR#fknUxHUxJ0uoBa_M6WNWeqIg~6QE69c9o#eyhGvpiOA@W-aonk<7r1(?fC{oI5N*U!4 zfg=2N-7=cNnjjOr{yriy6mMFgG#l znCF=fnQv8CDz++o6_Lscl}eQ+l^ZHARH>?_s@|##Rr6KLRFA1%Q+=*RRWnoLsR`7U zt5vFIcfW3@?wFpwUVxrVZ>QdQz32KIeJ}k~{cZZE^+ya? z2D1z#2HOnI7(B%_ac?{wFUQ;QQA1tBKtrWrm0_3Rgps+?Jfqb{jYbcQX~taRB;#$y zZN{S}1|}gUOHJxc?wV3fxuz+mJ4`!F$IZ;mqRrNsHJd##*D~ju=bP7?-?v~|cv>vB zsJ6IeNwVZxrdjT`yl#bBIa#GxRa#xMMy;K#CDyyGyQdMSxlWT#tDe?p!?5wT$+oGt z8L;Kp2HUQ-ZMJ=3XJQv;x5ci*?vuTfeY$;({XGW_huIFR9a(?@3)XSs8O^N5RyOM=TTmp(3=8^+zpz2r)C z^>JO{deZfso3oq3?Wo(Y?l$ge?uXo;%ru`Vo>?<<(8I_>;8Eq#KMS9gFl*neeosSB zfoHYnBQIkwkyowPu(zdms`p{<7e4kra-ZWq<2*OsGTvEV%s0Td$hXT+!*8Bnh2KMe zBmZRodjHV?r+_5^X9J0WL4jKW`}lf%A-|44I@@LTvf1rHjG(ze6+w@Jt%Bvjts!X0 z?2xS?_ve_-kiKB_KiJlZ$9G`c^=E@oNG)mWWaNo-3TIW8)$Hg0Ub-~8?KhvJ>$ z3*&nim@mj(aCxE5!t{lw7O5^0EIO7zOo&c6l<+|iDySBWCGrz@C5{St!X3hAA}`T4 z(TLbXTq+(;@<=L8dXnssyft|w#WSTW<++3>sgS%(4NTpeI-VAqb|7ssJvzNHgOZVu zaYCvgO_R1~>SyL=cFU|~g|hy|Zi}}s9+d~lYqOB71z9Z$wnC=pR9Yz4DhIM>Wmjgu z&56o6maCpC&F##y%G;1PobR9i?GnNg;gYtchD%p19a!eQtZF&3JaKv33gZ<8D~47E ztUS1iwkmDaPpj=$m#%)jCVEY4fnLGNg2A-`YwHVD3gv};>)hAvT~AmqS>Lr``i7kw zJ{5_It`yrBmlc25DBO7E8;5VoznR>Ww5hAaxn$2~(q`%A-YuS64wkBy=9dm`4cXeX z4c}I@?e+FW+b@^RDBHV(wnMq2zdX3SWv9u`%{xC-q*U}&`cyXV(%rRT*Z6MH?i+i& z_B8C(+grT%{XWUQ+f@NoP1R=AW&26{v-dx)iK^-Nmiuj8txj!m?Z*Ss1N{dh4z}01 z)YTo*JycSU)+_5r4#yw9{+;i4Ee$peRgIj+;v;ZGdF1K$3E%e~4LaI(jC-u%2h$&R z9cLXcYC@Xwnns&bn)_Q~Te?roKGD|d-g^8;+aC{{G(1^(O7m37Y1-+6)01cN&y1aw zoqc{T`P^XJqPBbIW6s}d4{z_f5Om?vMgNQEJG?v2T=KYd^0M3I6IZxbny)%vZR&LD zJpPl@Psh8QyPB@KTx+@RdcC!KX7}kEo;S|j^u2lU7XQ}Oo;f|;z4Ll+_r>@1-xl3| zawq-H%e&ckC+@AhPrP6BKT#_XdT7&;F71j}Joy zkC~6lh7E@6o;W@^IpRNZ{ptLtL(gQ-CY~4mqW;US7Zxvm_|@yz&e53Bp_lTPlfP|z zrTyx_>lv@x#=^!PzR7qqF<$gm`|ZJZ+;<)Cqu&ot2z=0000WV@Og>004R=004l4008;_004mL004C`008P>0026e000+nl3&F} z0006*NklW1DFAT8RiMP z4l6x=w7PHo5>on^2s938zeD?NJJ*?f5CcdkDpnn&6U=@hB`kMOkRD+M5-5;_N%%!E z5fFiDy+)lk>B`lTuYOQ)-{8K+6IK@3qQ^=PxjP3)7XY;obt7rrpWJ47>o$h3ksg|R zAtg9K78d{!NCDCr(&_4(yD$xCfa81LfpiL-VUDHT0R0hZ@#$BnR)`9U`$#`7Z+?rp zL+aXN7L-puWBBv&7og|BVTgZbIhwTz9K-O3t=Gfm?u!2XX%hg6qe-$A*c zlpuobZ@;vZByh((ohYTVOwuB7IPEV*;FeS;0aR3EqDB1Y1pH2kEXG8 zH3M_Z9Ww{oRA#z@%~Ol=A|NX9`c><)T(u+s%ny5mLAKI2tHTaOyaS2d7P`_bM>N~+ z=gZe(&bC;90ub80{m(xFz4-^_jB3R^VJS7x`C?^D9ds4d8Jk8l&j1Whem`(5XKZskH)0000KLZ*U+IBfRsybQWXdwQbLP>6pAqfylh#{fb6;Z(vMMVS~$e@S=j*ftg6;Uhf59&ghTmgWD0l;*T zI709Y^p6lP1rIRMx#05C~cW=H_Aw*bJ-5DT&Z2n+x)QHX^p z00esgV8|mQcmRZ%02D^@S3L16t`O%c004NIvOKvYIYoh62rY33S640`D9%Y2D-rV&neh&#Q1i z007~1e$oCcFS8neI|hJl{-P!B1ZZ9hpmq0)X0i`JwE&>$+E?>%_LC6RbVIkUx0b+_+BaR3cnT7Zv!AJxW zizFb)h!jyGOOZ85F;a?DAXP{m@;!0_IfqH8(HlgRxt7s3}k3K`kFu>>-2Q$QMFfPW!La{h336o>X zu_CMttHv6zR;&ZNiS=X8v3CR#fknUxHUxJ0uoBa_M6WNWeqIg~6QE69c9o#eyhGvpiOA@W-aonk<7r1(?fC{oI5N*U!4 zfg=2N-7=cNnjjOr{yriy6mMFgG#l znCF=fnQv8CDz++o6_Lscl}eQ+l^ZHARH>?_s@|##Rr6KLRFA1%Q+=*RRWnoLsR`7U zt5vFIcfW3@?wFpwUVxrVZ>QdQz32KIeJ}k~{cZZE^+ya? z2D1z#2HOnI7(B%_ac?{wFUQ;QQA1tBKtrWrm0_3Rgps+?Jfqb{jYbcQX~taRB;#$y zZN{S}1|}gUOHJxc?wV3fxuz+mJ4`!F$IZ;mqRrNsHJd##*D~ju=bP7?-?v~|cv>vB zsJ6IeNwVZxrdjT`yl#bBIa#GxRa#xMMy;K#CDyyGyQdMSxlWT#tDe?p!?5wT$+oGt z8L;Kp2HUQ-ZMJ=3XJQv;x5ci*?vuTfeY$;({XGW_huIFR9a(?@3)XSs8O^N5RyOM=TTmp(3=8^+zpz2r)C z^>JO{deZfso3oq3?Wo(Y?l$ge?uXo;%ru`Vo>?<<(8I_>;8Eq#KMS9gFl*neeosSB zfoHYnBQIkwkyowPu(zdms`p{<7e4kra-ZWq<2*OsGTvEV%s0Td$hXT+!*8Bnh2KMe zBmZRodjHV?r+_5^X9J0WL4jKW`}lf%A-|44I@@LTvf1rHjG(ze6+w@Jt%Bvjts!X0 z?2xS?_ve_-kiKB_KiJlZ$9G`c^=E@oNG)mWWaNo-3TIW8)$Hg0Ub-~8?KhvJ>$ z3*&nim@mj(aCxE5!t{lw7O5^0EIO7zOo&c6l<+|iDySBWCGrz@C5{St!X3hAA}`T4 z(TLbXTq+(;@<=L8dXnssyft|w#WSTW<++3>sgS%(4NTpeI-VAqb|7ssJvzNHgOZVu zaYCvgO_R1~>SyL=cFU|~g|hy|Zi}}s9+d~lYqOB71z9Z$wnC=pR9Yz4DhIM>Wmjgu z&56o6maCpC&F##y%G;1PobR9i?GnNg;gYtchD%p19a!eQtZF&3JaKv33gZ<8D~47E ztUS1iwkmDaPpj=$m#%)jCVEY4fnLGNg2A-`YwHVD3gv};>)hAvT~AmqS>Lr``i7kw zJ{5_It`yrBmlc25DBO7E8;5VoznR>Ww5hAaxn$2~(q`%A-YuS64wkBy=9dm`4cXeX z4c}I@?e+FW+b@^RDBHV(wnMq2zdX3SWv9u`%{xC-q*U}&`cyXV(%rRT*Z6MH?i+i& z_B8C(+grT%{XWUQ+f@NoP1R=AW&26{v-dx)iK^-Nmiuj8txj!m?Z*Ss1N{dh4z}01 z)YTo*JycSU)+_5r4#yw9{+;i4Ee$peRgIj+;v;ZGdF1K$3E%e~4LaI(jC-u%2h$&R z9cLXcYC@Xwnns&bn)_Q~Te?roKGD|d-g^8;+aC{{G(1^(O7m37Y1-+6)01cN&y1aw zoqc{T`P^XJqPBbIW6s}d4{z_f5Om?vMgNQEJG?v2T=KYd^0M3I6IZxbny)%vZR&LD zJpPl@Psh8QyPB@KTx+@RdcC!KX7}kEo;S|j^u2lU7XQ}Oo;f|;z4Ll+_r>@1-xl3| zawq-H%e&ckC+@AhPrP6BKT#_XdT7&;F71j}Joy zkC~6lh7E@6o;W@^IpRNZ{ptLtL(gQ-CY~4mqW;US7Zxvm_|@yz&e53Bp_lTPlfP|z zrTyx_>lv@x#=^!PzR7qqF<$gm`|ZJZ+;<)Cqu&ot2z=0000WV@Og>004R=004l4008;_004mL004C`008P>0026e000+nl3&F} z0009_NklNVg7>MLeEP9&QlL&DULYXbfeXa_j z7E!cFF$mgNL}gfF2HHh2%p~lg5iAC(fEH`=TiO9fw#ep4NIq4<@OK23tNdZ%t@uRX0sy!f|eql5S({}*ZfGqtk1a1Pc zz>HRuq#ZJMD+$=7F>&%KRDYH*eFcyUlo;)+z+q+l3#~Mgi~#Kl=!kqBDG_$m050nM zo&Zxo9&lHEKY$!y6_8-~B*>NnjLUXMzEjGjfUrH2Fhd|CKoPJSxB~1{u>176t=yJB zz%!s+x$#NZ89;ZW9XKRi3yg_e_5fY7*8!ct7huxxo0P2+cqrRz`SuE{)|$h02b@}e zYT1*e%az%VI5%0WMM?*CB&IY1XMk;9Igl#-*0RS*kNaTFam&Y%chVcY0&uv!S#_Lf zt#HCe027u^qOh8+IXszgEpP+4u7p`6d$tm44ESYLK@1SDBQO~>s@X zXqDbA{aYpI+yiAwqM!>E2h~?*+y`C4KqYC;;TpgL>Ek+2duLL8Bw?C#uVIT9Ccu5K z0J?!Tt*Fw-ksj%E9|3HXesAPir7+t}7K~H{=ap<_O2S5iaB@X3(^eHMQ{F5wh$bDl zt*uWMMundTNf@XBPRg;^=*t%6Myh>Nimi4H@Z9fvpd9Eh+D!(N{Z!W5cfqK!D{tiE zNQW?eKS-v4d?SdEai*V@@h1Pr%J$Wu@{lokzA)$XC>_>)Uag1aG1=Dvmw^;xY>HmP zYt$~1Pc^XJ>k$*}RXi;o&(llCk+TLjI5mIHXt!(P(nu?x>;HlQ)MpOy>|8qLu0sI{R=?TXA<4YH100000NkvXXu0mjfFNL%H literal 0 HcmV?d00001 diff --git a/Icons.qrc b/Icons.qrc new file mode 100644 index 00000000..6d32fc62 --- /dev/null +++ b/Icons.qrc @@ -0,0 +1,31 @@ + + + EditorAssets/Down.png + EditorAssets/Free Camera.png + EditorAssets/Material Highlight.png + EditorAssets/Minus v2.png + EditorAssets/Minus.png + EditorAssets/Orbit Camera v2.png + EditorAssets/Orbit Camera.png + EditorAssets/Plus.png + EditorAssets/Rotate.png + EditorAssets/Samus Silhouette Gradient.png + EditorAssets/Scale.png + EditorAssets/Sphere Preview.png + EditorAssets/Square Preview.png + EditorAssets/Translate.png + EditorAssets/Up.png + EditorAssets/Hide.png + EditorAssets/Highlight.png + EditorAssets/Model Preview.png + EditorAssets/Show.png + EditorAssets/GridLight.png + EditorAssets/Create.png + EditorAssets/Display.png + EditorAssets/Instances.png + EditorAssets/Link.png + EditorAssets/Modify.png + EditorAssets/Unlink.png + EditorAssets/World.png + + diff --git a/OpenGL/CDynamicVertexBuffer.cpp b/OpenGL/CDynamicVertexBuffer.cpp new file mode 100644 index 00000000..9bd4e633 --- /dev/null +++ b/OpenGL/CDynamicVertexBuffer.cpp @@ -0,0 +1,136 @@ +#include "CDynamicVertexBuffer.h" +#include "CVertexArrayManager.h" + +static const u32 gskAttribSize[] = { + 0xC, 0xC, 0x4, 0x4, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8 +}; + +CDynamicVertexBuffer::CDynamicVertexBuffer() +{ + mAttribFlags = eNoAttributes; + mBufferedFlags = eNoAttributes; + mNumVertices = 0; +} + +CDynamicVertexBuffer::~CDynamicVertexBuffer() +{ + CVertexArrayManager::DeleteAllArraysForVBO(this); + ClearBuffers(); +} + +void CDynamicVertexBuffer::SetVertexCount(u32 NumVerts) +{ + ClearBuffers(); + mNumVertices = NumVerts; + InitBuffers(); +} + +void CDynamicVertexBuffer::Bind() +{ + CVertexArrayManager::Current()->BindVAO(this); +} + +void CDynamicVertexBuffer::Unbind() +{ + glBindVertexArray(0); +} + +void CDynamicVertexBuffer::SetActiveAttribs(u32 AttribFlags) +{ + ClearBuffers(); + mAttribFlags = (EVertexDescription) AttribFlags; + InitBuffers(); +} + +void CDynamicVertexBuffer::BufferAttrib(EVertexDescription Attrib, const void *pData) +{ + u32 Index; + + switch (Attrib) + { + case ePosition: Index = 0; break; + case eNormal: Index = 1; break; + case eColor0: Index = 2; break; + case eColor1: Index = 3; break; + case eTex0: Index = 4; break; + case eTex1: Index = 5; break; + case eTex2: Index = 6; break; + case eTex3: Index = 7; break; + case eTex4: Index = 8; break; + case eTex5: Index = 9; break; + case eTex6: Index = 10; break; + case eTex7: Index = 11; break; + default: return; + } + + glBindBuffer(GL_ARRAY_BUFFER, mAttribBuffers[Index]); + glBufferSubData(GL_ARRAY_BUFFER, 0, gskAttribSize[Index] * mNumVertices, pData); +} + +void CDynamicVertexBuffer::ClearBuffers() +{ + for (u32 iAttrib = 0; iAttrib < 12; iAttrib++) + { + u8 Bit = 1 << iAttrib; + + if (mBufferedFlags & Bit) + glDeleteBuffers(1, &mAttribBuffers[iAttrib]); + } + + mBufferedFlags = eNoAttributes; +} + +GLuint CDynamicVertexBuffer::CreateVAO() +{ + GLuint VertexArray; + glGenVertexArrays(1, &VertexArray); + glBindVertexArray(VertexArray); + + for (u32 iAttrib = 0; iAttrib < 12; iAttrib++) + { + bool HasAttrib = ((3 << (iAttrib * 2)) != 0); + + if (HasAttrib) + { + glBindBuffer(GL_ARRAY_BUFFER, mAttribBuffers[iAttrib]); + GLuint NumComponents; + GLenum DataType; + + if ((iAttrib == 2) || (iAttrib == 3)) + { + NumComponents = 4; + DataType = GL_UNSIGNED_BYTE; + } + else + { + NumComponents = gskAttribSize[iAttrib] / 4; + DataType = GL_FLOAT; + } + + glVertexAttribPointer(iAttrib, NumComponents, DataType, GL_FALSE, 0, (void*) 0); + glEnableVertexAttribArray(iAttrib); + } + } + + glBindVertexArray(0); + return VertexArray; +} + +// ************ PRIVATE ************ +void CDynamicVertexBuffer::InitBuffers() +{ + if (mBufferedFlags) ClearBuffers(); + + for (u32 iAttrib = 0; iAttrib < 12; iAttrib++) + { + bool HasAttrib = ((3 << (iAttrib * 2)) != 0); + + if (HasAttrib) + { + glGenBuffers(1, &mAttribBuffers[iAttrib]); + glBindBuffer(GL_ARRAY_BUFFER, mAttribBuffers[iAttrib]); + glBufferData(GL_ARRAY_BUFFER, gskAttribSize[iAttrib] * mNumVertices, NULL, GL_DYNAMIC_DRAW); + } + } + mBufferedFlags = mAttribFlags; +} diff --git a/OpenGL/CDynamicVertexBuffer.h b/OpenGL/CDynamicVertexBuffer.h new file mode 100644 index 00000000..1ea015fa --- /dev/null +++ b/OpenGL/CDynamicVertexBuffer.h @@ -0,0 +1,32 @@ +#ifndef CDYNAMICVERTEXBUFFER_H +#define CDYNAMICVERTEXBUFFER_H + +#include +#include +#include +#include +#include +#include + +class CDynamicVertexBuffer +{ + EVertexDescription mAttribFlags; + EVertexDescription mBufferedFlags; + u32 mNumVertices; + GLuint mAttribBuffers[12]; + +public: + CDynamicVertexBuffer(); + ~CDynamicVertexBuffer(); + void SetVertexCount(u32 NumVerts); + void Bind(); + void Unbind(); + void SetActiveAttribs(u32 AttribFlags); + void BufferAttrib(EVertexDescription Attrib, const void *pData); + void ClearBuffers(); + GLuint CreateVAO(); +private: + void InitBuffers(); +}; + +#endif // CDYNAMICVERTEXBUFFER_H diff --git a/OpenGL/CFramebuffer.cpp b/OpenGL/CFramebuffer.cpp new file mode 100644 index 00000000..129bcd4d --- /dev/null +++ b/OpenGL/CFramebuffer.cpp @@ -0,0 +1,111 @@ +#include "CFramebuffer.h" +#include + +CFramebuffer::CFramebuffer() +{ + mInitialized = false; + mWidth = 0; + mHeight = 0; + mpRenderbuffer = nullptr; + mpTexture = nullptr; +} + +CFramebuffer::CFramebuffer(u32 Width, u32 Height) +{ + mInitialized = false; + mWidth = 0; + mHeight = 0; + mpRenderbuffer = nullptr; + mpTexture = nullptr; + Resize(Width, Height); +} + +CFramebuffer::~CFramebuffer() +{ + if (mInitialized) + { + glDeleteFramebuffers(1, &mFramebuffer); + delete mpRenderbuffer; + delete mpTexture; + } +} + +void CFramebuffer::Init() +{ + if (!smStaticsInitialized) + { + glGetIntegerv(GL_FRAMEBUFFER_BINDING, &smDefaultFramebuffer); + smStaticsInitialized = true; + } + + if (!mInitialized) + { + glGenFramebuffers(1, &mFramebuffer); + glBindFramebuffer(GL_FRAMEBUFFER, mFramebuffer); + + mpRenderbuffer = new CRenderbuffer(mWidth, mHeight); + mpRenderbuffer->Bind(); + glFramebufferRenderbuffer( + GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, mpRenderbuffer->BufferID() + ); + + mpTexture = new CTexture(mWidth, mHeight); + mpTexture->Bind(0); + glFramebufferTexture2D( + GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mpTexture->TextureID(), 0 + ); + + mStatus = glCheckFramebufferStatus(GL_FRAMEBUFFER); + + if (mStatus != GL_FRAMEBUFFER_COMPLETE) + std::cout << "\rError: Framebuffer not complete\n"; + + mInitialized = true; + } +} + +void CFramebuffer::Bind() +{ + if (!mInitialized) Init(); + glBindFramebuffer(GL_FRAMEBUFFER, mFramebuffer); +} + +void CFramebuffer::Resize(u32 Width, u32 Height) +{ + if ((mWidth != Width) || (mHeight != Height)) + { + mWidth = Width; + mHeight = Height; + + if (mInitialized) + { + mpRenderbuffer->Resize(Width, Height); + mpTexture->Resize(Width, Height); + + glBindFramebuffer(GL_FRAMEBUFFER, mFramebuffer); + mpRenderbuffer->Bind(); + glFramebufferRenderbuffer( + GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, mpRenderbuffer->BufferID() + ); + + mpTexture->Bind(0); + glFramebufferTexture2D( + GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mpTexture->TextureID(), 0 + ); + } + } +} + +CTexture* CFramebuffer::Texture() +{ + return mpTexture; +} + +// ************ STATIC ************ +void CFramebuffer::BindDefaultFramebuffer() +{ + glBindFramebuffer(GL_FRAMEBUFFER, smDefaultFramebuffer); +} + +GLint CFramebuffer::smDefaultFramebuffer; +bool CFramebuffer::smStaticsInitialized; diff --git a/OpenGL/CFramebuffer.h b/OpenGL/CFramebuffer.h new file mode 100644 index 00000000..4d890ced --- /dev/null +++ b/OpenGL/CFramebuffer.h @@ -0,0 +1,32 @@ +#ifndef CFRAMEBUFFER_H +#define CFRAMEBUFFER_H + +#include "CRenderbuffer.h" +#include +#include + +class CFramebuffer +{ + GLuint mFramebuffer; + CRenderbuffer *mpRenderbuffer; + CTexture *mpTexture; + u32 mWidth, mHeight; + bool mInitialized; + GLenum mStatus; + + static GLint smDefaultFramebuffer; + static bool smStaticsInitialized; + +public: + CFramebuffer(); + CFramebuffer(u32 Width, u32 Height); + ~CFramebuffer(); + void Init(); + void Bind(); + void Resize(u32 Width, u32 Height); + CTexture* Texture(); + static void BindDefaultFramebuffer(); + +}; + +#endif // CFRAMEBUFFER_H diff --git a/OpenGL/CGL.cpp b/OpenGL/CGL.cpp new file mode 100644 index 00000000..38e3cce3 --- /dev/null +++ b/OpenGL/CGL.cpp @@ -0,0 +1,41 @@ +#include "CGL.h" + +// ************ PUBLIC ************ +void CGL::SetBlendMode(EBlendFactor Source, EBlendFactor Dest) +{ + glBlendFuncSeparate(Source, Dest, eBlendZero, eBlendZero); + mBlendSrcFac = Source; + mBlendDstFac = Dest; +} + +void CGL::SetOpaqueBlend() +{ + SetBlendMode(eBlendOne, eBlendZero); +} + +void CGL::SetAlphaBlend() +{ + SetBlendMode(eBlendSrcAlpha, eBlendInvSrcAlpha); +} + +void CGL::SetAdditiveBlend() +{ + SetBlendMode(eBlendOne, eBlendOne); +} + +// ************ PRIVATE ************ +void CGL::Init() +{ + if (!mInitialized) + { + glBlendFuncSeparate(GL_ONE, GL_ZERO, GL_ZERO, GL_ZERO); + mBlendSrcFac = eBlendOne; + mBlendDstFac = eBlendZero; + mInitialized = true; + } +} + +// ************ STATIC MEMBER INITIALIZATION ************ +bool CGL::mInitialized; +EBlendFactor CGL::mBlendSrcFac; +EBlendFactor CGL::mBlendDstFac; diff --git a/OpenGL/CGL.h b/OpenGL/CGL.h new file mode 100644 index 00000000..8573f16f --- /dev/null +++ b/OpenGL/CGL.h @@ -0,0 +1,26 @@ +#ifndef CGL_H +#define CGL_H + +#include "GLCommon.h" +#include +#include + +class CGL +{ +public: + void SetBlendMode(EBlendFactor Source, EBlendFactor Dest); + void SetOpaqueBlend(); + void SetAlphaBlend(); + void SetAdditiveBlend(); + +private: + static void Init(); + + static bool mInitialized; + static EBlendFactor mBlendSrcFac, mBlendDstFac; + static u8 mColorMask; + static bool mDepthMask; + static bool mStencilMask; +}; + +#endif // CGL_H diff --git a/OpenGL/CIndexBuffer.cpp b/OpenGL/CIndexBuffer.cpp new file mode 100644 index 00000000..3e97cd7c --- /dev/null +++ b/OpenGL/CIndexBuffer.cpp @@ -0,0 +1,156 @@ +#include "CIndexBuffer.h" + +CIndexBuffer::CIndexBuffer() +{ + mBuffered = false; +} + +CIndexBuffer::CIndexBuffer(GLenum type) +{ + mPrimitiveType = type; + mBuffered = false; +} + +CIndexBuffer::~CIndexBuffer() +{ + if (mBuffered) + glDeleteBuffers(1, &mIndexBuffer); +} + +void CIndexBuffer::AddIndex(u16 idx) +{ + mIndices.push_back(idx); +} + +void CIndexBuffer::AddIndices(u16 *indicesPtr, u32 count) +{ + Reserve(count); + for (u32 i = 0; i < count; i++) + mIndices.push_back(*indicesPtr++); +} + +void CIndexBuffer::Reserve(u32 size) +{ + mIndices.reserve(mIndices.size() + size); +} + +void CIndexBuffer::Clear() +{ + if (mBuffered) + glDeleteBuffers(1, &mIndexBuffer); + + mBuffered = false; + mIndices.clear(); +} + +void CIndexBuffer::Buffer() +{ + if (mBuffered) + glDeleteBuffers(1, &mIndexBuffer); + + glGenBuffers(1, &mIndexBuffer); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mIndexBuffer); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, mIndices.size() * sizeof(u16), mIndices.data(), GL_STATIC_DRAW); + + mBuffered = true; +} + +void CIndexBuffer::Bind() +{ + if (!mBuffered) Buffer(); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mIndexBuffer); +} + +void CIndexBuffer::Unbind() +{ +} + +void CIndexBuffer::DrawElements() +{ + Bind(); + glDrawElements(mPrimitiveType, mIndices.size(), GL_UNSIGNED_SHORT, (void*) 0); + Unbind(); +} + +void CIndexBuffer::DrawElements(u32 Offset, u32 Size) +{ + Bind(); + glDrawElements(mPrimitiveType, Size, GL_UNSIGNED_SHORT, (void*) (Offset * 2)); + Unbind(); +} + +bool CIndexBuffer::IsBuffered() +{ + return mBuffered; +} + +u32 CIndexBuffer::GetSize() +{ + return mIndices.size(); +} + +GLenum CIndexBuffer::GetPrimitiveType() +{ + return mPrimitiveType; +} + +void CIndexBuffer::SetPrimitiveType(GLenum type) +{ + mPrimitiveType = type; +} + +void CIndexBuffer::TrianglesToStrips(u16 *indicesPtr, u32 count) +{ + Reserve(count + (count / 3)); + + for (u32 i = 0; i < count; i += 3) + { + mIndices.push_back(*indicesPtr++); + mIndices.push_back(*indicesPtr++); + mIndices.push_back(*indicesPtr++); + mIndices.push_back(0xFFFF); + } +} + +void CIndexBuffer::FansToStrips(u16 *indicesPtr, u32 count) +{ + Reserve(count); + u16 FirstIndex = *indicesPtr; + + for (u32 i = 2; i < count; i += 3) + { + mIndices.push_back(indicesPtr[i - 1]); + mIndices.push_back(indicesPtr[i]); + mIndices.push_back(FirstIndex); + if (i + 1 < count) + mIndices.push_back(indicesPtr[i + 1]); + if (i + 2 < count) + mIndices.push_back(indicesPtr[i + 2]); + mIndices.push_back(0xFFFF); + } +} + +void CIndexBuffer::QuadsToStrips(u16 *indicesPtr, u32 count) +{ + Reserve((u32) (count * 1.25)); + + u32 i = 3; + for (; i < count; i += 4) + { + mIndices.push_back(indicesPtr[i - 2]); + mIndices.push_back(indicesPtr[i - 1]); + mIndices.push_back(indicesPtr[i - 3]); + mIndices.push_back(indicesPtr[i]); + mIndices.push_back(0xFFFF); + } + + // if there's three indices present that indicates a single triangle + if (i == count) + { + mIndices.push_back(indicesPtr[i - 3]); + mIndices.push_back(indicesPtr[i - 2]); + mIndices.push_back(indicesPtr[i - 1]); + mIndices.push_back(0xFFFF); + } + +} diff --git a/OpenGL/CIndexBuffer.h b/OpenGL/CIndexBuffer.h new file mode 100644 index 00000000..c8829ded --- /dev/null +++ b/OpenGL/CIndexBuffer.h @@ -0,0 +1,39 @@ +#ifndef CINDEXBUFFER_H +#define CINDEXBUFFER_H + +#include +#include +#include + +class CIndexBuffer +{ + GLuint mIndexBuffer; + std::vector mIndices; + GLenum mPrimitiveType; + bool mBuffered; + +public: + CIndexBuffer(); + CIndexBuffer(GLenum type); + ~CIndexBuffer(); + void AddIndex(u16 idx); + void AddIndices(u16 *indicesPtr, u32 count); + void Reserve(u32 size); + void Clear(); + void Buffer(); + void Bind(); + void Unbind(); + void DrawElements(); + void DrawElements(u32 Offset, u32 Size); + bool IsBuffered(); + + u32 GetSize(); + GLenum GetPrimitiveType(); + void SetPrimitiveType(GLenum type); + + void TrianglesToStrips(u16 *indicesPtr, u32 count); + void FansToStrips(u16 *indicesPtr, u32 count); + void QuadsToStrips(u16 *indicesPtr, u32 count); +}; + +#endif // CINDEXBUFFER_H diff --git a/OpenGL/CRenderbuffer.cpp b/OpenGL/CRenderbuffer.cpp new file mode 100644 index 00000000..2c484df6 --- /dev/null +++ b/OpenGL/CRenderbuffer.cpp @@ -0,0 +1,52 @@ +#include "CRenderbuffer.h" + +CRenderbuffer::CRenderbuffer() +{ + mInitialized = false; + mWidth = 0; + mHeight = 0; +} + +CRenderbuffer::CRenderbuffer(u32 Width, u32 Height) +{ + mInitialized = false; + mWidth = Width; + mHeight = Height; +} + +CRenderbuffer::~CRenderbuffer() +{ + if (mInitialized) + glDeleteRenderbuffers(1, &mRenderbuffer); +} + +void CRenderbuffer::Init() +{ + glGenRenderbuffers(1, &mRenderbuffer); + glBindRenderbuffer(GL_RENDERBUFFER, mRenderbuffer); + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, mWidth, mHeight); + mInitialized = true; +} + +void CRenderbuffer::Resize(u32 Width, u32 Height) +{ + mWidth = Width; + mHeight = Height; + + if (mInitialized) + { + Bind(); + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, mWidth, mHeight); + } +} + +void CRenderbuffer::Bind() +{ + if (!mInitialized) Init(); + glBindRenderbuffer(GL_RENDERBUFFER, mRenderbuffer); +} + +void CRenderbuffer::Unbind() +{ + glBindRenderbuffer(GL_RENDERBUFFER, 0); +} diff --git a/OpenGL/CRenderbuffer.h b/OpenGL/CRenderbuffer.h new file mode 100644 index 00000000..577713bd --- /dev/null +++ b/OpenGL/CRenderbuffer.h @@ -0,0 +1,31 @@ +#ifndef CRENDERBUFFER_H +#define CRENDERBUFFER_H + +#include +#include + +class CRenderbuffer +{ + GLuint mRenderbuffer; + u32 mWidth, mHeight; + bool mInitialized; + +public: + CRenderbuffer(); + CRenderbuffer(u32 Width, u32 Height); + ~CRenderbuffer(); + void Init(); + void Resize(u32 Width, u32 Height); + void Bind(); + void Unbind(); + + // Getters + GLuint BufferID(); +}; + +inline GLuint CRenderbuffer::BufferID() +{ + return mRenderbuffer; +} + +#endif // CRENDERBUFFER_H diff --git a/OpenGL/CShader.cpp b/OpenGL/CShader.cpp new file mode 100644 index 00000000..13fcf786 --- /dev/null +++ b/OpenGL/CShader.cpp @@ -0,0 +1,263 @@ +#include "CShader.h" +#include +#include +#include + +#include +#include +#include +#include + +bool gDebugDumpShaders = false; +u64 gFailedCompileCount = 0; +u64 gSuccessfulCompileCount = 0; + +CShader* CShader::spCurrentShader = nullptr; + +CShader::CShader() +{ + mVertexShaderExists = false; + mPixelShaderExists = false; + mProgramExists = false; +} + +CShader::CShader(const char *kpVertexSource, const char *kpPixelSource) +{ + mVertexShaderExists = false; + mPixelShaderExists = false; + mProgramExists = false; + + CompileVertexSource(kpVertexSource); + CompilePixelSource(kpPixelSource); + LinkShaders(); +} + +CShader::~CShader() +{ + if (mVertexShaderExists) glDeleteShader(mVertexShader); + if (mPixelShaderExists) glDeleteShader(mPixelShader); + if (mProgramExists) glDeleteProgram(mProgram); + + if (spCurrentShader == this) spCurrentShader = 0; +} + +bool CShader::CompileVertexSource(const char* kpSource) +{ + mVertexShader = glCreateShader(GL_VERTEX_SHADER); + glShaderSource(mVertexShader, 1, (const GLchar**) &kpSource, NULL); + glCompileShader(mVertexShader); + + // Shader should be compiled - check for errors + GLint CompileStatus; + glGetShaderiv(mVertexShader, GL_COMPILE_STATUS, &CompileStatus); + + if (CompileStatus == GL_FALSE) + { + std::string Out = "dump/BadVS_" + std::to_string(gFailedCompileCount) + ".txt"; + std::cout << "ERROR: Unable to compile vertex shader; dumped to " << Out << "\n"; + DumpShaderSource(mVertexShader, Out); + + gFailedCompileCount++; + glDeleteShader(mVertexShader); + return false; + } + + // Debug dump + else if (gDebugDumpShaders == true) + { + std::string Out = "dump/VS_" + std::to_string(gSuccessfulCompileCount) + ".txt"; + std::cout << "Debug shader dumping enabled; dumped to " << Out << "\n"; + DumpShaderSource(mVertexShader, Out); + + gSuccessfulCompileCount++; + } + + mVertexShaderExists = true; + return true; +} + +bool CShader::CompilePixelSource(const char* kpSource) +{ + mPixelShader = glCreateShader(GL_FRAGMENT_SHADER); + glShaderSource(mPixelShader, 1, (const GLchar**) &kpSource, NULL); + glCompileShader(mPixelShader); + + // Shader should be compiled - check for errors + GLint CompileStatus; + glGetShaderiv(mPixelShader, GL_COMPILE_STATUS, &CompileStatus); + + if (CompileStatus == GL_FALSE) + { + std::string Out = "dump/BadPS_" + std::to_string(gFailedCompileCount) + ".txt"; + std::cout << "ERROR: Unable to compile pixel shader; dumped to " << Out << "\n"; + DumpShaderSource(mPixelShader, Out); + + gFailedCompileCount++; + glDeleteShader(mPixelShader); + return false; + } + + // Debug dump + else if (gDebugDumpShaders == true) + { + std::string Out = "dump/PS_" + std::to_string(gSuccessfulCompileCount) + ".txt"; + std::cout << "Debug shader dumping enabled; dumped to " << Out << "\n"; + DumpShaderSource(mPixelShader, Out); + + gSuccessfulCompileCount++; + } + + mPixelShaderExists = true; + return true; +} + +bool CShader::LinkShaders() +{ + if ((!mVertexShaderExists) || (!mPixelShaderExists)) return false; + + mProgram = glCreateProgram(); + glAttachShader(mProgram, mVertexShader); + glAttachShader(mProgram, mPixelShader); + glLinkProgram(mProgram); + + glDeleteShader(mVertexShader); + glDeleteShader(mPixelShader); + mVertexShaderExists = false; + mPixelShaderExists = false; + + // Shader should be linked - check for errors + GLint LinkStatus; + glGetProgramiv(mProgram, GL_LINK_STATUS, &LinkStatus); + + if (LinkStatus == GL_FALSE) + { + std::string Out = "dump/BadLink_" + std::to_string(gFailedCompileCount) + ".txt"; + std::cout << "ERROR: Unable to link shaders. Dumped error log to " << Out << "\n"; + + GLint LogLen; + glGetProgramiv(mProgram, GL_INFO_LOG_LENGTH, &LogLen); + GLchar *InfoLog = new GLchar[LogLen]; + glGetProgramInfoLog(mProgram, LogLen, NULL, InfoLog); + + std::ofstream LinkOut; + LinkOut.open(Out.c_str()); + + if (LogLen > 0) + LinkOut << InfoLog; + + LinkOut.close(); + delete[] InfoLog; + + gFailedCompileCount++; + glDeleteProgram(mProgram); + return false; + } + + mMVPBlockIndex = GetUniformBlockIndex("MVPBlock"); + mVertexBlockIndex = GetUniformBlockIndex("VertexBlock"); + mPixelBlockIndex = GetUniformBlockIndex("PixelBlock"); + mLightBlockIndex = GetUniformBlockIndex("LightBlock"); + + mProgramExists = true; + return true; +} + +bool CShader::IsValidProgram() +{ + return mProgramExists; +} + +GLuint CShader::GetProgramID() +{ + return mProgram; +} + +GLuint CShader::GetUniformLocation(const char* Uniform) +{ + return glGetUniformLocation(mProgram, Uniform); +} + +GLuint CShader::GetUniformBlockIndex(const char* UniformBlock) +{ + return glGetUniformBlockIndex(mProgram, UniformBlock); +} + +void CShader::SetCurrent() +{ + if (spCurrentShader != this) + { + glUseProgram(mProgram); + spCurrentShader = this; + + glUniformBlockBinding(mProgram, mMVPBlockIndex, CGraphics::MVPBlockBindingPoint()); + glUniformBlockBinding(mProgram, mVertexBlockIndex, CGraphics::VertexBlockBindingPoint()); + glUniformBlockBinding(mProgram, mPixelBlockIndex, CGraphics::PixelBlockBindingPoint()); + glUniformBlockBinding(mProgram, mLightBlockIndex, CGraphics::LightBlockBindingPoint()); + } +} + +// ************ STATIC ************ +CShader* CShader::FromResourceFile(std::string ShaderName) +{ + std::string VertexShaderFilename = "../resources/shaders/" + ShaderName + ".vs"; + std::string PixelShaderFilename = "../resources/shaders/" + ShaderName + ".ps"; + CTextInStream VertexShaderFile(VertexShaderFilename); + CTextInStream PixelShaderFile(PixelShaderFilename); + + if (!VertexShaderFile.IsValid()) + std::cout << "Error: Couldn't load vertex shader file for " << ShaderName << "\n"; + if (!PixelShaderFile.IsValid()) + std::cout << "Error: Couldn't load pixel shader file for " << ShaderName << "\n"; + if ((!VertexShaderFile.IsValid()) || (!PixelShaderFile.IsValid())) return nullptr; + + std::stringstream VertexShader; + while (!VertexShaderFile.EoF()) + VertexShader << VertexShaderFile.GetString(); + + std::stringstream PixelShader; + while (!PixelShaderFile.EoF()) + PixelShader << PixelShaderFile.GetString(); + + CShader *pShader = new CShader(); + pShader->CompileVertexSource(VertexShader.str().c_str()); + pShader->CompilePixelSource(PixelShader.str().c_str()); + pShader->LinkShaders(); + return pShader; +} + +CShader* CShader::CurrentShader() +{ + return spCurrentShader; +} + +void CShader::KillCachedShader() +{ + spCurrentShader = 0; +} + +// ************ PRIVATE ************ +void CShader::DumpShaderSource(GLuint Shader, std::string Out) +{ + GLint SourceLen; + glGetShaderiv(Shader, GL_SHADER_SOURCE_LENGTH, &SourceLen); + GLchar *Source = new GLchar[SourceLen]; + glGetShaderSource(Shader, SourceLen, NULL, Source); + + GLint LogLen; + glGetShaderiv(Shader, GL_INFO_LOG_LENGTH, &LogLen); + GLchar *InfoLog = new GLchar[LogLen]; + glGetShaderInfoLog(Shader, LogLen, NULL, InfoLog); + + std::ofstream ShaderOut; + ShaderOut.open(Out.c_str()); + + if (SourceLen > 0) + ShaderOut << Source; + if (LogLen > 0) + ShaderOut << InfoLog; + + ShaderOut.close(); + + delete[] Source; + delete[] InfoLog; +} diff --git a/OpenGL/CShader.h b/OpenGL/CShader.h new file mode 100644 index 00000000..0825af13 --- /dev/null +++ b/OpenGL/CShader.h @@ -0,0 +1,45 @@ +#ifndef CSHADER_H +#define CSHADER_H + +#include +#include + +class CShader +{ + bool mVertexShaderExists; + bool mPixelShaderExists; + bool mProgramExists; + GLuint mVertexShader; + GLuint mPixelShader; + GLuint mProgram; + + GLuint mMVPBlockIndex; + GLuint mVertexBlockIndex; + GLuint mPixelBlockIndex; + GLuint mLightBlockIndex; + + static CShader* spCurrentShader; + +public: + CShader(); + CShader(const char* kpVertexSource, const char* kpPixelSource); + ~CShader(); + bool CompileVertexSource(const char* kpSource); + bool CompilePixelSource(const char* kpSource); + bool LinkShaders(); + bool IsValidProgram(); + GLuint GetProgramID(); + GLuint GetUniformLocation(const char* Uniform); + GLuint GetUniformBlockIndex(const char* UniformBlock); + void SetCurrent(); + + // Static + static CShader* FromResourceFile(std::string ShaderName); + static CShader* CurrentShader(); + static void KillCachedShader(); + +private: + void DumpShaderSource(GLuint Shader, std::string Out); +}; + +#endif // CSHADER_H diff --git a/OpenGL/CShaderGenerator.cpp b/OpenGL/CShaderGenerator.cpp new file mode 100644 index 00000000..de0489d6 --- /dev/null +++ b/OpenGL/CShaderGenerator.cpp @@ -0,0 +1,445 @@ +#include +#include +#include +#include +#include "CShaderGenerator.h" + +const std::string gkCoordSrc[] = { + "RawPosition.xyz", + "RawNormal.xyz", + "0.0, 0.0, 0.0", + "0.0, 0.0, 0.0", + "RawTex0.xy, 1.0", + "RawTex1.xy, 1.0", + "RawTex2.xy, 1.0", + "RawTex3.xy, 1.0", + "RawTex4.xy, 1.0", + "RawTex5.xy, 1.0", + "RawTex6.xy, 1.0", + "RawTex7.xy, 1.0" +}; + +const std::string gkRasSel[] = { + "vec4(COLOR0A0.rgb, 1.0)", + "vec4(COLOR1A1.rgb, 1.0)", + "vec4(0.0, 0.0, 0.0, COLOR0A0.a)", + "vec4(0.0, 0.0, 0.0, COLOR1A1.a)", + "COLOR0A0", + "COLOR1A1", + "vec4(0.0, 0.0, 0.0, 0.0)" +}; + +const std::string gkKonstColor[] = { + "1.0, 1.0, 1.0", + "0.875, 0.875, 0.875", + "0.75, 0.75, 0.75", + "0.625, 0.625, 0.625", + "0.5, 0.5, 0.5", + "0.375, 0.375, 0.375", + "0.25, 0.25, 0.25", + "0.125, 0.125, 0.125", + "", + "", + "", + "", + "KonstColors[0].rgb", + "KonstColors[1].rgb", + "KonstColors[2].rgb", + "KonstColors[3].rgb", + "KonstColors[0].rrr", + "KonstColors[1].rrr", + "KonstColors[2].rrr", + "KonstColors[3].rrr", + "KonstColors[0].ggg", + "KonstColors[1].ggg", + "KonstColors[2].ggg", + "KonstColors[3].ggg", + "KonstColors[0].bbb", + "KonstColors[1].bbb", + "KonstColors[2].bbb", + "KonstColors[3].bbb", + "KonstColors[0].aaa", + "KonstColors[1].aaa", + "KonstColors[2].aaa", + "KonstColors[3].aaa" +}; + +const std::string gkKonstAlpha[] = { + "1.0", + "0.875", + "0.75", + "0.625", + "0.5", + "0.375", + "0.25", + "0.125", + "", + "", + "", + "", + "", + "", + "", + "", + "KonstColors[0].r", + "KonstColors[1].r", + "KonstColors[2].r", + "KonstColors[3].r", + "KonstColors[0].g", + "KonstColors[1].g", + "KonstColors[2].g", + "KonstColors[3].g", + "KonstColors[0].b", + "KonstColors[1].b", + "KonstColors[2].b", + "KonstColors[3].b", + "KonstColors[0].a", + "KonstColors[1].a", + "KonstColors[2].a", + "KonstColors[3].a" +}; + +const std::string gkTevColor[] = { + "Prev.rgb", + "Prev.aaa", + "C0.rgb", + "C0.aaa", + "C1.rgb", + "C1.aaa", + "C2.rgb", + "C2.aaa", + "Tex.rgb", + "Tex.aaa", + "Ras.rgb", + "Ras.aaa", + "1.0, 1.0, 1.0", + "0.5, 0.5, 0.5", + "Konst.rgb", + "0.0, 0.0, 0.0" +}; + +const std::string gkTevAlpha[] = { + "Prev.a", + "C0.a", + "C1.a", + "C2.a", + "Tex.a", + "Ras.a", + "Konst.a", + "0.0" +}; + +const std::string gkTevRigid[] = { + "Prev", + "C0", + "C1", + "C2" +}; + +CShaderGenerator::CShaderGenerator() +{ +} + +CShaderGenerator::~CShaderGenerator() +{ +} + +bool CShaderGenerator::CreateVertexShader(const CMaterial& Mat) +{ + std::stringstream ShaderCode; + + ShaderCode << "#version 330 core\n" + << "\n"; + + // Input + ShaderCode << "// Input\n"; + EVertexDescription VtxDesc = Mat.VtxDesc(); + if (VtxDesc & ePosition) ShaderCode << "layout(location = 0) in vec3 RawPosition;\n"; + if (VtxDesc & eNormal) ShaderCode << "layout(location = 1) in vec3 RawNormal;\n"; + if (VtxDesc & eColor0) ShaderCode << "layout(location = 2) in vec4 RawColor0;\n"; + if (VtxDesc & eColor1) ShaderCode << "layout(location = 3) in vec4 RawColor1;\n"; + if (VtxDesc & eTex0) ShaderCode << "layout(location = 4) in vec2 RawTex0;\n"; + if (VtxDesc & eTex1) ShaderCode << "layout(location = 5) in vec2 RawTex1;\n"; + if (VtxDesc & eTex2) ShaderCode << "layout(location = 6) in vec2 RawTex2;\n"; + if (VtxDesc & eTex3) ShaderCode << "layout(location = 7) in vec2 RawTex3;\n"; + if (VtxDesc & eTex4) ShaderCode << "layout(location = 8) in vec2 RawTex4;\n"; + if (VtxDesc & eTex5) ShaderCode << "layout(location = 9) in vec2 RawTex5;\n"; + if (VtxDesc & eTex6) ShaderCode << "layout(location = 10) in vec2 RawTex6;\n"; + ShaderCode << "\n"; + + // Output + ShaderCode << "// Output\n"; + if (VtxDesc & eNormal) ShaderCode << "out vec3 Normal;\n"; + if (VtxDesc & eColor0) ShaderCode << "out vec4 Color0;\n"; + if (VtxDesc & eColor1) ShaderCode << "out vec4 Color1;\n"; + + for (u32 iPass = 0; iPass < Mat.PassCount(); iPass++) + if (Mat.Pass(iPass)->TexCoordSource() != 0xFF) + ShaderCode << "out vec3 Tex" << iPass << ";\n"; + + ShaderCode << "out vec4 COLOR0A0;\n" + << "out vec4 COLOR1A1;\n"; + ShaderCode << "\n"; + + // Uniforms + ShaderCode << "// Uniforms\n" + << "layout(std140) uniform MVPBlock\n" + << "{\n" + << " mat4 ModelMtx;\n" + << " mat4 ViewMtx;\n" + << " mat4 ProjMtx;\n" + << "};\n" + << "\n" + << "layout(std140) uniform VertexBlock\n" + << "{\n" + << " mat4 TexMtx[10];\n" + << " mat4 PostMtx[20];\n" + << " vec4 COLOR0_Amb;\n" + << " vec4 COLOR0_Mat;\n" + << " vec4 COLOR1_Amb;\n" + << " vec4 COLOR1_Mat;\n" + << "};\n" + << "\n" + << "struct GXLight\n" + << "{\n" + << " vec4 Position;\n" + << " vec4 Direction;\n" + << " vec4 Color;\n" + << " vec4 DistAtten;\n" + << " vec4 AngleAtten;\n" + << "};\n" + << "layout(std140) uniform LightBlock {\n" + << " GXLight Lights[8];\n" + << "};\n" + << "uniform int NumLights;\n" + << "\n"; + + // Main + ShaderCode << "// Main\n" + << "void main()\n" + << "{\n" + << " mat4 MVP = ModelMtx * ViewMtx * ProjMtx;\n" + << " mat4 MV = ModelMtx * ViewMtx;\n"; + + if (VtxDesc & ePosition) ShaderCode << " gl_Position = vec4(RawPosition, 1) * MVP;\n"; + if (VtxDesc & eNormal) ShaderCode << " Normal = normalize(RawNormal.xyz * inverse(transpose(mat3(MV))));\n"; + if (VtxDesc & eColor0) ShaderCode << " Color0 = RawColor0;\n"; + if (VtxDesc & eColor1) ShaderCode << " Color1 = RawColor1;\n"; + + // Per-vertex lighting + ShaderCode << "\n" + << " // Dynamic Lighting\n"; + + // This bit could do with some cleaning up + // It took a lot of experimentation to get dynamic lights working and I never went back and cleaned it up after + if (Mat.IsLightingEnabled()) + { + ShaderCode << " vec4 Illum = vec4(0.0);\n" + << " vec3 PositionMV = vec3(vec4(RawPosition, 1.0) * MV);\n" + << " \n" + << " for (int iLight = 0; iLight < NumLights; iLight++)\n" + << " {\n" + << " vec3 LightPosMV = vec3(Lights[iLight].Position * ViewMtx);\n" + << " vec3 LightDirMV = normalize(Lights[iLight].Direction.xyz * inverse(transpose(mat3(ViewMtx))));\n" + << " vec3 LightDist = LightPosMV.xyz - PositionMV.xyz;\n" + << " float DistSquared = dot(LightDist, LightDist);\n" + << " float Dist = sqrt(DistSquared);\n" + << " LightDist /= Dist;\n" + << " vec3 AngleAtten = Lights[iLight].AngleAtten.xyz;\n" + << " AngleAtten = vec3(AngleAtten.x, AngleAtten.y, AngleAtten.z);\n" + << " float Atten = max(0, dot(LightDist, LightDirMV.xyz));\n" + << " Atten = max(0, dot(AngleAtten, vec3(1.0, Atten, Atten * Atten))) / dot(Lights[iLight].DistAtten.xyz, vec3(1.0, Dist, DistSquared));\n" + << " float DiffuseAtten = max(0, dot(Normal, LightDist));\n" + << " Illum += (Atten * DiffuseAtten * Lights[iLight].Color);\n" + << " }\n" + << " COLOR0A0 = COLOR0_Mat * (Illum + COLOR0_Amb);\n" + << " COLOR1A1 = COLOR1_Mat * (Illum + COLOR1_Amb);\n" + << " \n"; + } + + else + { + ShaderCode << " COLOR0A0 = COLOR0_Mat;\n" + << " COLOR1A1 = COLOR1_Mat;\n" + << "\n"; + } + + // Texture coordinate generation + ShaderCode << " \n" + << " // TexGen\n"; + + u32 PassCount = Mat.PassCount(); + + for (u32 iPass = 0; iPass < PassCount; iPass++) + { + CMaterialPass *pPass = Mat.Pass(iPass); + if (pPass->TexCoordSource() == 0xFF) continue; + + EUVAnimMode AnimMode = pPass->AnimMode(); + + if (AnimMode == eNoUVAnim) // No animation + ShaderCode << " Tex" << iPass << " = vec3(" << gkCoordSrc[pPass->TexCoordSource()] << ");\n"; + + else // Animation used - texture matrix at least, possibly normalize/post-transform + { + // Texture Matrix + ShaderCode << " Tex" << iPass << " = vec3(vec4(" << gkCoordSrc[pPass->TexCoordSource()] << ", 1.0) * TexMtx[" << iPass << "]).xyz;\n"; + + if ((AnimMode < 2) || (AnimMode > 5)) + { + // Normalization + Post-Transform + ShaderCode << " Tex" << iPass << " = normalize(Tex" << iPass << ");\n"; + ShaderCode << " Tex" << iPass << " = vec3(vec4(Tex" << iPass << ", 1.0) * PostMtx[" << iPass << "]).xyz;\n"; + } + } + + ShaderCode << "\n"; + } + ShaderCode << "}\n\n"; + + + // Done! + return mShader->CompileVertexSource(ShaderCode.str().c_str()); +} + +bool CShaderGenerator::CreatePixelShader(const CMaterial& Mat) +{ + std::stringstream ShaderCode; + ShaderCode << "#version 330 core\n" + << "\n" + << "#extension GL_ARB_shading_language_420pack : enable\n" // Needed to set texture binding layouts + << "\n"; + + EVertexDescription VtxDesc = Mat.VtxDesc(); + if (VtxDesc & ePosition) ShaderCode << "in vec3 Position;\n"; + if (VtxDesc & eNormal) ShaderCode << "in vec3 Normal;\n"; + if (VtxDesc & eColor0) ShaderCode << "in vec4 Color0;\n"; + if (VtxDesc & eColor1) ShaderCode << "in vec4 Color1;\n"; + + u32 PassCount = Mat.PassCount(); + + for (u32 iPass = 0; iPass < PassCount; iPass++) + if (Mat.Pass(iPass)->TexCoordSource() != 0xFF) + ShaderCode << "in vec3 Tex" << iPass << ";\n"; + + ShaderCode << "in vec4 COLOR0A0;\n" + << "in vec4 COLOR1A1;\n" + << "\n" + << "out vec4 PixelColor;\n" + << "\n" + << "layout(std140) uniform PixelBlock {\n" + << " vec4 KonstColors[4];\n" + << " vec4 TevColor;\n" + << " vec4 TintColor;\n" + << "};\n\n"; + + for (u32 iPass = 0; iPass < PassCount; iPass++) + if (Mat.Pass(iPass)->Texture() != nullptr) + ShaderCode << "layout(binding = " << iPass << ") uniform sampler2D Texture" << iPass << ";\n"; + + ShaderCode <<"\n"; + + ShaderCode << "void main()\n" + << "{\n" + << " vec4 TevInA = vec4(0, 0, 0, 0), TevInB = vec4(0, 0, 0, 0), TevInC = vec4(0, 0, 0, 0), TevInD = vec4(0, 0, 0, 0);\n" + << " vec4 Prev = vec4(0, 0, 0, 0), C0 = TevColor, C1 = C0, C2 = C0;\n" + << " vec4 Ras = vec4(0, 0, 0, 1), Tex = vec4(0, 0, 0, 0);\n" + << " vec4 Konst = vec4(1, 1, 1, 1);\n"; + + ShaderCode << " vec2 TevCoord = vec2(0, 0);\n" + << " \n"; + + bool Lightmap = false; + for (u32 iPass = 0; iPass < PassCount; iPass++) + { + const CMaterialPass *pPass = Mat.Pass(iPass); + CFourCC PassType = pPass->Type(); + + ShaderCode << " // TEV Stage " << iPass << " - " << PassType.ToString() << "\n"; + if (pPass->Type() == "DIFF") Lightmap = true; + + if (!pPass->IsEnabled()) + { + ShaderCode << " // Pass is disabled\n\n"; + continue; + } + + if (pPass->TexCoordSource() != 0xFF) + ShaderCode << " TevCoord = (Tex" << iPass << ".z == 0.0 ? Tex" << iPass << ".xy : Tex" << iPass << ".xy / Tex" << iPass << ".z);\n"; + + if (pPass->Texture() != nullptr) + ShaderCode << " Tex = texture(Texture" << iPass << ", TevCoord)"; + + // A couple pass types require special swizzles to access different texture color channels as alpha + if ((PassType == "TRAN") || (PassType == "INCA") || (PassType == "BLOI")) + ShaderCode << ".rgbr"; + else if (PassType == "BLOL") + ShaderCode << ".rgbg"; + + ShaderCode << ";\n"; + + ShaderCode << " Konst = vec4(" << gkKonstColor[pPass->KColorSel()] << ", " << gkKonstAlpha[pPass->KAlphaSel()] << ");\n"; + + if (pPass->RasSel() != eRasColorNull) + ShaderCode << " Ras = " << gkRasSel[pPass->RasSel()] << ";\n"; + + for (u8 iInput = 0; iInput < 4; iInput++) + { + char TevChar = iInput + 0x41; // the current stage number represented as an ASCII letter; eg 0 is 'A' + + ShaderCode << " TevIn" << TevChar << " = vec4(" + << gkTevColor[pPass->ColorInput(iInput) & 0xF] + << ", " + << gkTevAlpha[pPass->AlphaInput(iInput) & 0x7] + << ");\n"; + } + + ShaderCode << " // RGB Combine\n" + << " " + << gkTevRigid[pPass->ColorOutput()] + << ".rgb = "; + + ShaderCode << "clamp(vec3(TevInD.rgb + ((1.0 - TevInC.rgb) * TevInA.rgb + TevInC.rgb * TevInB.rgb))"; + if ((PassType == "CLR ") && (Lightmap)) ShaderCode << "* 2.0"; // Apply tevscale 2.0 on the color pass if lightmap is present + ShaderCode << ", vec3(0, 0, 0), vec3(1.0, 1.0, 1.0));\n"; + + ShaderCode << " // Alpha Combine\n" + << " " + << gkTevRigid[pPass->AlphaOutput()] + << ".a = "; + + ShaderCode << "clamp(TevInD.a + ((1.0 - TevInC.a) * TevInA.a + TevInC.a * TevInB.a), 0.0, 1.0);\n\n"; + } + + if (Mat.Options() & CMaterial::ePunchthrough) + { + if (Mat.Version() < eCorruptionProto) + { + ShaderCode << " if (Prev.a <= 0.25) discard;\n" + << " else Prev.a = 1.0;\n\n"; + } + else + { + ShaderCode << " if (Prev.a <= 0.75) discard;\n" + " else Prev.a = 0.0;\n\n"; + } + } + + ShaderCode << " PixelColor = Prev.rgba;\n" + << "}\n\n"; + + // Done! + return mShader->CompilePixelSource(ShaderCode.str().c_str()); +} + +CShader* CShaderGenerator::GenerateShader(const CMaterial& Mat) +{ + CShaderGenerator Generator; + Generator.mShader = new CShader(); + + bool Success = Generator.CreateVertexShader(Mat); + if (Success) Success = Generator.CreatePixelShader(Mat); + + Generator.mShader->LinkShaders(); + return Generator.mShader; +} diff --git a/OpenGL/CShaderGenerator.cpp.bW5112 b/OpenGL/CShaderGenerator.cpp.bW5112 new file mode 100644 index 00000000..2f73f1fb --- /dev/null +++ b/OpenGL/CShaderGenerator.cpp.bW5112 @@ -0,0 +1,445 @@ +#include +#include +#include +#include +#include "CShaderGenerator.h" + +const std::string gkCoordSrc[] = { + "RawPosition.xyz", + "RawNormal.xyz", + "0.0, 0.0, 0.0", + "0.0, 0.0, 0.0", + "RawTex0.xy, 1.0", + "RawTex1.xy, 1.0", + "RawTex2.xy, 1.0", + "RawTex3.xy, 1.0", + "RawTex4.xy, 1.0", + "RawTex5.xy, 1.0", + "RawTex6.xy, 1.0", + "RawTex7.xy, 1.0" +}; + +const std::string gkKonstColor[] = { + "1.0, 1.0, 1.0", + "0.875, 0.875, 0.875", + "0.75, 0.75, 0.75", + "0.625, 0.625, 0.625", + "0.5, 0.5, 0.5", + "0.375, 0.375, 0.375", + "0.25, 0.25, 0.25", + "0.125, 0.125, 0.125", + "", + "", + "", + "", + "KonstColors[0].rgb", + "KonstColors[1].rgb", + "KonstColors[2].rgb", + "KonstColors[3].rgb", + "KonstColors[0].rrr", + "KonstColors[1].rrr", + "KonstColors[2].rrr", + "KonstColors[3].rrr", + "KonstColors[0].ggg", + "KonstColors[1].ggg", + "KonstColors[2].ggg", + "KonstColors[3].ggg", + "KonstColors[0].bbb", + "KonstColors[1].bbb", + "KonstColors[2].bbb", + "KonstColors[3].bbb", + "KonstColors[0].aaa", + "KonstColors[1].aaa", + "KonstColors[2].aaa", + "KonstColors[3].aaa" +}; + +const std::string gkKonstAlpha[] = { + "1.0", + "0.875", + "0.75", + "0.625", + "0.5", + "0.375", + "0.25", + "0.125", + "", + "", + "", + "", + "", + "", + "", + "", + "KonstColors[0].r", + "KonstColors[1].r", + "KonstColors[2].r", + "KonstColors[3].r", + "KonstColors[0].g", + "KonstColors[1].g", + "KonstColors[2].g", + "KonstColors[3].g", + "KonstColors[0].b", + "KonstColors[1].b", + "KonstColors[2].b", + "KonstColors[3].b", + "KonstColors[0].a", + "KonstColors[1].a", + "KonstColors[2].a", + "KonstColors[3].a" +}; + +const std::string gkTevColor[] = { + "Prev.rgb", + "Prev.aaa", + "C0.rgb", + "C0.aaa", + "C1.rgb", + "C1.aaa", + "C2.rgb", + "C2.aaa", + "Tex.rgb", + "Tex.aaa", + "Ras.rgb", + "Ras.aaa", + "1.0, 1.0, 1.0", + "0.5, 0.5, 0.5", + "Konst.rgb", + "0, 0, 0" +}; + +const std::string gkTevAlpha[] = { + "Prev.a", + "C0.a", + "C1.a", + "C2.a", + "Tex.a", + "Ras.a", + "Konst.a", + "0" +}; + +const std::string gkTevRigid[] = { + "Prev", + "C0", + "C1", + "C2" +}; + +CShaderGenerator::CShaderGenerator() +{ +} + +CShaderGenerator::~CShaderGenerator() +{ +} + +bool CShaderGenerator::CreateVertexShader(const CMaterial& Mat) +{ + std::stringstream ShaderCode; + + ShaderCode << "#version 330 core\n" + << "\n"; + + // Input + ShaderCode << "// Input\n"; + EVertexDescription VtxDesc = Mat.GetVtxDesc(); + if (VtxDesc & ePosition) ShaderCode << "layout(location = 0) in vec3 RawPosition;\n"; + if (VtxDesc & eNormal) ShaderCode << "layout(location = 1) in vec3 RawNormal;\n"; + if (VtxDesc & eColor0) ShaderCode << "layout(location = 2) in vec4 RawColor0;\n"; + if (VtxDesc & eColor1) ShaderCode << "layout(location = 3) in vec4 RawColor1;\n"; + if (VtxDesc & eTex0) ShaderCode << "layout(location = 4) in vec2 RawTex0;\n"; + if (VtxDesc & eTex1) ShaderCode << "layout(location = 5) in vec2 RawTex1;\n"; + if (VtxDesc & eTex2) ShaderCode << "layout(location = 6) in vec2 RawTex2;\n"; + if (VtxDesc & eTex3) ShaderCode << "layout(location = 7) in vec2 RawTex3;\n"; + if (VtxDesc & eTex4) ShaderCode << "layout(location = 8) in vec2 RawTex4;\n"; + if (VtxDesc & eTex5) ShaderCode << "layout(location = 9) in vec2 RawTex5;\n"; + if (VtxDesc & eTex6) ShaderCode << "layout(location = 10) in vec2 RawTex6;\n"; + ShaderCode << "\n"; + + // Output + ShaderCode << "// Output\n"; + if (VtxDesc & eNormal) ShaderCode << "out vec3 Normal;\n"; + if (VtxDesc & eColor0) ShaderCode << "out vec4 Color0;\n"; + if (VtxDesc & eColor1) ShaderCode << "out vec4 Color1;\n"; + + for (u32 iPass = 0; iPass < Mat.mPasses.size(); iPass++) + if (Mat.mPasses[iPass].TexCoordSource != 0xFF) + ShaderCode << "out vec3 Tex" << iPass << ";\n"; + + ShaderCode << "out vec4 COLOR0A0;\n" + << "out vec4 COLOR1A1;\n"; + ShaderCode << "\n"; + + // Uniforms + ShaderCode << "// Uniforms\n" + << "layout(std140) uniform MVPBlock\n" + << "{\n" + << " mat4 ModelMtx;\n" + << " mat4 ViewMtx;\n" + << " mat4 ProjMtx;\n" + << "};\n" + << "\n" + << "layout(std140) uniform VertexBlock\n" + << "{\n" + << " mat4 TexMtx[10];\n" + << " mat4 PostMtx[20];\n" + << " vec4 COLOR0_Amb;\n" + << " vec4 COLOR0_Mat;\n" + << " vec4 COLOR1_Amb;\n" + << " vec4 COLOR1_Mat;\n" + << "};\n" + << "\n" + << "struct GXLight\n" + << "{\n" + << " vec4 Position;\n" + << " vec4 Direction;\n" + << " vec4 Color;\n" + << " vec4 DistAtten;\n" + << " vec4 AngleAtten;\n" + << "};\n" + << "layout(std140) uniform LightBlock {\n" + << " GXLight Lights[8];\n" + << "};\n" + << "uniform int NumLights;\n" + << "\n"; + + // Main + ShaderCode << "// Main\n" + << "void main()\n" + << "{\n" + << " mat4 MVP = ModelMtx * ViewMtx * ProjMtx;\n" + << " mat4 MV = ModelMtx * ViewMtx;\n"; + + if (VtxDesc & ePosition) ShaderCode << " gl_Position = vec4(RawPosition, 1) * MVP;\n"; + if (VtxDesc & eNormal) ShaderCode << " Normal = normalize(RawNormal.xyz * inverse(transpose(mat3(MV))));\n"; + if (VtxDesc & eColor0) ShaderCode << " Color1 = RawColor0;\n"; + if (VtxDesc & eColor1) ShaderCode << " Color2 = RawColor1;\n"; + + // Per-vertex lighting + ShaderCode << "\n" + << " // Dynamic Lighting\n"; + + // The 0x1 bit on the flag determines whether lighting is enabled for COLOR0 + if (Mat.mChanCtrlFlags & 0x1) + { + u8 DiffuseFunction = (Mat.mChanCtrlFlags >> 11) & 0x3; + + if (Mat.mChanCount > 0) + { + ShaderCode << " vec4 Illum = vec4(0.0);\n" + << " vec3 PositionMV = vec3(vec4(RawPosition, 1.0) * MV);\n" + << " \n" + << " for (int iLight = 0; iLight < NumLights; iLight++)\n" + << " {\n" + << " vec3 LightPosMV = vec3(Lights[iLight].Position * ViewMtx);\n" + << " vec3 LightDirMV = normalize(Lights[iLight].Direction.xyz * inverse(transpose(mat3(ViewMtx))));\n" + << " vec3 LightDist = LightPosMV.xyz - PositionMV.xyz;\n" + << " float DistSquared = dot(LightDist, LightDist);\n" + << " float Dist = sqrt(DistSquared);\n" + << " LightDist /= Dist;\n" + << " vec3 AngleAtten = Lights[iLight].AngleAtten.xyz;\n" + << " AngleAtten = vec3(AngleAtten.x, AngleAtten.y, AngleAtten.z);\n" + << " float Atten = max(0, dot(LightDist, LightDirMV.xyz));\n" + << " Atten = max(0, dot(AngleAtten, vec3(1.0, Atten, Atten * Atten))) / dot(Lights[iLight].DistAtten.xyz, vec3(1.0, Dist, DistSquared));\n"; + + if (DiffuseFunction == 2) ShaderCode << " float DiffuseAtten = max(0, dot(Normal, LightDist));\n"; + else if (DiffuseFunction == 1) ShaderCode << " float DiffuseAtten = dot(Normal, LightDist);\n"; + else ShaderCode << " float DiffuseAtten = 1.0;\n"; + + ShaderCode << " Illum += (Atten * DiffuseAtten * Lights[iLight].Color);\n" + << " }\n" + << " COLOR0A0 = COLOR0_Mat * (Illum + COLOR0_Amb);\n" + << " COLOR1A1 = COLOR1_Mat * (Illum + COLOR1_Amb);\n" + << " \n"; + } + else + { + ShaderCode << " COLOR0A0 = COLOR0_Mat;\n" + << " COLOR1A1 = COLOR1_Mat;\n"; + } + } + + else + { + ShaderCode << " COLOR0A0 = COLOR0_Mat;\n" + << " COLOR1A1 = COLOR1_Mat;\n" + << "\n"; + } + + // Texture coordinate generation + ShaderCode << " \n" + << " // TexGen\n"; + + for (u32 iCoord = 0; iCoord < Mat.mPasses.size(); iCoord++) + { + if (Mat.mPasses[iCoord].TexCoordSource == 0xFF) continue; + + s32 AnimType = Mat.mPasses[iCoord].AnimMode; + + // Texture Matrix + if (AnimType == -1) // No animation + ShaderCode << " Tex" << iCoord << " = vec3(" << gkCoordSrc[Mat.mPasses[iCoord].TexCoordSource] << ");\n"; + + else // Animation used - texture matrix at least, possibly normalization/post-transform + { + // Texture Matrix + ShaderCode << " Tex" << iCoord << " = vec3(vec4(" << gkCoordSrc[Mat.mPasses[iCoord].TexCoordSource] << ", 1.0) * TexMtx[" << iCoord << "]).xyz;\n"; + + if ((AnimType < 2) || (AnimType > 5)) + { + // Normalization + Post-Transform + ShaderCode << " Tex" << iCoord << " = normalize(Tex" << iCoord << ");\n"; + ShaderCode << " Tex" << iCoord << " = vec3(vec4(Tex" << iCoord << ", 1.0) * PostMtx[" << iCoord << "]).xyz;\n"; + } + } + + ShaderCode << "\n"; + } + ShaderCode << "}\n\n"; + + + // Done! + return mShader->CompileVertexSource(ShaderCode.str().c_str()); +} + +bool CShaderGenerator::CreatePixelShader(const CMaterial& Mat) +{ + std::stringstream ShaderCode; + ShaderCode << "#version 330 core\n" + << "\n" + << "#extension GL_ARB_shading_language_420pack : enable\n" // Needed to set texture binding layouts + << "\n"; + + EVertexDescription VtxDesc = Mat.GetVtxDesc(); + if (VtxDesc & ePosition) ShaderCode << "in vec3 Position;\n"; + if (VtxDesc & eNormal) ShaderCode << "in vec3 Normal;\n"; + if (VtxDesc & eColor0) ShaderCode << "in vec4 Color0;\n"; + if (VtxDesc & eColor1) ShaderCode << "in vec4 Color1;\n"; + + for (u32 iPass = 0; iPass < Mat.mPasses.size(); iPass++) + if (Mat.mPasses[iPass].TexCoordSource != 0xFF) + ShaderCode << "in vec3 Tex" << iPass << ";\n"; + + ShaderCode << "in vec4 COLOR0A0;\n" + << "in vec4 COLOR1A1;\n" + << "\n" + << "out vec4 PixelColor;\n" + << "\n" + << "layout(std140) uniform PixelBlock {\n" + << " vec4 KonstColors[4];\n" + << " vec4 TevColor;\n" + << " vec4 TintColor;\n" + << "};\n\n"; + + for (u32 iTex = 0; iTex < Mat.mPasses.size(); iTex++) + if (Mat.mPasses[iTex].pTexture != nullptr) + ShaderCode << "layout(binding = " << iTex << ") uniform sampler2D Texture" << iTex << ";\n"; + + ShaderCode <<"\n"; + + ShaderCode << "void main()\n" + << "{\n" + << " vec4 TevInA = vec4(0, 0, 0, 0), TevInB = vec4(0, 0, 0, 0), TevInC = vec4(0, 0, 0, 0), TevInD = vec4(0, 0, 0, 0);\n" + << " vec4 Prev = vec4(0, 0, 0, 0), C0 = TevColor, C1 = C0, C2 = C0;\n" + << " vec4 Ras = vec4(0, 0, 0, 1), Tex = vec4(0, 0, 0, 0);\n" + << " vec4 Konst = vec4(1, 1, 1, 1);\n"; + + ShaderCode << " vec2 TevCoord = vec2(0, 0);\n" + << " \n"; + + for (u32 iPass = 0; iPass < Mat.mPasses.size(); iPass++) + { + ShaderCode << " // TEV Stage " << iPass << "\n"; + const CMaterial::SPass *pPass = &Mat.mPasses[iPass]; + + if (pPass->Hidden) + { + ShaderCode << " // Pass is hidden\n\n"; + continue; + } + + if (pPass->TexCoordSource != 0xFF) + ShaderCode << " TevCoord = (Tex" << iPass << ".z == 0.0 ? Tex" << iPass << ".xy : Tex" << iPass << ".xy / Tex" << iPass << ".z);\n"; + + if (pPass->pTexture != nullptr) + ShaderCode << " Tex = texture(Texture" << iPass << ", TevCoord);\n"; + + ShaderCode << " Konst = vec4(" << gkKonstColor[pPass->KonstColorSel] << ", " << gkKonstAlpha[pPass->KonstAlphaSel] << ");\n"; + + if (pPass->RasSel != 0xFF) + { + if (pPass->RasSel == 0x0) ShaderCode << " Ras = vec4(COLOR0A0.xyz, 1.0);\n"; + else if (pPass->RasSel == 0x1) ShaderCode << " Ras = vec4(COLOR1A1.xyz, 1.0);\n"; + else if (pPass->RasSel == 0x2) ShaderCode << " Ras = vec4(0.0, 0.0, 0.0, COLOR0A0.w);\n"; + else if (pPass->RasSel == 0x3) ShaderCode << " Ras = vec4(0.0, 0.0, 0.0, COLOR1A1.w);\n"; + else if (pPass->RasSel == 0x4) ShaderCode << " Ras = COLOR0A0;\n"; + else if (pPass->RasSel == 0x5) ShaderCode << " Ras = COLOR1A1;\n"; + else if (pPass->RasSel == 0x6) ShaderCode << " Ras = vec4(0.0, 0.0, 0.0, 0.0);\n"; + } + + for (u8 iInput = 0; iInput < 4; iInput++) + { + u8 TevCharacter = iInput + 0x41; // the current stage number represented as an ASCII letter; eg 0 is 'A' + + ShaderCode << " TevIn" << TevCharacter << " = vec4(" + << gkTevColor[Mat.GetTevColorIn(iPass, iInput) & 0xF] + << ", " + << gkTevAlpha[Mat.GetTevAlphaIn(iPass, iInput) & 0x7] + << ");\n"; + } + + // Applying TRAN and BLOL (opacity and bloom maps) in Corruption require accessing specific color channels + // This feels hacky and might not be the best way to implement this + if (pPass->Type == "TRAN") + { + ShaderCode << " // TRAN Combine\n" + << " Prev.a = 1.0 - Tex.r;\n\n"; + } + /*else if (pPass->Type == "BLOL") + { + ShaderCode << " // BLOL Combine\n" + << " C0.rgb += vec3(Tex.g, Tex.g, Tex.g);\n\n"; + }*/ + + else + { + ShaderCode << " // RGB Combine\n" + << " " + << gkTevRigid[pPass->ColorOutputRegister] + << ".rgb = "; + + ShaderCode << "clamp(vec3(TevInD.rgb + ((1.0 - TevInC.rgb) * TevInA.rgb + TevInC.rgb * TevInB.rgb)), vec3(0, 0, 0), vec3(1.0, 1.0, 1.0));\n"; + + ShaderCode << " // Alpha Combine\n" + << " " + << gkTevRigid[pPass->AlphaOutputRegister] + << ".a = "; + + ShaderCode << "clamp(TevInD.a + ((1.0 - TevInC.a) * TevInA.a + TevInC.a * TevInB.a), 0.0, 1.0);\n\n"; + } + } + + if (Mat.GetOptions() & ePunchthrough) { + ShaderCode << " if (Prev.a <= 0.25) discard;\n" + << " else Prev.a = 1.0;\n"; + } + + ShaderCode << " PixelColor = Prev.rgba * TintColor;\n" + << "}\n\n"; + + // Done! + return mShader->CompilePixelSource(ShaderCode.str().c_str()); +} + +CShader* CShaderGenerator::GenerateShader(const CMaterial& Mat) +{ + CShaderGenerator Generator; + Generator.mShader = new CShader(); + + bool success = Generator.CreateVertexShader(Mat); + if (success) success = Generator.CreatePixelShader(Mat); + + Generator.mShader->LinkShaders(); + return Generator.mShader; +} diff --git a/OpenGL/CShaderGenerator.h b/OpenGL/CShaderGenerator.h new file mode 100644 index 00000000..ec521296 --- /dev/null +++ b/OpenGL/CShaderGenerator.h @@ -0,0 +1,22 @@ +#ifndef SHADERGEN_H +#define SHADERGEN_H + +#include + +#include "CShader.h" +#include + +class CShaderGenerator +{ + CShader *mShader; + + CShaderGenerator(); + ~CShaderGenerator(); + bool CreateVertexShader(const CMaterial& Mat); + bool CreatePixelShader(const CMaterial& Mat); + +public: + static CShader* GenerateShader(const CMaterial& Mat); +}; + +#endif // SHADERGEN_H diff --git a/OpenGL/CUniformBuffer.cpp b/OpenGL/CUniformBuffer.cpp new file mode 100644 index 00000000..93827856 --- /dev/null +++ b/OpenGL/CUniformBuffer.cpp @@ -0,0 +1,60 @@ +#include "CUniformBuffer.h" + +CUniformBuffer::CUniformBuffer() +{ + glGenBuffers(1, &mUniformBuffer); + SetBufferSize(0); +} + +CUniformBuffer::CUniformBuffer(u32 Size) +{ + glGenBuffers(1, &mUniformBuffer); + SetBufferSize(Size); +} + +CUniformBuffer::~CUniformBuffer() +{ + glDeleteBuffers(1, &mUniformBuffer); +} + +void CUniformBuffer::InitializeBuffer() +{ + Bind(); + glBufferData(GL_UNIFORM_BUFFER, mBufferSize, 0, GL_DYNAMIC_DRAW); + Unbind(); +} + +void CUniformBuffer::Bind() +{ + glBindBuffer(GL_UNIFORM_BUFFER, mUniformBuffer); +} + +void CUniformBuffer::Unbind() +{ + glBindBuffer(GL_UNIFORM_BUFFER, 0); +} + +void CUniformBuffer::BindBase(GLuint index) +{ + Bind(); + glBindBufferBase(GL_UNIFORM_BUFFER, index, mUniformBuffer); + Unbind(); +} + +void CUniformBuffer::Buffer(void *pData) +{ + Bind(); + glBufferSubData(GL_UNIFORM_BUFFER, 0, mBufferSize, pData); + Unbind(); +} + +void CUniformBuffer::SetBufferSize(u32 Size) +{ + mBufferSize = Size; + InitializeBuffer(); +} + +u32 CUniformBuffer::GetBufferSize() +{ + return mBufferSize; +} diff --git a/OpenGL/CUniformBuffer.h b/OpenGL/CUniformBuffer.h new file mode 100644 index 00000000..66757d77 --- /dev/null +++ b/OpenGL/CUniformBuffer.h @@ -0,0 +1,28 @@ +#ifndef CUNIFORMBUFFER_H +#define CUNIFORMBUFFER_H + +#include +#include + +class CUniformBuffer +{ + GLuint mUniformBuffer; + u32 mBufferSize; + +public: + CUniformBuffer(); + CUniformBuffer(u32 Size); + ~CUniformBuffer(); + void Bind(); + void Unbind(); + void BindBase(GLuint index); + void Buffer(void *pData); + + void SetBufferSize(u32 Size); + u32 GetBufferSize(); + +private: + void InitializeBuffer(); +}; + +#endif // CUNIFORMBUFFER_H diff --git a/OpenGL/CVertexArrayManager.cpp b/OpenGL/CVertexArrayManager.cpp new file mode 100644 index 00000000..2dffb345 --- /dev/null +++ b/OpenGL/CVertexArrayManager.cpp @@ -0,0 +1,105 @@ +#include "CVertexArrayManager.h" + +// ************ STATIC MEMBER INITIALIZATION ************ +std::vector CVertexArrayManager::sVAManagers; +CVertexArrayManager *CVertexArrayManager::spCurrentManager; + +// ************ CONSTRUCTORS/DESTRUCTORS ************ +CVertexArrayManager::CVertexArrayManager() +{ + mVectorIndex = sVAManagers.size(); + sVAManagers.push_back(this); +} + +CVertexArrayManager::~CVertexArrayManager() +{ + for (auto it = mVBOMap.begin(); it != mVBOMap.end(); it = mVBOMap.begin()) + DeleteVAO(it->first); + + for (auto it = mDynamicVBOMap.begin(); it != mDynamicVBOMap.end(); it = mDynamicVBOMap.begin()) + DeleteVAO(it->first); + + sVAManagers.erase(sVAManagers.begin() + mVectorIndex); + + if (sVAManagers.size() > mVectorIndex) + for (auto it = sVAManagers.begin() + mVectorIndex; it != sVAManagers.end(); it++) + (*it)->mVectorIndex--; +} + +// ************ PUBLIC ************ +void CVertexArrayManager::SetCurrent() +{ + spCurrentManager = this; +} + +void CVertexArrayManager::BindVAO(CVertexBuffer *pVBO) +{ + auto it = mVBOMap.find(pVBO); + + if (it != mVBOMap.end()) + glBindVertexArray(it->second); + + else + { + GLuint VAO = pVBO->CreateVAO(); + mVBOMap[pVBO] = VAO; + glBindVertexArray(VAO); + } +} + +void CVertexArrayManager::BindVAO(CDynamicVertexBuffer *pVBO) +{ + // Overload for CDynamicVertexBuffer + auto it = mDynamicVBOMap.find(pVBO); + + if (it != mDynamicVBOMap.end()) + glBindVertexArray(it->second); + + else + { + GLuint VAO = pVBO->CreateVAO(); + mDynamicVBOMap[pVBO] = VAO; + glBindVertexArray(VAO); + } +} + +void CVertexArrayManager::DeleteVAO(CVertexBuffer *pVBO) +{ + auto it = mVBOMap.find(pVBO); + + if (it != mVBOMap.end()) + { + glDeleteVertexArrays(1, &it->second); + mVBOMap.erase(it); + } +} + +void CVertexArrayManager::DeleteVAO(CDynamicVertexBuffer *pVBO) +{ + // Overload for CDynamicVertexBuffer + auto it = mDynamicVBOMap.find(pVBO); + + if (it != mDynamicVBOMap.end()) + { + glDeleteVertexArrays(1, &it->second); + mDynamicVBOMap.erase(it); + } +} + +// ************ STATIC ************ +CVertexArrayManager* CVertexArrayManager::Current() +{ + return spCurrentManager; +} + +void CVertexArrayManager::DeleteAllArraysForVBO(CVertexBuffer *pVBO) +{ + for (u32 iVAM = 0; iVAM < sVAManagers.size(); iVAM++) + sVAManagers[iVAM]->DeleteVAO(pVBO); +} + +void CVertexArrayManager::DeleteAllArraysForVBO(CDynamicVertexBuffer *pVBO) +{ + for (u32 iVAM = 0; iVAM < sVAManagers.size(); iVAM++) + sVAManagers[iVAM]->DeleteVAO(pVBO); +} diff --git a/OpenGL/CVertexArrayManager.h b/OpenGL/CVertexArrayManager.h new file mode 100644 index 00000000..0d732990 --- /dev/null +++ b/OpenGL/CVertexArrayManager.h @@ -0,0 +1,34 @@ +#ifndef CVERTEXARRAYMANAGER_H +#define CVERTEXARRAYMANAGER_H + +#include "CDynamicVertexBuffer.h" +#include "CVertexBuffer.h" + +#include +#include +#include + +class CVertexArrayManager +{ + std::unordered_map mVBOMap; + std::unordered_map mDynamicVBOMap; + u32 mVectorIndex; + + static std::vector sVAManagers; + static CVertexArrayManager *spCurrentManager; + +public: + CVertexArrayManager(); + ~CVertexArrayManager(); + void SetCurrent(); + void BindVAO(CVertexBuffer *pVBO); + void BindVAO(CDynamicVertexBuffer *pVBO); + void DeleteVAO(CVertexBuffer *pVBO); + void DeleteVAO(CDynamicVertexBuffer *pVBO); + + static CVertexArrayManager* Current(); + static void DeleteAllArraysForVBO(CVertexBuffer *pVBO); + static void DeleteAllArraysForVBO(CDynamicVertexBuffer *pVBO); +}; + +#endif // CVERTEXARRAYMANAGER_H diff --git a/OpenGL/CVertexBuffer.cpp b/OpenGL/CVertexBuffer.cpp new file mode 100644 index 00000000..cd0d6f44 --- /dev/null +++ b/OpenGL/CVertexBuffer.cpp @@ -0,0 +1,230 @@ +#include "CVertexBuffer.h" +#include "CVertexArrayManager.h" +#include + +CVertexBuffer::CVertexBuffer() +{ + mBuffered = false; + SetVertexDesc(ePosition | eNormal | eTex0 | eTex1 | eTex2 | eTex3 | eTex4 | eTex5 | eTex6 | eTex7); +} + +CVertexBuffer::CVertexBuffer(EVertexDescription Desc) +{ + mBuffered = false; + SetVertexDesc(Desc); +} + +CVertexBuffer::~CVertexBuffer() +{ + CVertexArrayManager::DeleteAllArraysForVBO(this); + + if (mBuffered) + glDeleteBuffers(12, mAttribBuffers); +} + +u16 CVertexBuffer::AddVertex(const CVertex& Vert) +{ + if (mPositions.size() == 0xFFFF) throw std::overflow_error("VBO contains too many vertices"); + + if (mVtxDesc & ePosition) mPositions.push_back(Vert.Position); + if (mVtxDesc & eNormal) mNormals.push_back(Vert.Normal); + if (mVtxDesc & eColor0) mColors[0].push_back(Vert.Color[0]); + if (mVtxDesc & eColor1) mColors[1].push_back(Vert.Color[1]); + + for (u32 iTex = 0; iTex < 8; iTex++) + if (mVtxDesc & (eTex0 << (iTex * 2))) mTexCoords[iTex].push_back(Vert.Tex[iTex]); + + for (u32 iMtx = 0; iMtx < 8; iMtx++) + if (mVtxDesc & (ePosMtx << iMtx)) mTexCoords[iMtx].push_back(Vert.MatrixIndices[iMtx]); + + return (mPositions.size() - 1); +} + +u16 CVertexBuffer::AddIfUnique(const CVertex& Vert, u16 Start) +{ + if (Start < mPositions.size()) + { + for (u16 iVert = Start; iVert < mPositions.size(); iVert++) + { + // I use a bool because "continue" doesn't work properly within the iTex loop + bool Unique = false; + + if (mVtxDesc & ePosition) + if (Vert.Position != mPositions[iVert]) Unique = true; + + if ((!Unique) && (mVtxDesc & eNormal)) + if (Vert.Normal != mNormals[iVert]) Unique = true; + + if ((!Unique) && (mVtxDesc & eColor0)) + if (Vert.Color[0] != mColors[0][iVert]) Unique = true; + + if ((!Unique) && (mVtxDesc & eColor1)) + if (Vert.Color[1] != mColors[1][iVert]) Unique = true; + + if (!Unique) + for (u32 iTex = 0; iTex < 8; iTex++) + if ((mVtxDesc & (eTex0 << (iTex * 2)))) + if (Vert.Tex[iTex] != mTexCoords[iTex][iVert]) + { + Unique = true; + break; + } + + if (!Unique) return iVert; + } + } + + return AddVertex(Vert); +} + +void CVertexBuffer::Reserve(u16 size) +{ + u32 ReserveSize = mPositions.size() + size; + + if (mVtxDesc & ePosition) + mPositions.reserve(ReserveSize); + + if (mVtxDesc & eNormal) + mNormals.reserve(ReserveSize); + + if (mVtxDesc & eColor0) + mColors[0].reserve(ReserveSize); + + if (mVtxDesc & eColor1) + mColors[1].reserve(ReserveSize); + + for (u32 iTex = 0; iTex < 8; iTex++) + if (mVtxDesc & (eTex0 << (iTex * 2))) + mTexCoords[iTex].reserve(ReserveSize); +} + +void CVertexBuffer::Clear() +{ + if (mBuffered) + glDeleteBuffers(12, mAttribBuffers); + + mBuffered = false; + mPositions.clear(); + mNormals.clear(); + mColors[0].clear(); + mColors[1].clear(); + + for (u32 iTex = 0; iTex < 8; iTex++) + mTexCoords[iTex].clear(); +} + +void CVertexBuffer::Buffer() +{ + // Make sure we don't end up with two buffers for the same data... + if (mBuffered) + { + glDeleteBuffers(12, mAttribBuffers); + mBuffered = false; + } + + // Generate buffers + glGenBuffers(12, mAttribBuffers); + + for (u32 iAttrib = 0; iAttrib < 12; iAttrib++) + { + int Attrib = (ePosition << (iAttrib * 2)); + bool HasAttrib = ((mVtxDesc & Attrib) != 0); + if (!HasAttrib) continue; + + if (iAttrib < 2) + { + std::vector *pBuffer = (iAttrib == 0) ? &mPositions : &mNormals; + + glBindBuffer(GL_ARRAY_BUFFER, mAttribBuffers[iAttrib]); + glBufferData(GL_ARRAY_BUFFER, pBuffer->size() * sizeof(CVector3f), pBuffer->data(), GL_STATIC_DRAW); + } + + else if (iAttrib < 4) + { + u8 idx = (u8) (iAttrib - 2); + + glBindBuffer(GL_ARRAY_BUFFER, mAttribBuffers[iAttrib]); + glBufferData(GL_ARRAY_BUFFER, mColors[idx].size() * sizeof(CColor), mColors[idx].data(), GL_STATIC_DRAW); + } + + else + { + u8 idx = (u8) (iAttrib - 4); + + glBindBuffer(GL_ARRAY_BUFFER, mAttribBuffers[iAttrib]); + glBufferData(GL_ARRAY_BUFFER, mTexCoords[idx].size() * sizeof(CVector2f), mTexCoords[idx].data(), GL_STATIC_DRAW); + } + } + + mBuffered = true; +} + +void CVertexBuffer::Bind() +{ + if (!mBuffered) Buffer(); + CVertexArrayManager::Current()->BindVAO(this); +} + +void CVertexBuffer::Unbind() +{ + glBindVertexArray(0); +} + +bool CVertexBuffer::IsBuffered() +{ + return mBuffered; +} + +EVertexDescription CVertexBuffer::VertexDesc() +{ + return mVtxDesc; +} + +void CVertexBuffer::SetVertexDesc(EVertexDescription Desc) +{ + Clear(); + mVtxDesc = Desc; +} + +u32 CVertexBuffer::Size() +{ + return mPositions.size(); +} + +GLuint CVertexBuffer::CreateVAO() +{ + GLuint VertexArray; + glGenVertexArrays(1, &VertexArray); + glBindVertexArray(VertexArray); + + for (u32 iAttrib = 0; iAttrib < 12; iAttrib++) + { + int Attrib = (ePosition << (iAttrib * 2)); + bool HasAttrib = ((mVtxDesc & Attrib) != 0); + if (!HasAttrib) continue; + + if (iAttrib < 2) + { + glBindBuffer(GL_ARRAY_BUFFER, mAttribBuffers[iAttrib]); + glVertexAttribPointer(iAttrib, 3, GL_FLOAT, GL_FALSE, sizeof(CVector3f), (void*) 0); + glEnableVertexAttribArray(iAttrib); + } + + else if (iAttrib < 4) + { + glBindBuffer(GL_ARRAY_BUFFER, mAttribBuffers[iAttrib]); + glVertexAttribPointer(iAttrib, 1, GL_UNSIGNED_INT, GL_FALSE, sizeof(CColor), (void*) 0); + glEnableVertexAttribArray(iAttrib); + } + + else + { + glBindBuffer(GL_ARRAY_BUFFER, mAttribBuffers[iAttrib]); + glVertexAttribPointer(iAttrib, 2, GL_FLOAT, GL_FALSE, sizeof(CVector2f), (void*) 0); + glEnableVertexAttribArray(iAttrib); + } + } + + glBindVertexArray(0); + return VertexArray; +} diff --git a/OpenGL/CVertexBuffer.h b/OpenGL/CVertexBuffer.h new file mode 100644 index 00000000..31efa47e --- /dev/null +++ b/OpenGL/CVertexBuffer.h @@ -0,0 +1,37 @@ +#ifndef CVERTEXBUFFER_H +#define CVERTEXBUFFER_H + +#include +#include +#include +#include + +class CVertexBuffer +{ + EVertexDescription mVtxDesc; // Flags that indicate what vertex attributes are enabled on this vertex buffer + GLuint mAttribBuffers[12]; // Separate GL buffer for each attribute to allow not tracking unused attribs. No support for matrix indices currently. + std::vector mPositions; // Vector of vertex positions + std::vector mNormals; // Vector of vertex normals + std::vector mColors[2]; // Vectors of vertex colors + std::vector mTexCoords[8]; // Vectors of texture coordinates + bool mBuffered; // Bool value that indicates whether the attributes have been buffered. + +public: + CVertexBuffer(); + CVertexBuffer(EVertexDescription Desc); + ~CVertexBuffer(); + u16 AddVertex(const CVertex& vtx); + u16 AddIfUnique(const CVertex& vtx, u16 start); + void Reserve(u16 size); + void Clear(); + void Buffer(); + void Bind(); + void Unbind(); + bool IsBuffered(); + EVertexDescription VertexDesc(); + void SetVertexDesc(EVertexDescription Desc); + u32 Size(); + GLuint CreateVAO(); +}; + +#endif // CVERTEXBUFFER_H diff --git a/OpenGL/GLCommon.cpp b/OpenGL/GLCommon.cpp new file mode 100644 index 00000000..74135f28 --- /dev/null +++ b/OpenGL/GLCommon.cpp @@ -0,0 +1,37 @@ +#include "GLCommon.h" +#include + +GLenum glBlendFactor[] = { + GL_ZERO, // GX_BL_ZERO + GL_ONE, // GX_BL_ONE + GL_SRC_COLOR, // GX_BL_SRCCLR / GX_BL_DSTCLR + GL_ONE_MINUS_SRC_COLOR, // GX_BL_INVSRCCLR / GX_BL_INVDSTCLR + GL_SRC_ALPHA, // GX_BL_SRCALPHA + GL_ONE_MINUS_SRC_ALPHA, // GX_BL_INVSRCALPHA + GL_DST_ALPHA, // GX_BL_DSTALPHA + GL_ONE_MINUS_DST_ALPHA // GX_BL_INVDSTALPHA +}; + + +GLenum glZMode[] = { + GL_NEVER, // GX_NEVER + GL_LESS, // GX_LESS + GL_EQUAL, // GX_EQUAL + GL_LEQUAL, // GX_LEQUAL + GL_GREATER, // GX_GREATER + GL_NOTEQUAL, // GX_NEQUAL + GL_ALWAYS // GX_ALWAYS +}; + +GLenum GXPrimToGLPrim(EGXPrimitiveType t) { + switch (t) { + case eGX_Quads: return GL_TRIANGLE_STRIP; // Quads are converted to strips + case eGX_Triangles: return GL_TRIANGLE_STRIP; // Triangles are converted to strips + case eGX_TriangleStrip: return GL_TRIANGLE_STRIP; + case eGX_TriangleFan: return GL_TRIANGLE_STRIP; // Fans are converted to strips + case eGX_Lines: return GL_LINES; + case eGX_LineStrip: return GL_LINE_STRIP; + case eGX_Points: return GL_POINTS; + default: throw std::invalid_argument("Invalid GX primitive type"); + } +} diff --git a/OpenGL/GLCommon.h b/OpenGL/GLCommon.h new file mode 100644 index 00000000..27f2717a --- /dev/null +++ b/OpenGL/GLCommon.h @@ -0,0 +1,34 @@ +#ifndef GLCOMMON_H +#define GLCOMMON_H + +#include +#include + +enum EBlendFactor +{ + eBlendZero = GL_ZERO, + eBlendOne = GL_ONE, + eBlendSrcColor = GL_SRC_COLOR, + eBlendInvSrcColor = GL_ONE_MINUS_SRC_COLOR, + eBlendSrcAlpha = GL_SRC_ALPHA, + eBlendInvSrcAlpha = GL_ONE_MINUS_SRC_ALPHA, + eBlendDstAlpha = GL_DST_ALPHA, + eBlendInvDstAlpha = GL_ONE_MINUS_DST_ALPHA +}; + +enum EGXPrimitiveType +{ + eGX_Quads = 0x80, + eGX_Triangles = 0x90, + eGX_TriangleStrip = 0x98, + eGX_TriangleFan = 0xA0, + eGX_Lines = 0xA8, + eGX_LineStrip = 0xB0, + eGX_Points = 0xB8 +}; + +extern GLenum glBlendFactor[]; +extern GLenum glZMode[]; +GLenum GXPrimToGLPrim(EGXPrimitiveType t); + +#endif // GLCOMMON_H diff --git a/OpenGL/SMeshPointer.h b/OpenGL/SMeshPointer.h new file mode 100644 index 00000000..842f98a0 --- /dev/null +++ b/OpenGL/SMeshPointer.h @@ -0,0 +1,18 @@ +#ifndef SMESHPOINTER_H +#define SMESHPOINTER_H + +#include +#include +#include +#include +#include + +struct SMeshPointer +{ + CSceneNode *pNode; + u32 Asset; + CAABox AABox; + ERenderCommand Command; +}; + +#endif // SMESHPOINTER_H diff --git a/PrimeWorldEditor.pro b/PrimeWorldEditor.pro new file mode 100644 index 00000000..e12c4bd0 --- /dev/null +++ b/PrimeWorldEditor.pro @@ -0,0 +1,339 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2014-08-09T16:15:10 +# +#------------------------------------------------- + +QT += core gui opengl + +greaterThan(QT_MAJOR_VERSION, 4): QT += widgets + +CONFIG += console + +TARGET = PrimeWorldEditor +TEMPLATE = app + +SOURCES += \ + Common/AnimUtil.cpp \ + Common/CAABox.cpp \ + Common/CColor.cpp \ + Common/CFourCC.cpp \ + Common/CMatrix4f.cpp \ + Common/CompressionUtil.cpp \ + Common/CQuaternion.cpp \ + Common/CTimer.cpp \ + Common/CTransform4f.cpp \ + Common/CVector2f.cpp \ + Common/CVector3f.cpp \ + Common/CVector4f.cpp \ + Common/StringUtil.cpp \ + Core/main.cpp \ + Core/CSceneManager.cpp \ + Core/CRenderer.cpp \ + Core/CResCache.cpp \ + Core/CCamera.cpp \ + OpenGL/CIndexBuffer.cpp \ + OpenGL/CShaderGenerator.cpp \ + OpenGL/CVertexBuffer.cpp \ + OpenGL/GLCommon.cpp \ + Resource/CCollisionMesh.cpp \ + Resource/CGameArea.cpp \ + Resource/CMaterial.cpp \ + Resource/CMaterialSet.cpp \ + Resource/CPakFile.cpp \ + Resource/CTexture.cpp \ + Resource/CWorld.cpp \ + Resource/factory/CAreaLoader.cpp \ + Resource/factory/CBlockMgr.cpp \ + Resource/factory/CCollisionLoader.cpp \ + Resource/factory/CMaterialLoader.cpp \ + Resource/factory/CModelLoader.cpp \ + Resource/factory/CTextureDecoder.cpp \ + Resource/model/CBasicModel.cpp \ + Resource/model/CModel.cpp \ + Resource/model/CStaticModel.cpp \ + Resource/script/CScriptObject.cpp \ + Scene/CCollisionNode.cpp \ + Scene/CLightNode.cpp \ + Scene/CModelNode.cpp \ + Scene/CSceneNode.cpp \ + Scene/CStaticNode.cpp \ + UI/CWorldEditorWindow.cpp \ + UI/CStartWindow.cpp \ + Resource/script/CScriptTemplate.cpp \ + Resource/script/CScriptLayer.cpp \ + Resource/CAnimSet.cpp \ + Resource/factory/CAnimSetLoader.cpp \ + Resource/factory/CScriptLoader.cpp \ + Scene/CScriptNode.cpp \ + Resource/CLight.cpp \ + OpenGL/CShader.cpp \ + OpenGL/CUniformBuffer.cpp \ + Resource/factory/CWorldLoader.cpp \ + Resource/CStringTable.cpp \ + Resource/factory/CStringLoader.cpp \ + UI/CEditorGLWidget.cpp \ + Core/CGraphics.cpp \ + Resource/CFont.cpp \ + Resource/factory/CFontLoader.cpp \ + OpenGL/CDynamicVertexBuffer.cpp \ + UI/WColorPicker.cpp \ + UI/WDraggableSpinBox.cpp \ + Resource/cooker/CModelCooker.cpp \ + Resource/cooker/CMaterialCooker.cpp \ + Common/CUniqueID.cpp \ + Resource/cooker/CSectionMgrOut.cpp \ + Common/CHashFNV1A.cpp \ + UI/CModelEditorWindow.cpp \ + Resource/CResource.cpp \ + Core/CToken.cpp \ + Core/CRenderBucket.cpp \ + Core/CDrawUtil.cpp \ + UI/WTextureGLWidget.cpp \ + UI/TestDialog.cpp \ + UI/WRollout.cpp \ + UI/CMaterialEditor.cpp \ + UI/WResourceSelector.cpp \ + UI/IPreviewPanel.cpp \ + UI/WTexturePreviewPanel.cpp \ + UI/UICommon.cpp \ + OpenGL/CVertexArrayManager.cpp \ + UI/WCollapsibleGroupBox.cpp \ + UI/CSimpleDelegate.cpp \ + UI/CDarkStyle.cpp \ + Resource/cooker/CTextureEncoder.cpp \ + Resource/CMaterialPass.cpp \ + OpenGL/CFramebuffer.cpp \ + OpenGL/CRenderbuffer.cpp \ + OpenGL/CGL.cpp \ + UI/CWorldEditor.cpp \ + UI/WorldEditor/WCreateTab.cpp \ + UI/WorldEditor/WModifyTab.cpp \ + UI/WorldEditor/WInstancesTab.cpp \ + Common/CRay.cpp \ + Resource/model/SSurface.cpp \ + Common/CRayCollisionTester.cpp \ + Common/Math.cpp \ + Scene/CBoundingBoxNode.cpp \ + Core/Log.cpp \ + Common/CVector2i.cpp \ + UI/CNodeSelection.cpp \ + UI/WPropertyEditor.cpp \ + UI/WVectorEditor.cpp \ + Resource/script/CMasterTemplate.cpp \ + Resource/factory/CTemplateLoader.cpp \ + Core/CAreaAttributes.cpp \ + UI/WorldEditor/CLinkModel.cpp \ + UI/WorldEditor/CLayersInstanceModel.cpp \ + UI/WorldEditor/CTypesInstanceModel.cpp \ + UI/WorldEditor/CLayerEditor.cpp \ + UI/WorldEditor/CLayerModel.cpp \ + Resource/CScan.cpp \ + Resource/factory/CScanLoader.cpp \ + UI/WStringPreviewPanel.cpp \ + UI/WScanPreviewPanel.cpp + +HEADERS += \ + Common/AnimUtil.h \ + Common/CAABox.h \ + Common/CColor.h \ + Common/CFourCC.h \ + Common/CMatrix4f.h \ + Common/CompressionUtil.h \ + Common/CQuaternion.h \ + Common/CTimer.h \ + Common/CTransform4f.h \ + Common/CVector2f.h \ + Common/CVector3f.h \ + Common/CVector4f.h \ + Common/StringUtil.h \ + Common/types.h \ + Core/CCamera.h \ + Core/CRenderer.h \ + Core/CResCache.h \ + Core/CSceneManager.h \ + OpenGL/CIndexBuffer.h \ + OpenGL/CShaderGenerator.h \ + OpenGL/CVertexBuffer.h \ + OpenGL/GLCommon.h \ + OpenGL/SMeshPointer.h \ + UI/CWorldEditorWindow.h \ + UI/PWEMaterialEditor.h \ + UI/CStartWindow.h \ + Resource/CCollisionMesh.h \ + Resource/CGameArea.h \ + Resource/CPakFile.h \ + Resource/CMaterial.h \ + Resource/CMaterialSet.h \ + Resource/CResource.h \ + Resource/CTexture.h \ + Resource/CWorld.h \ + Resource/EFormatVersion.h \ + Resource/factory/CAreaLoader.h \ + Resource/factory/CBlockMgrIn.h \ + Resource/factory/CCollisionLoader.h \ + Resource/factory/CMaterialLoader.h \ + Resource/factory/CModelLoader.h \ + Resource/factory/CTextureDecoder.h \ + Resource/model/CBasicModel.h \ + Resource/model/CModel.h \ + Resource/model/CStaticModel.h \ + Resource/model/CVertex.h \ + Resource/model/SModelData.h \ + Resource/script/CProperty.h \ + Resource/script/CScriptLayer.h \ + Resource/script/CScriptObject.h \ + Resource/script/CScriptTemplate.h \ + Resource/script/EObjectType.h \ + Resource/script/SConnection.h \ + Resource/SNamedResource.h \ + Resource/SResInfo.h \ + Scene/CCollisionNode.h \ + Scene/CLightNode.h \ + Scene/CModelNode.h \ + Scene/CSceneNode.h \ + Scene/CStaticNode.h \ + Resource/script/EAttribType.h \ + Resource/CAnimSet.h \ + Resource/factory/CAnimSetLoader.h \ + Resource/factory/CScriptLoader.h \ + Resource/script/EPropertyType.h \ + Scene/CScriptNode.h \ + Resource/CLight.h \ + OpenGL/CShader.h \ + OpenGL/CUniformBuffer.h \ + Resource/factory/CWorldLoader.h \ + Resource/SDependency.h \ + Resource/CStringTable.h \ + Resource/factory/CStringLoader.h \ + UI/CEditorGLWidget.h \ + Core/CGraphics.h \ + Resource/CFont.h \ + Resource/factory/CFontLoader.h \ + OpenGL/CDynamicVertexBuffer.h \ + UI/WColorPicker.h \ + UI/WDraggableSpinBox.h \ + Resource/cooker/CModelCooker.h \ + Resource/cooker/CMaterialCooker.h \ + Common/CUniqueID.h \ + Resource/model/SSurface.h \ + Resource/model/EVertexDescription.h \ + Common/EnumUtil.h \ + Resource/cooker/CSectionMgrOut.h \ + Common/CHashFNV1A.h \ + Core/ERenderOptions.h \ + UI/CModelEditorWindow.h \ + Core/CToken.h \ + Core/CRenderBucket.h \ + Common/EMouseInputs.h \ + Common/EKeyInputs.h \ + Core/CDrawUtil.h \ + UI/WTextureGLWidget.h \ + UI/TestDialog.h \ + Resource/ETexelFormat.h \ + UI/WRollout.h \ + UI/CMaterialEditor.h \ + UI/WResourceSelector.h \ + Resource/EResType.h \ + UI/IPreviewPanel.h \ + UI/WTexturePreviewPanel.h \ + UI/UICommon.h \ + OpenGL/CVertexArrayManager.h \ + UI/WCollapsibleGroupBox.h \ + UI/CSimpleDelegate.h \ + UI/CDarkStyle.h \ + Resource/cooker/CTextureEncoder.h \ + Resource/CMaterialPass.h \ + Resource/ETevEnums.h \ + OpenGL/CFramebuffer.h \ + OpenGL/CRenderbuffer.h \ + OpenGL/CGL.h \ + UI/CWorldEditor.h \ + UI/WorldEditor/WCreateTab.h \ + UI/WorldEditor/WModifyTab.h \ + Common/CRay.h \ + Common/SRayIntersection.h \ + Common/CRayCollisionTester.h \ + Scene/ENodeType.h \ + Common/Math.h \ + Scene/CBoundingBoxNode.h \ + Core/Log.h \ + Scene/CRootNode.h \ + Common/CVector2i.h \ + Core/ERenderCommand.h \ + UI/CNodeSelection.h \ + UI/WPropertyEditor.h \ + UI/WVectorEditor.h \ + Resource/script/CMasterTemplate.h \ + Resource/script/CTemplateCategory.h \ + Resource/factory/CTemplateLoader.h \ + Core/CAreaAttributes.h \ + UI/WorldEditor/CLinkModel.h \ + UI/WorldEditor/WInstancesTab.h \ + UI/WorldEditor/CLayersInstanceModel.h \ + UI/WorldEditor/CTypesInstanceModel.h \ + UI/WorldEditor/CLayerEditor.h \ + UI/WorldEditor/CLayerModel.h \ + Resource/CScan.h \ + Resource/factory/CScanLoader.h \ + UI/WStringPreviewPanel.h \ + UI/WScanPreviewPanel.h + +FORMS += \ + UI/CWorldEditorWindow.ui \ + UI/CStartWindow.ui \ + UI/CModelEditorWindow.ui \ + UI/TestDialog.ui \ + UI/CMaterialEditor.ui \ + UI/WTexturePreviewPanel.ui \ + UI/CWorldEditor.ui \ + UI/WorldEditor/WCreateTab.ui \ + UI/WorldEditor/WModifyTab.ui \ + UI/WorldEditor/WInstancesTab.ui \ + UI/WorldEditor/CLayerEditor.ui \ + UI/WScanPreviewPanel.ui + +INCLUDEPATH += E:\C++\Libraries\glm\glm .\ + +LIBS += -lOpenGL32 + +LIBS += -LE:/C++/Libraries/zlib/lib -lzdll +INCLUDEPATH += E:/C++/Libraries/zlib/include + +LIBS += -LE:/C++/Libraries/lzo-2.08/lib -llzo-2.08 +INCLUDEPATH += E:/C++/Libraries/lzo-2.08/include + +unix|win32: LIBS += -LE:/C++/Libraries/glew-1.9.0/lib/ -lglew32s +INCLUDEPATH += E:/C++/Libraries/glew-1.9.0/include +DEPENDPATH += E:/C++/Libraries/glew-1.9.0/include +PRE_TARGETDEPS += E:/C++/Libraries/glew-1.9.0/lib/glew32s.lib +DEFINES += GLEW_STATIC + +INCLUDEPATH += E:/C++/Libraries/tinyxml2/include +win32:CONFIG(release, debug|release): LIBS += -LE:/C++/Libraries/tinyxml2/lib/ -ltinyxml2 +else:win32:CONFIG(debug, debug|release): LIBS += -LE:/C++/Libraries/tinyxml2/lib/ -ltinyxml2d +win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += E:/C++/Libraries/tinyxml2/lib/libtinyxml2.a +else:win32-g++:CONFIG(debug, debug|release): PRE_TARGETDEPS += E:/C++/Libraries/tinyxml2/lib/libtinyxml2d.a +else:win32:!win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += E:/C++/Libraries/tinyxml2/lib/tinyxml2.lib +else:win32:!win32-g++:CONFIG(debug, debug|release): PRE_TARGETDEPS += E:/C++/Libraries/tinyxml2/lib/tinyxml2d.lib + +win32:CONFIG(release, debug|release): LIBS += -LE:/C++/Libraries/FileIO/lib/ -lFileIO +else:win32:CONFIG(debug, debug|release): LIBS += -LE:/C++/Libraries/FileIO/lib/ -lFileIOd + +INCLUDEPATH += E:/C++/Libraries/FileIO/include +DEPENDPATH += E:/C++/Libraries/FileIO/include +CONFIG(release, debug|release): PRE_TARGETDEPS += E:/C++/Libraries/FileIO/lib/FileIO.lib +CONFIG(debug, debug|release): PRE_TARGETDEPS += E:/C++/Libraries/FileIO/lib/FileIOd.lib + +DISTFILES += \ + ../../../../../PWEassets/Icons/Free Camera.png \ + ../../../../../PWEassets/Icons/Material Highlight.png \ + ../../../../../PWEassets/Icons/Minus v2.png \ + ../../../../../PWEassets/Icons/Orbit Camera v2.png \ + ../../../../../PWEassets/Icons/Orbit Camera.png \ + ../../../../../PWEassets/Icons/Samus Silhouette Gradient.png \ + ../../../../../PWEassets/Icons/Square Preview.png + +RESOURCES += \ + Icons.qrc + diff --git a/Resource/CAnimSet.cpp b/Resource/CAnimSet.cpp new file mode 100644 index 00000000..959c141f --- /dev/null +++ b/Resource/CAnimSet.cpp @@ -0,0 +1,36 @@ +#include "CAnimSet.h" +#include + +CAnimSet::CAnimSet() : CResource() +{ +} + +CAnimSet::~CAnimSet() +{ +} + +EResType CAnimSet::Type() +{ + return eCharacter; +} + +u32 CAnimSet::getNodeCount() +{ + return nodes.size(); +} + +std::string CAnimSet::getNodeName(u32 node) +{ + if (node >= nodes.size()) + return nodes[0].name; + else + return nodes[node].name; +} + +CModel* CAnimSet::getNodeModel(u32 node) +{ + if (node >= nodes.size()) + return nodes[0].model; + else + return nodes[node].model; +} diff --git a/Resource/CAnimSet.h b/Resource/CAnimSet.h new file mode 100644 index 00000000..5244ccb4 --- /dev/null +++ b/Resource/CAnimSet.h @@ -0,0 +1,37 @@ +#ifndef CANIMSET_H +#define CANIMSET_H + +#include +#include +#include +#include "model/CModel.h" +#include "CResource.h" + +// will expand later! this is where animation support will come in +class CAnimSet : public CResource +{ + friend class CAnimSetLoader; + + struct SNode + { + std::string name; + CModel *model; + u32 skinID; + u32 skelID; + CToken ModelToken; + + SNode() { model = nullptr; } + }; + std::vector nodes; + +public: + CAnimSet(); + ~CAnimSet(); + EResType Type(); + + u32 getNodeCount(); + std::string getNodeName(u32 node); + CModel* getNodeModel(u32 node); +}; + +#endif // CCHARACTERSET_H diff --git a/Resource/CCollisionMesh.cpp b/Resource/CCollisionMesh.cpp new file mode 100644 index 00000000..da631b03 --- /dev/null +++ b/Resource/CCollisionMesh.cpp @@ -0,0 +1,119 @@ +#include "CCollisionMesh.h" +#include + +CCollisionMesh::CCollisionMesh() : CResource() +{ + mVBO.SetVertexDesc(ePosition); + mVertexCount = 0; + mLineCount = 0; + mFaceCount = 0; + mBuffered = false; + mIBO.SetPrimitiveType(GL_TRIANGLES); +} + +CCollisionMesh::~CCollisionMesh() +{ + if (mBuffered) + { + mIBO.Clear(); + mVBO.Clear(); + mBuffered = false; + } +} + +EResType CCollisionMesh::Type() +{ + return eCollisionMesh; +} + +void CCollisionMesh::BufferGL() +{ + if (mBuffered) + { + mIBO.Clear(); + mVBO.Clear(); + mBuffered = false; + } + + // Add all the verts to our VBO, first... + mVBO.Reserve(mCollisionVertices.size()); + for (u16 v = 0; v < mCollisionVertices.size(); v++) + mVBO.AddVertex(CVertex(mCollisionVertices[v].Pos)); + + // Then add all the relevant indices to the IBO + mIBO.Reserve(mCollisionFaces.size() * 3); + for (u32 v = 0; v < mCollisionFaces.size(); v++) + { + u16 Verts[3]; + + CCollisionFace *Face = &mCollisionFaces[v]; + CCollisionLine *LineA = GetLine(Face->Lines[0]); + CCollisionLine *LineB = GetLine(Face->Lines[1]); + Verts[0] = LineA->Vertices[0]; + Verts[1] = LineA->Vertices[1]; + + // We have two vertex indices; the last one is one of the ones on line B, but we're not sure which one + if ((LineB->Vertices[0] != Verts[0]) && + (LineB->Vertices[0] != Verts[1])) + Verts[2] = LineB->Vertices[0]; + else + Verts[2] = LineB->Vertices[1]; + + // Some faces have a property that indicates they need to be inverted + if (!Face->Properties.Invert) + mIBO.AddIndices(&Verts[0], 3); + else { + mIBO.AddIndex(Verts[2]); + mIBO.AddIndex(Verts[1]); + mIBO.AddIndex(Verts[0]); + } + } + + // Buffer, and done + mVBO.Buffer(); + mIBO.Buffer(); + mBuffered = true; +} + +void CCollisionMesh::Draw() +{ + if (!mBuffered) BufferGL(); + + mVBO.Bind(); + mIBO.Bind(); + + glDrawElements(GL_TRIANGLES, mIBO.GetSize(), GL_UNSIGNED_SHORT, (void*) 0); + gDrawCount++; + mIBO.Unbind(); + mVBO.Unbind(); +} + +void CCollisionMesh::DrawLines() +{ + if (!mBuffered) BufferGL(); + + mVBO.Bind(); + mIBO.Bind(); + for (u32 f = 0; f < mFaceCount; f++) + { + glDrawElements(GL_LINE_LOOP, 3, GL_UNSIGNED_SHORT, (void*) (f * 6)); + gDrawCount++; + } + mIBO.Unbind(); + mVBO.Unbind(); +} + +CCollisionMesh::CCollisionVertex* CCollisionMesh::GetVertex(u16 index) +{ + return &mCollisionVertices[index]; +} + +CCollisionMesh::CCollisionLine* CCollisionMesh::GetLine(u16 index) +{ + return &mCollisionLines[index]; +} + +CCollisionMesh::CCollisionFace* CCollisionMesh::GetFace(u16 index) +{ + return &mCollisionFaces[index]; +} diff --git a/Resource/CCollisionMesh.h b/Resource/CCollisionMesh.h new file mode 100644 index 00000000..e3d7c797 --- /dev/null +++ b/Resource/CCollisionMesh.h @@ -0,0 +1,89 @@ +#ifndef CCOLLISIONMESH_H +#define CCOLLISIONMESH_H + +#include +#include +#include +#include "CResource.h" + +class CCollisionMesh : public CResource +{ + friend class CCollisionLoader; + + class CCollisionOctree + { + friend class CCollisionLoader; + struct SOctreeNode {}; + + struct SLeaf : public SOctreeNode + { + CAABox AABox; + std::vector FaceIndices; + }; + + struct SBranch : public SOctreeNode + { + u16 Flags; + SOctreeNode *pChildren[8]; + }; + + SOctreeNode* Root; + }; + + struct SCollisionProperties + { + // todo: figure out what the other properties are + bool Invert; + }; + + class CCollisionVertex + { + public: + SCollisionProperties Properties; + CVector3f Pos; + }; + + class CCollisionLine + { + public: + SCollisionProperties Properties; + u16 Vertices[2]; + }; + + class CCollisionFace + { + public: + SCollisionProperties Properties; + u16 Lines[3]; + }; + + CVertexBuffer mVBO; + CIndexBuffer mIBO; + u32 mVertexCount; + u32 mLineCount; + u32 mFaceCount; + bool mBuffered; + + CAABox mAABox; + CCollisionOctree *mOctree; + std::vector mFlags; + std::vector mCollisionVertices; + std::vector mCollisionLines; + std::vector mCollisionFaces; + bool mOctreeLoaded; + + CCollisionVertex *GetVertex(u16 index); + CCollisionLine *GetLine(u16 index); + CCollisionFace *GetFace(u16 index); + +public: + CCollisionMesh(); + ~CCollisionMesh(); + EResType Type(); + + void BufferGL(); + void Draw(); + void DrawLines(); +}; + +#endif // CCOLLISIONMESH_H diff --git a/Resource/CFont.cpp b/Resource/CFont.cpp new file mode 100644 index 00000000..2d4f2802 --- /dev/null +++ b/Resource/CFont.cpp @@ -0,0 +1,182 @@ +#include "CFont.h" +#include +#include +#include +#include + +CDynamicVertexBuffer CFont::smGlyphVertices; + CIndexBuffer CFont::smGlyphIndices; + bool CFont::smBuffersInitialized = false; + +CFont::CFont() : CResource() +{ +} + +CFont::~CFont() +{ +} + +EResType CFont::Type() +{ + return eFont; +} + +inline float PtsToFloat(s32 pt) +{ + // This is a bit of an arbitrary number but it works + // 1 / (1280 / 1.333333f / 2) + return 0.00208333f * pt; +} + +CVector2f CFont::RenderString(std::string String, CRenderer *pRenderer, float, + CVector2f, CColor FillColor, CColor StrokeColor, u32 FontSize) +{ + // Not using parameter 3 (float - AspectRatio) + // Not using parameter 4 (CVector2f - Position) + // WIP + if (!smBuffersInitialized) InitBuffers(); + + // Shader setup + CShader *pTextShader = CDrawUtil::GetTextShader(); + pTextShader->SetCurrent(); + + GLuint ModelMtxLoc = pTextShader->GetUniformLocation("ModelMtx"); + GLuint ColorLoc = pTextShader->GetUniformLocation("FontColor"); + GLuint LayerLoc = pTextShader->GetUniformLocation("RGBALayer"); + CVector4f FillColor4f = FillColor.ToVector4f(); + CVector4f StrokeColor4f = StrokeColor.ToVector4f(); + mpFontTexture->Bind(0); + smGlyphVertices.Bind(); + glDisable(GL_DEPTH_TEST); + + // Initialize some more stuff before we start the character loop + CVector2f PrintHead(-1.f, 1.f); + CTransform4f PtScale = CTransform4f::ScaleMatrix(PtsToFloat(1)); + SGlyph *pPrevGlyph = nullptr; + + float Scale; + if (FontSize == CFONT_DEFAULT_SIZE) Scale = 1.f; + else Scale = (float) FontSize / (mDefaultSize != 0 ? mDefaultSize : 18); + + for (u32 iChar = 0; iChar < String.length(); iChar++) + { + // Get character, check for newline + char Char = String[iChar]; + + if (Char == '\n') + { + pPrevGlyph = nullptr; + PrintHead.x = -1; + PrintHead.y -= (PtsToFloat(mLineHeight) + PtsToFloat(mLineMargin) + PtsToFloat(mUnknown)) * Scale; + continue; + } + + // Get glyph + auto iGlyph = mGlyphs.find(Char); + if (iGlyph == mGlyphs.end()) continue; + SGlyph *pGlyph = &iGlyph->second; + + // Apply left padding and kerning + PrintHead.x += PtsToFloat(pGlyph->LeftPadding) * Scale; + + if (pPrevGlyph) + { + if (pPrevGlyph->KerningIndex != -1) + { + for (u32 iKern = pPrevGlyph->KerningIndex; iKern < mKerningTable.size(); iKern++) + { + if (mKerningTable[iKern].CharacterA != pPrevGlyph->Character) break; + if (mKerningTable[iKern].CharacterB == String[iChar]) + { + PrintHead.x += PtsToFloat(mKerningTable[iKern].Adjust) * Scale; + break; + } + } + } + } + + // Add a newline if this character goes over the right edge of the screen + if (PrintHead.x + ((PtsToFloat(pGlyph->PrintAdvance) + PtsToFloat(pGlyph->RightPadding)) * Scale) > 1) + { + PrintHead.x = -1; + PrintHead.y -= (PtsToFloat(mLineHeight) + PtsToFloat(mLineMargin) + PtsToFloat(mUnknown)) * Scale; + + if (Char == ' ') continue; + } + + float XTrans = PrintHead.x; + float YTrans = PrintHead.y + ((PtsToFloat(pGlyph->BaseOffset * 2) - PtsToFloat(mVerticalOffset * 2)) * Scale); + + CTransform4f GlyphTransform = PtScale; + GlyphTransform.Scale(CVector3f((float) pGlyph->Width / 2, (float) pGlyph->Height, 1.f)); + GlyphTransform.Scale(Scale); + GlyphTransform.Translate(CVector3f(XTrans, YTrans, 0.f)); + CMatrix4f Glyph4f = GlyphTransform.ToMatrix4f(); + + // Get glyph layer + u8 GlyphLayer = pGlyph->RGBAChannel; + if (mTextureFormat == 3) GlyphLayer *= 2; + else if (mTextureFormat == 8) GlyphLayer = 3; + + // Load shader uniforms, buffer texture + glUniformMatrix4fv(ModelMtxLoc, 1, GL_FALSE, (GLfloat*) &Glyph4f); + smGlyphVertices.BufferAttrib(eTex0, &pGlyph->TexCoords); + + // Draw fill + glUniform1i(LayerLoc, GlyphLayer); + glUniform4fv(ColorLoc, 1, &FillColor4f.x); + smGlyphIndices.DrawElements(); + + // Draw stroke + if ((mTextureFormat == 1) || (mTextureFormat == 3) || (mTextureFormat == 8)) + { + u8 StrokeLayer; + if (mTextureFormat == 1) StrokeLayer = 1; + else if (mTextureFormat == 3) StrokeLayer = GlyphLayer + 1; + else if (mTextureFormat == 8) StrokeLayer = GlyphLayer - 2; + + glUniform1i(LayerLoc, StrokeLayer); + glUniform4fv(ColorLoc, 1, &StrokeColor4f.x); + smGlyphIndices.DrawElements(); + } + + // Update print head + PrintHead.x += PtsToFloat(pGlyph->PrintAdvance) * Scale; + PrintHead.x += PtsToFloat(pGlyph->RightPadding) * Scale; + pPrevGlyph = pGlyph; + } + + glEnable(GL_DEPTH_TEST); + return PrintHead; +} + +void CFont::InitBuffers() +{ + smGlyphVertices.SetActiveAttribs(ePosition | eTex0); + smGlyphVertices.SetVertexCount(4); + + CVector3f Vertices[4] = { + CVector3f( 0.f, 0.f, 0.f), + CVector3f( 2.f, 0.f, 0.f), + CVector3f( 0.f, -2.f, 0.f), + CVector3f( 2.f, -2.f, 0.f) + }; + smGlyphVertices.BufferAttrib(ePosition, Vertices); + + CVector2f TexCoords[4] = { + CVector2f(0.f, 0.f), + CVector2f(1.f, 0.f), + CVector2f(0.f, 1.f), + CVector2f(1.f, 1.f) + }; + smGlyphVertices.BufferAttrib(eTex0, TexCoords); + + smGlyphIndices.Reserve(4); + smGlyphIndices.AddIndex(0); + smGlyphIndices.AddIndex(2); + smGlyphIndices.AddIndex(1); + smGlyphIndices.AddIndex(3); + smGlyphIndices.SetPrimitiveType(GL_TRIANGLE_STRIP); + + smBuffersInitialized = true; +} diff --git a/Resource/CFont.h b/Resource/CFont.h new file mode 100644 index 00000000..308fe3a9 --- /dev/null +++ b/Resource/CFont.h @@ -0,0 +1,73 @@ +#ifndef CFONT_H +#define CFONT_H + +#include "CResource.h" +#include "CTexture.h" +#include "model/CVertex.h" +#include +#include +#include +#include + +#include +#include + +#define CFONT_DEFAULT_SIZE -1 + +class CRenderer; + +class CFont : public CResource +{ + friend class CFontLoader; + static CDynamicVertexBuffer smGlyphVertices; // This is the vertex buffer used to draw glyphs. It has two attributes - Pos and Tex0. Tex0 should be updated for each glyph. + static CIndexBuffer smGlyphIndices; // This is the index buffer used to draw glyphs. It uses a triangle strip. + static bool smBuffersInitialized; // This bool indicates whether the vertex/index buffer have been initialized. Checked at the start of RenderString(). + + u32 mUnknown; // Value at offset 0x8. Not sure what this is. Including for experimentation purposes. + u32 mLineHeight; // Height of each line, in points + u32 mLineMargin; // Gap between lines, in points - this is added to the line height + u32 mVerticalOffset; // In points. This is used to reposition glyphs after the per-glyph vertical offset is applied + u32 mDefaultSize; // In points. + std::string mFontName; // Self-explanatory + CTexture *mpFontTexture; // The texture used by this font + CToken mTextureToken; // Token for the font + u32 mTextureFormat; // Indicates which layers on the texture are for what - multiple glyph layers or fill/stroke + + struct SGlyph + { + u16 Character; // The UTF-16 character that this glyph corresponds to + CVector2f TexCoords[4]; // The format only lists the min/max X/Y values; tracking absolute coordinates in memory is faster + s32 LeftPadding; // The amount of padding applied left of this glyph, in points + s32 RightPadding; // The amount of padding applied right of this glyph, in points + u32 Width; // The width of the glyph, in points + u32 Height; // The height of the glyph, in points + u32 PrintAdvance; // How far the print head advances horizontally after printing this glyph, in points + u32 BaseOffset; // Vertical offset for this glyph, in points; the font-wide offset is added to this + u32 KerningIndex; // Index into the kerning table of the first kerning pair for this glyph. -1 if no pairs. + u8 RGBAChannel; // Fonts can store multiple glyphs in the same space on different RGBA channels. This value corresponds to R, G, B, or A. + }; + std::unordered_map mGlyphs; + + struct SKerningPair + { + u16 CharacterA; // Left character + u16 CharacterB; // Right character + s32 Adjust; // The horizontal offset to apply to CharacterB if this pair is encountered, in points + }; + std::vector mKerningTable; // The kerning table should be laid out in alphabetical order for the indices to work properly + + +public: + CFont(); + ~CFont(); + EResType Type(); + CResource* MakeCopy(CResCache *pCopyCache); + CVector2f RenderString(std::string String, CRenderer *pRenderer, float AspectRatio, + CVector2f Position = CVector2f(0,0), + CColor FillColor = CColor::skWhite, CColor StrokeColor = CColor::skBlack, + u32 FontSize = CFONT_DEFAULT_SIZE); +private: + void InitBuffers(); +}; + +#endif // CFONT_H diff --git a/Resource/CGameArea.cpp b/Resource/CGameArea.cpp new file mode 100644 index 00000000..3f731e6c --- /dev/null +++ b/Resource/CGameArea.cpp @@ -0,0 +1,184 @@ +#include "CGameArea.h" +#include + +CGameArea::CGameArea() : CResource() +{ + mVertexCount = 0; + mTriangleCount = 0; + mTerrainMerged = false; + mMaterialSet = nullptr; + mpGeneratorLayer = nullptr; + mCollision = nullptr; +} + +CGameArea::~CGameArea() +{ + ClearTerrain(); + + delete mCollision; + delete mpGeneratorLayer; + + for (u32 iSCLY = 0; iSCLY < mScriptLayers.size(); iSCLY++) + delete mScriptLayers[iSCLY]; + + for (u32 lyr = 0; lyr < mLightLayers.size(); lyr++) + for (u32 lit = 0; lit < mLightLayers[lyr].size(); lit++) + delete mLightLayers[lyr][lit]; +} + +EResType CGameArea::Type() +{ + return eArea; +} + +void CGameArea::AddWorldModel(CModel *mdl) +{ + mTerrainModels.push_back(mdl); + mVertexCount += mdl->GetVertexCount(); + mTriangleCount += mdl->GetTriangleCount(); + mAABox.ExpandBounds(mdl->AABox()); +} + +void CGameArea::MergeTerrain() +{ + if (mTerrainMerged) return; + + // Nothing really complicated here - iterate through every terrain submesh, add each to a static model + for (u32 tm = 0; tm < mTerrainModels.size(); tm++) + { + CModel *mdl = mTerrainModels[tm]; + u32 SubmeshCount = mdl->GetSurfaceCount(); + + for (u32 sub = 0; sub < SubmeshCount; sub++) + { + SSurface *s = mdl->GetSurface(sub); + CMaterial *mat = mMaterialSet->materials[s->MaterialID]; + + bool NewMat = true; + for (std::vector::iterator it = mStaticTerrainModels.begin(); it != mStaticTerrainModels.end(); it++) + { + if ((*it)->GetMaterial() == mat) + { + // When we append a new submesh to an existing static model, we bump it to the back of the vector. + // This is because mesh ordering actually matters sometimes + // (particularly with multi-layered transparent meshes) + // so we need to at least try to maintain it. + // This is maybe not the most efficient way to do this, but it works. + CStaticModel *mdl = *it; + mdl->AddSurface(s); + mStaticTerrainModels.erase(it); + mStaticTerrainModels.push_back(mdl); + NewMat = false; + break; + } + } + + if (NewMat) + { + CStaticModel *smdl = new CStaticModel(mat); + smdl->AddSurface(s); + mStaticTerrainModels.push_back(smdl); + } + } + } +} + +void CGameArea::ClearTerrain() +{ + for (u32 t = 0; t < mTerrainModels.size(); t++) + delete mTerrainModels[t]; + mTerrainModels.clear(); + + for (u32 s = 0; s < mStaticTerrainModels.size(); s++) + delete mStaticTerrainModels[s]; + mStaticTerrainModels.clear(); + + if (mMaterialSet) delete mMaterialSet; + + mVertexCount = 0; + mTriangleCount = 0; + mTerrainMerged = false; + mAABox = CAABox::skInfinite; +} + +void CGameArea::ClearScriptLayers() +{ + for (auto it = mScriptLayers.begin(); it != mScriptLayers.end(); it++) + delete *it; + mScriptLayers.clear(); + delete mpGeneratorLayer; + mpGeneratorLayer = nullptr; +} + +CTransform4f CGameArea::GetTransform() +{ + return mTransform; +} + +u32 CGameArea::GetTerrainModelCount() +{ + return mTerrainModels.size(); +} + +u32 CGameArea::GetStaticModelCount() +{ + return mStaticTerrainModels.size(); +} + +CModel* CGameArea::GetTerrainModel(u32 mdl) +{ + return mTerrainModels[mdl]; +} + +CStaticModel* CGameArea::GetStaticModel(u32 mdl) +{ + return mStaticTerrainModels[mdl]; +} + +CCollisionMesh* CGameArea::GetCollision() +{ + return mCollision; +} + +u32 CGameArea::GetScriptLayerCount() +{ + return mScriptLayers.size(); +} + +CScriptLayer* CGameArea::GetScriptLayer(u32 index) +{ + return mScriptLayers[index]; +} + +CScriptLayer* CGameArea::GetGeneratorLayer() +{ + return mpGeneratorLayer; +} + +CScriptObject* CGameArea::GetInstanceByID(u32 InstanceID) +{ + auto it = mObjectMap.find(InstanceID); + if (it != mObjectMap.end()) return it->second; + else return nullptr; +} + +u32 CGameArea::GetLightLayerCount() +{ + return mLightLayers.size(); +} + +u32 CGameArea::GetLightCount(u32 layer) +{ + if (mLightLayers.empty()) return 0; + return mLightLayers[layer].size(); +} + +CLight* CGameArea::GetLight(u32 layer, u32 light) +{ + return mLightLayers[layer][light]; +} + +CAABox CGameArea::AABox() +{ + return mAABox; +} diff --git a/Resource/CGameArea.h b/Resource/CGameArea.h new file mode 100644 index 00000000..b810c819 --- /dev/null +++ b/Resource/CGameArea.h @@ -0,0 +1,66 @@ +#ifndef CGAMEAREA_H +#define CGAMEAREA_H + +#include "CCollisionMesh.h" +#include "CLight.h" +#include "CMaterialSet.h" +#include "model/CModel.h" +#include "model/CStaticModel.h" +#include "CResource.h" +#include "script/CScriptLayer.h" +#include +#include +#include + +class CGameArea : public CResource +{ + friend class CAreaLoader; + + u32 mVertexCount; + u32 mTriangleCount; + bool mTerrainMerged; + CTransform4f mTransform; + CAABox mAABox; + + // Geometry + CMaterialSet *mMaterialSet; + std::vector mTerrainModels; // TerrainModels is the original version of each model; this is used by the editor (bounding box checks, material editing, etc) + std::vector mStaticTerrainModels; // StaticTerrainModels is the merged terrain for faster rendering in the world editor + // Script + std::vector mScriptLayers; + CScriptLayer *mpGeneratorLayer; + std::unordered_map mObjectMap; + + // Collision + CCollisionMesh *mCollision; + // Lights + std::vector> mLightLayers; + +public: + CGameArea(); + ~CGameArea(); + EResType Type(); + + void AddWorldModel(CModel *mdl); + void MergeTerrain(); + void ClearTerrain(); + void ClearScriptLayers(); + + // Getters + CTransform4f GetTransform(); + u32 GetTerrainModelCount(); + u32 GetStaticModelCount(); + CModel* GetTerrainModel(u32 mdl); + CStaticModel* GetStaticModel(u32 mdl); + CCollisionMesh* GetCollision(); + u32 GetScriptLayerCount(); + CScriptLayer* GetScriptLayer(u32 index); + CScriptLayer* GetGeneratorLayer(); + CScriptObject* GetInstanceByID(u32 InstanceID); + u32 GetLightLayerCount(); + u32 GetLightCount(u32 layer); + CLight* GetLight(u32 layer, u32 light); + CAABox AABox(); +}; + +#endif // CGAMEAREA_H diff --git a/Resource/CLight.cpp b/Resource/CLight.cpp new file mode 100644 index 00000000..7fbaa1c7 --- /dev/null +++ b/Resource/CLight.cpp @@ -0,0 +1,273 @@ +#include "CLight.h" +#include +#include +#include + +#define CLIGHT_NO_RADIUS 0x40 +#define CLIGHT_NO_INTENSITY 0x80 + +CLight::CLight() +{ + mPosition = skDefaultLightPos; + mDirection = skDefaultLightDir; + mDistAttenCoefficients = CVector3f(0.f, 1.f, 0.f); + mAngleAttenCoefficients = CVector3f(0.f, 1.f, 0.f); + mRadius = 0.f; + mIntensity = 0.f; + mFlags = CLIGHT_NO_RADIUS | CLIGHT_NO_INTENSITY; +} + +// ************ DATA MANIPULATION ************ + +// This function is reverse engineered from the kiosk demo's code +float CLight::CalculateRadius() +{ + if ((mDistAttenCoefficients.y >= FLT_EPSILON) || + (mDistAttenCoefficients.z >= FLT_EPSILON)) + { + float Intensity = GetIntensity(); + + if (mDistAttenCoefficients.z > FLT_EPSILON) + { + if (Intensity <= FLT_EPSILON) + return 0.f; + + float IntensityMod = (Intensity * 5.f / 255.f * mDistAttenCoefficients.z); + return sqrt(Intensity / IntensityMod); + } + + else + { + if (mDistAttenCoefficients.y <= FLT_EPSILON) + return 0.f; + + float IntensityMod = (Intensity * 5.f) / 255.f; + if (IntensityMod < 0.2f) + IntensityMod = 0.2f; + + return Intensity / (IntensityMod * mDistAttenCoefficients.y); + } + } + + else return 3000000000000000000000000000000000000.f; +} + +// This function is also reverse engineered from the kiosk demo's code +float CLight::CalculateIntensity() +{ + float Multiplier = (mType == eCustom) ? mAngleAttenCoefficients.x : 1.0f; + float ColorR = float(mColor.r) / 255.f; + float ColorG = float(mColor.g) / 255.f; + float ColorB = float(mColor.b) / 255.f; + + // Get the color component with the greatest numeric value + float Greatest = (ColorG >= ColorB) ? ColorG : ColorB; + Greatest = (ColorR >= Greatest) ? ColorR : Greatest; + + return Greatest * Multiplier; +} + +// As is this one... partly +CVector3f CLight::CalculateSpotAngleAtten() +{ + if (mType != eSpot) return CVector3f(1.f, 0.f, 0.f); + + if ((mSpotCutoff < 0.f) || (mSpotCutoff > 90.f)) + return CVector3f(1.f, 0.f, 0.f); + + float RadianCutoff = (mSpotCutoff * 3.1415927f) / 180.f; + float RadianCosine = cosf(RadianCutoff); + float InvCosine = 1.f - RadianCosine; + + return CVector3f(0.f, -RadianCosine / InvCosine, 1.f / InvCosine); +} + +// ************ GETTERS ************ +ELightType CLight::GetType() const +{ + return mType; +} + +CVector3f CLight::GetPosition() const +{ + return mPosition; +} + +CVector3f CLight::GetDirection() const +{ + return mDirection; +} + +CColor CLight::GetColor() const +{ + return mColor; +} + +CVector3f CLight::GetDistAttenuation() const +{ + return mDistAttenCoefficients; +} + +CVector3f CLight::GetAngleAttenuation() const +{ + return mAngleAttenCoefficients; +} + +float CLight::GetRadius() +{ + if (mFlags & CLIGHT_NO_RADIUS) + { + mRadius = CalculateRadius(); + mFlags &= ~CLIGHT_NO_RADIUS; + } + + return mRadius * 2; +} + +float CLight::GetIntensity() +{ + if (mFlags & CLIGHT_NO_INTENSITY) + { + mIntensity = CalculateIntensity(); + mFlags &= ~CLIGHT_NO_INTENSITY; + } + + return mIntensity; +} + +// ************ SETTERS ************ +void CLight::SetPosition(const CVector3f& Position) +{ + mPosition = Position; +} + +void CLight::SetDirection(const CVector3f& Direction) +{ + mDirection = Direction; +} + +void CLight::SetColor(const CColor& Color) +{ + mColor = Color; + mFlags = CLIGHT_NO_RADIUS | CLIGHT_NO_INTENSITY; +} + +void CLight::SetSpotCutoff(float Cutoff) +{ + mSpotCutoff = Cutoff * 0.5f; + CalculateSpotAngleAtten(); +} + +void CLight::SetDistAtten(float DistCoefA, float DistCoefB, float DistCoefC) +{ + mDistAttenCoefficients.x = DistCoefA; + mDistAttenCoefficients.y = DistCoefB; + mDistAttenCoefficients.z = DistCoefC; +} + +void CLight::SetAngleAtten(float AngleCoefA, float AngleCoefB, float AngleCoefC) +{ + mAngleAttenCoefficients.x = AngleCoefA; + mAngleAttenCoefficients.y = AngleCoefB; + mAngleAttenCoefficients.z = AngleCoefC; +} + +// ************ OTHER ************ +void CLight::Load() const +{ + u8 Index = (u8) CGraphics::sNumLights; + if (Index >= 8) return; + + CGraphics::SLightBlock::SGXLight *Light = &CGraphics::sLightBlock.Lights[Index]; + CVector3f PosView = CGraphics::sMVPBlock.ViewMatrix * mPosition; + CVector3f DirView = CGraphics::sMVPBlock.ViewMatrix * mDirection; + + switch (mType) + { + case eLocalAmbient: + // LocalAmbient is already accounted for in CGraphics::sAreaAmbientColor + return; + case eDirectional: + Light->Position = CVector4f(-mDirection * 1048576.f, 1.f); + Light->Direction = CVector4f(mDirection, 0.f); + Light->Color = mColor.ToVector4f() * CGraphics::sWorldLightMultiplier; + Light->DistAtten = CVector4f(1.f, 0.f, 0.f, 0.f); + Light->AngleAtten = CVector4f(1.f, 0.f, 0.f, 0.f); + break; + case eSpot: + Light->Position = CVector4f(mPosition, 1.f); + Light->Direction = CVector4f(mDirection, 0.f); + Light->Color = mColor.ToVector4f() * CGraphics::sWorldLightMultiplier; + Light->DistAtten = mDistAttenCoefficients; + Light->AngleAtten = mAngleAttenCoefficients; + break; + case eCustom: + Light->Position = CVector4f(mPosition, 1.f); + Light->Direction = CVector4f(mDirection, 0.f); + Light->Color = mColor.ToVector4f() * CGraphics::sWorldLightMultiplier; + Light->DistAtten = mDistAttenCoefficients; + Light->AngleAtten = mAngleAttenCoefficients; + break; + default: + return; + } + CGraphics::sNumLights++; +} + +// ************ STATIC ************ +CLight* CLight::BuildLocalAmbient(const CVector3f& Position, const CColor& Color) +{ + CLight *Light = new CLight; + Light->mType = eLocalAmbient; + Light->mPosition = Position; + Light->mDirection = skDefaultLightDir; + Light->mColor = Color; + Light->mSpotCutoff = 0.f; + return Light; +} + +CLight* CLight::BuildDirectional(const CVector3f& Position, const CVector3f& Direction, const CColor& Color) +{ + CLight *Light = new CLight; + Light->mType = eDirectional; + Light->mPosition = Position; + Light->mDirection = Direction; + Light->mColor = Color; + Light->mSpotCutoff = 0.f; + return Light; +} + +CLight* CLight::BuildSpot(const CVector3f& Position, const CVector3f& Direction, const CColor& Color, float Cutoff) +{ + CLight *Light = new CLight; + Light->mType = eSpot; + Light->mPosition = Position; + Light->mDirection = -Direction.Normalized(); + Light->mColor = Color; + Light->mSpotCutoff = Cutoff * 0.5f; + Light->mAngleAttenCoefficients = Light->CalculateSpotAngleAtten(); + return Light; +} + +CLight* CLight::BuildCustom(const CVector3f& Position, const CVector3f& Direction, const CColor& Color, + float DistAttenA, float DistAttenB, float DistAttenC, + float AngleAttenA, float AngleAttenB, float AngleAttenC) +{ + CLight *Light = new CLight; + Light->mType = eCustom; + Light->mPosition = Position; + Light->mDirection = Direction; + Light->mColor = Color; + Light->mSpotCutoff = 0.f; + Light->mDistAttenCoefficients.x = DistAttenA; + Light->mDistAttenCoefficients.y = DistAttenB; + Light->mDistAttenCoefficients.z = DistAttenC; + Light->mAngleAttenCoefficients.x = AngleAttenA; + Light->mAngleAttenCoefficients.y = AngleAttenB; + Light->mAngleAttenCoefficients.z = AngleAttenC * AngleAttenC; + return Light; +} + +// ************ CONSTANTS ************ +const CVector3f CLight::skDefaultLightPos(0.f, 0.f, 0.f); +const CVector3f CLight::skDefaultLightDir(0.f,-1.f, 0.f); diff --git a/Resource/CLight.h b/Resource/CLight.h new file mode 100644 index 00000000..6e882ae3 --- /dev/null +++ b/Resource/CLight.h @@ -0,0 +1,80 @@ +#ifndef CLIGHT_H +#define CLIGHT_H + +#include +#include +#include +#include + +/** + * CLight is currently heavily based on the lights system from Metroid Prime, + * including code reverse engineered from the game's executable. Not yet sure + * how much needs to be modified to properly support Prime 3 and DKCR; they + * have a new light structure. + */ +enum ELightType +{ + eLocalAmbient = 0, + eDirectional = 1, + eSpot = 3, + eCustom = 2 +}; + +class CLight +{ + ELightType mType; + CVector3f mPosition; + CVector3f mDirection; + CColor mColor; + float mSpotCutoff; + CVector3f mDistAttenCoefficients; + CVector3f mAngleAttenCoefficients; + float mRadius; + float mIntensity; + u8 mFlags; + +public: + CLight(); + +private: + // Data Manipulation + float CalculateRadius(); + float CalculateIntensity(); + CVector3f CalculateSpotAngleAtten(); + +public: + // Getters + ELightType GetType() const; + CVector3f GetPosition() const; + CVector3f GetDirection() const; + CColor GetColor() const; + CVector3f GetDistAttenuation() const; + CVector3f GetAngleAttenuation() const; + float GetRadius(); + float GetIntensity(); + + // Setters + void SetPosition(const CVector3f& Position); + void SetDirection(const CVector3f& Direction); + void SetColor(const CColor& Color); + void SetSpotCutoff(float Cutoff); + void SetDistAtten(float DistCoefA, float DistCoefB, float DistCoefC); + void SetAngleAtten(float AngleCoefA, float AngleCoefB, float AngleCoefC); + + // Other + void Load() const; + + // Static + static CLight* BuildLocalAmbient(const CVector3f& Position, const CColor& Color); + static CLight* BuildDirectional(const CVector3f& Position, const CVector3f& Direction, const CColor& Color); + static CLight* BuildSpot(const CVector3f& Position, const CVector3f& Direction, const CColor& Color, float Cutoff); + static CLight* BuildCustom(const CVector3f& Position, const CVector3f& Direction, const CColor& Color, + float DistAttenA, float DistAttenB, float DistAttenC, + float AngleAttenA, float AngleAttenB, float AngleAttenC); + + // Constants + static const CVector3f skDefaultLightPos; + static const CVector3f skDefaultLightDir; +}; + +#endif // CLIGHT_H diff --git a/Resource/CMaterial.cpp b/Resource/CMaterial.cpp new file mode 100644 index 00000000..059dd8b4 --- /dev/null +++ b/Resource/CMaterial.cpp @@ -0,0 +1,240 @@ +#include "CMaterial.h" +#include +#include +#include +#include +#include +#include + +#include +#include + +u64 CMaterial::sCurrentMaterial = 0; +CColor CMaterial::sCurrentTint = CColor::skWhite; + +CMaterial::CMaterial() +{ + mpShader = nullptr; + mShaderStatus = eNoShader; + mRecalcHash = true; + mEnableBloom = false; + mVersion = eUnknownVersion; + mOptions = eNoSettings; + mVtxDesc = eNoAttributes; + mBlendSrcFac = GL_ONE; + mBlendDstFac = GL_ZERO; + mLightingEnabled = true; + mEchoesUnknownA = 0; + mEchoesUnknownB = 0; + mpIndirectTexture = nullptr; +} + +CMaterial::~CMaterial() +{ + for (u32 iPass = 0; iPass < mPasses.size(); iPass++) + delete mPasses[iPass]; + + delete mpShader; +} + +void CMaterial::GenerateShader() +{ + delete mpShader; + mpShader = CShaderGenerator::GenerateShader(*this); + + if (!mpShader->IsValidProgram()) mShaderStatus = eShaderFailed; + else mShaderStatus = eShaderExists; +} + +bool CMaterial::SetCurrent(ERenderOptions Options) +{ + // Bind textures + for (u32 iPass = 0; iPass < mPasses.size(); iPass++) + mPasses[iPass]->LoadTexture(iPass); + + // Skip material setup if the currently bound material is identical + if (sCurrentMaterial == HashParameters()) + { + GLuint NumLightsLoc = CShader::CurrentShader()->GetUniformLocation("NumLights"); + glUniform1i(NumLightsLoc, CGraphics::sNumLights); + return true; + } + + // Shader setup + if (mShaderStatus == eNoShader) GenerateShader(); + mpShader->SetCurrent(); + + if (mShaderStatus == eShaderFailed) + return false; + + // Set blend equation - force to ZERO/ONE if alpha is disabled + if (Options & eNoAlpha) + glBlendFunc(GL_ONE, GL_ZERO); + else + glBlendFunc(mBlendSrcFac, mBlendDstFac); + + // Set color mask. This is for rendering bloom - bloom maps render to the framebuffer alpha + // We can only render bloom on materials that aren't alpha blended. + bool AlphaBlended = ((mBlendSrcFac == GL_SRC_ALPHA) && (mBlendDstFac == GL_ONE_MINUS_SRC_ALPHA)); + + if ((mEnableBloom) && (Options & eEnableBloom) && (!AlphaBlended)) + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + else + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_FALSE); + + // Set konst inputs + for (u32 iKonst = 0; iKonst < 4; iKonst++) + CGraphics::sPixelBlock.Konst[iKonst] = mKonstColors[iKonst].ToVector4f(); + + // Set color channels + // COLOR0_Amb is initialized by the node instead of by the material + CGraphics::sVertexBlock.COLOR0_Mat = CColor::skWhite.ToVector4f(); + + // Set depth write - force on if alpha is disabled (lots of weird depth issues otherwise) + if ((mOptions & eDepthWrite) || (Options & eNoAlpha)) glDepthMask(GL_TRUE); + else glDepthMask(GL_FALSE); + + // Load uniforms + for (u32 iPass = 0; iPass < mPasses.size(); iPass++) + mPasses[iPass]->SetAnimCurrent(Options, iPass); + + GLuint NumLightsLoc = mpShader->GetUniformLocation("NumLights"); + glUniform1i(NumLightsLoc, CGraphics::sNumLights); + + CGraphics::UpdateVertexBlock(); + CGraphics::UpdatePixelBlock(); + sCurrentMaterial = HashParameters(); + return true; +} + +u64 CMaterial::HashParameters() +{ + if (mRecalcHash) + { + CHashFNV1A Hash; + Hash.Init64(); + + Hash.HashLong(mVersion); + Hash.HashLong(mOptions); + Hash.HashLong(mVtxDesc); + Hash.HashData(mKonstColors, sizeof(CColor) * 4); + Hash.HashLong(mBlendSrcFac); + Hash.HashLong(mBlendDstFac); + Hash.HashByte(mLightingEnabled); + Hash.HashLong(mEchoesUnknownA); + Hash.HashLong(mEchoesUnknownB); + + for (u32 iPass = 0; iPass < mPasses.size(); iPass++) + mPasses[iPass]->HashParameters(Hash); + + mParametersHash = Hash.GetHash64(); + mRecalcHash = false; + } + + return mParametersHash; +} + +void CMaterial::Update() +{ + mRecalcHash = true; + mShaderStatus = eNoShader; +} + +// ************ GETTERS ************ +EGame CMaterial::Version() const +{ + return mVersion; +} + +CMaterial::EMaterialOptions CMaterial::Options() const +{ + return mOptions; +} + +EVertexDescription CMaterial::VtxDesc() const +{ + return mVtxDesc; +} + +GLenum CMaterial::BlendSrcFac() const { + return mBlendSrcFac; +} + +GLenum CMaterial::BlendDstFac() const { + return mBlendDstFac; +} + +CColor CMaterial::Konst(u32 KIndex) const +{ + if (KIndex > 3) return CColor::skTransparentBlack; + else return mKonstColors[KIndex]; +} + +CTexture* CMaterial::IndTexture() const +{ + return mpIndirectTexture; +} + +bool CMaterial::IsLightingEnabled() const +{ + return mLightingEnabled; +} + +u32 CMaterial::EchoesUnknownA() const +{ + return mEchoesUnknownA; +} + +u32 CMaterial::EchoesUnknownB() const +{ + return mEchoesUnknownB; +} + +u32 CMaterial::PassCount() const +{ + return mPasses.size(); +} + +CMaterialPass* CMaterial::Pass(u32 PassIndex) const +{ + return mPasses[PassIndex]; +} + + +// ************ SETTERS ************ +void CMaterial::SetOptions(EMaterialOptions Options) +{ + mOptions = Options; + mRecalcHash = true; +} + +void CMaterial::SetBlendMode(GLenum SrcFac, GLenum DstFac) +{ + mBlendSrcFac = SrcFac; + mBlendDstFac = DstFac; + mRecalcHash = true; +} + +void CMaterial::SetKonst(CColor& Konst, u32 KIndex) +{ + mKonstColors[KIndex] = Konst; + mRecalcHash = true; +} + +void CMaterial::SetIndTexture(CTexture *pTex) +{ + mpIndirectTexture = pTex; +} + +void CMaterial::SetLightingEnabled(bool Enabled) +{ + mLightingEnabled = Enabled; + mRecalcHash = true; +} + +// ************ STATIC ************ +void CMaterial::KillCachedMaterial() +{ + sCurrentMaterial = 0; + CShader::KillCachedShader(); +} diff --git a/Resource/CMaterial.h b/Resource/CMaterial.h new file mode 100644 index 00000000..71078439 --- /dev/null +++ b/Resource/CMaterial.h @@ -0,0 +1,107 @@ +#ifndef MATERIAL_H +#define MATERIAL_H + +#include "CMaterialPass.h" +#include "CTexture.h" +#include "EFormatVersion.h" +#include "model/EVertexDescription.h" +#include +#include +#include +#include +#include +#include +#include +#include + +class CMaterialSet; + +class CMaterial +{ +public: + friend class CMaterialLoader; + friend class CMaterialCooker; + + // Enums + enum EMaterialOptions + { + eNoSettings = 0, + eKonst = 0x8, + eTransparent = 0x10, + ePunchthrough = 0x20, + eReflection = 0x40, + eDepthWrite = 0x80, + eSurfaceReflection = 0x100, + eOccluder = 0x200, + eIndStage = 0x400, + eLightmap = 0x800, + eShortTexCoord = 0x2000, + eAllSettings = 0x2FF8 + }; + +private: + enum EShaderStatus + { + eNoShader, eShaderExists, eShaderFailed + }; + + // Statics + static u64 sCurrentMaterial; // The hash for the currently bound material + static CColor sCurrentTint; // The tint for the currently bound material + + // Members + CShader *mpShader; // This material's generated shader. Created with GenerateShader(). + EShaderStatus mShaderStatus; // A status variable so that PWE won't crash if a shader fails to compile. + u64 mParametersHash; // A hash of all the parameters that can identify this TEV setup. + bool mRecalcHash; // Indicates the hash needs to be recalculated. Set true when parameters are changed. + bool mEnableBloom; // Bool that toggles bloom on or off. On by default on MP3 materials, off by default on MP1 materials. + + EGame mVersion; + EMaterialOptions mOptions; // See the EMaterialOptions enum above + EVertexDescription mVtxDesc; // Descriptor of vertex attributes used by this material + CColor mKonstColors[4]; // Konst color values for TEV + GLenum mBlendSrcFac; // Source blend factor + GLenum mBlendDstFac; // Dest blend factor + bool mLightingEnabled; // Color channel control flags; indicate whether lighting is enabled + u32 mEchoesUnknownA; // First unknown value introduced in Echoes. Included for cooking. + u32 mEchoesUnknownB; // Second unknown value introduced in Echoes. Included for cooking. + CTexture *mpIndirectTexture; // Optional texture used for the indirect stage for reflections + CToken mIndTextureToken; // Token for indirect texture + + std::vector mPasses; + +public: + CMaterial(); + ~CMaterial(); + void GenerateShader(); + bool SetCurrent(ERenderOptions Options); + u64 HashParameters(); + void Update(); + + // Getters + EGame Version() const; + EMaterialOptions Options() const; + EVertexDescription VtxDesc() const; + GLenum BlendSrcFac() const; + GLenum BlendDstFac() const; + CColor Konst(u32 KIndex) const; + CTexture* IndTexture() const; + bool IsLightingEnabled() const; + u32 EchoesUnknownA() const; + u32 EchoesUnknownB() const; + u32 PassCount() const; + CMaterialPass* Pass(u32 PassIndex) const; + + // Setters + void SetOptions(EMaterialOptions Options); + void SetBlendMode(GLenum SrcFac, GLenum DstFac); + void SetKonst(CColor& Konst, u32 KIndex); + void SetIndTexture(CTexture *pTex); + void SetLightingEnabled(bool Enabled); + + // Static + static void KillCachedMaterial(); +}; +DEFINE_ENUM_FLAGS(CMaterial::EMaterialOptions) + +#endif // MATERIAL_H diff --git a/Resource/CMaterialPass.cpp b/Resource/CMaterialPass.cpp new file mode 100644 index 00000000..ccbb6899 --- /dev/null +++ b/Resource/CMaterialPass.cpp @@ -0,0 +1,296 @@ +#include "CMaterialPass.h" +#include "CMaterial.h" +#include +#include + +CMaterialPass::CMaterialPass(CMaterial *pParent) +{ + mPassType = "CUST"; + mpTexture = nullptr; + mEnabled = true; + mpParentMat = pParent; + + mColorOutput = ePrevReg; + mAlphaOutput = ePrevReg; + mKColorSel = eKonstOne; + mKAlphaSel = eKonstOne; + mRasSel = eRasColorNull; + mTexCoordSource = 0; + mAnimMode = eNoUVAnim; + + for (u32 iParam = 0; iParam < 4; iParam++) + { + mColorInputs[iParam] = eZeroRGB; + mAlphaInputs[iParam] = eZeroAlpha; + mAnimParams[iParam] = 0.f; + } +} + +CMaterialPass::~CMaterialPass() +{ +} + +void CMaterialPass::HashParameters(CHashFNV1A &Hash) +{ + if (mEnabled) + { + Hash.HashLong(mPassType.ToLong()); + Hash.HashLong(mSettings); + Hash.HashData(&mColorInputs[0], sizeof(ETevColorInput) * 4); + Hash.HashData(&mAlphaInputs[0], sizeof(ETevAlphaInput) * 4); + Hash.HashLong(mColorOutput); + Hash.HashLong(mAlphaOutput); + Hash.HashLong(mKColorSel); + Hash.HashLong(mKAlphaSel); + Hash.HashLong(mRasSel); + Hash.HashLong(mTexCoordSource); + Hash.HashLong(mAnimMode); + Hash.HashData(mAnimParams, sizeof(float) * 4); + Hash.HashByte(mEnabled); + } +} + +void CMaterialPass::LoadTexture(u32 PassIndex) +{ + if (mpTexture) + mpTexture->Bind(PassIndex); +} + +void CMaterialPass::SetAnimCurrent(ERenderOptions Options, u32 PassIndex) +{ + if (mAnimMode == eNoUVAnim) return; + + float s = AnimUtil::SecondsMod900(); + const CMatrix4f& ModelMtx = CGraphics::sMVPBlock.ModelMatrix; + const CMatrix4f& ViewMtx = CGraphics::sMVPBlock.ViewMatrix; + + CMatrix4f TexMtx = CMatrix4f::skIdentity; + CMatrix4f PostMtx = CMatrix4f::skIdentity; + + switch (mAnimMode) + { + + case eInverseMV: // Mode 0 + case eSimpleMode: // Mode 10 - maybe not correct? + { + glm::mat4 mtx = glm::inverse(glm::transpose(CGraphics::sMVPBlock.ViewMatrix.ToGlmMat4()) * glm::transpose(CGraphics::sMVPBlock.ModelMatrix.ToGlmMat4())); + mtx[0][3] = mtx[1][3] = mtx[2][3] = 0.f; + TexMtx = CMatrix4f::FromGlmMat4(mtx); + PostMtx = CMatrix4f(0.5f, 0.0f, 0.0f, 0.5f, + 0.0f, 0.5f, 0.0f, 0.5f, + 0.0f, 0.0f, 0.0f, 1.0f, + 0.0f, 0.0f, 0.0f, 1.0f); + break; + } + + case eInverseMVTranslated: // Mode 1 + { + glm::mat4 mtx = glm::inverse(glm::transpose(CGraphics::sMVPBlock.ViewMatrix.ToGlmMat4()) * glm::transpose(CGraphics::sMVPBlock.ModelMatrix.ToGlmMat4())); + TexMtx = CMatrix4f::FromGlmMat4(mtx); + PostMtx = CMatrix4f(0.5f, 0.0f, 0.0f, 0.5f, + 0.0f, 0.5f, 0.0f, 0.5f, + 0.0f, 0.0f, 0.0f, 1.0f, + 0.0f, 0.0f, 0.0f, 1.0f); + } + + case eUVScroll: // Mode 2 + { + if (Options & eUVScroll) + { + TexMtx[0][3] = (s * mAnimParams[2]) + mAnimParams[0]; + TexMtx[1][3] = (s * mAnimParams[3]) + mAnimParams[1]; + } + break; + } + + case eUVRotation: // Mode 3 + { + if (Options & eUVScroll) + { + float Angle = (s * mAnimParams[1]) + mAnimParams[0]; + + float ACos = cos(Angle); + float ASin = sin(Angle); + float TransX = (1.f - (ACos - ASin)) * 0.5f; + float TransY = (1.f - (ASin + ACos)) * 0.5f; + + TexMtx = CMatrix4f(ACos, -ASin, 0.0f, TransX, + ASin, ACos, 0.0f, TransY, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f); + } + break; + } + + case eHFilmstrip: // Mode 4 + case eVFilmstrip: // Mode 5 + { + if (Options & eUVScroll) + { + float Offset = mAnimParams[2] * mAnimParams[0] * (mAnimParams[3] + s); + Offset = (float)(short)(float)(mAnimParams[1] * fmod(Offset, 1.0f)) * mAnimParams[2]; + if (mAnimMode == eHFilmstrip) TexMtx[0][3] = Offset; + if (mAnimMode == eVFilmstrip) TexMtx[1][3] = Offset; + } + break; + } + + case eModelMatrix: // Mode 6 + { + // It looks ok, but I can't tell whether it's correct... + TexMtx = CMatrix4f::FromGlmMat4(glm::transpose(CGraphics::sMVPBlock.ModelMatrix.ToGlmMat4())); + PostMtx = CMatrix4f(0.5f, 0.0f, 0.0f, TexMtx[0][3] * 0.50000001f, + 0.0f, 0.5f, 0.0f, TexMtx[1][3] * 0.50000001f, + 0.0f, 0.0f, 0.0f, 1.0f, + 0.0f, 0.0f, 0.0f, 1.0f); + TexMtx[0][3] = 0.f; + TexMtx[1][3] = 0.f; + TexMtx[2][3] = 0.f; + } + + case eConvolutedModeA: // Mode 7 + { + CMatrix4f view = CGraphics::sMVPBlock.ViewMatrix; + + // Oh god I seriously need a CMatrix4f inverse function. + glm::mat4 mtx = glm::inverse(glm::transpose(CGraphics::sMVPBlock.ViewMatrix.ToGlmMat4()) * glm::transpose(CGraphics::sMVPBlock.ModelMatrix.ToGlmMat4())); + mtx[0][3] = mtx[1][3] = mtx[2][3] = 0.f; + TexMtx = CMatrix4f::FromGlmMat4(mtx); + + float xy = (view[3][0] + view[3][1]) * 0.025f * mAnimParams[1]; + xy = (xy - (int) xy); + + float z = view[3][2] * 0.05f * mAnimParams[1]; + z = (z - (int) z); + + float halfA = mAnimParams[0] * 0.5f; + + PostMtx = CMatrix4f(halfA, 0.0f, 0.0f, xy, + 0.0f, 0.0f, halfA, z, + 0.0f, 0.0f, 0.0f, 1.0f, + 0.0f, 0.0f, 0.0f, 1.0f); + break; + } + + case eConvolutedModeB: // Mode 8 (MP3/DKCR only) + { + // todo + break; + } + + default: + break; + } + + CGraphics::sVertexBlock.TexMatrices[PassIndex] = TexMtx; + CGraphics::sVertexBlock.PostMatrices[PassIndex] = PostMtx; +} + +// ************ SETTERS ************ +void CMaterialPass::SetType(CFourCC Type) +{ + mPassType = Type; + mpParentMat->Update(); +} + +void CMaterialPass::SetColorInputs(ETevColorInput InputA, ETevColorInput InputB, ETevColorInput InputC, ETevColorInput InputD) +{ + mColorInputs[0] = InputA; + mColorInputs[1] = InputB; + mColorInputs[2] = InputC; + mColorInputs[3] = InputD; + mpParentMat->Update(); +} + +void CMaterialPass::SetAlphaInputs(ETevAlphaInput InputA, ETevAlphaInput InputB, ETevAlphaInput InputC, ETevAlphaInput InputD) +{ + mAlphaInputs[0] = InputA; + mAlphaInputs[1] = InputB; + mAlphaInputs[2] = InputC; + mAlphaInputs[3] = InputD; + mpParentMat->Update(); +} + +void CMaterialPass::SetColorOutput(ETevOutput OutputReg) +{ + mColorOutput = OutputReg; + mpParentMat->Update(); +} + +void CMaterialPass::SetAlphaOutput(ETevOutput OutputReg) +{ + mAlphaOutput = OutputReg; + mpParentMat->Update(); +} + +void CMaterialPass::SetKColorSel(ETevKSel Sel) +{ + mKColorSel = Sel; + mpParentMat->Update(); +} + +void CMaterialPass::SetKAlphaSel(ETevKSel Sel) +{ + // Konst RGB is invalid for alpha, so reset to One if that's the selection + if ((Sel >= eKonst0_RGB) && (Sel <= eKonst3_RGB)) + Sel = eKonstOne; + + mKAlphaSel = Sel; + mpParentMat->Update(); +} + +void CMaterialPass::SetRasSel(ETevRasSel Sel) +{ + mRasSel = Sel; + mpParentMat->Update(); +} + +void CMaterialPass::SetTexCoordSource(u32 Source) +{ + mTexCoordSource = Source; + mpParentMat->Update(); +} + +void CMaterialPass::SetTexture(CTexture *pTex) +{ + mpTexture = pTex; + mTexToken = CToken(pTex); +} + +void CMaterialPass::SetAnimMode(EUVAnimMode Mode) +{ + mAnimMode = Mode; + mpParentMat->Update(); +} + +void CMaterialPass::SetAnimParam(u32 ParamIndex, float Value) +{ + mAnimParams[ParamIndex] = Value; +} + +void CMaterialPass::SetEnabled(bool Enabled) +{ + mEnabled = Enabled; + mpParentMat->Update(); +} + +// ************ STATIC ************ +std::string CMaterialPass::PassTypeName(CFourCC Type) +{ + if (Type == "CUST") return "Custom"; + if (Type == "DIFF") return "Light"; + if (Type == "RIML") return "Rim Light"; + if (Type == "BLOL") return "Bloom Light"; + // BLOD + if (Type == "CLR ") return "Diffuse"; + if (Type == "TRAN") return "Opacity"; + if (Type == "INCA") return "Emissive"; + if (Type == "RFLV") return "Specular"; + if (Type == "RFLD") return "Reflection"; + // LRLD + // LURD + if (Type == "BLOI") return "Bloom Diffuse"; + if (Type == "XRAY") return "X-Ray"; + // TOON + return Type.ToString(); +} diff --git a/Resource/CMaterialPass.h b/Resource/CMaterialPass.h new file mode 100644 index 00000000..125fcdad --- /dev/null +++ b/Resource/CMaterialPass.h @@ -0,0 +1,127 @@ +#ifndef CMATERIALPASS_H +#define CMATERIALPASS_H + +#include +#include +#include +#include +#include +#include "ETevEnums.h" + +class CMaterial; + +class CMaterialPass +{ + friend class CMaterialLoader; + friend class CMaterialCooker; + +public: + enum EPassSettings + { + eEmissiveBloom = 0x4, + eInvertOpacityMap = 0x10 + }; + +private: + CMaterial *mpParentMat; + CFourCC mPassType; + EPassSettings mSettings; + + ETevColorInput mColorInputs[4]; + ETevAlphaInput mAlphaInputs[4]; + ETevOutput mColorOutput; + ETevOutput mAlphaOutput; + ETevKSel mKColorSel; + ETevKSel mKAlphaSel; + ETevRasSel mRasSel; + u32 mTexCoordSource; // Should maybe be an enum but worried about conflicts with EVertexDescriptionn + CTexture *mpTexture; + CToken mTexToken; + EUVAnimMode mAnimMode; + float mAnimParams[4]; + bool mEnabled; + +public: + CMaterialPass(CMaterial *pParent); + ~CMaterialPass(); + void HashParameters(CHashFNV1A& Hash); + void LoadTexture(u32 PassIndex); + void SetAnimCurrent(ERenderOptions Options, u32 PassIndex); + + // Setters + void SetType(CFourCC Type); + void SetColorInputs(ETevColorInput InputA, ETevColorInput InputB, ETevColorInput InputC, ETevColorInput InputD); + void SetAlphaInputs(ETevAlphaInput InputA, ETevAlphaInput InputB, ETevAlphaInput InputC, ETevAlphaInput InputD); + void SetColorOutput(ETevOutput OutputReg); + void SetAlphaOutput(ETevOutput OutputReg); + void SetKColorSel(ETevKSel Sel); + void SetKAlphaSel(ETevKSel Sel); + void SetRasSel(ETevRasSel Sel); + void SetTexCoordSource(u32 Source); + void SetTexture(CTexture *pTex); + void SetAnimMode(EUVAnimMode Mode); + void SetAnimParam(u32 ParamIndex, float Value); + void SetEnabled(bool Enabled); + + // Getters + inline CFourCC Type() const { + return mPassType; + } + + inline std::string NamedType() const { + return PassTypeName(mPassType); + } + + inline ETevColorInput ColorInput(u32 Input) const { + return mColorInputs[Input]; + } + + inline ETevAlphaInput AlphaInput(u32 Input) const { + return mAlphaInputs[Input]; + } + + inline ETevOutput ColorOutput() const { + return mColorOutput; + } + + inline ETevOutput AlphaOutput() const { + return mAlphaOutput; + } + + inline ETevKSel KColorSel() const { + return mKColorSel; + } + + inline ETevKSel KAlphaSel() const { + return mKAlphaSel; + } + + inline ETevRasSel RasSel() const { + return mRasSel; + } + + inline u32 TexCoordSource() const { + return mTexCoordSource; + } + + inline CTexture* Texture() const { + return mpTexture; + } + + inline EUVAnimMode AnimMode() const { + return mAnimMode; + } + + inline float AnimParam(u32 ParamIndex) const { + return mAnimParams[ParamIndex]; + } + + inline bool IsEnabled() const { + return mEnabled; + } + + // Static + static std::string PassTypeName(CFourCC Type); +}; + +#endif // CMATERIALPASS_H diff --git a/Resource/CMaterialSet.cpp b/Resource/CMaterialSet.cpp new file mode 100644 index 00000000..6b79b72f --- /dev/null +++ b/Resource/CMaterialSet.cpp @@ -0,0 +1,11 @@ +#include "CMaterialSet.h" +#include +#include + +CMaterialSet::CMaterialSet() {} + +CMaterialSet::~CMaterialSet() +{ + for (u32 m = 0; m < materials.size(); m++) + delete materials[m]; +} diff --git a/Resource/CMaterialSet.h b/Resource/CMaterialSet.h new file mode 100644 index 00000000..78badd45 --- /dev/null +++ b/Resource/CMaterialSet.h @@ -0,0 +1,19 @@ +#ifndef CMATERIALSET_H +#define CMATERIALSET_H + +#include +#include +#include +#include "EFormatVersion.h" + +class CMaterialSet +{ +public: + std::vector textures; + std::vector materials; + + CMaterialSet(); + ~CMaterialSet(); +}; + +#endif // CMATERIALSET_H diff --git a/Resource/CPakFile.cpp b/Resource/CPakFile.cpp new file mode 100644 index 00000000..4d6854ad --- /dev/null +++ b/Resource/CPakFile.cpp @@ -0,0 +1,174 @@ +#include "CPakFile.h" +#include +#include +#include +#include +#include +#include +#include + +CPakFile::CPakFile() +{ + pak = nullptr; +} + +CPakFile::CPakFile(CInputStream* pakfile) +{ + pak = pakfile; + if (!pak->IsValid()) return; + + version = pak->ReadLong(); + pak->Seek(0x4, SEEK_CUR); + + u32 namedResCount = pak->ReadLong(); + NamedResTable.resize(namedResCount); + + for (u32 n = 0; n < namedResCount; n++) + { + SNamedResource *res = &NamedResTable[n]; + res->resType = CFourCC(*pak); + res->resID = (u64) pak->ReadLong(); + u32 resNameLength = pak->ReadLong(); + res->resName = pak->ReadString(resNameLength); + } + + u32 resCount = pak->ReadLong(); + ResInfoTable.resize(resCount); + + for (u32 r = 0; r < resCount; r++) + { + SResInfo *res = &ResInfoTable[r]; + res->compressed = (pak->ReadLong() != 0); + res->resType = CFourCC(*pak); + res->resID = (u64) pak->ReadLong(); + res->size = pak->ReadLong(); + res->offset = pak->ReadLong(); + } +} + +CPakFile::~CPakFile() +{ + if (pak) delete pak; +} + +std::vector CPakFile::getNamedResources() +{ + return NamedResTable; +} + +SResInfo CPakFile::getResourceInfo(u64 assetID, CFourCC assetType) +{ + // TODO: figure out how the game finds assets in paks, implement similar system to speed things up + if (ResInfoTable.empty()) + return SResInfo(); + + for (u32 r = 0; r < ResInfoTable.size(); r++) + { + if (((u64) (ResInfoTable[r].resID & 0xFFFFFFFF) == (u64) (assetID & 0xFFFFFFFF)) && (ResInfoTable[r].resType == assetType)) + return ResInfoTable[r]; + } + + return SResInfo(); +} + +std::vector* CPakFile::getResource(u64 assetID, CFourCC assetType) +{ + SResInfo info = getResourceInfo(assetID, assetType); + + // make sure SResInfo is valid + if ((u64) (info.resID & 0xFFFFFFFF) != (u64) (assetID & 0xFFFFFFFF)) return nullptr; + else return getResource(info); +} + +std::vector* CPakFile::getResource(SResInfo& info) +{ + pak->Seek(info.offset, SEEK_SET); + std::vector *res_buf = new std::vector; + + if (info.compressed) + { + u32 decmp_size = pak->ReadLong(); + res_buf->resize(decmp_size); + + std::vector cmp_buf(info.size - 4); + pak->ReadBytes(&cmp_buf[0], info.size - 4); + + bool dcmp = decompress(cmp_buf.data(), cmp_buf.size(), res_buf->data(), res_buf->size()); + + if (!dcmp) { + std::cout << "Error: Unable to decompress " << info.resType.ToString() << " 0x" << std::hex << std::setw(8) << std::setfill('0') << info.resID << std::dec << "\n"; + delete res_buf; + return nullptr; + } + } + + else { + res_buf->resize(info.size); + pak->ReadBytes(res_buf->data(), info.size); + } + + return res_buf; +} + +bool CPakFile::decompress(u8 *src, u32 src_len, u8 *dst, u32 dst_len) +{ + if ((src[0] == 0x78) && (src[1] == 0xda)) + { + // zlib + z_stream z; + z.zalloc = Z_NULL; + z.zfree = Z_NULL; + z.opaque = Z_NULL; + z.avail_in = src_len; + z.next_in = src; + z.avail_out = dst_len; + z.next_out = dst; + + s32 ret = inflateInit(&z); + + if (ret == Z_OK) + { + ret = inflate(&z, Z_NO_FLUSH); + + if ((ret == Z_OK) || (ret == Z_STREAM_END)) + ret = inflateEnd(&z); + } + + if ((ret != Z_OK) && (ret != Z_STREAM_END)) { + std::cout << "zlib error: " << std::dec << ret << "\n"; + return false; + } + + else return true; + } + + else { + // LZO + lzo_uint decmp; + s32 ret; + u8 *src_end = src + src_len; + u8 *dst_end = dst + dst_len; + lzo_init(); + + while ((src < src_end) && (dst < dst_end)) { + short block_size; + memcpy(&block_size, src, 2); + if (IOUtil::SystemEndianness == IOUtil::LittleEndian) IOUtil::SwapBytes(block_size); + src += 2; + + ret = lzo1x_decompress(src, block_size, dst, &decmp, LZO1X_MEM_DECOMPRESS); + if (ret != LZO_E_OK) break; + src += block_size; + dst += decmp; + } + + if (ret != LZO_E_OK) { + std::cout << "LZO error: " << std::dec << ret << "\n"; + return false; + } + + else return true; + } + + return false; +} diff --git a/Resource/CPakFile.h b/Resource/CPakFile.h new file mode 100644 index 00000000..862295df --- /dev/null +++ b/Resource/CPakFile.h @@ -0,0 +1,31 @@ +#ifndef CPAKFILE_H +#define CPAKFILE_H + +#include +#include +#include "SNamedResource.h" +#include "SResInfo.h" +#include + +class CPakFile +{ +private: + u32 version; + std::vector NamedResTable; + std::vector ResInfoTable; + CInputStream* pak; + + bool decompress(u8 *src, u32 src_len, u8 *dst, u32 dst_len); + +public: + CPakFile(); + CPakFile(CInputStream* pakfile); + ~CPakFile(); + + std::vector getNamedResources(); + SResInfo getResourceInfo(u64 assetID, CFourCC assetType); + std::vector* getResource(u64 assetID, CFourCC assetType); + std::vector* getResource(SResInfo& info); +}; + +#endif // CPAKFILE_H diff --git a/Resource/CResource.cpp b/Resource/CResource.cpp new file mode 100644 index 00000000..0b29e93f --- /dev/null +++ b/Resource/CResource.cpp @@ -0,0 +1,96 @@ +#include "CResource.h" +#include +#include + +CResource::CResource() +{ + mRefCount = 0; +} + +CResource::~CResource() +{ +} + +EResType CResource::Type() +{ + return eResource; +} + +std::string CResource::Source() +{ + return StringUtil::GetFileNameWithExtension(mResSource); +} + +std::string CResource::FullSource() +{ + return mResSource; +} + +CUniqueID CResource::ResID() +{ + return mID; +} + +void CResource::Lock() +{ + mRefCount++; +} + +void CResource::Release() +{ + mRefCount--; +} + +bool CResource::IsValidResource() +{ + return (Type() != eResource); +} + +// ************ STATIC ************ +EResType CResource::ResTypeForExtension(CFourCC Extension) +{ + Extension = Extension.ToUpper(); + + if (Extension == "AFSM") return eStateMachine; + if (Extension == "AGSC") return eAudioGrp; + if (Extension == "ANCS") return eCharacter; + if (Extension == "ANIM") return eAnimation; + if (Extension == "ATBL") return eAudioTable; + if (Extension == "CAUD") return eAudioData; + if (Extension == "CINF") return eSkeleton; + if (Extension == "CMDL") return eModel; + if (Extension == "CRSC") return eCollisionResponse; + if (Extension == "CSKR") return eSkin; + if (Extension == "CSMP") return eAudioSample; + if (Extension == "CSNG") return eMidi; + if (Extension == "CTWK") return eTweak; + if (Extension == "DCLN") return eCollisionMesh; + if (Extension == "DGRP") return eDependencyGroup; + if (Extension == "DSP ") return eMusicTrack; + if (Extension == "DUMB") return eDataDump; + if (Extension == "ELSC") return eParticleElectric; + if (Extension == "EVNT") return eAnimEventData; + if (Extension == "FONT") return eFont; + if (Extension == "FRME") return eGuiFrame; + if (Extension == "FSM2") return eStateMachine; + if (Extension == "HINT") return eHintSystem; + if (Extension == "MAPA") return eMapArea; + if (Extension == "MAPW") return eMapWorld; + if (Extension == "MAPU") return eMapUniverse; + if (Extension == "MLVL") return eWorld; + if (Extension == "MREA") return eArea; + if (Extension == "NTWK") return eTweak; + if (Extension == "PAK ") return ePackFile; + if (Extension == "PART") return eParticle; + if (Extension == "PATH") return eNavMesh; + if (Extension == "SAVW") return eSaveWorld; + if (Extension == "SCAN") return eScan; + if (Extension == "STRG") return eStringTable; + if (Extension == "STRM") return eAudioStream; + if (Extension == "SWHC") return eParticleSwoosh; + if (Extension == "THP ") return eVideo; + if (Extension == "TXTR") return eTexture; + if (Extension == "WPSC") return eProjectile; + + return eInvalidResType; +} diff --git a/Resource/CResource.h b/Resource/CResource.h new file mode 100644 index 00000000..bdc15886 --- /dev/null +++ b/Resource/CResource.h @@ -0,0 +1,33 @@ +#ifndef CRESOURCE_H +#define CRESOURCE_H + +#include "EResType.h" +#include +#include +#include +#include + +class CResCache; + +class CResource +{ + friend class CResCache; + + std::string mResSource; + CUniqueID mID; + int mRefCount; + +public: + CResource(); + virtual ~CResource(); + virtual EResType Type(); + std::string Source(); + std::string FullSource(); + CUniqueID ResID(); + void Lock(); + void Release(); + bool IsValidResource(); + static EResType ResTypeForExtension(CFourCC Extension); +}; + +#endif // CRESOURCE_H diff --git a/Resource/CScan.cpp b/Resource/CScan.cpp new file mode 100644 index 00000000..ec076c32 --- /dev/null +++ b/Resource/CScan.cpp @@ -0,0 +1,39 @@ +#include "CScan.h" + +CScan::CScan() +{ + mpFrame = nullptr; + mpStringTable = nullptr; + mIsSlow = false; + mIsImportant = false; + mCategory = eNone; +} + +CScan::~CScan() +{ +} + +EResType CScan::Type() +{ + return eScan; +} + +CStringTable* CScan::ScanText() +{ + return mpStringTable; +} + +bool CScan::IsImportant() +{ + return mIsImportant; +} + +bool CScan::IsSlow() +{ + return mIsSlow; +} + +CScan::ELogbookCategory CScan::LogbookCategory() +{ + return mCategory; +} diff --git a/Resource/CScan.h b/Resource/CScan.h new file mode 100644 index 00000000..26a29dd2 --- /dev/null +++ b/Resource/CScan.h @@ -0,0 +1,42 @@ +#ifndef CSCAN_H +#define CSCAN_H + +#include "CResource.h" +#include "CStringTable.h" +#include + +class CScan : public CResource +{ + friend class CScanLoader; + +public: + // This likely needs revising when MP2/MP3 support is added + enum ELogbookCategory + { + eNone, + ePirateData, + eChozoLore, + eCreatures, + eResearch + }; + +private: + CResource *mpFrame; + CStringTable *mpStringTable; + CToken mFrameToken; + CToken mStringToken; + bool mIsSlow; + bool mIsImportant; + ELogbookCategory mCategory; + +public: + CScan(); + ~CScan(); + EResType Type(); + CStringTable* ScanText(); + bool IsImportant(); + bool IsSlow(); + ELogbookCategory LogbookCategory(); +}; + +#endif // CSCAN_H diff --git a/Resource/CStringTable.cpp b/Resource/CStringTable.cpp new file mode 100644 index 00000000..dbdbc8d8 --- /dev/null +++ b/Resource/CStringTable.cpp @@ -0,0 +1,57 @@ +#include "CStringTable.h" + +CStringTable::CStringTable() : CResource() +{ +} + +CStringTable::~CStringTable() +{ +} + +EResType CStringTable::Type() +{ + return eStringTable; +} + +CResource* CStringTable::MakeCopy(CResCache*) +{ + // Not using parameter 1 (CResCache* - pResCache) + return new CStringTable(*this); +} + +// ************ SETTERS ************ +u32 CStringTable::GetStringCount() +{ + return mNumStrings; +} + +u32 CStringTable::GetLangCount() +{ + return mLangTables.size(); +} + +CFourCC CStringTable::GetLangTag(u32 Index) +{ + return mLangTables[Index].Language; +} + +std::wstring CStringTable::GetString(CFourCC Lang, u32 StringIndex) +{ + for (u32 iLang = 0; iLang < GetLangCount(); iLang++) + { + if (GetLangTag(iLang) == Lang) + return GetString(iLang, StringIndex); + } + + return std::wstring(); +} + +std::wstring CStringTable::GetString(u32 LangIndex, u32 StringIndex) +{ + return mLangTables[LangIndex].Strings[StringIndex]; +} + +std::string CStringTable::GetStringName(u32 StringIndex) +{ + return mStringNames[StringIndex]; +} diff --git a/Resource/CStringTable.h b/Resource/CStringTable.h new file mode 100644 index 00000000..c9d5d26b --- /dev/null +++ b/Resource/CStringTable.h @@ -0,0 +1,39 @@ +#ifndef CSTRINGTABLE_H +#define CSTRINGTABLE_H + +#include "CResource.h" +#include +#include +#include +#include + +class CStringTable : public CResource +{ + friend class CStringLoader; + + std::vector mStringNames; + u32 mNumStrings; + + struct SLangTable + { + CFourCC Language; + std::vector Strings; + }; + std::vector mLangTables; + +public: + CStringTable(); + ~CStringTable(); + EResType Type(); + CResource* MakeCopy(CResCache *pCopyCache); + + // Getters + u32 GetStringCount(); + u32 GetLangCount(); + CFourCC GetLangTag(u32 Index); + std::wstring GetString(CFourCC Lang, u32 StringIndex); + std::wstring GetString(u32 LangIndex, u32 StringIndex); + std::string GetStringName(u32 StringIndex); +}; + +#endif // CSTRINGTABLE_H diff --git a/Resource/CTexture.cpp b/Resource/CTexture.cpp new file mode 100644 index 00000000..b5b4d4c5 --- /dev/null +++ b/Resource/CTexture.cpp @@ -0,0 +1,337 @@ +#include "CTexture.h" + +CTexture::CTexture() : CResource() +{ + mTexelFormat = eRGBA8; + mSourceTexelFormat = eRGBA8; + mWidth = 0; + mHeight = 0; + mNumMipMaps = 0; + mLinearSize = 0; + + mBufferExists = false; + mImgDataBuffer = nullptr; + mImgDataSize = 0; + + mGLBufferExists = false; +} + +CTexture::CTexture(const CTexture& Source) +{ + mTexelFormat = Source.mTexelFormat; + mSourceTexelFormat = Source.mSourceTexelFormat; + mWidth = Source.mWidth; + mHeight = Source.mHeight; + mLinearSize = Source.mLinearSize; + + mBufferExists = Source.mBufferExists; + mImgDataSize = Source.mImgDataSize; + mImgDataBuffer = new u8[mImgDataSize]; + memcpy(mImgDataBuffer, Source.mImgDataBuffer, mImgDataSize); + + mGLBufferExists = false; +} + +CTexture::CTexture(u32 Width, u32 Height) +{ + mTexelFormat = eRGBA8; + mSourceTexelFormat = eRGBA8; + mWidth = (u16) Width; + mHeight = (u16) Height; + mNumMipMaps = 1; + mLinearSize = Width * Height * 4; + + mBufferExists = false; + mImgDataBuffer = nullptr; + mImgDataSize = 0; + + mGLBufferExists = false; +} + +CTexture::~CTexture() +{ + DeleteBuffers(); +} + +EResType CTexture::Type() +{ + return eTexture; +} + +bool CTexture::BufferGL() +{ + glGenTextures(1, &mTextureID); + glBindTexture(GL_TEXTURE_2D, mTextureID); + + GLenum GLFormat, GLType; + bool IsCompressed = false; + + switch (mTexelFormat) { + + case eLuminance: + GLFormat = GL_LUMINANCE; + GLType = GL_UNSIGNED_BYTE; + break; + case eLuminanceAlpha: + GLFormat = GL_LUMINANCE_ALPHA; + GLType = GL_UNSIGNED_BYTE; + break; + case eRGB565: + GLFormat = GL_RGB; + GLType = GL_UNSIGNED_SHORT_5_6_5; + break; + case eRGBA4: + GLFormat = GL_RGBA; + GLType = GL_UNSIGNED_SHORT_4_4_4_4; + break; + case eRGBA8: + GLFormat = GL_RGBA; + GLType = GL_UNSIGNED_BYTE; + break; + case eDXT1: + GLFormat = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT; + IsCompressed = true; + break; + } + + // The smallest mipmaps are probably not being loaded correctly, because mipmaps in GX textures have a minimum size depending on the format, and these don't. + // Not sure specifically what accomodations should be made to fix that though so whatever. + u32 MipSize = mLinearSize; + u32 MipOffset = 0; + u16 MipW = mWidth, MipH = mHeight; + + for (u32 iMip = 0; iMip < mNumMipMaps; iMip++) + { + GLvoid *pData = (mBufferExists) ? (mImgDataBuffer + MipOffset) : NULL; + + if (!IsCompressed) + glTexImage2D(GL_TEXTURE_2D, iMip, GLFormat, MipW, MipH, 0, GLFormat, GLType, pData); + else + glCompressedTexImage2D(GL_TEXTURE_2D, iMip, GLFormat, MipW, MipH, 0, MipSize, pData); + + MipW /= 2; + MipH /= 2; + MipOffset += MipSize; + MipSize /= 4; + } + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, mNumMipMaps - 1); + + // Linear filtering on mipmaps: + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + + // Anisotropic filtering: + float MaxAnisotropy; + glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &MaxAnisotropy); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, MaxAnisotropy); + + mGLBufferExists = true; + return true; +} + +void CTexture::Bind(u32 GLTextureUnit) +{ + glActiveTexture(GL_TEXTURE0 + GLTextureUnit); + + if (!mGLBufferExists) + BufferGL(); + + glBindTexture(GL_TEXTURE_2D, mTextureID); +} + +void CTexture::Resize(u32 Width, u32 Height) +{ + if ((mWidth != Width) || (mHeight != Height)) + { + DeleteBuffers(); + mWidth = (u16) Width; + mHeight = (u16) Height; + mNumMipMaps = 1; + } +} + +bool CTexture::WriteDDS(COutputStream& out) +{ + if (!out.IsValid()) return false; + + CopyGLBuffer(); + + out.WriteString("DDS ", 4); // "DDS " fourCC + out.WriteLong(0x7C); // dwSize + out.WriteLong(0x21007); // dwFlags + out.WriteLong(mHeight); // dwHeight + out.WriteLong(mWidth); // dwWidth + out.WriteLong(mLinearSize); // dwPitchOrLinearSize + out.WriteLong(0); // dwDepth + out.WriteLong(mNumMipMaps - 1); // dwMipMapCount + + for (u32 i = 0; i < 11; i++) + out.WriteLong(0); // dwReserved1[11] + + // DDS_PIXELFORMAT + out.WriteLong(32); // DDS_PIXELFORMAT.dwSize + + u32 pfFlags = 0, pfBpp = 0, pfRBitMask = 0, pfGBitMask = 0, pfBBitMask = 0, pfABitMask = 0; + switch (mTexelFormat) { + case eLuminance: + pfFlags = 0x20000; + pfBpp = 0x8; + pfRBitMask = 0xFF; + break; + case eLuminanceAlpha: + pfFlags = 0x20001; + pfBpp = 0x10; + pfRBitMask = 0x00FF; + pfABitMask = 0xFF00; + break; + case eRGBA4: + pfFlags = 0x41; + pfBpp = 0x10; + pfRBitMask = 0x0F00; + pfGBitMask = 0x00F0; + pfBBitMask = 0x000F; + pfABitMask = 0xF000; + break; + case eRGB565: + pfFlags = 0x40; + pfBpp = 0x10; + pfRBitMask = 0xF800; + pfGBitMask = 0x7E0; + pfBBitMask = 0x1F; + break; + case eRGBA8: + pfFlags = 0x41; + pfBpp = 0x20; + pfRBitMask = 0x00FF0000; + pfGBitMask = 0x0000FF00; + pfBBitMask = 0x000000FF; + pfABitMask = 0xFF000000; + break; + case eDXT1: + pfFlags = 0x4; + break; + } + + out.WriteLong(pfFlags); // DDS_PIXELFORMAT.dwFlags + (mTexelFormat == eDXT1) ? out.WriteString("DXT1", 4) : out.WriteLong(0); // DDS_PIXELFORMAT.dwFourCC + out.WriteLong(pfBpp); // DDS_PIXELFORMAT.dwRGBBitCount + out.WriteLong(pfRBitMask); // DDS_PIXELFORMAT.dwRBitMask + out.WriteLong(pfGBitMask); // DDS_PIXELFORMAT.dwGBitMask + out.WriteLong(pfBBitMask); // DDS_PIXELFORMAT.dwBBitMask + out.WriteLong(pfABitMask); // DDS_PIXELFORMAT.dwABitMask + + out.WriteLong(0x401000); // dwCaps + out.WriteLong(0); // dwCaps2 + out.WriteLong(0); // dwCaps3 + out.WriteLong(0); // dwCaps4 + out.WriteLong(0); // dwReserved2 + + out.WriteBytes(mImgDataBuffer, mImgDataSize); // Image data + return true; +} + +// ************ STATIC ************ +u32 CTexture::FormatBPP(ETexelFormat Format) +{ + switch (Format) + { + case eGX_I4: return 4; + case eGX_I8: return 8; + case eGX_IA4: return 8; + case eGX_IA8: return 16; + case eGX_C4: return 4; + case eGX_C8: return 8; + case eGX_RGB565: return 16; + case eGX_RGB5A3: return 16; + case eGX_RGBA8: return 32; + case eGX_CMPR: return 4; + case eLuminance: return 8; + case eLuminanceAlpha: return 16; + case eRGBA4: return 16; + case eRGB565: return 16; + case eRGBA8: return 32; + case eDXT1: return 4; + default: return 0; + } +} + +// ************ PRIVATE ************ +void CTexture::CalcLinearSize() +{ + float BytesPerPixel = FormatBPP(mTexelFormat) / 8.f; + mLinearSize = mWidth * mHeight * BytesPerPixel; +} + +u32 CTexture::CalcTotalSize() +{ + float BytesPerPixel = FormatBPP(mTexelFormat) / 8.f; + u32 MipW = mWidth, MipH = mHeight; + u32 Size = 0; + + for (u32 iMip = 0; iMip < mNumMipMaps; iMip++) + { + Size += MipW * MipH * BytesPerPixel; + MipW /= 2; + MipH /= 2; + } + + return Size; +} + +void CTexture::CopyGLBuffer() +{ + if (!mGLBufferExists) return; + + // Clear existing buffer + if (mBufferExists) + { + delete[] mImgDataBuffer; + mBufferExists = false; + mImgDataBuffer = nullptr; + mImgDataSize = 0; + } + + // Calculate buffer size + mImgDataSize = CalcTotalSize(); + mImgDataBuffer = new u8[mImgDataSize]; + mBufferExists = true; + + // Get texture + u32 MipW = mWidth, MipH = mHeight, MipOffset = 0; + float BytesPerPixel = FormatBPP(mTexelFormat) / 8.f; + + glBindTexture(GL_TEXTURE_2D, mTextureID); + + for (u32 iMip = 0; iMip < mNumMipMaps; iMip++) + { + void *pData = mImgDataBuffer + MipOffset; + + glGetTexImage(GL_TEXTURE_2D, iMip, GL_RGBA, GL_UNSIGNED_BYTE, pData); + + MipOffset += MipW * MipH * BytesPerPixel; + MipW /= 2; + MipH /= 2; + } + + mTexelFormat = eRGBA8; + mLinearSize = mWidth * mHeight * 4; +} + +void CTexture::DeleteBuffers() +{ + if (mBufferExists) + { + delete[] mImgDataBuffer; + mBufferExists = false; + mImgDataBuffer = nullptr; + mImgDataSize = 0; + } + + if (mGLBufferExists) + { + glDeleteTextures(1, &mTextureID); + mGLBufferExists = false; + } +} diff --git a/Resource/CTexture.h b/Resource/CTexture.h new file mode 100644 index 00000000..6330e5a0 --- /dev/null +++ b/Resource/CTexture.h @@ -0,0 +1,83 @@ +#ifndef CTEXTURE_H +#define CTEXTURE_H + +#include +#include +#include +#include "CResource.h" +#include "ETexelFormat.h" + +class CTexture : public CResource +{ + friend class CTextureDecoder; + friend class CTextureEncoder; + + ETexelFormat mTexelFormat; // Format of decoded image data + ETexelFormat mSourceTexelFormat; // Format of input TXTR file + u16 mWidth, mHeight; // Image dimensions + u32 mNumMipMaps; // The number of mipmaps this texture has + u32 mLinearSize; // The size of the top level mipmap, in bytes + + bool mBufferExists; // Boolean that indicates whether image data buffer has valid data + u8 *mImgDataBuffer; // Pointer to image data buffer + u32 mImgDataSize; // Size of image data buffer + + bool mGLBufferExists; // Boolean that indicates whether GL buffer has valid data + GLuint mTextureID; // ID for texture GL buffer + +public: + CTexture(); + CTexture(const CTexture& Source); + CTexture(u32 Width, u32 Height); + ~CTexture(); + EResType Type(); + + bool BufferGL(); + void Bind(u32 GLTextureUnit); + void Resize(u32 Width, u32 Height); + bool WriteDDS(COutputStream& out); + + // Getters + ETexelFormat TexelFormat(); + ETexelFormat SourceTexelFormat(); + u32 Width(); + u32 Height(); + u32 NumMipMaps(); + GLuint TextureID(); + + // Static + static u32 FormatBPP(ETexelFormat Format); + + // Private +private: + void CalcLinearSize(); + u32 CalcTotalSize(); + void CopyGLBuffer(); + void DeleteBuffers(); +}; + +inline ETexelFormat CTexture::TexelFormat() { + return mTexelFormat; +} + +inline ETexelFormat CTexture::SourceTexelFormat() { + return mSourceTexelFormat; +} + +inline u32 CTexture::Width() { + return (u32) mWidth; +} + +inline u32 CTexture::Height() { + return (u32) mHeight; +} + +inline u32 CTexture::NumMipMaps() { + return mNumMipMaps; +} + +inline GLuint CTexture::TextureID() { + return mTextureID; +} + +#endif // CTEXTURE_H diff --git a/Resource/CWorld.cpp b/Resource/CWorld.cpp new file mode 100644 index 00000000..2a92ae5e --- /dev/null +++ b/Resource/CWorld.cpp @@ -0,0 +1,101 @@ +#include "CWorld.h" +#include + +CWorld::CWorld() : CResource() +{ + mWorldVersion = eUnknownVersion; + mpWorldName = nullptr; + mpDarkWorldName = nullptr; + mpSaveWorld = nullptr; + mpDefaultSkybox = nullptr; + mpMapWorld = nullptr; +} + +CWorld::~CWorld() +{ +} + +EResType CWorld::Type() +{ + return eWorld; +} + +void CWorld::SetAreaLayerInfo(CGameArea *pArea, u32 AreaIndex) +{ + // The AreaIndex parameter is a placeholder until an improved world loader is implemented. + // For now it's the easiest/fastest way to do this because this function is called from + // the start window and the start window already knows the area index. + SArea& AreaInfo = mAreas[AreaIndex]; + + for (u32 iLyr = 0; iLyr < pArea->GetScriptLayerCount(); iLyr++) + { + CScriptLayer *pLayer = pArea->GetScriptLayer(iLyr); + SArea::SLayer& LayerInfo = AreaInfo.Layers[iLyr]; + + pLayer->SetName(LayerInfo.LayerName); + pLayer->SetActive(LayerInfo.EnabledByDefault); + } +} + +// ************ GETTERS ************ +// World +EGame CWorld::Version() +{ + return mWorldVersion; +} + +CStringTable* CWorld::GetWorldName() +{ + return mpWorldName; +} + +CStringTable* CWorld::GetDarkWorldName() +{ + return mpDarkWorldName; +} + +CResource* CWorld::GetSaveWorld() +{ + return mpSaveWorld; +} + +CModel* CWorld::GetDefaultSkybox() +{ + return mpDefaultSkybox; +} + +CResource* CWorld::GetMapWorld() +{ + return mpMapWorld; +} + +// Area +u32 CWorld::GetNumAreas() +{ + return mAreas.size(); +} + +u64 CWorld::GetAreaResourceID(u32 AreaIndex) +{ + return mAreas[AreaIndex].FileID; +} + +u32 CWorld::GetAreaAttachedCount(u32 AreaIndex) +{ + return mAreas[AreaIndex].AttachedAreaIDs.size(); +} + +u32 CWorld::GetAreaAttachedID(u32 AreaIndex, u32 AttachedIndex) +{ + return (u32) mAreas[AreaIndex].AttachedAreaIDs[AttachedIndex]; +} + +std::string CWorld::GetAreaInternalName(u32 AreaIndex) +{ + return mAreas[AreaIndex].InternalName; +} + +CStringTable* CWorld::GetAreaName(u32 AreaIndex) +{ + return mAreas[AreaIndex].pAreaName; +} diff --git a/Resource/CWorld.h b/Resource/CWorld.h new file mode 100644 index 00000000..d04fc639 --- /dev/null +++ b/Resource/CWorld.h @@ -0,0 +1,107 @@ +#ifndef CWORLD_H +#define CWORLD_H + +#include "CResource.h" +#include "CGameArea.h" +#include "CStringTable.h" +#include "SDependency.h" +#include "model/CModel.h" +#include + +class CWorld : public CResource +{ + friend class CWorldLoader; + + // Instances of CResource pointers are placeholders for unimplemented resource types (eg CMapWorld) + EGame mWorldVersion; + CStringTable *mpWorldName; + CStringTable *mpDarkWorldName; + CResource *mpSaveWorld; + CModel *mpDefaultSkybox; + CResource *mpMapWorld; + CToken mResTokens[5]; + + u32 mUnknown1; + u32 mUnknownAreas; + + u32 mUnknownAGSC; + struct SAudioGrp + { + u32 ResID; + u32 Unknown; + }; + std::vector mAudioGrps; + + struct SMemoryRelay + { + u32 InstanceID; + u32 TargetID; + u16 Message; + u8 Unknown; + }; + std::vector mMemoryRelays; + + struct SArea + { + std::string InternalName; + CStringTable *pAreaName; + CTransform4f Transform; + CAABox AetherBox; + u64 FileID; // Loading every single area as a CResource would be a very bad idea + u64 AreaID; + CToken AreaNameToken; + + std::vector AttachedAreaIDs; + std::vector Dependencies; + std::vector RelFilenames; + std::vector RelOffsets; + u32 CommonDependenciesStart; + + struct SDock + { + struct SConnectingDock + { + u32 AreaIndex; + u32 DockIndex; + }; + std::vector ConnectingDocks; + CVector3f DockCoordinates[4]; + }; + std::vector Docks; + + struct SLayer + { + std::string LayerName; + bool EnabledByDefault; + u8 LayerID[16]; + u32 LayerDependenciesStart; // Offset into Dependencies vector + }; + std::vector Layers; + }; + std::vector mAreas; + + +public: + CWorld(); + ~CWorld(); + EResType Type(); + + void SetAreaLayerInfo(CGameArea *pArea, u32 AreaIndex); + + // Setters + EGame Version(); + CStringTable* GetWorldName(); + CStringTable* GetDarkWorldName(); + CResource* GetSaveWorld(); + CModel* GetDefaultSkybox(); + CResource* GetMapWorld(); + + u32 GetNumAreas(); + u64 GetAreaResourceID(u32 AreaIndex); + u32 GetAreaAttachedCount(u32 AreaIndex); + u32 GetAreaAttachedID(u32 AreaIndex, u32 AttachedIndex); + std::string GetAreaInternalName(u32 AreaIndex); + CStringTable* GetAreaName(u32 AreaIndex); +}; + +#endif // CWORLD_H diff --git a/Resource/EFormatVersion.h b/Resource/EFormatVersion.h new file mode 100644 index 00000000..cbd1a0ba --- /dev/null +++ b/Resource/EFormatVersion.h @@ -0,0 +1,18 @@ +#ifndef EFORMATVERSION_H +#define EFORMATVERSION_H + +// Global version enum that can be easily shared between loaders +enum EGame +{ + ePrimeKioskDemo = 0, + ePrime = 1, + eEchoesDemo = 2, + eEchoes = 3, + eCorruptionProto = 4, + eCorruption = 5, + eReturns = 6, + TropicalFreeze = 7, + eUnknownVersion = -1 +}; + +#endif // EFORMATVERSION_H diff --git a/Resource/EResType.h b/Resource/EResType.h new file mode 100644 index 00000000..a5137357 --- /dev/null +++ b/Resource/EResType.h @@ -0,0 +1,50 @@ +#ifndef ERESTYPE +#define ERESTYPE + +enum EResType +{ + eAnimation = 0, + eAnimEventData = 1, + eArea = 2, + eAudioData = 3, + eAudioGrp = 4, + eAudioSample = 5, + eAudioStream = 6, + eAudioTable = 7, + eCharacter = 8, + eCollisionMesh = 9, + eCollisionResponse = 10, + eDataDump = 11, + eDecal = 12, + eDependencyGroup = 13, + eFont = 14, + eGuiFrame = 15, + eHintSystem = 16, + eInvalidResType = 17, + eMapArea = 18, + eMapWorld = 19, + eMapUniverse = 20, + eMidi = 21, + eModel = 22, + eMusicTrack = 23, + eNavMesh = 24, + ePackFile = 25, + eParticle = 26, + eParticleElectric = 27, + eParticleSwoosh = 28, + eProjectile = 29, + eResource = 30, + eSaveWorld = 31, + eScan = 32, + eSkeleton = 33, + eSkin = 34, + eStateMachine = 35, + eStringTable = 36, + eTexture = 37, + eTweak = 38, + eVideo = 39, + eWorld = 40 +}; + +#endif // ERESTYPE + diff --git a/Resource/ETevEnums.h b/Resource/ETevEnums.h new file mode 100644 index 00000000..0e19b053 --- /dev/null +++ b/Resource/ETevEnums.h @@ -0,0 +1,106 @@ +#ifndef ETEVENUMS +#define ETEVENUMS + +enum ETevColorInput +{ + ePrevRGB = 0x0, + ePrevAAA = 0x1, + eColor0RGB = 0x2, + eColor0AAA = 0x3, + eColor1RGB = 0x4, + eColor1AAA = 0x5, + eColor2RGB = 0x6, + eColor2AAA = 0x7, + eTextureRGB = 0x8, + eTextureAAA = 0x9, + eRasRGB = 0xA, + eRasAAA = 0xB, + eOneRGB = 0xC, + eHalfRGB = 0xD, + eKonstRGB = 0xE, + eZeroRGB = 0xF +}; + +enum ETevAlphaInput +{ + ePrevAlpha = 0x0, + eColor0Alpha = 0x1, + eColor1Alpha = 0x2, + eColor2Alpha = 0x3, + eTextureAlpha = 0x4, + eRasAlpha = 0x5, + eKonstAlpha = 0x6, + eZeroAlpha = 0x7 +}; + +enum ETevOutput +{ + ePrevReg = 0x0, + eColor0Reg = 0x1, + eColor1Reg = 0x2, + eColor2Reg = 0x3 +}; + +enum ETevKSel +{ + eKonstOne = 0x0, + eKonstSevenEighths = 0x1, + eKonstThreeFourths = 0x2, + eKonstFiveEighths = 0x3, + eKonstOneHalf = 0x4, + eKonstThreeEighths = 0x5, + eKonstOneFourth = 0x6, + eKonstOneEighth = 0x7, + eKonst0_RGB = 0xC, + eKonst1_RGB = 0xD, + eKonst2_RGB = 0xE, + eKonst3_RGB = 0xF, + eKonst0_R = 0x10, + eKonst1_R = 0x11, + eKonst2_R = 0x12, + eKonst3_R = 0x13, + eKonst0_G = 0x14, + eKonst1_G = 0x15, + eKonst2_G = 0x16, + eKonst3_G = 0x17, + eKonst0_B = 0x18, + eKonst1_B = 0x19, + eKonst2_B = 0x1A, + eKonst3_B = 0x1B, + eKonst0_A = 0x1C, + eKonst1_A = 0x1D, + eKonst2_A = 0x1E, + eKonst3_A = 0x1F +}; + +enum ETevRasSel +{ + eRasColor0 = 0x0, + eRasColor1 = 0x1, + eRasAlpha0 = 0x2, + eRasAlpha1 = 0x3, + eRasColor0A0 = 0x4, + eRasColor1A1 = 0x5, + eRasColorZero = 0x6, + eRasAlphaBump = 0x7, + eRasAlphaBumpN = 0x8, + eRasColorNull = 0xFF +}; + +enum EUVAnimMode +{ + eInverseMV = 0x0, + eInverseMVTranslated = 0x1, + eUVScroll = 0x2, + eUVRotation = 0x3, + eHFilmstrip = 0x4, + eVFilmstrip = 0x5, + eModelMatrix = 0x6, + eConvolutedModeA = 0x7, + eConvolutedModeB = 0x8, + eSimpleMode = 0xA, + eNoUVAnim = 0xFFFFFFFF +}; + +#endif // ETEVENUMS + diff --git a/Resource/ETexelFormat.h b/Resource/ETexelFormat.h new file mode 100644 index 00000000..a9c7b01f --- /dev/null +++ b/Resource/ETexelFormat.h @@ -0,0 +1,38 @@ +#ifndef ETEXELFORMAT +#define ETEXELFORMAT + +// ETexelFormat - supported internal formats for decoded textures +enum ETexelFormat +{ + // Supported texel formats in GX using Retro's numbering + eGX_I4 = 0x0, + eGX_I8 = 0x1, + eGX_IA4 = 0x2, + eGX_IA8 = 0x3, + eGX_C4 = 0x4, + eGX_C8 = 0x5, + eGX_C14x2 = 0x6, + eGX_RGB565 = 0x7, + eGX_RGB5A3 = 0x8, + eGX_RGBA8 = 0x9, + eGX_CMPR = 0xA, + // Supported internal texel formats for decoded textures + eLuminance, + eLuminanceAlpha, + eRGBA4, + eRGB565, + eRGBA8, + eDXT1, + eInvalidTexelFormat +}; + +// EGXPaletteFormat - GX's supported palette texel formats for C4/C8 +enum EGXPaletteFormat +{ + ePalette_IA8 = 0, + ePalette_RGB565 = 1, + ePalette_RGB5A3 = 2 +}; + +#endif // ETEXELFORMAT + diff --git a/Resource/SDependency.h b/Resource/SDependency.h new file mode 100644 index 00000000..9c0c29bd --- /dev/null +++ b/Resource/SDependency.h @@ -0,0 +1,14 @@ +#ifndef SDEPENDENCY +#define SDEPENDENCY + +#include +#include + +struct SDependency +{ + u64 ResID; + CFourCC ResType; +}; + +#endif // SDEPENDENCY + diff --git a/Resource/SNamedResource.h b/Resource/SNamedResource.h new file mode 100644 index 00000000..e675d399 --- /dev/null +++ b/Resource/SNamedResource.h @@ -0,0 +1,14 @@ +#ifndef SNAMEDRESOURCE_H +#define SNAMEDRESOURCE_H + +#include +#include + +struct SNamedResource +{ + CFourCC resType; + std::string resName; + u64 resID; +}; + +#endif // SNAMEDRESOURCE_H diff --git a/Resource/SResInfo.h b/Resource/SResInfo.h new file mode 100644 index 00000000..518a27f4 --- /dev/null +++ b/Resource/SResInfo.h @@ -0,0 +1,16 @@ +#ifndef SRESINFO_H +#define SRESINFO_H + +#include +#include + +struct SResInfo +{ + bool compressed; + CFourCC resType; + u64 resID; + u32 offset; + u32 size; +}; + +#endif // SRESINFO_H diff --git a/Resource/cooker/CMaterialCooker.cpp b/Resource/cooker/CMaterialCooker.cpp new file mode 100644 index 00000000..6dca4472 --- /dev/null +++ b/Resource/cooker/CMaterialCooker.cpp @@ -0,0 +1,364 @@ +#include "CMaterialCooker.h" + +#include + +CMaterialCooker::CMaterialCooker() +{ + mpMat = nullptr; +} + +void CMaterialCooker::WriteMatSetPrime(COutputStream& Out) +{ + // Gather texture list from the materials before starting + mTextureIDs.clear(); + u32 NumMats = mpSet->materials.size(); + + for (u32 iMat = 0; iMat < NumMats; iMat++) + { + CMaterial *pMat = mpSet->materials[iMat]; + + u32 NumPasses = pMat->PassCount(); + for (u32 iPass = 0; iPass < NumPasses; iPass++) + { + CTexture *pTex = pMat->Pass(iPass)->Texture(); + if (pTex) + mTextureIDs.push_back(pTex->ResID().ToLong()); + } + } + + // Sort/remove duplicates + std::sort(mTextureIDs.begin(), mTextureIDs.end()); + mTextureIDs.erase(std::unique(mTextureIDs.begin(), mTextureIDs.end()), mTextureIDs.end()); + + // Write texture IDs + Out.WriteLong(mTextureIDs.size()); + + for (u32 iTex = 0; iTex < mTextureIDs.size(); iTex++) + Out.WriteLong(mTextureIDs[iTex]); + + // Write material offset filler + Out.WriteLong(NumMats); + u32 MatOffsetsStart = Out.Tell(); + + for (u32 iMat = 0; iMat < NumMats; iMat++) + Out.WriteLong(0); + + // Write materials + u32 MatsStart = Out.Tell(); + std::vector MatEndOffsets(NumMats); + + for (u32 iMat = 0; iMat < NumMats; iMat++) + { + mpMat = mpSet->materials[iMat]; + WriteMaterialPrime(Out); + MatEndOffsets[iMat] = Out.Tell() - MatsStart; + } + + // Write material offsets + u32 MatsEnd = Out.Tell(); + Out.Seek(MatOffsetsStart, SEEK_SET); + + for (u32 iMat = 0; iMat < NumMats; iMat++) + Out.WriteLong(MatEndOffsets[iMat]); + + // Done! + Out.Seek(MatsEnd, SEEK_SET); +} + +void CMaterialCooker::WriteMatSetCorruption(COutputStream&) +{ + // Not using parameter 1 (COutputStream& - Out) + // todo +} + +void CMaterialCooker::WriteMaterialPrime(COutputStream& Out) +{ + // Gather data from the passes before we start writing + u32 TexFlags = 0; + u32 NumKonst = 0; + std::vector TexIndices; + + for (u32 iPass = 0; iPass < mpMat->mPasses.size(); iPass++) + { + CMaterialPass *pPass = mpMat->Pass(iPass); + + if ((pPass->KColorSel() >= 0xC) || (pPass->KAlphaSel() >= 0x10)) + { + // Determine the highest Konst index being used + u32 KColorIndex = pPass->KColorSel() % 4; + u32 KAlphaIndex = pPass->KAlphaSel() % 4; + + if (KColorIndex >= NumKonst) + NumKonst = KColorIndex + 1; + if (KAlphaIndex >= NumKonst) + NumKonst = KAlphaIndex + 1; + } + + CTexture *pPassTex = pPass->Texture(); + if (pPassTex != nullptr) + { + TexFlags |= (1 << iPass); + u32 TexID = pPassTex->ResID().ToLong(); + + for (u32 iTex = 0; iTex < mTextureIDs.size(); iTex++) + { + if (mTextureIDs[iTex] == TexID) + { + TexIndices.push_back(iTex); + break; + } + } + } + } + + // Get group index + u32 GroupIndex; + u64 MatHash = mpMat->HashParameters(); + bool NewHash = true; + + for (u32 iHash = 0; iHash < mMaterialHashes.size(); iHash++) + { + if (mMaterialHashes[iHash] == MatHash) + { + GroupIndex = iHash; + NewHash = false; + break; + } + } + + if (NewHash) + { + GroupIndex = mMaterialHashes.size(); + mMaterialHashes.push_back(MatHash); + } + + // Start writing! + // Generate flags value + bool HasKonst = (NumKonst > 0); + u32 Flags; + + if (mVersion <= ePrime) + Flags = 0x1003; + else + Flags = 0x4002; + + Flags |= (HasKonst << 3) | mpMat->Options() | (TexFlags << 16); + + Out.WriteLong(Flags); + + // Texture indices + Out.WriteLong(TexIndices.size()); + for (u32 iTex = 0; iTex < TexIndices.size(); iTex++) + Out.WriteLong(TexIndices[iTex]); + + // Vertex description + EVertexDescription Desc = mpMat->VtxDesc(); + + if (mVersion < eEchoes) + Desc = (EVertexDescription) (Desc & 0x00FFFFFF); + + Out.WriteLong(Desc); + + // Echoes unknowns + if (mVersion == eEchoes) + { + Out.WriteLong(mpMat->EchoesUnknownA()); + Out.WriteLong(mpMat->EchoesUnknownB()); + } + + // Group index + Out.WriteLong(GroupIndex); + + // Konst + if (HasKonst) + { + Out.WriteLong(NumKonst); + for (u32 iKonst = 0; iKonst < NumKonst; iKonst++) + Out.WriteLong( mpMat->Konst(iKonst).AsLongRGBA() ); + } + + // Blend Mode + // Some modifications are done to convert the GLenum to the corresponding GX enum + u16 BlendSrcFac = (u16) mpMat->BlendSrcFac(); + u16 BlendDstFac = (u16) mpMat->BlendDstFac(); + if (BlendSrcFac >= 0x300) BlendSrcFac -= 0x2FE; + if (BlendDstFac >= 0x300) BlendDstFac -= 0x2FE; + Out.WriteShort(BlendDstFac); + Out.WriteShort(BlendSrcFac); + + // Color Channels + Out.WriteLong(1); + Out.WriteLong(0x3000 | mpMat->IsLightingEnabled()); + + // TEV + u32 NumPasses = mpMat->PassCount(); + Out.WriteLong(NumPasses); + + for (u32 iPass = 0; iPass < NumPasses; iPass++) + { + CMaterialPass *pPass = mpMat->Pass(iPass); + + u32 ColorInputFlags = ((pPass->ColorInput(0)) | + (pPass->ColorInput(1) << 5) | + (pPass->ColorInput(2) << 10) | + (pPass->ColorInput(3) << 15)); + u32 AlphaInputFlags = ((pPass->AlphaInput(0)) | + (pPass->AlphaInput(1) << 5) | + (pPass->AlphaInput(2) << 10) | + (pPass->AlphaInput(3) << 15)); + + u32 ColorOpFlags = 0x100 | (pPass->ColorOutput() << 9); + u32 AlphaOpFlags = 0x100 | (pPass->AlphaOutput() << 9); + + Out.WriteLong(ColorInputFlags); + Out.WriteLong(AlphaInputFlags); + Out.WriteLong(ColorOpFlags); + Out.WriteLong(AlphaOpFlags); + Out.WriteByte(0); // Padding + Out.WriteByte(pPass->KAlphaSel()); + Out.WriteByte(pPass->KColorSel()); + Out.WriteByte(pPass->RasSel()); + } + + // TEV Tex/UV input selection + u32 CurTexIdx = 0; + + for (u32 iPass = 0; iPass < NumPasses; iPass++) + { + Out.WriteShort(0); // Padding + + if (mpMat->Pass(iPass)->Texture()) + { + Out.WriteByte((u8) CurTexIdx); + Out.WriteByte((u8) CurTexIdx); + CurTexIdx++; + } + + else + Out.WriteShort(0xFFFF); + } + + // TexGen + u32 NumTexCoords = CurTexIdx; // TexIdx is currently equal to the tex coord count + Out.WriteLong(NumTexCoords); + u32 CurTexMtx = 0; + + for (u32 iPass = 0; iPass < NumPasses; iPass++) + { + CMaterialPass *pPass = mpMat->Pass(iPass); + if (pPass->Texture() == nullptr) continue; + + u32 AnimType = pPass->AnimMode(); + u32 CoordSource = pPass->TexCoordSource(); + + u32 TexMtxIdx, PostMtxIdx; + bool Normalize; + + // No animation - set TexMtx and PostMtx to identity, disable normalization + if (AnimType == eNoUVAnim) + { + TexMtxIdx = 30; + PostMtxIdx = 61; + Normalize = false; + } + + // Animation - set parameters as the animation mode needs them + else + { + TexMtxIdx = CurTexMtx; + + if ((AnimType < 2) || (AnimType > 5)) + { + PostMtxIdx = CurTexMtx; + Normalize = true; + } + else + { + PostMtxIdx = 61; + Normalize = false; + } + CurTexMtx += 3; + } + + u32 TexGenFlags = (CoordSource << 4) | (TexMtxIdx << 9) | (Normalize << 14) | (PostMtxIdx << 15); + Out.WriteLong(TexGenFlags); + } + + // Animations + u32 AnimSizeOffset = Out.Tell(); + u32 NumAnims = CurTexMtx; // CurTexMtx is currently equal to the anim count + Out.WriteLong(0); // Anim size filler + u32 AnimsStart = Out.Tell(); + Out.WriteLong(NumAnims); + + for (u32 iPass = 0; iPass < NumPasses; iPass++) + { + CMaterialPass *pPass = mpMat->Pass(iPass); + u32 AnimMode = pPass->AnimMode(); + if (AnimMode == eNoUVAnim) continue; + + Out.WriteLong(AnimMode); + + if ((AnimMode > 1) && (AnimMode != 6)) + { + Out.WriteFloat(pPass->AnimParam(0)); + Out.WriteFloat(pPass->AnimParam(1)); + + if ((AnimMode == 2) || (AnimMode == 4) || (AnimMode == 5)) + { + Out.WriteFloat(pPass->AnimParam(2)); + Out.WriteFloat(pPass->AnimParam(3)); + } + } + } + + u32 AnimsEnd = Out.Tell(); + u32 AnimsSize = AnimsEnd - AnimsStart; + Out.Seek(AnimSizeOffset, SEEK_SET); + Out.WriteLong(AnimsSize); + Out.Seek(AnimsEnd, SEEK_SET); + + // Done! +} + +void CMaterialCooker::WriteMaterialCorruption(COutputStream&) +{ + // Not using parameter 1 (COutputStream& - Out) + // todo +} + +// ************ STATIC ************ +void CMaterialCooker::WriteCookedMatSet(CMaterialSet *pSet, EGame Version, COutputStream &Out) +{ + CMaterialCooker Cooker; + Cooker.mpSet = pSet; + Cooker.mVersion = Version; + + switch (Version) + { + case ePrimeKioskDemo: + case ePrime: + case eEchoesDemo: + case eEchoes: + Cooker.WriteMatSetPrime(Out); + break; + } +} + +void CMaterialCooker::WriteCookedMaterial(CMaterial *pMat, EGame Version, COutputStream &Out) +{ + CMaterialCooker Cooker; + Cooker.mpMat = pMat; + Cooker.mVersion = Version; + + switch (Version) + { + case ePrimeKioskDemo: + case ePrime: + case eEchoesDemo: + case eEchoes: + Cooker.WriteMaterialPrime(Out); + break; + // TODO: Corruption/Uncooked + } +} diff --git a/Resource/cooker/CMaterialCooker.h b/Resource/cooker/CMaterialCooker.h new file mode 100644 index 00000000..72f1975f --- /dev/null +++ b/Resource/cooker/CMaterialCooker.h @@ -0,0 +1,27 @@ +#ifndef CMATERIALCOOKER_H +#define CMATERIALCOOKER_H + +#include "../CMaterial.h" +#include "../CMaterialSet.h" +#include "../EFormatVersion.h" + +class CMaterialCooker +{ + CMaterialSet *mpSet; + CMaterial *mpMat; + EGame mVersion; + std::vector mTextureIDs; + std::vector mMaterialHashes; + + CMaterialCooker(); + void WriteMatSetPrime(COutputStream& Out); + void WriteMatSetCorruption(COutputStream& Out); + void WriteMaterialPrime(COutputStream& Out); + void WriteMaterialCorruption(COutputStream& Out); + +public: + static void WriteCookedMatSet(CMaterialSet *pSet, EGame Version, COutputStream& Out); + static void WriteCookedMaterial(CMaterial *pMat, EGame Version, COutputStream& Out); +}; + +#endif // CMATERIALCOOKER_H diff --git a/Resource/cooker/CModelCooker.cpp b/Resource/cooker/CModelCooker.cpp new file mode 100644 index 00000000..8d79b5f8 --- /dev/null +++ b/Resource/cooker/CModelCooker.cpp @@ -0,0 +1,275 @@ +#include "CModelCooker.h" +#include "CMaterialCooker.h" +#include "CSectionMgrOut.h" + +#include +#include + +CModelCooker::CModelCooker() +{ +} + +bool SortVertsByArrayPos(const CVertex& A, const CVertex& B) { + return (A.ArrayPosition < B.ArrayPosition); +} + +bool CheckDuplicateVertsByArrayPos(const CVertex& A, const CVertex& B) { + return (A.ArrayPosition == B.ArrayPosition); +} + +void CModelCooker::GenerateSurfaceData() +{ + // Need to gather metadata from the model before we can start + mNumMatSets = mpModel->mMaterialSets.size(); + mNumSurfaces = mpModel->mSurfaces.size(); + mNumVertices = mpModel->mVertexCount; + mVertices.resize(mNumVertices); + + // Get vertex attributes + mVtxAttribs = eNoAttributes; + + for (u32 iMat = 0; iMat < mpModel->GetMatCount(); iMat++) + { + CMaterial *pMat = mpModel->GetMaterialByIndex(0, iMat); + mVtxAttribs |= pMat->VtxDesc(); + } + + // Get vertices + u32 MaxIndex = 0; + + for (u32 iSurf = 0; iSurf < mNumSurfaces; iSurf++) + { + u32 NumPrimitives = mpModel->mSurfaces[iSurf]->Primitives.size(); + + for (u32 iPrim = 0; iPrim < NumPrimitives; iPrim++) + { + SSurface::SPrimitive *pPrim = &mpModel->mSurfaces[iSurf]->Primitives[iPrim]; + u32 NumVerts = pPrim->Vertices.size(); + + for (u32 iVtx = 0; iVtx < NumVerts; iVtx++) + { + u32 VertIndex = pPrim->Vertices[iVtx].ArrayPosition; + mVertices[VertIndex] = pPrim->Vertices[iVtx]; + + if (VertIndex > MaxIndex) MaxIndex = VertIndex; + } + } + } + + mVertices.resize(MaxIndex + 1); + mNumVertices = mVertices.size(); +} + +void CModelCooker::WriteEditorModel(COutputStream& Out) +{ +} + +void CModelCooker::WriteModelPrime(COutputStream& Out) +{ + GenerateSurfaceData(); + + // Header + Out.WriteLong(0xDEADBABE); + Out.WriteLong(GetCMDLVersion(mVersion)); + Out.WriteLong(5); + mpModel->mAABox.Write(Out); + + u32 NumSections = mNumMatSets + mNumSurfaces + 6; + Out.WriteLong(NumSections); + Out.WriteLong(mNumMatSets); + + u32 SectionSizesOffset = Out.Tell(); + for (u32 iSec = 0; iSec < NumSections; iSec++) + Out.WriteLong(0); + + Out.WriteToBoundary(32, 0); + + std::vector SectionSizes; + SectionSizes.reserve(NumSections); + + CSectionMgrOut SectionMgr; + SectionMgr.SetSectionCount(NumSections); + SectionMgr.Init(Out); + + // Materials + for (u32 iSet = 0; iSet < mNumMatSets; iSet++) + { + CMaterialCooker::WriteCookedMatSet(mpModel->mMaterialSets[iSet], mVersion, Out); + Out.WriteToBoundary(32, 0); + SectionMgr.AddSize(Out); + } + + // Vertices + for (u32 iPos = 0; iPos < mNumVertices; iPos++) + mVertices[iPos].Position.Write(Out); + + Out.WriteToBoundary(32, 0); + SectionMgr.AddSize(Out); + + // Normals + for (u32 iNrm = 0; iNrm < mNumVertices; iNrm++) + mVertices[iNrm].Normal.Write(Out); + + Out.WriteToBoundary(32, 0); + SectionMgr.AddSize(Out); + + // Colors + for (u32 iColor = 0; iColor < mNumVertices; iColor++) + mVertices[iColor].Color[0].Write(Out); + + Out.WriteToBoundary(32, 0); + SectionMgr.AddSize(Out); + + // Float UV coordinates + for (u32 iTexSlot = 0; iTexSlot < 8; iTexSlot++) + { + bool HasTexSlot = (mVtxAttribs & (eTex0 << (iTexSlot * 2))) != 0; + if (HasTexSlot) + { + for (u32 iTex = 0; iTex < mNumVertices; iTex++) + mVertices[iTex].Tex[iTexSlot].Write(Out); + } + } + + Out.WriteToBoundary(32, 0); + SectionMgr.AddSize(Out); + SectionMgr.AddSize(Out); // Skipping short UV coordinates + + // Surface offsets + Out.WriteLong(mNumSurfaces); + u32 SurfaceOffsetsStart = Out.Tell(); + + for (u32 iSurf = 0; iSurf < mNumSurfaces; iSurf++) + Out.WriteLong(0); + + Out.WriteToBoundary(32, 0); + SectionMgr.AddSize(Out); + + // Surfaces + u32 SurfacesStart = Out.Tell(); + std::vector SurfaceEndOffsets(mNumSurfaces); + + for (u32 iSurf = 0; iSurf < mNumSurfaces; iSurf++) + { + SSurface *pSurface = mpModel->GetSurface(iSurf); + + pSurface->CenterPoint.Write(Out); + Out.WriteLong(pSurface->MaterialID); + Out.WriteShort(0x8000); + u32 PrimTableSizeOffset = Out.Tell(); + Out.WriteShort(0); + Out.WriteLongLong(0); + Out.WriteLong(0); + pSurface->ReflectionDirection.Write(Out); + Out.WriteToBoundary(32, 0); + + u32 PrimTableStart = Out.Tell(); + EVertexDescription MatAttribs = mpModel->GetMaterialBySurface(0, iSurf)->VtxDesc(); + + for (u32 iPrim = 0; iPrim < pSurface->Primitives.size(); iPrim++) + { + SSurface::SPrimitive *pPrimitive = &pSurface->Primitives[iPrim]; + Out.WriteByte((u8) pPrimitive->Type); + Out.WriteShort((u16) pPrimitive->Vertices.size()); + + for (u32 iVert = 0; iVert < pPrimitive->Vertices.size(); iVert++) + { + CVertex *pVert = &pPrimitive->Vertices[iVert]; + + if (mVersion == eEchoes) + { + for (u32 iMtxAttribs = 0; iMtxAttribs < 8; iMtxAttribs++) + if (MatAttribs & (ePosMtx << iMtxAttribs)) + Out.WriteByte(pVert->MatrixIndices[iMtxAttribs]); + } + + u16 VertexIndex = pVert->ArrayPosition; + + if (MatAttribs & ePosition) + Out.WriteShort(VertexIndex); + + if (MatAttribs & eNormal) + Out.WriteShort(VertexIndex); + + if (MatAttribs & eColor0) + Out.WriteShort(VertexIndex); + + if (MatAttribs & eColor1) + Out.WriteShort(VertexIndex); + + u16 TexOffset = 0; + for (u32 iTex = 0; iTex < 8; iTex++) + { + if (MatAttribs & (eTex0 << (iTex * 2))) + { + Out.WriteShort(VertexIndex + TexOffset); + TexOffset += mNumVertices; + } + } + } + } + + Out.WriteToBoundary(32, 0); + u32 PrimTableEnd = Out.Tell(); + u32 PrimTableSize = PrimTableEnd - PrimTableStart; + Out.Seek(PrimTableSizeOffset, SEEK_SET); + Out.WriteShort((u16) PrimTableSize); + Out.Seek(PrimTableEnd, SEEK_SET); + + SectionMgr.AddSize(Out); + SurfaceEndOffsets[iSurf] = Out.Tell() - SurfacesStart; + } + + // Done writing the file - now we go back to fill in surface offsets + section sizes + Out.Seek(SurfaceOffsetsStart, SEEK_SET); + + for (u32 iSurf = 0; iSurf < mNumSurfaces; iSurf++) + Out.WriteLong(SurfaceEndOffsets[iSurf]); + + Out.Seek(SectionSizesOffset, SEEK_SET); + SectionMgr.WriteSizes(Out); + + // Done! +} + +void CModelCooker::WriteCookedModel(CModel *pModel, EGame Version, COutputStream& CMDL) +{ + CModelCooker Cooker; + Cooker.mpModel = pModel; + Cooker.mVersion = Version; + + switch (Version) + { + case ePrimeKioskDemo: + case ePrime: + case eEchoesDemo: + case eEchoes: + Cooker.WriteModelPrime(CMDL); + break; + } +} + +void CModelCooker::WriteUncookedModel(CModel *pModel, COutputStream& EMDL) +{ +} + +u32 CModelCooker::GetCMDLVersion(EGame Version) +{ + switch (Version) + { + case ePrimeKioskDemo: + case ePrime: + return 0x2; + case eEchoesDemo: + return 0x3; + case eEchoes: + return 0x4; + case eCorruptionProto: + case eCorruption: + return 0x5; + case eReturns: + return 0xA; + default: + return 0; + } +} diff --git a/Resource/cooker/CModelCooker.h b/Resource/cooker/CModelCooker.h new file mode 100644 index 00000000..1626d4f1 --- /dev/null +++ b/Resource/cooker/CModelCooker.h @@ -0,0 +1,30 @@ +#ifndef CMODELCOOKER_H +#define CMODELCOOKER_H + +#include "../model/CModel.h" +#include "../EFormatVersion.h" +#include + +class CModelCooker +{ + CModel *mpModel; + EGame mVersion; + u32 mNumMatSets; + u32 mNumSurfaces; + u32 mNumVertices; + u8 mVertexFormat; + std::vector mVertices; + EVertexDescription mVtxAttribs; + + CModelCooker(); + void GenerateSurfaceData(); + void WriteEditorModel(COutputStream& Out); + void WriteModelPrime(COutputStream& Out); + +public: + static void WriteCookedModel(CModel *pModel, EGame Version, COutputStream& Out); + static void WriteUncookedModel(CModel *pModel, COutputStream& Out); + static u32 GetCMDLVersion(EGame Version); +}; + +#endif // CMODELCOOKER_H diff --git a/Resource/cooker/CSectionMgrOut.cpp b/Resource/cooker/CSectionMgrOut.cpp new file mode 100644 index 00000000..ad89aea9 --- /dev/null +++ b/Resource/cooker/CSectionMgrOut.cpp @@ -0,0 +1,33 @@ +#include "CSectionMgrOut.h" + +CSectionMgrOut::CSectionMgrOut() +{ + mSectionCount = 0; + mCurSectionStart = 0; + mCurSectionIndex = 0; +} + +void CSectionMgrOut::SetSectionCount(u32 Count) +{ + mSectionCount = Count; + mSectionSizes.resize(Count); +} + +void CSectionMgrOut::Init(const COutputStream& OutputStream) +{ + mCurSectionStart = OutputStream.Tell(); + mCurSectionIndex = 0; +} + +void CSectionMgrOut::AddSize(COutputStream& OutputStream) +{ + mSectionSizes[mCurSectionIndex] = OutputStream.Tell() - mCurSectionStart; + mCurSectionIndex++; + mCurSectionStart = OutputStream.Tell(); +} + +void CSectionMgrOut::WriteSizes(COutputStream& OutputStream) +{ + for (u32 iSec = 0; iSec < mSectionCount; iSec++) + OutputStream.WriteLong(mSectionSizes[iSec]); +} diff --git a/Resource/cooker/CSectionMgrOut.h b/Resource/cooker/CSectionMgrOut.h new file mode 100644 index 00000000..da17743e --- /dev/null +++ b/Resource/cooker/CSectionMgrOut.h @@ -0,0 +1,24 @@ +#ifndef CBLOCKMGROUT_H +#define CBLOCKMGROUT_H + +#include +#include +#include + +// Small class to manage file sections for CMDL/MREA output +class CSectionMgrOut +{ + u32 mSectionCount; + u32 mCurSectionStart; + u32 mCurSectionIndex; + std::vector mSectionSizes; + +public: + CSectionMgrOut(); + void SetSectionCount(u32 Count); + void Init(const COutputStream& OutputStream); + void AddSize(COutputStream& OutputStream); + void WriteSizes(COutputStream& OutputStream); +}; + +#endif // CBLOCKMGROUT_H diff --git a/Resource/cooker/CTextureEncoder.cpp b/Resource/cooker/CTextureEncoder.cpp new file mode 100644 index 00000000..2d5b6d8d --- /dev/null +++ b/Resource/cooker/CTextureEncoder.cpp @@ -0,0 +1,106 @@ +#include "CTextureEncoder.h" +#include + +CTextureEncoder::CTextureEncoder() +{ + mpTexture = nullptr; +} + +void CTextureEncoder::WriteTXTR(COutputStream& TXTR) +{ + // Only DXT1->CMPR supported at the moment + TXTR.WriteLong(mOutputFormat); + TXTR.WriteShort(mpTexture->mWidth); + TXTR.WriteShort(mpTexture->mHeight); + TXTR.WriteLong(mpTexture->mNumMipMaps); + + u32 MipW = mpTexture->Width() / 4; + u32 MipH = mpTexture->Height() / 4; + CMemoryInStream Image(mpTexture->mImgDataBuffer, mpTexture->mImgDataSize, IOUtil::LittleEndian); + u32 MipOffset = Image.Tell(); + + for (u32 iMip = 0; iMip < mpTexture->mNumMipMaps; iMip++) + { + for (u32 BlockY = 0; BlockY < MipH; BlockY += 2) + for (u32 BlockX = 0; BlockX < MipW; BlockX += 2) + for (u32 ImgY = BlockY; ImgY < BlockY + 2; ImgY++) + for (u32 ImgX = BlockX; ImgX < BlockX + 2; ImgX++) + { + u32 SrcPos = ((ImgY * MipW) + ImgX) * 8; + Image.Seek(MipOffset + SrcPos, SEEK_SET); + + ReadSubBlockCMPR(Image, TXTR); + } + + MipOffset += MipW * MipH * 8; + MipW /= 2; + MipH /= 2; + if (MipW < 2) MipW = 2; + if (MipH < 2) MipH = 2; + } +} + +void CTextureEncoder::DetermineBestOutputFormat() +{ + // todo +} + +void CTextureEncoder::ReadSubBlockCMPR(CInputStream& Source, COutputStream& Dest) +{ + Dest.WriteShort(Source.ReadShort()); + Dest.WriteShort(Source.ReadShort()); + + for (u32 byte = 0; byte < 4; byte++) { + u8 b = Source.ReadByte(); + b = ((b & 0x3) << 6) | ((b & 0xC) << 2) | ((b & 0x30) >> 2) | ((b & 0xC0) >> 6); + Dest.WriteByte(b); + } +} + +// ************ STATIC ************ +void CTextureEncoder::EncodeTXTR(COutputStream& TXTR, CTexture *pTex) +{ + if (pTex->mTexelFormat != eDXT1) + { + std::cout << "\rError: Unsupported texel format for decoding\n"; + return; + } + + CTextureEncoder Encoder; + Encoder.mpTexture = pTex; + Encoder.mSourceFormat = eDXT1; + Encoder.mOutputFormat = eGX_CMPR; + Encoder.WriteTXTR(TXTR); +} + +void CTextureEncoder::EncodeTXTR(COutputStream& TXTR, CTexture *pTex, ETexelFormat OutputFormat) +{ + // todo: support for encoding a specific format + EncodeTXTR(TXTR, pTex); +} + +ETexelFormat CTextureEncoder::GetGXFormat(ETexelFormat Format) +{ + switch (Format) + { + case eLuminance: return eGX_I8; + case eLuminanceAlpha: return eGX_IA8; + case eRGBA4: return eGX_RGB5A3; + case eRGB565: return eGX_RGB565; + case eRGBA8: return eGX_RGBA8; + case eDXT1: return eGX_CMPR; + } +} + +ETexelFormat CTextureEncoder::GetFormat(ETexelFormat Format) +{ + switch (Format) + { + case eGX_I4: return eLuminance; + case eGX_I8: return eLuminance; + case eGX_IA4: return eLuminanceAlpha; + case eGX_IA8: return eLuminanceAlpha; + // todo rest of these + case eGX_CMPR: return eDXT1; + } +} diff --git a/Resource/cooker/CTextureEncoder.h b/Resource/cooker/CTextureEncoder.h new file mode 100644 index 00000000..c738da1d --- /dev/null +++ b/Resource/cooker/CTextureEncoder.h @@ -0,0 +1,27 @@ +#ifndef CTEXTUREENCODER_H +#define CTEXTUREENCODER_H + +#include "../CTexture.h" + +// Class contains basic functionality right now - only supports directly converting DXT1 to CMPR +// More advanced functions (including actual encoding!) coming later +class CTextureEncoder +{ + + CTexture *mpTexture; + ETexelFormat mSourceFormat; + ETexelFormat mOutputFormat; + + CTextureEncoder(); + void WriteTXTR(COutputStream& TXTR); + void DetermineBestOutputFormat(); + void ReadSubBlockCMPR(CInputStream& Source, COutputStream& Dest); + +public: + static void EncodeTXTR(COutputStream& TXTR, CTexture *pTex); + static void EncodeTXTR(COutputStream& TXTR, CTexture *pTex, ETexelFormat OutputFormat); + static ETexelFormat GetGXFormat(ETexelFormat Format); + static ETexelFormat GetFormat(ETexelFormat Format); +}; + +#endif // CTEXTUREENCODER_H diff --git a/Resource/factory/CAnimSetLoader.cpp b/Resource/factory/CAnimSetLoader.cpp new file mode 100644 index 00000000..d8684cae --- /dev/null +++ b/Resource/factory/CAnimSetLoader.cpp @@ -0,0 +1,199 @@ +#include "CAnimSetLoader.h" +#include +#include + +CAnimSetLoader::CAnimSetLoader() +{ +} + +CAnimSet* CAnimSetLoader::LoadCorruptionCHAR(CInputStream& CHAR) +{ + // For now, we only read enough to fetch the model + CHAR.Seek(0x1, SEEK_CUR); + set->nodes.resize(1); + CAnimSet::SNode& node = set->nodes[0]; + + node.name = CHAR.ReadString(); + node.model = (CModel*) gResCache.GetResource(CHAR.ReadLongLong(), "CMDL"); + node.ModelToken = CToken(node.model); + return set; +} + +CAnimSet* CAnimSetLoader::LoadReturnsCHAR(CInputStream& CHAR) +{ + // For now, we only read enough to fetch the model + CHAR.Seek(0x16, SEEK_CUR); + set->nodes.resize(1); + CAnimSet::SNode& node = set->nodes[0]; + + node.name = CHAR.ReadString(); + CHAR.Seek(0x14, SEEK_CUR); + CHAR.ReadString(); + node.model = (CModel*) gResCache.GetResource(CHAR.ReadLongLong(), "CMDL"); + node.ModelToken = CToken(node.model); + return set; +} + +void CAnimSetLoader::LoadPASDatabase(CInputStream& PAS4) +{ + // For now, just parse the data; don't store it + PAS4.Seek(0x4, SEEK_CUR); // Skipping PAS4 FourCC + u32 anim_state_count = PAS4.ReadLong(); + PAS4.Seek(0x4, SEEK_CUR); // Skipping default anim state + + for (u32 s = 0; s < anim_state_count; s++) + { + PAS4.Seek(0x4, SEEK_CUR); // Skipping unknown value + u32 parm_info_count = PAS4.ReadLong(); + u32 anim_info_count = PAS4.ReadLong(); + + u32 skip = 0; + for (u32 p = 0; p < parm_info_count; p++) + { + u32 type = PAS4.ReadLong(); + PAS4.Seek(0x8, SEEK_CUR); + + switch (type) { + case 0: // Int32 + case 1: // Uint32 + case 2: // Real32 + case 4: // Enum + PAS4.Seek(0x8, SEEK_CUR); + skip += 4; + break; + case 3: // Bool + PAS4.Seek(0x2, SEEK_CUR); + skip++; + break; + } + } + + for (u32 a = 0; a < anim_info_count; a++) + PAS4.Seek(0x4 + skip, SEEK_CUR); + } +} + +// ************ STATIC ************ +CAnimSet* CAnimSetLoader::LoadANCS(CInputStream& ANCS) +{ + if (!ANCS.IsValid()) return nullptr; + Log::Write("Loading " + ANCS.GetSourceString()); + + u32 magic = ANCS.ReadLong(); + if (magic != 0x00010001) + { + Log::FileError(ANCS.GetSourceString(), "Invalid ANCS magic: " + StringUtil::ToHexString(magic)); + return nullptr; + } + + CAnimSetLoader loader; + loader.set = new CAnimSet; + + u32 node_count = ANCS.ReadLong(); + loader.set->nodes.resize(node_count); + + for (u32 n = 0; n < node_count; n++) + { + CAnimSet::SNode *node = &loader.set->nodes[n]; + + ANCS.Seek(0x4, SEEK_CUR); // Skipping node self-index + u16 unknown1 = ANCS.ReadShort(); + if (n == 0) loader.mVersion = (unknown1 == 0xA) ? eEchoes : ePrime; // Best version indicator we know of unfortunately + node->name = ANCS.ReadString(); + node->model = (CModel*) gResCache.GetResource(ANCS.ReadLong(), "CMDL"); + node->skinID = ANCS.ReadLong(); + node->skelID = ANCS.ReadLong(); + node->ModelToken = CToken(node->model); + + // Unfortunately that's all that's actually supported at the moment. Hope to expand later. + // Since there's no size value I have to actually read the rest of the node to reach the next one + u32 anim_count = ANCS.ReadLong(); + for (u32 a = 0; a < anim_count; a++) + { + ANCS.Seek(0x4, SEEK_CUR); + if (loader.mVersion == ePrime) ANCS.Seek(0x1, SEEK_CUR); + ANCS.ReadString(); + } + + // PAS Database + loader.LoadPASDatabase(ANCS); + + // Particles + u32 particle_count = ANCS.ReadLong(); + ANCS.Seek(particle_count * 4, SEEK_CUR); + u32 swoosh_count = ANCS.ReadLong(); + ANCS.Seek(swoosh_count * 4, SEEK_CUR); + if (unknown1 != 5) ANCS.Seek(0x4, SEEK_CUR); + u32 electric_count = ANCS.ReadLong(); + ANCS.Seek(electric_count * 4, SEEK_CUR); + if (loader.mVersion == eEchoes) { + u32 spsc_count = ANCS.ReadLong(); + ANCS.Seek(spsc_count * 4, SEEK_CUR); + } + ANCS.Seek(0x4, SEEK_CUR); + if (loader.mVersion == eEchoes) ANCS.Seek(0x4, SEEK_CUR); + + u32 anim_count2 = ANCS.ReadLong(); + for (u32 a = 0; a < anim_count2; a++) + { + ANCS.ReadString(); + ANCS.Seek(0x18, SEEK_CUR); + } + + u32 EffectGroupCount = ANCS.ReadLong(); + for (u32 g = 0; g < EffectGroupCount; g++) + { + ANCS.ReadString(); + u32 EffectCount = ANCS.ReadLong(); + + for (u32 e = 0; e < EffectCount; e++) + { + ANCS.ReadString(); + ANCS.Seek(0x8, SEEK_CUR); + if (loader.mVersion == ePrime) ANCS.ReadString(); + if (loader.mVersion == eEchoes) ANCS.Seek(0x4, SEEK_CUR); + ANCS.Seek(0xC, SEEK_CUR); + } + } + ANCS.Seek(0x8, SEEK_CUR); + + u32 unknown_count = ANCS.ReadLong(); + ANCS.Seek(unknown_count * 4, SEEK_CUR); + + if (loader.mVersion == eEchoes) + { + ANCS.Seek(0x5, SEEK_CUR); + u32 unknown_count2 = ANCS.ReadLong(); + ANCS.Seek(unknown_count2 * 0x1C, SEEK_CUR); + } + // Lots of work for data I'm not even using x.x + } + + return loader.set; +} + +CAnimSet* CAnimSetLoader::LoadCHAR(CInputStream &CHAR) +{ + if (!CHAR.IsValid()) return nullptr; + Log::Write("Loading " + CHAR.GetSourceString()); + + CAnimSetLoader loader; + u8 check = CHAR.ReadByte(); + + if (check == 0x5) + { + loader.mVersion = eCorruption; + loader.set = new CAnimSet(); + return loader.LoadCorruptionCHAR(CHAR); + } + + if (check == 0x59) + { + loader.mVersion = eReturns; + loader.set = new CAnimSet(); + return loader.LoadReturnsCHAR(CHAR); + } + + Log::FileError(CHAR.GetSourceString(), "CHAR has invalid first byte: " + StringUtil::ToHexString(check)); + return nullptr; +} diff --git a/Resource/factory/CAnimSetLoader.h b/Resource/factory/CAnimSetLoader.h new file mode 100644 index 00000000..fa6938ea --- /dev/null +++ b/Resource/factory/CAnimSetLoader.h @@ -0,0 +1,24 @@ +#ifndef CCHARACTERLOADER_H +#define CCHARACTERLOADER_H + +#include "../CAnimSet.h" +#include "../EFormatVersion.h" +#include + +class CAnimSetLoader +{ + CAnimSet *set; + CResCache *mpResCache; + EGame mVersion; + + CAnimSetLoader(); + CAnimSet* LoadCorruptionCHAR(CInputStream& CHAR); + CAnimSet* LoadReturnsCHAR(CInputStream& CHAR); + void LoadPASDatabase(CInputStream& PAS4); + +public: + static CAnimSet* LoadANCS(CInputStream& ANCS); + static CAnimSet* LoadCHAR(CInputStream& CHAR); +}; + +#endif // CCHARACTERLOADER_H diff --git a/Resource/factory/CAreaLoader.cpp b/Resource/factory/CAreaLoader.cpp new file mode 100644 index 00000000..e7ee7b2b --- /dev/null +++ b/Resource/factory/CAreaLoader.cpp @@ -0,0 +1,538 @@ +#include "CAreaLoader.h" +#include "CCollisionLoader.h" +#include "CModelLoader.h" +#include "CMaterialLoader.h" +#include "CScriptLoader.h" +#include +#include +#include +#include + +CAreaLoader::CAreaLoader() +{ + mpMREA = nullptr; + mHasDecompressedBuffer = false; + mGeometryBlockNum = -1; + mScriptLayerBlockNum = -1; + mCollisionBlockNum = -1; + mUnknownBlockNum = -1; + mLightsBlockNum = -1; + mEmptyBlockNum = -1; + mPathBlockNum = -1; + mOctreeBlockNum = -1; + mScriptGeneratorBlockNum = -1; + mFFFFBlockNum = -1; + mUnknown2BlockNum = -1; + mEGMCBlockNum = -1; + mBoundingBoxesBlockNum = -1; + mDependenciesBlockNum = -1; + mGPUBlockNum = -1; + mPVSBlockNum = -1; + mRSOBlockNum = -1; +} + +CAreaLoader::~CAreaLoader() +{ + if (mHasDecompressedBuffer) + { + delete mpMREA; + delete[] mDecmpBuffer; + } +} + +// ************ PRIME ************ +void CAreaLoader::ReadHeaderPrime() +{ + Log::FileWrite(mpMREA->GetSourceString(), "Reading MREA header (MP1)"); + mpArea->mTransform = CTransform4f(*mpMREA); + mNumMeshes = mpMREA->ReadLong(); + u32 mNumBlocks = mpMREA->ReadLong(); + + mGeometryBlockNum = mpMREA->ReadLong(); + mScriptLayerBlockNum = mpMREA->ReadLong(); + mCollisionBlockNum = mpMREA->ReadLong(); + mUnknownBlockNum = mpMREA->ReadLong(); + mLightsBlockNum = mpMREA->ReadLong(); + mEmptyBlockNum = mpMREA->ReadLong(); + mPathBlockNum = mpMREA->ReadLong(); + mOctreeBlockNum = mpMREA->ReadLong(); + + mBlockMgr = new CBlockMgrIn(mNumBlocks, mpMREA); + mpMREA->SeekToBoundary(32); + mBlockMgr->Init(); +} + +void CAreaLoader::ReadGeometryPrime() +{ + Log::FileWrite(mpMREA->GetSourceString(), "Reading MREA world geometry (MP1/MP2)"); + mBlockMgr->ToBlock(mGeometryBlockNum); + + // Materials + mpArea->mMaterialSet = CMaterialLoader::LoadMaterialSet(*mpMREA, mVersion); + mBlockMgr->ToNextBlock(); + + // Geometry + for (u32 m = 0; m < mNumMeshes; m++) { + std::cout << "\rLoading mesh " << std::dec << m + 1 << "/" << mNumMeshes; + + SModelData *data = CModelLoader::LoadWorldModel(*mpMREA, *mBlockMgr, *mpArea->mMaterialSet, mVersion); + CModel *pTerrainModel = new CModel(data, mpArea->mMaterialSet); + mpArea->AddWorldModel(pTerrainModel); + + if (mVersion >= eEchoes) { + mBlockMgr->ToNextBlock(); + mBlockMgr->ToNextBlock(); + } + } + mpArea->MergeTerrain(); + std::cout << "\n"; +} + +void CAreaLoader::ReadSCLYPrime() +{ + Log::FileWrite(mpMREA->GetSourceString(), "Reading MREA script layers (MP1)"); + mBlockMgr->ToBlock(mScriptLayerBlockNum); + + CFourCC SCLY(*mpMREA); + if (SCLY != "SCLY") + { + Log::Error(mpMREA->GetSourceString() + " - Invalid SCLY magic: " + SCLY.ToString()); + return; + } + + mpMREA->Seek(0x4, SEEK_CUR); + mNumLayers = mpMREA->ReadLong(); + mpArea->mScriptLayers.reserve(mNumLayers); + + std::vector LayerSizes(mNumLayers); + for (u32 l = 0; l < mNumLayers; l++) + LayerSizes[l] = mpMREA->ReadLong(); + + for (u32 l = 0; l < mNumLayers; l++) + { + u32 next = mpMREA->Tell() + LayerSizes[l]; + + CScriptLayer *layer = CScriptLoader::LoadLayer(*mpMREA, mpArea, mVersion); + if (layer) + mpArea->mScriptLayers.push_back(layer); + + mpMREA->Seek(next, SEEK_SET); + } + + SetUpObjects(); +} + +void CAreaLoader::ReadLightsPrime() +{ + Log::FileWrite(mpMREA->GetSourceString(), "Reading MREA dynamic lights (MP1/MP2)"); + mBlockMgr->ToBlock(mLightsBlockNum); + + u32 babedead = mpMREA->ReadLong(); + if (babedead != 0xbabedead) return; + + mpArea->mLightLayers.resize(2); + + for (u32 ly = 0; ly < 2; ly++) + { + u32 NumLights = mpMREA->ReadLong(); + mpArea->mLightLayers[ly].resize(NumLights); + + for (u32 l = 0; l < NumLights; l++) + { + ELightType Type = ELightType(mpMREA->ReadLong()); + CVector3f Color(*mpMREA); + CVector3f Position(*mpMREA); + CVector3f Direction(*mpMREA); + float Multiplier = mpMREA->ReadFloat(); + float SpotCutoff = mpMREA->ReadFloat(); + mpMREA->Seek(0x9, SEEK_CUR); + u32 FalloffType = mpMREA->ReadLong(); + mpMREA->Seek(0x4, SEEK_CUR); + + // Relevant data is read - now we process and form a CLight out of it + CLight *Light; + + CColor LightColor = CColor(Color.x, Color.y, Color.z, 0.f); + if (Multiplier < FLT_EPSILON) + Multiplier = FLT_EPSILON; + + // Local Ambient + if (Type == eLocalAmbient) + { + Color *= Multiplier; + + // Clamp + if (Color.x > 1.f) Color.x = 1.f; + if (Color.y > 1.f) Color.y = 1.f; + if (Color.z > 1.f) Color.z = 1.f; + CColor MultColor(Color.x, Color.y, Color.z, 1.f); + + Light = CLight::BuildLocalAmbient(Position, MultColor); + } + + // Directional + else if (Type == eDirectional) + { + Light = CLight::BuildDirectional(Position, Direction, LightColor); + } + + // Spot + else if (Type == eSpot) + { + Light = CLight::BuildSpot(Position, Direction.Normalized(), LightColor, SpotCutoff); + + float DistAttenA = (FalloffType == 0) ? (2.f / Multiplier) : 0.f; + float DistAttenB = (FalloffType == 1) ? (250.f / Multiplier) : 0.f; + float DistAttenC = (FalloffType == 2) ? (25000.f / Multiplier) : 0.f; + Light->SetDistAtten(DistAttenA, DistAttenB, DistAttenC); + } + + // Custom + else + { + float DistAttenA = (FalloffType == 0) ? (2.f / Multiplier) : 0.f; + float DistAttenB = (FalloffType == 1) ? (249.9998f / Multiplier) : 0.f; + float DistAttenC = (FalloffType == 2) ? (25000.f / Multiplier) : 0.f; + + Light = CLight::BuildCustom(Position, Direction, LightColor, + DistAttenA, DistAttenB, DistAttenC, + 1.f, 0.f, 0.f); + } + + mpArea->mLightLayers[ly][l] = Light; + } + } +} + +// ************ ECHOES ************ +void CAreaLoader::ReadHeaderEchoes() +{ + // This function reads the header for Echoes and the Echoes demo disc + Log::FileWrite(mpMREA->GetSourceString(), "Reading MREA header (MP2)"); + mpArea->mTransform = CTransform4f(*mpMREA); + mNumMeshes = mpMREA->ReadLong(); + if (mVersion == eEchoes) mNumLayers = mpMREA->ReadLong(); + u32 numBlocks = mpMREA->ReadLong(); + + mGeometryBlockNum = mpMREA->ReadLong(); + mScriptLayerBlockNum = mpMREA->ReadLong(); + mScriptGeneratorBlockNum = mpMREA->ReadLong(); + mCollisionBlockNum = mpMREA->ReadLong(); + mUnknownBlockNum = mpMREA->ReadLong(); + mLightsBlockNum = mpMREA->ReadLong(); + mEmptyBlockNum = mpMREA->ReadLong(); + mPathBlockNum = mpMREA->ReadLong(); + mFFFFBlockNum = mpMREA->ReadLong(); + mUnknown2BlockNum = mpMREA->ReadLong(); + mEGMCBlockNum = mpMREA->ReadLong(); + if (mVersion == eEchoes) mClusters.resize(mpMREA->ReadLong()); + mpMREA->SeekToBoundary(32); + + mBlockMgr = new CBlockMgrIn(numBlocks, mpMREA); + mpMREA->SeekToBoundary(32); + + if (mVersion == eEchoes) + { + ReadCompressedBlocks(); + Decompress(); + } + + mBlockMgr->Init(); +} + +void CAreaLoader::ReadSCLYEchoes() +{ + Log::FileWrite(mpMREA->GetSourceString(), "Reading MREA script layers (MP2/MP3/DKCR)"); + mBlockMgr->ToBlock(mScriptLayerBlockNum); + + // SCLY + for (u32 l = 0; l < mNumLayers; l++) + { + CScriptLayer *pLayer = CScriptLoader::LoadLayer(*mpMREA, mpArea, mVersion); + + if (pLayer) + mpArea->mScriptLayers.push_back(pLayer); + + mBlockMgr->ToNextBlock(); + } + + // SCGN + mBlockMgr->ToBlock(mScriptGeneratorBlockNum); + CScriptLayer *pLayer = CScriptLoader::LoadLayer(*mpMREA, mpArea, mVersion); + + if (pLayer) + mpArea->mpGeneratorLayer = pLayer; + + SetUpObjects(); +} + +// ************ CORRUPTION ************ +void CAreaLoader::ReadHeaderCorruption() +{ + // This function reads the header for MP3, the MP3 prototype, and DKCR + Log::FileWrite(mpMREA->GetSourceString(), "Reading MREA header (MP3/DKCR)"); + mpArea->mTransform = CTransform4f(*mpMREA); + mNumMeshes = mpMREA->ReadLong(); + mNumLayers = mpMREA->ReadLong(); + u32 NumSections = mpMREA->ReadLong(); + mClusters.resize(mpMREA->ReadLong()); + u32 SectionNumberCount = mpMREA->ReadLong(); + mpMREA->SeekToBoundary(32); + + mBlockMgr = new CBlockMgrIn(NumSections, mpMREA); + mpMREA->SeekToBoundary(32); + + ReadCompressedBlocks(); + + for (u32 iNum = 0; iNum < SectionNumberCount; iNum++) + { + CFourCC Type(*mpMREA); + u32 Num = mpMREA->ReadLong(); + + if (Type == "AABB") mBoundingBoxesBlockNum = Num; + else if (Type == "COLI") mCollisionBlockNum = Num; + else if (Type == "DEPS") mDependenciesBlockNum = Num; + else if (Type == "EGMC") mEGMCBlockNum = Num; + else if (Type == "GPUD") mGPUBlockNum = Num; + else if (Type == "LITE") mLightsBlockNum = Num; + else if (Type == "PFL2") mPathBlockNum = Num; + else if (Type == "PVS!") mPVSBlockNum = Num; + else if (Type == "ROCT") mOctreeBlockNum = Num; + else if (Type == "RSOS") mRSOBlockNum = Num; + else if (Type == "SOBJ") mScriptLayerBlockNum = Num; + else if (Type == "SGEN") mScriptGeneratorBlockNum = Num; + else if (Type == "WOBJ") mGeometryBlockNum = Num; // note WOBJ can show up multiple times, but is always 0 + } + + mpMREA->SeekToBoundary(32); + Decompress(); + mBlockMgr->Init(); +} + +void CAreaLoader::ReadGeometryCorruption() +{ + Log::FileWrite(mpMREA->GetSourceString(), "Reading MREA world geometry (MP3)"); + mBlockMgr->ToBlock(mGeometryBlockNum); + + // Materials + mpArea->mMaterialSet = CMaterialLoader::LoadMaterialSet(*mpMREA, mVersion); + mBlockMgr->ToNextBlock(); + + // Geometry + u32 CurWOBJSection = 1; + u32 CurGPUSection = mGPUBlockNum; + + for (u32 iMesh = 0; iMesh < mNumMeshes; iMesh++) + { + std::cout << "\rLoading mesh " << std::dec << iMesh + 1 << "/" << mNumMeshes; + + SModelData *pData = CModelLoader::LoadCorruptionWorldModel(*mpMREA, *mBlockMgr, *mpArea->mMaterialSet, CurWOBJSection, CurGPUSection, mVersion); + CModel *pWorldModel = new CModel(pData, mpArea->mMaterialSet); + mpArea->AddWorldModel(pWorldModel); + + CurWOBJSection += 4; + CurGPUSection = mBlockMgr->CurrentBlock(); + } + + mpArea->MergeTerrain(); + std::cout << "\n"; +} + +// ************ RETURNS ************ + +// ************ COMMON ************ +void CAreaLoader::ReadCompressedBlocks() +{ + mTotalDecmpSize = 0; + + for (u32 c = 0; c < mClusters.size(); c++) + { + mClusters[c].BufferSize = mpMREA->ReadLong(); + mClusters[c].DecompressedSize = mpMREA->ReadLong(); + mClusters[c].CompressedSize = mpMREA->ReadLong(); + mClusters[c].NumSections = mpMREA->ReadLong(); + mTotalDecmpSize += mClusters[c].DecompressedSize; + } + + mpMREA->SeekToBoundary(32); +} + +void CAreaLoader::Decompress() +{ + // This function decompresses compressed clusters into a buffer. + // It should be called at the beginning of the first compressed cluster. + Log::FileWrite(mpMREA->GetSourceString(), "Decompressing MREA data"); + if (mVersion < eEchoes) return; + + // Decompress clusters + mDecmpBuffer = new u8[mTotalDecmpSize]; + u32 Offset = 0; + + for (u32 c = 0; c < mClusters.size(); c++) + { + SCompressedCluster *cc = &mClusters[c]; + + // Is it decompressed already? + if (mClusters[c].CompressedSize == 0) + { + mpMREA->ReadBytes(mDecmpBuffer + Offset, cc->DecompressedSize); + Offset += cc->DecompressedSize; + } + + else + { + u32 StartOffset = 32 - (mClusters[c].CompressedSize % 32); // For some reason they pad the beginning instead of the end + if (StartOffset != 32) + mpMREA->Seek(StartOffset, SEEK_CUR); + + std::vector cmp(mClusters[c].CompressedSize); + mpMREA->ReadBytes(cmp.data(), cmp.size()); + + bool Success = CompressionUtil::DecompressAreaLZO(cmp.data(), cmp.size(), mDecmpBuffer + Offset, cc->DecompressedSize); + if (!Success) + throw "Failed to decompress MREA!"; + + Offset += cc->DecompressedSize; + } + } + + std::string Source = mpMREA->GetSourceString(); + mpMREA = new CMemoryInStream(mDecmpBuffer, mTotalDecmpSize, IOUtil::BigEndian); + mpMREA->SetSourceString(Source); + mBlockMgr->SetInputStream(mpMREA); + mHasDecompressedBuffer = true; +} + +void CAreaLoader::ReadCollision() +{ + Log::FileWrite(mpMREA->GetSourceString(), "Reading collision (MP1/MP2/MP3)"); + mBlockMgr->ToBlock(mCollisionBlockNum); + mpArea->mCollision = CCollisionLoader::LoadAreaCollision(*mpMREA); +} + +void CAreaLoader::SetUpObjects() +{ + // Iterate over all objects + for (u32 iLyr = 0; iLyr < mpArea->GetScriptLayerCount() + 1; iLyr++) + { + CScriptLayer *pLayer; + if (iLyr < mpArea->GetScriptLayerCount()) pLayer = mpArea->mScriptLayers[iLyr]; + + else + { + pLayer = mpArea->GetGeneratorLayer(); + if (!pLayer) break; + } + + for (u32 iObj = 0; iObj < pLayer->GetNumObjects(); iObj++) + { + // Add object to object map + CScriptObject *pObj = (*pLayer)[iObj]; + mpArea->mObjectMap[pObj->InstanceID()] = pObj; + + // Store outgoing connections + for (u32 iCon = 0; iCon < pObj->NumOutLinks(); iCon++) + { + SLink Connection = pObj->OutLink(iCon); + + SLink NewConnection; + NewConnection.State = Connection.State; + NewConnection.Message = Connection.Message; + NewConnection.ObjectID = pObj->InstanceID(); + mConnectionMap[Connection.ObjectID].push_back(NewConnection); + } + } + } + + // Store connections + for (auto it = mpArea->mObjectMap.begin(); it != mpArea->mObjectMap.end(); it++) + { + u32 InstanceID = it->first; + auto iConMap = mConnectionMap.find(InstanceID); + + if (iConMap != mConnectionMap.end()) + { + CScriptObject *pObj = mpArea->GetInstanceByID(InstanceID); + pObj->mInConnections = iConMap->second; + } + } +} + +// ************ STATIC ************ +CGameArea* CAreaLoader::LoadMREA(CInputStream& MREA) +{ + CAreaLoader Loader; + + // Validation + if (!MREA.IsValid()) return nullptr; + Log::Write("Loading " + MREA.GetSourceString()); + + u32 deadbeef = MREA.ReadLong(); + if (deadbeef != 0xdeadbeef) + { + Log::FileError(MREA.GetSourceString(), "Invalid MREA magic: " + StringUtil::ToHexString(deadbeef)); + return nullptr; + } + + // Header + Loader.mpArea = new CGameArea; + u32 version = MREA.ReadLong(); + Loader.mVersion = GetFormatVersion(version); + Loader.mpMREA = &MREA; + + switch (Loader.mVersion) + { + case ePrimeKioskDemo: + case ePrime: + Loader.ReadHeaderPrime(); + Loader.ReadGeometryPrime(); + Loader.ReadSCLYPrime(); + Loader.ReadCollision(); + Loader.ReadLightsPrime(); + break; + case eEchoesDemo: + case eEchoes: + Loader.ReadHeaderEchoes(); + Loader.ReadGeometryPrime(); + Loader.ReadSCLYEchoes(); + Loader.ReadCollision(); + Loader.ReadLightsPrime(); + break; + case eCorruptionProto: + Loader.ReadHeaderCorruption(); + Loader.ReadGeometryPrime(); + Loader.ReadSCLYEchoes(); + Loader.ReadCollision(); + break; + case eCorruption: + case eReturns: + Loader.ReadHeaderCorruption(); + Loader.ReadGeometryCorruption(); + Loader.ReadSCLYEchoes(); + if (Loader.mVersion != eReturns) Loader.ReadCollision(); + break; + default: + Log::FileError(MREA.GetSourceString(), "Unsupported MREA version: " + StringUtil::ToHexString(version)); + delete Loader.mpArea; + return nullptr; + } + + // Cleanup + delete Loader.mBlockMgr; + return Loader.mpArea; +} + +EGame CAreaLoader::GetFormatVersion(u32 version) +{ + switch (version) + { + case 0xC: return ePrimeKioskDemo; + case 0xF: return ePrime; + case 0x15: return eEchoesDemo; + case 0x19: return eEchoes; + case 0x1D: return eCorruptionProto; + case 0x1E: return eCorruption; + case 0x20: return eReturns; + default: return eUnknownVersion; + } +} diff --git a/Resource/factory/CAreaLoader.h b/Resource/factory/CAreaLoader.h new file mode 100644 index 00000000..be684c13 --- /dev/null +++ b/Resource/factory/CAreaLoader.h @@ -0,0 +1,82 @@ +#ifndef CAREALOADER_H +#define CAREALOADER_H + +#include +#include "../CGameArea.h" +#include "../EFormatVersion.h" +#include "CBlockMgrIn.h" +#include + +class CAreaLoader +{ + struct SCompressedCluster; + + // Area data + CGameArea *mpArea; + CInputStream *mpMREA; + CBlockMgrIn *mBlockMgr; + EGame mVersion; + u32 mNumMeshes; + u32 mNumLayers; + + // Object connections + std::unordered_map> mConnectionMap; + + // Compression + u8 *mDecmpBuffer; + bool mHasDecompressedBuffer; + std::vector mClusters; + u32 mTotalDecmpSize; + + // Block numbers + u32 mGeometryBlockNum; + u32 mScriptLayerBlockNum; + u32 mCollisionBlockNum; + u32 mUnknownBlockNum; + u32 mLightsBlockNum; + u32 mEmptyBlockNum; + u32 mPathBlockNum; + u32 mOctreeBlockNum; + u32 mScriptGeneratorBlockNum; + u32 mFFFFBlockNum; + u32 mUnknown2BlockNum; + u32 mEGMCBlockNum; + u32 mBoundingBoxesBlockNum; + u32 mDependenciesBlockNum; + u32 mGPUBlockNum; + u32 mPVSBlockNum; + u32 mRSOBlockNum; + + struct SCompressedCluster { + u32 BufferSize, DecompressedSize, CompressedSize, NumSections; + }; + + CAreaLoader(); + ~CAreaLoader(); + + // Prime + void ReadHeaderPrime(); + void ReadGeometryPrime(); + void ReadSCLYPrime(); + void ReadLightsPrime(); + + // Echoes + void ReadHeaderEchoes(); + void ReadSCLYEchoes(); + + // Corruption + void ReadHeaderCorruption(); + void ReadGeometryCorruption(); + + // Common + void ReadCompressedBlocks(); + void Decompress(); + void ReadCollision(); + void SetUpObjects(); + +public: + static CGameArea* LoadMREA(CInputStream& MREA); + static EGame GetFormatVersion(u32 version); +}; + +#endif // CAREALOADER_H diff --git a/Resource/factory/CBlockMgr.cpp b/Resource/factory/CBlockMgr.cpp new file mode 100644 index 00000000..81f44dea --- /dev/null +++ b/Resource/factory/CBlockMgr.cpp @@ -0,0 +1,58 @@ +#include "CBlockMgrIn.h" + +CBlockMgrIn::CBlockMgrIn(unsigned long count, CInputStream* src) +{ + mpInputStream = src; + mBlockCount = count; + mBlockSizes.resize(count); + + for (unsigned long b = 0; b < count; b++) + mBlockSizes[b] = src->ReadLong(); +} + +void CBlockMgrIn::Init() +{ + // Initialize the block manager; this marks the start of the first block + mCurBlock = 0; + mCurBlockStart = mpInputStream->Tell(); + mBlocksStart = mpInputStream->Tell(); +} + +void CBlockMgrIn::ToBlock(unsigned long block) +{ + unsigned long offset = mBlocksStart; + for (unsigned long b = 0; b < block; b++) + offset += mBlockSizes[b]; + + mpInputStream->Seek(offset, SEEK_SET); + + mCurBlock = block; + mCurBlockStart = mpInputStream->Tell(); +} + +void CBlockMgrIn::ToNextBlock() +{ + mpInputStream->Seek(mCurBlockStart + mBlockSizes[mCurBlock], SEEK_SET); + mCurBlock++; + mCurBlockStart = mpInputStream->Tell(); +} + +long CBlockMgrIn::NextOffset() +{ + return mCurBlockStart + mBlockSizes[mCurBlock]; +} + +long CBlockMgrIn::CurrentBlock() +{ + return mCurBlock; +} + +long CBlockMgrIn::CurrentBlockSize() +{ + return mBlockSizes[mCurBlock]; +} + +void CBlockMgrIn::SetInputStream(CInputStream *in) +{ + mpInputStream = in; +} diff --git a/Resource/factory/CBlockMgrIn.h b/Resource/factory/CBlockMgrIn.h new file mode 100644 index 00000000..627c720a --- /dev/null +++ b/Resource/factory/CBlockMgrIn.h @@ -0,0 +1,28 @@ +#ifndef CBLOCKMGRIN_H +#define CBLOCKMGRIN_H + +#include +#include + +// The purpose of this class is to keep track of data block navigation - required to read CMDL and MREA files correctly +class CBlockMgrIn +{ + CInputStream *mpInputStream; + unsigned long mBlockCount; + std::vector mBlockSizes; + unsigned long mCurBlock; + unsigned long mCurBlockStart; + unsigned long mBlocksStart; + +public: + CBlockMgrIn(unsigned long count, CInputStream* src); + void Init(); + void ToBlock(unsigned long block); + void ToNextBlock(); + long NextOffset(); + long CurrentBlock(); + long CurrentBlockSize(); + void SetInputStream(CInputStream *in); +}; + +#endif // CBLOCKMGRIN_H diff --git a/Resource/factory/CCollisionLoader.cpp b/Resource/factory/CCollisionLoader.cpp new file mode 100644 index 00000000..264444cb --- /dev/null +++ b/Resource/factory/CCollisionLoader.cpp @@ -0,0 +1,136 @@ +#include "CCollisionLoader.h" +#include +#include + +CCollisionLoader::CCollisionLoader() +{ +} + +CCollisionMesh* CCollisionLoader::LoadAreaCollision(CInputStream& MREA) +{ + if (!MREA.IsValid()) return nullptr; + CCollisionLoader loader; + + MREA.Seek(0x8, SEEK_CUR); + u32 deafbabe = MREA.ReadLong(); + if (deafbabe != 0xdeafbabe) + { + Log::FileError(MREA.GetSourceString(), MREA.Tell() - 4, "Invalid collision magic: " + StringUtil::ToHexString(deafbabe)); + return nullptr; + } + + u32 version = MREA.ReadLong(); + loader.version = ECollisionVersion(version); + if ((loader.version != Prime) && (loader.version != Echoes)) + { + Log::FileError(MREA.GetSourceString(), MREA.Tell() - 4, "Unsupported collision version: " + StringUtil::ToHexString(version)); + return nullptr; + } + + loader.mesh = new CCollisionMesh; + CCollisionMesh *cmesh = loader.mesh; + + // Octree - structure is known, but not coding this right now + cmesh->mAABox = CAABox(MREA); + MREA.Seek(0x4, SEEK_CUR); + u32 octreeSize = MREA.ReadLong(); + MREA.Seek(octreeSize, SEEK_CUR); // Skipping the octree for now + cmesh->mOctreeLoaded = false; + + // Properties + u32 propertySetCount = MREA.ReadLong(); + for (u32 p = 0; p < propertySetCount; p++) + loader.readPropertyFlags(MREA); + + // Property indices for vertices/lines/faces + u32 vtxIndexCount = MREA.ReadLong(); + std::vector vtxIndices(vtxIndexCount); + MREA.ReadBytes(vtxIndices.data(), vtxIndices.size()); + + u32 lineIndexCount = MREA.ReadLong(); + std::vector lineIndices(lineIndexCount); + MREA.ReadBytes(lineIndices.data(), lineIndices.size()); + + u32 faceIndexCount = MREA.ReadLong(); + std::vector faceIndices(faceIndexCount); + MREA.ReadBytes(faceIndices.data(), faceIndices.size()); + + // Lines + cmesh->mLineCount = MREA.ReadLong(); + cmesh->mCollisionLines.resize(cmesh->mLineCount); + for (u32 l = 0; l < cmesh->mLineCount; l++) + { + CCollisionMesh::CCollisionLine *Line = &cmesh->mCollisionLines[l]; + Line->Vertices[0] = MREA.ReadShort(); + Line->Vertices[1] = MREA.ReadShort(); + Line->Properties = loader.properties[lineIndices[l]]; + } + + // Faces + cmesh->mFaceCount = MREA.ReadLong() / 3; // Not sure why they store it this way. It's inconsistent. + cmesh->mCollisionFaces.resize(cmesh->mFaceCount); + for (u32 f = 0; f < cmesh->mFaceCount; f++) + { + CCollisionMesh::CCollisionFace *face = &cmesh->mCollisionFaces[f]; + face->Lines[0] = MREA.ReadShort(); + face->Lines[1] = MREA.ReadShort(); + face->Lines[2] = MREA.ReadShort(); + face->Properties = loader.properties[faceIndices[f]]; + } + + // Echoes introduces a new data chunk; don't know what it is yet, skipping for now + if (loader.version == Echoes) + { + u32 unknown_count = MREA.ReadLong(); + MREA.Seek(unknown_count * 2, SEEK_CUR); + } + + // Vertices + cmesh->mVertexCount = MREA.ReadLong(); + cmesh->mCollisionVertices.resize(cmesh->mVertexCount); + for (u32 v = 0; v < cmesh->mVertexCount; v++) + { + CCollisionMesh::CCollisionVertex *vtx = &cmesh->mCollisionVertices[v]; + vtx->Pos = CVector3f(MREA); + vtx->Properties = loader.properties[vtxIndices[v]]; + } + + return cmesh; +} + +CCollisionMesh::CCollisionOctree* CCollisionLoader::parseOctree(CInputStream&) +{ + // Not using: Parameter 1 (CInputStream& - src) + return nullptr; +} + +CCollisionMesh::CCollisionOctree::SBranch* CCollisionLoader::parseOctreeBranch(CInputStream&) +{ + // Not using: Parameter 1 (CInputStream& - src) + return nullptr; +} + +CCollisionMesh::CCollisionOctree::SLeaf* CCollisionLoader::parseOctreeLeaf(CInputStream&) +{ + // Not using: Parameter 1 (CInputStream& - src) + return nullptr; +} + +void CCollisionLoader::readPropertyFlags(CInputStream& src) +{ + CCollisionMesh::SCollisionProperties property; + + if (version == Prime) + { + u32 flag = src.ReadLong(); + property.Invert = (flag >> 25) & 0x1; + } + + if (version == Echoes) + { + u64 flag = src.ReadLongLong(); + property.Invert = (flag >> 24) & 0x1; + } + + properties.push_back(property); +} diff --git a/Resource/factory/CCollisionLoader.h b/Resource/factory/CCollisionLoader.h new file mode 100644 index 00000000..25c1e6c1 --- /dev/null +++ b/Resource/factory/CCollisionLoader.h @@ -0,0 +1,31 @@ +#ifndef CCOLLISIONLOADER_H +#define CCOLLISIONLOADER_H + +#include "../CCollisionMesh.h" + +class CCollisionLoader +{ + enum ECollisionVersion; + + CCollisionMesh *mesh; + ECollisionVersion version; + std::vector properties; + + enum ECollisionVersion + { + Prime = 0x3, + Echoes = 0x4, + DonkeyKongCountryReturns = 0x5 + }; + + CCollisionLoader(); + CCollisionMesh::CCollisionOctree* parseOctree(CInputStream& src); + CCollisionMesh::CCollisionOctree::SBranch* parseOctreeBranch(CInputStream& src); + CCollisionMesh::CCollisionOctree::SLeaf* parseOctreeLeaf(CInputStream& src); + void readPropertyFlags(CInputStream& src); + +public: + static CCollisionMesh* LoadAreaCollision(CInputStream& MREA); +}; + +#endif // CCOLLISIONLOADER_H diff --git a/Resource/factory/CFontLoader.cpp b/Resource/factory/CFontLoader.cpp new file mode 100644 index 00000000..a5faded6 --- /dev/null +++ b/Resource/factory/CFontLoader.cpp @@ -0,0 +1,119 @@ +#include "CFontLoader.h" +#include +#include + +CFontLoader::CFontLoader() +{ +} + +CFont* CFontLoader::LoadFont(CInputStream& FONT) +{ + // If I seek past a value without reading it, then it's because I don't know what it is + mpFont->mUnknown = FONT.ReadLong(); + mpFont->mLineHeight = FONT.ReadLong(); + mpFont->mVerticalOffset = FONT.ReadLong(); + mpFont->mLineMargin = FONT.ReadLong(); + if (mVersion > ePrimeKioskDemo) FONT.Seek(0x4, SEEK_CUR); + FONT.Seek(0x2, SEEK_CUR); + mpFont->mDefaultSize = FONT.ReadLong(); + mpFont->mFontName = FONT.ReadString(); + + if (mVersion <= eEchoes) mpFont->mpFontTexture = (CTexture*) gResCache.GetResource(FONT.ReadLong(), "TXTR"); + else mpFont->mpFontTexture = (CTexture*) gResCache.GetResource(FONT.ReadLongLong(), "TXTR"); + mpFont->mTextureToken = CToken(mpFont->mpFontTexture); + + mpFont->mTextureFormat = FONT.ReadLong(); + u32 NumGlyphs = FONT.ReadLong(); + mpFont->mGlyphs.reserve(NumGlyphs); + + for (u32 iGlyph = 0; iGlyph < NumGlyphs; iGlyph++) + { + CFont::SGlyph Glyph; + Glyph.Character = FONT.ReadShort(); + + float TexCoordL = FONT.ReadFloat(); + float TexCoordU = FONT.ReadFloat(); + float TexCoordR = FONT.ReadFloat(); + float TexCoordD = FONT.ReadFloat(); + Glyph.TexCoords[0] = CVector2f(TexCoordL, TexCoordU); // Upper-left + Glyph.TexCoords[1] = CVector2f(TexCoordR, TexCoordU); // Upper-right + Glyph.TexCoords[2] = CVector2f(TexCoordL, TexCoordD); // Lower-left + Glyph.TexCoords[3] = CVector2f(TexCoordR, TexCoordD); // Lower-right + + if (mVersion <= ePrime) + { + Glyph.RGBAChannel = 0; + Glyph.LeftPadding = FONT.ReadLong(); + Glyph.PrintAdvance = FONT.ReadLong(); + Glyph.RightPadding = FONT.ReadLong(); + Glyph.Width = FONT.ReadLong(); + Glyph.Height = FONT.ReadLong(); + Glyph.BaseOffset = FONT.ReadLong(); + Glyph.KerningIndex = FONT.ReadLong(); + } + else if (mVersion >= eEchoes) + { + Glyph.RGBAChannel = FONT.ReadByte(); + Glyph.LeftPadding = FONT.ReadByte(); + Glyph.PrintAdvance = FONT.ReadByte(); + Glyph.RightPadding = FONT.ReadByte(); + Glyph.Width = FONT.ReadByte(); + Glyph.Height = FONT.ReadByte(); + Glyph.BaseOffset = FONT.ReadByte(); + Glyph.KerningIndex = FONT.ReadShort(); + } + mpFont->mGlyphs[Glyph.Character] = Glyph; + } + + u32 NumKerningPairs = FONT.ReadLong(); + mpFont->mKerningTable.reserve(NumKerningPairs); + + for (u32 iKern = 0; iKern < NumKerningPairs; iKern++) + { + CFont::SKerningPair Pair; + Pair.CharacterA = FONT.ReadShort(); + Pair.CharacterB = FONT.ReadShort(); + Pair.Adjust = FONT.ReadLong(); + mpFont->mKerningTable.push_back(Pair); + } + + return mpFont; +} + +CFont* CFontLoader::LoadFONT(CInputStream& FONT) +{ + if (!FONT.IsValid()) return nullptr; + Log::Write("Loading " + FONT.GetSourceString()); + + CFourCC Magic(FONT); + if (Magic != "FONT") + { + Log::FileError(FONT.GetSourceString(), "Invalid FONT magic: " + StringUtil::ToHexString((u32) Magic.ToLong())); + return nullptr; + } + + u32 FileVersion = FONT.ReadLong(); + EGame Version = GetFormatVersion(FileVersion); + if (Version == eUnknownVersion) + { + Log::FileError(FONT.GetSourceString(), "Unsupported FONT version: " + StringUtil::ToHexString(FileVersion)); + return nullptr; + } + + CFontLoader Loader; + Loader.mpFont = new CFont(); + Loader.mVersion = Version; + return Loader.LoadFont(FONT); +} + +EGame CFontLoader::GetFormatVersion(u32 Version) +{ + switch (Version) + { + case 1: return ePrimeKioskDemo; + case 2: return ePrime; + case 4: return eEchoes; + case 5: return eCorruption; + default: return eUnknownVersion; + } +} diff --git a/Resource/factory/CFontLoader.h b/Resource/factory/CFontLoader.h new file mode 100644 index 00000000..57e92bfb --- /dev/null +++ b/Resource/factory/CFontLoader.h @@ -0,0 +1,21 @@ +#ifndef CFONTLOADER_H +#define CFONTLOADER_H + +#include "../CFont.h" +#include "../EFormatVersion.h" +#include + +class CFontLoader +{ + CFont *mpFont; + EGame mVersion; + + CFontLoader(); + CFont* LoadFont(CInputStream& FONT); + +public: + static CFont* LoadFONT(CInputStream& FONT); + static EGame GetFormatVersion(u32 Version); +}; + +#endif // CFONTLOADER_H diff --git a/Resource/factory/CMaterialLoader.cpp b/Resource/factory/CMaterialLoader.cpp new file mode 100644 index 00000000..3bece69e --- /dev/null +++ b/Resource/factory/CMaterialLoader.cpp @@ -0,0 +1,581 @@ +#include "CMaterialLoader.h" +#include +#include +#include +#include +#include + +CMaterialLoader::CMaterialLoader() +{ + mCorruptionFlags = 0; + mHasOPAC = false; +} + +CMaterialLoader::~CMaterialLoader() +{ +} + +CMaterialSet* CMaterialLoader::LoadMaterialSet(CInputStream& Mat, EGame Version) +{ + CMaterialLoader Loader; + Loader.mpSet = new CMaterialSet(); + Loader.mpFile = &Mat; + Loader.mVersion = Version; + + if ((Version >= ePrimeKioskDemo) && (Version <= eEchoes)) + Loader.ReadPrimeMatSet(); + else + Loader.ReadCorruptionMatSet(); + + return Loader.mpSet; +} + +void CMaterialLoader::ReadPrimeMatSet() +{ + // Textures + u32 TexCount = mpFile->ReadLong(); + mpSet->textures.resize(TexCount); + + for (u32 t = 0; t < TexCount; t++) + { + u32 TextureID = mpFile->ReadLong(); + mpSet->textures[t] = (CTexture*) gResCache.GetResource(TextureID, "TXTR"); + } + + // Materials + u32 MatCount = mpFile->ReadLong(); + std::vector offsets(MatCount); + for (u32 m = 0; m < MatCount; m++) + offsets[m] = mpFile->ReadLong(); + + u32 mats_start = mpFile->Tell(); + mpSet->materials.resize(MatCount); + for (u32 m = 0; m < MatCount; m++) + { + mpSet->materials[m] = ReadPrimeMaterial(); + mpSet->materials[m]->mVersion = mVersion; + mpFile->Seek(mats_start + offsets[m], SEEK_SET); + } +} + +CMaterial* CMaterialLoader::ReadPrimeMaterial() +{ + CMaterial *pMat = new CMaterial(); + pMat->mEnableBloom = false; + + // Flags + pMat->mOptions = (CMaterial::EMaterialOptions) (mpFile->ReadLong() & CMaterial::eAllSettings); + + // Textures + u32 NumTextures = mpFile->ReadLong(); + std::vector TextureIndices(NumTextures); + + for (u32 iTex = 0; iTex < NumTextures; iTex++) + { + u32 Index = mpFile->ReadLong(); + TextureIndices[iTex] = Index; + } + + // Vertex description + pMat->mVtxDesc = (EVertexDescription) mpFile->ReadLong(); + + // Unknowns + if (mVersion >= eEchoesDemo) + { + pMat->mEchoesUnknownA = mpFile->ReadLong(); + pMat->mEchoesUnknownB = mpFile->ReadLong(); + } + mpFile->Seek(0x4, SEEK_CUR); // Skipping group index + + // Konst + if (pMat->mOptions & CMaterial::eKonst) + { + u32 KonstCount = mpFile->ReadLong(); + + for (u32 iKonst = 0; iKonst < KonstCount; iKonst++) + { + if (iKonst >= 4) break; + pMat->mKonstColors[iKonst] = CColor(*mpFile); + } + if (KonstCount > 4) mpFile->Seek(0x4 * (KonstCount - 4), SEEK_CUR); + } + + // Blend mode + pMat->mBlendDstFac = glBlendFactor[mpFile->ReadShort()]; + pMat->mBlendSrcFac = glBlendFactor[mpFile->ReadShort()]; + + // Indirect texture + if (pMat->mOptions & CMaterial::eIndStage) + { + u32 IndTexIndex = mpFile->ReadLong(); + pMat->mpIndirectTexture = mpSet->textures[IndTexIndex]; + } + + // Color channels + u32 ChanCount = mpFile->ReadLong(); + pMat->mLightingEnabled = ((mpFile->ReadLong() & 0x1) == 1); + mpFile->Seek((4 * ChanCount) - 4, SEEK_CUR); + + // TEV + u32 TevCount = mpFile->ReadLong(); + pMat->mPasses.resize(TevCount); + + for (u32 iTev = 0; iTev < TevCount; iTev++) + { + CMaterialPass *pPass = new CMaterialPass(pMat); + + u32 ColorIn = mpFile->ReadLong(); + u32 AlphaIn = mpFile->ReadLong(); + pPass->mColorOutput = (ETevOutput) ((mpFile->ReadLong() & 0x600) >> 9); + pPass->mAlphaOutput = (ETevOutput) ((mpFile->ReadLong() & 0x600) >> 9); + mpFile->Seek(0x1, SEEK_CUR); // Padding byte + pPass->mKAlphaSel = (ETevKSel) mpFile->ReadByte(); + pPass->mKColorSel = (ETevKSel) mpFile->ReadByte(); + pPass->mRasSel = (ETevRasSel) (u8) mpFile->ReadByte(); + + for (u32 iInput = 0; iInput < 4; iInput++) + { + pPass->mColorInputs[iInput] = (ETevColorInput) ((ColorIn >> (iInput * 5)) & 0xF); + pPass->mAlphaInputs[iInput] = (ETevAlphaInput) ((AlphaIn >> (iInput * 5)) & 0x7); + } + + pMat->mPasses[iTev] = pPass; + } + + std::vector TevCoordIndices(TevCount); + for (u32 iTev = 0; iTev < TevCount; iTev++) + { + mpFile->Seek(0x2, SEEK_CUR); + CMaterialPass *pPass = pMat->Pass(iTev); + + u8 TexSel = mpFile->ReadByte(); + if ((TexSel == 0xFF) || (TexSel >= mpSet->textures.size())) + { + pPass->mpTexture = nullptr; + } + else + { + pPass->mpTexture = mpSet->textures[ TextureIndices[TexSel] ]; + pPass->mTexToken = CToken(pPass->mpTexture); + } + + TevCoordIndices[iTev] = mpFile->ReadByte(); + } + + // TexGens + u32 TexGenCount = mpFile->ReadLong(); + std::vector TexGens(TexGenCount); + + for (u32 iTex = 0; iTex < TexGenCount; iTex++) + TexGens[iTex] = mpFile->ReadLong(); + + // UV animations + mpFile->Seek(0x4, SEEK_CUR); // Skipping UV anims size + u32 NumAnims = mpFile->ReadLong(); + + struct SUVAnim { + s32 Mode; float Params[4]; + }; + std::vector Anims(NumAnims); + + for (u32 iAnim = 0; iAnim < NumAnims; iAnim++) + { + Anims[iAnim].Mode = mpFile->ReadLong(); + + switch (Anims[iAnim].Mode) + { + case 3: // Rotation + case 7: // ??? + Anims[iAnim].Params[0] = mpFile->ReadFloat(); + Anims[iAnim].Params[1] = mpFile->ReadFloat(); + break; + case 2: // UV Scroll + case 4: // U Scroll + case 5: // V Scroll + Anims[iAnim].Params[0] = mpFile->ReadFloat(); + Anims[iAnim].Params[1] = mpFile->ReadFloat(); + Anims[iAnim].Params[2] = mpFile->ReadFloat(); + Anims[iAnim].Params[3] = mpFile->ReadFloat(); + break; + case 0: // Inverse ModelView Matrix + case 1: // Inverse ModelView Matrix Translated + case 6: // Model Matrix + break; + default: + Log::FileError(mpFile->GetSourceString(), mpFile->Tell() - 4, "Unsupported animation mode encountered: " + StringUtil::ToHexString((u32) Anims[iAnim].Mode)); + break; + } + } + + // Move TexGen and anims into passes + for (u32 iPass = 0; iPass < pMat->mPasses.size(); iPass++) + { + CMaterialPass *pPass = pMat->mPasses[iPass]; + u8 TexCoordIdx = TevCoordIndices[iPass]; + + if ((TexGens.size() == 0) || (TexCoordIdx == 0xFF)) + { + pPass->mTexCoordSource = 0xFF; + pPass->mAnimMode = eNoUVAnim; + } + + else + { + pPass->mTexCoordSource = (u8) ((TexGens[TexCoordIdx] & 0x1F0) >> 4); + + // Next step - find which animation is used by this pass + // Texture matrix is a reliable way to tell, because every UV anim mode generates a texture matrix + u32 TexMtxIdx = ((TexGens[TexCoordIdx] & 0x3E00) >> 9) / 3; + + if (TexMtxIdx == 10) pPass->mAnimMode = eNoUVAnim; // 10 is identity matrix; indicates no UV anim for this pass + + else + { + pPass->mAnimMode = (EUVAnimMode) Anims[TexMtxIdx].Mode; + + for (u32 iParam = 0; iParam < 4; iParam++) + pPass->mAnimParams[iParam] = Anims[TexMtxIdx].Params[iParam]; + } + } + } + + return pMat; +} + +void CMaterialLoader::ReadCorruptionMatSet() +{ + u32 NumMats = mpFile->ReadLong(); + mpSet->materials.resize(NumMats); + + for (u32 iMat = 0; iMat < NumMats; iMat++) + { + u32 Size = mpFile->ReadLong(); + u32 Next = mpFile->Tell() + Size; + mpSet->materials[iMat] = ReadCorruptionMaterial(); + mpSet->materials[iMat]->mVersion = mVersion; + mpFile->Seek(Next, SEEK_SET); + } +} + +CMaterial* CMaterialLoader::ReadCorruptionMaterial() +{ + CMaterial *pMat = new CMaterial(); + pMat->mOptions = CMaterial::eDepthWrite; + pMat->mEnableBloom = true; + + // Flags + u32 Flags = mpFile->ReadLong(); + if (Flags & 0x8) + { + pMat->mBlendSrcFac = GL_SRC_ALPHA; + pMat->mBlendDstFac = GL_ONE_MINUS_SRC_ALPHA; + pMat->mOptions |= CMaterial::eTransparent; + } + else if (Flags & 0x20) + { + pMat->mBlendSrcFac = GL_ONE; + pMat->mBlendDstFac = GL_ONE; + pMat->mOptions |= CMaterial::eTransparent; + } + + if (Flags & 0x10) pMat->mOptions |= CMaterial::ePunchthrough; + if (Flags & 0x100) pMat->mOptions |= CMaterial::eOccluder; + mHas0x400 = ((Flags & 0x400) != 0); + + mpFile->Seek(0x8, SEEK_CUR); // Don't know what any of this is + pMat->mVtxDesc = (EVertexDescription) mpFile->ReadLong(); + mpFile->Seek(0xC, SEEK_CUR); + + // Initialize all KColors to white + pMat->mKonstColors[0] = CColor::skWhite; + pMat->mKonstColors[1] = CColor::skWhite; + pMat->mKonstColors[2] = CColor::skWhite; + // Current usage of KColors: + // 0 - INT OPAC (transparency) + // 1 - CLR DIFB (lightmap multiplier) + // 2 - CLR CLR (additive color) + + while (true) + { + CFourCC Type = mpFile->ReadLong(); + + // END + if (Type == "END ") + break; + + // INT + if (Type == "INT ") + { + CFourCC IntType = mpFile->ReadLong(); + u8 IntVal = (u8) mpFile->ReadLong(); + + if (IntType == "OPAC") + { + pMat->mKonstColors[0] = CColor(1.f, 1.f, 1.f, (float) IntVal / 255); + mHasOPAC = true; + } + } + + // CLR + if (Type == "CLR ") + { + CFourCC ClrType = mpFile->ReadLong(); + CColor ClrVal(*mpFile); + + if (ClrType == "DIFB") + { + ClrVal.a = 0xFF; + pMat->mKonstColors[1] = ClrVal; + } + + if (ClrType == "CLR ") + { + // I'm not sure what this does. It has a clear and obvious ingame effect + // but I need to test it further to tell specifically what it's doing. + // All attempts at implementing this just break things. + } + } + + // PASS + if (Type == "PASS") + { + CMaterialPass *pPass = new CMaterialPass(pMat); + mPassOffsets.push_back(mpFile->Tell() - 4); + + u32 Size = mpFile->ReadLong(); + u32 Next = Size + mpFile->Tell(); + + pPass->mPassType = mpFile->ReadLong(); + pPass->mSettings = (CMaterialPass::EPassSettings) mpFile->ReadLong(); + + u64 TextureID = mpFile->ReadLongLong(); + if (TextureID == 0xFFFFFFFFFFFFFFFF) + { + Log::FileWarning(mpFile->GetSourceString(), mPassOffsets.back(), "Skipping " + pPass->mPassType.ToString() + " pass with no texture"); + delete pPass; + continue; + } + + CTexture *pTex = (CTexture*) gResCache.GetResource(TextureID, "TXTR"); + mpSet->textures.push_back(pTex); + pPass->mpTexture = pTex; + pPass->mTexToken = CToken(pTex); + + pPass->mTexCoordSource = 4 + (u8) mpFile->ReadLong(); + u32 AnimSize = mpFile->ReadLong(); + + if (AnimSize > 0) + { + u16 Unknown1 = mpFile->ReadShort(); + u16 Unknown2 = mpFile->ReadShort(); + pPass->mAnimMode = (EUVAnimMode) mpFile->ReadLong(); + + switch (pPass->mAnimMode) + { + case 3: // Rotation + case 7: // ??? + pPass->mAnimParams[0] = mpFile->ReadFloat(); + pPass->mAnimParams[1] = mpFile->ReadFloat(); + break; + case 2: // UV Scroll + case 4: // U Scroll + case 5: // V Scroll + pPass->mAnimParams[0] = mpFile->ReadFloat(); + pPass->mAnimParams[1] = mpFile->ReadFloat(); + pPass->mAnimParams[2] = mpFile->ReadFloat(); + pPass->mAnimParams[3] = mpFile->ReadFloat(); + break; + case 0: // Inverse ModelView Matrix + case 1: // Inverse ModelView Matrix Translated + case 6: // Model Matrix + case 10: // Yet-to-be-named + break; + default: + Log::FileError(mpFile->GetSourceString(), mpFile->Tell() - 8, "Unsupported animation mode encountered: " + StringUtil::ToHexString((u32) pPass->mAnimMode)); + break; + } + + // Hack until the correct way to determine tex coord source is figured out + if ((pPass->mAnimMode < 2) || (pPass->mAnimMode == 6) || (pPass->mAnimMode == 7) || (pPass->mAnimMode == 10)) + pPass->mTexCoordSource = 1; + } + + else pPass->mAnimMode = eNoUVAnim; + + pMat->mPasses.push_back(pPass); + mpFile->Seek(Next, SEEK_SET); + } + } + + CreateCorruptionPasses(pMat); + mHasOPAC = false; + return pMat; +} + +void CMaterialLoader::CreateCorruptionPasses(CMaterial *pMat) +{ + u32 NumPass = pMat->PassCount(); + bool Lightmap = false; + bool AlphaBlended = ((pMat->mBlendSrcFac == GL_SRC_ALPHA) && (pMat->mBlendDstFac == GL_ONE_MINUS_SRC_ALPHA)); + + for (u32 iPass = 0; iPass < NumPass; iPass++) + { + CMaterialPass *pPass = pMat->Pass(iPass); + CFourCC Type = pPass->Type(); + + // Color Map (Diffuse) + if (Type == "CLR ") + { + if (Lightmap) + { + pPass->SetColorInputs(eZeroRGB, eColor0RGB, eTextureRGB, ePrevRGB); + } + + else + { + pPass->SetColorInputs(eZeroRGB, eRasRGB, eTextureRGB, ePrevRGB); + pPass->SetRasSel(eRasColor0A0); + } + + + if (pMat->mOptions & CMaterial::ePunchthrough) + { + pPass->SetAlphaInputs(eZeroAlpha, eZeroAlpha, eZeroAlpha, eTextureAlpha); + } + else if (mHasOPAC) + { + pPass->SetAlphaInputs(eZeroAlpha, eZeroAlpha, eZeroAlpha, eKonstAlpha); + pPass->SetKColorSel(eKonst0_RGB); + pPass->SetKAlphaSel(eKonst0_A); + } + else + { + pPass->SetAlphaInputs(eZeroAlpha, eZeroAlpha, eZeroAlpha, ePrevAlpha); + } + + pPass->SetColorOutput(ePrevReg); + pPass->SetAlphaOutput(ePrevReg); + } + + // Lightmap + else if (Type == "DIFF") + { + pPass->SetColorInputs(eZeroRGB, eKonstRGB, eTextureRGB, eZeroRGB); + pPass->SetAlphaInputs(eZeroAlpha, eZeroAlpha, eZeroAlpha, eKonstAlpha); + pPass->SetColorOutput(eColor0Reg); + pPass->SetAlphaOutput(eColor0Reg); + pPass->SetKColorSel(eKonst1_RGB); + pPass->SetKAlphaSel(eKonst1_A); + pPass->SetRasSel(eRasColor0A0); + Lightmap = true; + } + + // Bloom Lightmap + else if (Type == "BLOL") + { + // Bloom maps work by writing to framebuffer alpha. Can't do this on alpha-blended mats. + pPass->SetColorInputs(eZeroRGB, eZeroRGB, eZeroRGB, ePrevRGB); + + if ((AlphaBlended) || (pMat->mOptions & CMaterial::ePunchthrough)) + pPass->SetAlphaInputs(eZeroAlpha, eZeroAlpha, eZeroAlpha, ePrevAlpha); + else + pPass->SetAlphaInputs(eZeroAlpha, eZeroAlpha, eZeroAlpha, eTextureAlpha); + + pPass->SetColorOutput(ePrevReg); + pPass->SetAlphaOutput(ePrevReg); + } + + // Rim Light Map + else if (Type == "RIML") + { + pPass->SetColorInputs(eZeroRGB, eOneRGB, ePrevRGB, eTextureRGB); + pPass->SetAlphaInputs(eZeroAlpha, eZeroAlpha, eZeroAlpha, ePrevAlpha); + pPass->SetColorOutput(ePrevReg); + pPass->SetAlphaOutput(ePrevReg); + } + + // Emissive Map + else if (Type == "INCA") + { + pPass->SetColorInputs(eZeroRGB, eTextureRGB, eOneRGB, ePrevRGB); + + if ((pPass->mSettings & CMaterialPass::eEmissiveBloom) && (!AlphaBlended)) + { + pPass->SetAlphaInputs(eZeroAlpha, eTextureAlpha, eKonstAlpha, ePrevAlpha); + pPass->SetKAlphaSel(eKonstOneFourth); + } + else + { + pPass->SetAlphaInputs(eZeroAlpha, eZeroAlpha, eZeroAlpha, ePrevAlpha); + } + + pPass->SetColorOutput(ePrevReg); + pPass->SetAlphaOutput(ePrevReg); + } + + // Opacity Map + else if (Type == "TRAN") + { + pPass->SetColorInputs(eZeroRGB, eZeroRGB, eZeroRGB, ePrevRGB); + + if (pPass->mSettings & CMaterialPass::eInvertOpacityMap) + pPass->SetAlphaInputs(eKonstAlpha, eZeroAlpha, eTextureAlpha, eZeroAlpha); + else + pPass->SetAlphaInputs(eZeroAlpha, eKonstAlpha, eTextureAlpha, eZeroAlpha); + + pPass->SetColorOutput(ePrevReg); + pPass->SetAlphaOutput(ePrevReg); + } + + // Specular Map + else if (Type == "RFLV") + { + pPass->SetColorInputs(eZeroRGB, eZeroRGB, eZeroRGB, eTextureRGB); + pPass->SetAlphaInputs(eZeroAlpha, eZeroAlpha, eZeroAlpha, ePrevAlpha); + pPass->SetColorOutput(eColor2Reg); + pPass->SetAlphaOutput(eColor2Reg); + } + + // Reflection Map + else if (Type == "RFLD") + { + pPass->SetColorInputs(eZeroRGB, eColor2RGB, eTextureRGB, ePrevRGB); + pPass->SetAlphaInputs(eZeroAlpha, eZeroAlpha, eZeroAlpha, ePrevAlpha); + pPass->SetColorOutput(ePrevReg); + pPass->SetAlphaOutput(ePrevReg); + if (mHas0x400) pPass->SetEnabled(false); + } + + // Bloom + else if (Type == "BLOI") + { + pPass->SetColorInputs(eZeroRGB, eZeroRGB, eZeroRGB, ePrevRGB); + + // Comes out wrong every time even though this is exactly how the Dolphin shaders say this is done. + if (AlphaBlended) + pPass->SetAlphaInputs(eZeroAlpha, eZeroAlpha, eZeroAlpha, ePrevAlpha); + else + pPass->SetAlphaInputs(eTextureAlpha, eZeroAlpha, eZeroAlpha, ePrevAlpha); + + pPass->SetColorOutput(ePrevReg); + pPass->SetAlphaOutput(ePrevReg); + } + + // Toon? Don't know what it's for but got TEV setup from shader dumps + else if (Type == "TOON") + { + pPass->SetColorInputs(eZeroRGB, ePrevRGB, eTextureRGB, eZeroRGB); + pPass->SetAlphaInputs(eZeroAlpha, eZeroAlpha, eZeroAlpha, eTextureAlpha); + pPass->SetColorOutput(ePrevReg); + pPass->SetColorOutput(ePrevReg); + } + + else if (Type == "CUST") {} + + else + { + Log::FileError(mpFile->GetSourceString(), mPassOffsets[iPass], "Unsupported material pass type: " + Type.ToString()); + pPass->mEnabled = false; + } + } +} diff --git a/Resource/factory/CMaterialLoader.h b/Resource/factory/CMaterialLoader.h new file mode 100644 index 00000000..42431f21 --- /dev/null +++ b/Resource/factory/CMaterialLoader.h @@ -0,0 +1,38 @@ +#ifndef CMATERIALLOADER_H +#define CMATERIALLOADER_H + +#include +#include "../CMaterialSet.h" +#include "../EFormatVersion.h" +#include + +class CMaterialLoader +{ + // Material data + CMaterialSet *mpSet; + CInputStream *mpFile; + EGame mVersion; + bool mHasOPAC; + bool mHas0x400; + + CColor mCorruptionColors[4]; + u8 mCorruptionInts[5]; + u32 mCorruptionFlags; + std::vector mPassOffsets; + + CMaterialLoader(); + ~CMaterialLoader(); + + // Load Functions + void ReadPrimeMatSet(); + CMaterial* ReadPrimeMaterial(); + + void ReadCorruptionMatSet(); + CMaterial* ReadCorruptionMaterial(); + void CreateCorruptionPasses(CMaterial *pMat); + +public: + static CMaterialSet* LoadMaterialSet(CInputStream& Mat, EGame Version); +}; + +#endif // CMATERIALLOADER_H diff --git a/Resource/factory/CModelLoader.cpp b/Resource/factory/CModelLoader.cpp new file mode 100644 index 00000000..816c2e4d --- /dev/null +++ b/Resource/factory/CModelLoader.cpp @@ -0,0 +1,445 @@ +#include "CModelLoader.h" +#include "CMaterialLoader.h" +#include + +CModelLoader::CModelLoader() +{ + mFlags = eNoFlags; +} + +CModelLoader::~CModelLoader() +{ +} + +void CModelLoader::LoadWorldMeshHeader(CInputStream &Model) +{ + // I don't really have any need for most of this data, so + Model.Seek(0x34, SEEK_CUR); + mAABox = CAABox(Model); + mpBlockMgr->ToNextBlock(); +} + +void CModelLoader::LoadAttribArrays(CInputStream& Model) +{ + // Positions + if (mFlags & eShortPositions) // Shorts (DKCR only) + { + mPositions.resize(mpBlockMgr->CurrentBlockSize() / 0x6); + float Divisor = 8192.f; // Might be incorrect! Needs verification via size comparison. + + for (u32 iVtx = 0; iVtx < mPositions.size(); iVtx++) + { + float x = Model.ReadShort() / Divisor; + float y = Model.ReadShort() / Divisor; + float z = Model.ReadShort() / Divisor; + mPositions[iVtx] = CVector3f(x, y, z); + } + } + + else // Floats + { + mPositions.resize(mpBlockMgr->CurrentBlockSize() / 0xC); + + for (u32 iVtx = 0; iVtx < mPositions.size(); iVtx++) + mPositions[iVtx] = CVector3f(Model); + } + + mpBlockMgr->ToNextBlock(); + + // Normals + if (mFlags & eShortNormals) // Shorts + { + mNormals.resize(mpBlockMgr->CurrentBlockSize() / 0x6); + float Divisor = (mVersion < eReturns) ? 32768.f : 16384.f; + + for (u32 iVtx = 0; iVtx < mNormals.size(); iVtx++) + { + float x = Model.ReadShort() / Divisor; + float y = Model.ReadShort() / Divisor; + float z = Model.ReadShort() / Divisor; + mNormals[iVtx] = CVector3f(x, y, z); + } + } + else // Floats + { + mNormals.resize(mpBlockMgr->CurrentBlockSize() / 0xC); + + for (u32 iVtx = 0; iVtx < mNormals.size(); iVtx++) + mNormals[iVtx] = CVector3f(Model); + } + + mpBlockMgr->ToNextBlock(); + + // Colors + mColors.resize(mpBlockMgr->CurrentBlockSize() / 4); + + for (u32 iVtx = 0; iVtx < mColors.size(); iVtx++) + mColors[iVtx] = CColor(Model); + + mpBlockMgr->ToNextBlock(); + + + // Float UVs + mTex0.resize(mpBlockMgr->CurrentBlockSize() / 0x8); + + for (u32 iVtx = 0; iVtx < mTex0.size(); iVtx++) + mTex0[iVtx] = CVector2f(Model); + + mpBlockMgr->ToNextBlock(); + + // Short UVs + if (mFlags & eHasTex1) + { + mTex1.resize(mpBlockMgr->CurrentBlockSize() / 0x4); + float Divisor = (mVersion < eReturns) ? 32768.f : 8192.f; + + for (u32 iVtx = 0; iVtx < mTex1.size(); iVtx++) + { + float x = Model.ReadShort() / Divisor; + float y = Model.ReadShort() / Divisor; + mTex1[iVtx] = CVector2f(x, y); + } + + mpBlockMgr->ToNextBlock(); + } +} + +void CModelLoader::LoadSurfaceOffsets(CInputStream& Model) +{ + mSurfaceCount = Model.ReadLong(); + mSurfaceOffsets.resize(mSurfaceCount); + + for (u32 iSurf = 0; iSurf < mSurfaceCount; iSurf++) + mSurfaceOffsets[iSurf] = Model.ReadLong(); + + mpBlockMgr->ToNextBlock(); +} + +SModelData* CModelLoader::LoadSurfaces(CInputStream& Model) +{ + // This function is meant to be called at the start of the first surface + SModelData *pData = new SModelData; + u32 Offset = Model.Tell(); + + // Surfaces + pData->mSurfaces.resize(mSurfaceCount); + + for (u32 iSurf = 0; iSurf < mSurfaceCount; iSurf++) + { + SSurface *pSurf = new SSurface; + pData->mSurfaces[iSurf] = pSurf; + u32 NextSurface = mpBlockMgr->NextOffset(); + + // Surface header + if (mVersion < eReturns) + LoadSurfaceHeaderPrime(Model, pSurf); + else + LoadSurfaceHeaderDKCR(Model, pSurf); + + bool HasAABB = (pSurf->AABox != CAABox::skInfinite); + CMaterial *pMat = mMaterials[0]->materials[pSurf->MaterialID]; + + // Primitive table + u8 Flag = Model.ReadByte(); + + while ((Flag != 0) && ((u32) Model.Tell() < NextSurface)) + { + SSurface::SPrimitive Prim; + Prim.Type = EGXPrimitiveType(Flag & 0xF8); + u16 VertexCount = Model.ReadShort(); + + for (u16 iVtx = 0; iVtx < VertexCount; iVtx++) + { + CVertex Vtx; + EVertexDescription VtxDesc = pMat->VtxDesc(); + + for (u32 iMtxAttr = 0; iMtxAttr < 8; iMtxAttr++) + if (VtxDesc & (ePosMtx << iMtxAttr)) Model.Seek(0x1, SEEK_CUR); + + // Only thing to do here is check whether each attribute is present, and if so, read it. + // A couple attributes have special considerations; normals can be floats or shorts, as can tex0, depending on vtxfmt. + // tex0 can also be read from either UV buffer; depends what the material says. + + // Position + if (VtxDesc & ePosition) + { + u16 PosIndex = Model.ReadShort() & 0xFFFF; + Vtx.Position = mPositions[PosIndex]; + Vtx.ArrayPosition = PosIndex; + + if (!HasAABB) pSurf->AABox.ExpandBounds(Vtx.Position); + } + + // Normal + if (VtxDesc & eNormal) + Vtx.Normal = mNormals[Model.ReadShort() & 0xFFFF]; + + // Color + for (u32 c = 0; c < 2; c++) + if (VtxDesc & (eColor0 << (c * 2))) + Vtx.Color[c] = mColors[Model.ReadShort() & 0xFFFF]; + + // Tex Coords - these are done a bit differently in DKCR than in the Prime series + if (mVersion < eReturns) + { + // Tex0 + if (VtxDesc & eTex0) + { + if ((mFlags & eHasTex1) && (pMat->Options() & CMaterial::eShortTexCoord)) + Vtx.Tex[0] = mTex1[Model.ReadShort() & 0xFFFF]; + else + Vtx.Tex[0] = mTex0[Model.ReadShort() & 0xFFFF]; + } + + // Tex1-7 + for (u32 iTex = 1; iTex < 7; iTex++) + if (VtxDesc & (eTex0 << (iTex * 2))) + Vtx.Tex[iTex] = mTex0[Model.ReadShort() & 0xFFFF]; + } + + else + { + // Tex0-7 + for (u32 iTex = 0; iTex < 7; iTex++) + { + if (VtxDesc & (eTex0 << iTex * 2)) + { + if (!mSurfaceUsingTex1) + Vtx.Tex[iTex] = mTex0[Model.ReadShort() & 0xFFFF]; + else + Vtx.Tex[iTex] = mTex1[Model.ReadShort() & 0xFFFF]; + } + } + } + + Prim.Vertices.push_back(Vtx); + } // Vertex array end + + // Update vertex/triangle count + pSurf->VertexCount += VertexCount; + + switch (Prim.Type) + { + case eGX_Triangles: + pSurf->TriangleCount += VertexCount / 3; + break; + case eGX_TriangleFan: + case eGX_TriangleStrip: + pSurf->TriangleCount += VertexCount - 2; + break; + } + + pSurf->Primitives.push_back(Prim); + Flag = Model.ReadByte(); + } // Primitive table end + + mpBlockMgr->ToNextBlock(); + } // Submesh table end + + return pData; +} + +void CModelLoader::LoadSurfaceHeaderPrime(CInputStream& Model, SSurface *pSurf) +{ + pSurf->CenterPoint = CVector3f(Model); + pSurf->MaterialID = Model.ReadLong(); + + Model.Seek(0xC, SEEK_CUR); + u32 ExtraSize = Model.ReadLong(); + pSurf->ReflectionDirection = CVector3f(Model); + if (mVersion >= eEchoesDemo) Model.Seek(0x4, SEEK_CUR); // Extra values in Echoes. Not sure what they are. + bool HasAABox = (ExtraSize >= 0x18); // MREAs have a set of bounding box coordinates here. + + // If this surface has a bounding box, we can just read it here. Otherwise we'll fill it in manually. + if (HasAABox) + { + ExtraSize -= 0x18; + pSurf->AABox = CAABox(Model); + } + else + pSurf->AABox = CAABox::skInfinite; + + Model.Seek(ExtraSize, SEEK_CUR); + Model.SeekToBoundary(32); +} + +void CModelLoader::LoadSurfaceHeaderDKCR(CInputStream& Model, SSurface *pSurf) +{ + pSurf->CenterPoint = CVector3f(Model); + Model.Seek(0xE, SEEK_CUR); + pSurf->MaterialID = (u32) Model.ReadShort(); + Model.Seek(0x2, SEEK_CUR); + mSurfaceUsingTex1 = (Model.ReadByte() == 1); + u32 ExtraSize = Model.ReadByte(); + + if (ExtraSize > 0) + { + ExtraSize -= 0x18; + pSurf->AABox = CAABox(Model); + } + else + pSurf->AABox = CAABox::skInfinite; + + Model.Seek(ExtraSize, SEEK_CUR); + Model.SeekToBoundary(32); +} + +// ************ STATIC ************ +CModel* CModelLoader::LoadCMDL(CInputStream& CMDL) +{ + CModelLoader Loader; + Log::Write("Loading " + CMDL.GetSourceString()); + + // CMDL header - same across the three Primes, but different structure in DKCR + u32 Magic = CMDL.ReadLong(); + + u32 Version, BlockCount, MatSetCount; + CAABox AABox; + + // 0xDEADBABE - Metroid Prime seres + if (Magic == 0xDEADBABE) + { + Version = CMDL.ReadLong(); + u32 Flags = CMDL.ReadLong(); + AABox = CAABox(CMDL); + BlockCount = CMDL.ReadLong(); + MatSetCount = CMDL.ReadLong(); + + if (Flags & 0x2) Loader.mFlags |= eShortNormals; + if (Flags & 0x4) Loader.mFlags |= eHasTex1; + } + + // 0x9381000A - Donkey Kong Country Returns + else if (Magic == 0x9381000A) + { + Version = Magic & 0xFFFF; + u32 Flags = CMDL.ReadLong(); + AABox = CAABox(CMDL); + BlockCount = CMDL.ReadLong(); + MatSetCount = CMDL.ReadLong(); + + // todo: unknown flags + Loader.mFlags = eShortNormals | eHasTex1; + if (Flags & 0x10) Loader.mFlags |= eHasVisGroups; + if (Flags & 0x20) Loader.mFlags |= eShortPositions; + + // Visibility group data + // Skipping for now - should read in eventually + if (Flags & 0x10) + { + CMDL.Seek(0x4, SEEK_CUR); + u32 VisGroupCount = CMDL.ReadLong(); + + for (u32 iVis = 0; iVis < VisGroupCount; iVis++) + { + u32 NameLength = CMDL.ReadLong(); + CMDL.Seek(NameLength, SEEK_CUR); + } + + CMDL.Seek(0x14, SEEK_CUR); // no clue what any of this is! + } + } + + else + { + Log::FileError(CMDL.GetSourceString(), "Invalid CMDL magic: " + StringUtil::ToHexString(Magic)); + return nullptr; + } + + // The rest is common to all CMDL versions + Loader.mVersion = GetFormatVersion(Version); + + if (Loader.mVersion == eUnknownVersion) + { + Log::FileError(CMDL.GetSourceString(), "Unsupported CMDL version: " + StringUtil::ToHexString(Magic)); + return nullptr; + } + + CModel *pModel = new CModel(); + Loader.mpModel = pModel; + Loader.mpBlockMgr = new CBlockMgrIn(BlockCount, &CMDL); + CMDL.SeekToBoundary(32); + Loader.mpBlockMgr->Init(); + + // Materials + Loader.mMaterials.resize(MatSetCount); + for (u32 iMat = 0; iMat < MatSetCount; iMat++) + { + Loader.mMaterials[iMat] = CMaterialLoader::LoadMaterialSet(CMDL, Loader.mVersion); + + if (Loader.mVersion < eCorruptionProto) + Loader.mpBlockMgr->ToNextBlock(); + } + + pModel->mMaterialSets = Loader.mMaterials; + pModel->mHasOwnMaterials = true; + if (Loader.mVersion >= eCorruptionProto) Loader.mpBlockMgr->ToNextBlock(); + + // Mesh + Loader.LoadAttribArrays(CMDL); + Loader.LoadSurfaceOffsets(CMDL); + SModelData *pData = Loader.LoadSurfaces(CMDL); + + pModel->SetData(pData); + pModel->mAABox = AABox; + pModel->mHasOwnSurfaces = true; + + // Cleanup + delete pData; + delete Loader.mpBlockMgr; + return pModel; +} + +SModelData* CModelLoader::LoadWorldModel(CInputStream& MREA, CBlockMgrIn& BlockMgr, CMaterialSet& MatSet, EGame Version) +{ + CModelLoader Loader; + Loader.mpBlockMgr = &BlockMgr; + Loader.mVersion = Version; + Loader.mFlags = eShortNormals; + if (Version != eCorruptionProto) Loader.mFlags |= eHasTex1; + Loader.mMaterials.resize(1); + Loader.mMaterials[0] = &MatSet; + + Loader.LoadWorldMeshHeader(MREA); + Loader.LoadAttribArrays(MREA); + Loader.LoadSurfaceOffsets(MREA); + SModelData *pData = Loader.LoadSurfaces(MREA); + pData->mAABox = Loader.mAABox; + + return pData; +} + +SModelData* CModelLoader::LoadCorruptionWorldModel(CInputStream &MREA, CBlockMgrIn &BlockMgr, CMaterialSet &MatSet, u32 HeaderSecNum, u32 GPUSecNum, EGame Version) +{ + CModelLoader Loader; + Loader.mpBlockMgr = &BlockMgr; + Loader.mVersion = Version; + Loader.mFlags = eShortNormals; + Loader.mMaterials.resize(1); + Loader.mMaterials[0] = &MatSet; + if (Version == eReturns) Loader.mFlags |= eHasTex1; + + // Corruption/DKCR MREAs split the mesh header and surface offsets away from the actual geometry data so I need two section numbers to read it + BlockMgr.ToBlock(HeaderSecNum); + Loader.LoadWorldMeshHeader(MREA); + Loader.LoadSurfaceOffsets(MREA); + + BlockMgr.ToBlock(GPUSecNum); + Loader.LoadAttribArrays(MREA); + SModelData *pData = Loader.LoadSurfaces(MREA); + pData->mAABox = Loader.mAABox; + return pData; +} + +EGame CModelLoader::GetFormatVersion(u32 Version) +{ + switch (Version) + { + case 0x2: return ePrime; + case 0x3: return eEchoesDemo; + case 0x4: return eEchoes; + case 0x5: return eCorruption; + case 0xA: return eReturns; + default: return eUnknownVersion; + } +} diff --git a/Resource/factory/CModelLoader.h b/Resource/factory/CModelLoader.h new file mode 100644 index 00000000..cf7868e1 --- /dev/null +++ b/Resource/factory/CModelLoader.h @@ -0,0 +1,63 @@ +#ifndef CMODELLOADER_H +#define CMODELLOADER_H + +#include "../model/SModelData.h" +#include "../model/CBasicModel.h" +#include "../model/CModel.h" +#include "../EFormatVersion.h" +#include "CBlockMgrIn.h" +#include +#include +#include + +class CModelLoader +{ +public: + enum EModelFlags + { + eNoFlags = 0x0, + eShortPositions = 0x1, + eShortNormals = 0x2, + eHasTex1 = 0x4, + eHasVisGroups = 0x8 + }; + +private: + CModel *mpModel; + std::vector mMaterials; + CBlockMgrIn *mpBlockMgr; + CAABox mAABox; + EGame mVersion; + + std::vector mPositions; + std::vector mNormals; + std::vector mColors; + std::vector mTex0; + std::vector mTex1; + bool mSurfaceUsingTex1; + + u32 mSurfaceCount; + std::vector mSurfaceOffsets; + + EModelFlags mFlags; + + CModelLoader(); + ~CModelLoader(); + void LoadWorldMeshHeader(CInputStream& Model); + void LoadAttribArrays(CInputStream& Model); + void LoadAttribArraysDKCR(CInputStream& Model); + void LoadSurfaceOffsets(CInputStream& Model); + SModelData* LoadSurfaces(CInputStream& Model); + void LoadSurfaceHeaderPrime(CInputStream& Model, SSurface *pSurf); + void LoadSurfaceHeaderDKCR(CInputStream& Model, SSurface *pSurf); + +public: + static CModel* LoadCMDL(CInputStream& CMDL); + static SModelData* LoadWorldModel(CInputStream& MREA, CBlockMgrIn& BlockMgr, CMaterialSet& MatSet, EGame Version); + static SModelData* LoadCorruptionWorldModel(CInputStream& MREA, CBlockMgrIn& BlockMgr, CMaterialSet& MatSet, u32 HeaderSecNum, u32 GPUSecNum, EGame Version); + static EGame GetFormatVersion(u32 Version); +}; + +DEFINE_ENUM_FLAGS(CModelLoader::EModelFlags) + +#endif // CMODELLOADER_H diff --git a/Resource/factory/CScanLoader.cpp b/Resource/factory/CScanLoader.cpp new file mode 100644 index 00000000..65609e23 --- /dev/null +++ b/Resource/factory/CScanLoader.cpp @@ -0,0 +1,150 @@ +#include "CScanLoader.h" +#include +#include + +CScanLoader::CScanLoader() +{ +} + +CScan* CScanLoader::LoadScanMP1(CInputStream &SCAN) +{ + // Basic support at the moment - don't read animation/scan image data + SCAN.Seek(0x4, SEEK_CUR); // Skip FRME ID + mpScan->mpStringTable = (CStringTable*) gResCache.GetResource(SCAN.ReadLong(), "STRG"); + mpScan->mStringToken = CToken(mpScan->mpStringTable); + mpScan->mIsSlow = (SCAN.ReadLong() != 0); + mpScan->mCategory = (CScan::ELogbookCategory) SCAN.ReadLong(); + mpScan->mIsImportant = (SCAN.ReadByte() == 1); + return mpScan; +} + +CScan* CScanLoader::LoadScanMP2(CInputStream& SCAN) +{ + // The SCAN format in MP2 embeds a SNFO object using the same format as SCLY + // However since the contents of the file are consistent there's no need to delegate to CScriptLoader + SCAN.Seek(0x1, SEEK_CUR); + u32 NumInstances = SCAN.ReadLong(); + + if (NumInstances != 1) { + Log::FileError(SCAN.GetSourceString(), "SCAN has multiple instances"); + return nullptr; + } + + u32 ScanInfoStart = SCAN.Tell(); + + CFourCC SNFO(SCAN); + if (SNFO != "SNFO") { + Log::FileError(SCAN.GetSourceString(), ScanInfoStart, "Unrecognized SCAN object type: " + SNFO.ToString()); + return nullptr; + } + + SCAN.Seek(0x6, SEEK_CUR); + u16 NumConnections = SCAN.ReadShort(); + if (NumConnections > 0) { + Log::FileWarning(SCAN.GetSourceString(), ScanInfoStart, "SNFO object in SCAN has connections"); + SCAN.Seek(NumConnections * 0xC, SEEK_CUR); + } + + u32 BasePropID = SCAN.ReadLong(); + if (BasePropID != 0xFFFFFFFF) { + Log::FileError(SCAN.GetSourceString(), SCAN.Tell() - 4, "Invalid base proprty ID: " + StringUtil::ToHexString(BasePropID)); + return nullptr; + } + + mpScan = new CScan(); + SCAN.Seek(0x2, SEEK_CUR); + u16 NumProperties = SCAN.ReadShort(); + + switch (NumProperties) + { + case 0x14: + LoadParamsMP2(SCAN); + break; + + default: + Log::FileError(SCAN.GetSourceString(), SCAN.Tell() - 2, "Invalid SNFO property count: " + StringUtil::ToHexString(NumProperties)); + delete mpScan; + return nullptr; + } + + return mpScan; +} + +void CScanLoader::LoadParamsMP2(CInputStream &SCAN) +{ + // Function begins after the SNFO property count + for (u32 iProp = 0; iProp < 20; iProp++) + { + u32 PropertyID = SCAN.ReadLong(); + u16 PropertySize = SCAN.ReadShort(); + u32 Next = SCAN.Tell() + PropertySize; + + switch (PropertyID) + { + case 0x2F5B6423: + mpScan->mpStringTable = (CStringTable*) gResCache.GetResource(SCAN.ReadLong(), "STRG"); + mpScan->mStringToken = CToken(mpScan->mpStringTable); + break; + + case 0xC308A322: + mpScan->mIsSlow = (SCAN.ReadLong() != 0); + break; + + case 0x7B714814: + mpScan->mIsImportant = (SCAN.ReadByte() != 0); + break; + + case 0x53336141: + u32 TextureID = SCAN.ReadLong(); + if (TextureID != 0xFFFFFFFF) + Log::FileWarning(SCAN.GetSourceString(), "SCAN with texture found!"); + break; + } + + SCAN.Seek(Next, SEEK_SET); + } + + mpScan->mCategory = CScan::eNone; +} + +// ************ STATIC/PUBLIC ************ +CScan* CScanLoader::LoadSCAN(CInputStream &SCAN) +{ + if (!SCAN.IsValid()) return nullptr; + Log::Write("Loading " + SCAN.GetSourceString()); + + /* Switching to EGame enum here isn't really useful unfortunately + * because the MP1 demo can be 1, 2, or 3, while MP1 is 5 and MP2+ is 2 + * MP1 is the only one that starts with 5 so that is a consistent check for now + * Better version checks will be implemented when the other versions are + * better-understood. */ + u32 fileVersion = SCAN.ReadLong(); + u32 magic = SCAN.ReadLong(); + + // Echoes+ + if (CFourCC(fileVersion) == "SCAN") + { + // The MP2 load function will check for MP3 + CScanLoader loader; + loader.mVersion = eEchoes; + return loader.LoadScanMP2(SCAN); + } + + if (magic != 0x0BADBEEF) + { + Log::FileError(SCAN.GetSourceString(), "Invalid SCAN magic: " + StringUtil::ToHexString(magic)); + return nullptr; + } + + if (fileVersion != 5) + { + Log::FileError(SCAN.GetSourceString(), "Unsupported SCAN version: " + StringUtil::ToHexString(fileVersion)); + return nullptr; + } + + // MP1 SCAN - read the file! + CScanLoader loader; + loader.mVersion = ePrime; + loader.mpScan = new CScan(); + return loader.LoadScanMP1(SCAN); +} diff --git a/Resource/factory/CScanLoader.h b/Resource/factory/CScanLoader.h new file mode 100644 index 00000000..3a737ed6 --- /dev/null +++ b/Resource/factory/CScanLoader.h @@ -0,0 +1,21 @@ +#ifndef CSCANLOADER_H +#define CSCANLOADER_H + +#include "../CScan.h" +#include "../EFormatVersion.h" + +class CScanLoader +{ + CScan *mpScan; + EGame mVersion; + + CScanLoader(); + CScan* LoadScanMP1(CInputStream& SCAN); + CScan* LoadScanMP2(CInputStream& SCAN); + void LoadParamsMP2(CInputStream& SCAN); + +public: + static CScan* LoadSCAN(CInputStream& SCAN); +}; + +#endif // CSCANLOADER_H diff --git a/Resource/factory/CScriptLoader.cpp b/Resource/factory/CScriptLoader.cpp new file mode 100644 index 00000000..57755076 --- /dev/null +++ b/Resource/factory/CScriptLoader.cpp @@ -0,0 +1,464 @@ +#include "CScriptLoader.h" +#include "../script/CMasterTemplate.h" +#include +#include +#include +#include + +CScriptLoader::CScriptLoader() +{ + mpObj = nullptr; +} + +CPropertyStruct* CScriptLoader::LoadStructMP1(CInputStream& SCLY, CStructTemplate *tmp) +{ + u32 StructStart = SCLY.Tell(); + CPropertyStruct *PropStruct = new CPropertyStruct(); + PropStruct->tmp = tmp; + + // Verify property count + s32 TemplatePropCount = tmp->TemplateCount(); + if (TemplatePropCount >= 0) + { + u32 FilePropCount = SCLY.ReadLong(); + if (TemplatePropCount != FilePropCount) + Log::FileWarning(SCLY.GetSourceString(), StructStart, "Struct \"" + tmp->Name() + "\" template prop count doesn't match file"); + } + + // Parse properties + u32 PropCount = tmp->Count(); + PropStruct->Reserve(PropCount); + + for (u32 p = 0; p < PropCount; p++) + { + CPropertyBase *prop = nullptr; + CPropertyTemplate *proptmp = tmp->PropertyByIndex(p); + EPropertyType type = proptmp->Type(); + + switch (type) + { + + case eBoolProperty: { + bool v = (SCLY.ReadByte() == 1); + prop = new CBoolProperty(v); + break; + } + case eByteProperty: { + char v = SCLY.ReadByte(); + prop = new CByteProperty(v); + break; + } + case eShortProperty: { + short v = SCLY.ReadShort(); + prop = new CShortProperty(v); + break; + } + case eLongProperty: { + long v = SCLY.ReadLong(); + prop = new CLongProperty(v); + break; + } + case eFloatProperty: { + float v = SCLY.ReadFloat(); + prop = new CFloatProperty(v); + break; + } + case eStringProperty: { + std::string v = SCLY.ReadString(); + prop = new CStringProperty(v); + break; + } + case eVector3Property: { + CVector3f v(SCLY); + prop = new CVector3Property(v); + break; + } + case eColorProperty: { + CVector4f color(SCLY); + CColor v(color.x, color.y, color.z, color.w); + prop = new CColorProperty(v); + break; + } + case eFileProperty: { + u32 ResID = SCLY.ReadLong(); + const CStringList& Extensions = static_cast(proptmp)->Extensions(); + + CResource *pRes = nullptr; + + for (auto it = Extensions.begin(); it != Extensions.end(); it++) + { + const std::string& ext = *it; + if ((ext != "MREA") && (ext != "MLVL")) // Let's avoid recursion please + pRes = gResCache.GetResource(ResID, ext); + + if (pRes) break; + } + + prop = new CFileProperty(pRes); + break; + } + case eStructProperty: { + CStructTemplate *StructTmp = tmp->StructByIndex(p); + prop = LoadStructMP1(SCLY, StructTmp); + break; + } + } + + if (prop) + { + prop->tmp = proptmp; + PropStruct->Properties.push_back(prop); + } + } + + return PropStruct; +} + +CScriptObject* CScriptLoader::LoadObjectMP1(CInputStream& SCLY) +{ + u32 ObjStart = SCLY.Tell(); + u8 type = SCLY.ReadByte(); + u32 size = SCLY.ReadLong(); + u32 end = SCLY.Tell() + size; + + CScriptTemplate *tmp = mpMaster->TemplateByID((u32) type); + if (!tmp) + { + // No valid template for this object; can't load + Log::FileError(SCLY.GetSourceString(), ObjStart, "Invalid object ID encountered - " + StringUtil::ToHexString(type)); + SCLY.Seek(end, SEEK_SET); + return nullptr; + } + + mpObj = new CScriptObject(mpArea, mpLayer, tmp); + mpObj->mInstanceID = SCLY.ReadLong(); + + // Load connections + u32 numConnections = SCLY.ReadLong(); + mpObj->mOutConnections.reserve(numConnections); + + for (u32 c = 0; c < numConnections; c++) + { + SLink con; + con.State = SCLY.ReadLong(); + con.Message = SCLY.ReadLong(); + con.ObjectID = SCLY.ReadLong(); + mpObj->mOutConnections.push_back(con); + } + + // Load object... + CStructTemplate *base = tmp->BaseStruct(); + mpObj->mpProperties = LoadStructMP1(SCLY, base); + SetupAttribs(); + + // Cleanup and return + SCLY.Seek(end, SEEK_SET); + return mpObj; +} + +CScriptLayer* CScriptLoader::LoadLayerMP1(CInputStream &SCLY) +{ + u32 LayerStart = SCLY.Tell(); + + SCLY.Seek(0x1, SEEK_CUR); // One unknown byte at the start of each layer + u32 NumObjects = SCLY.ReadLong(); + + mpLayer = new CScriptLayer(); + mpLayer->Reserve(NumObjects); + + for (u32 iObj = 0; iObj < NumObjects; iObj++) + { + CScriptObject *pObj = LoadObjectMP1(SCLY); + if (pObj) + mpLayer->AddObject(pObj); + } + + // Layer sizes are always a multiple of 32 - skip end padding before returning + u32 remaining = 32 - ((SCLY.Tell() - LayerStart) & 0x1F); + SCLY.Seek(remaining, SEEK_CUR); + return mpLayer; +} + +void CScriptLoader::LoadStructMP2(CInputStream& SCLY, CPropertyStruct *pStruct, CStructTemplate *pTemp) +{ + // Verify property count + if (!pTemp->IsSingleProperty()) + { + u16 NumProperties = SCLY.ReadShort(); + if ((pTemp->TemplateCount() >= 0) && (NumProperties != pTemp->TemplateCount())) + Log::FileWarning(SCLY.GetSourceString(), SCLY.Tell() - 2, "Struct \"" + pTemp->Name() + "\" template property count doesn't match file"); + } + + // Parse properties + u32 PropCount = pTemp->Count(); + pStruct->Reserve(PropCount); + + for (u32 p = 0; p < PropCount; p++) + { + CPropertyBase *pProp; + CPropertyTemplate *pPropTemp; + u32 PropertyStart = SCLY.Tell(); + u32 PropertyID = -1; + u16 PropertyLength = 0; + u32 NextProperty = 0; + + if (pTemp->IsSingleProperty()) + { + pProp = pStruct->PropertyByIndex(p); + pPropTemp = pTemp->PropertyByIndex(p); + } + else + { + PropertyID = SCLY.ReadLong(); + PropertyLength = SCLY.ReadShort(); + NextProperty = SCLY.Tell() + PropertyLength; + + pProp = pStruct->PropertyByID(PropertyID); + pPropTemp = pTemp->PropertyByID(PropertyID); + } + + if (!pPropTemp) + Log::FileError(SCLY.GetSourceString(), PropertyStart, "Can't find template for property " + StringUtil::ToHexString(PropertyID) + " - skipping"); + + else + { + switch (pPropTemp->Type()) + { + + case eBoolProperty: { + CBoolProperty *pBoolCast = static_cast(pProp); + pBoolCast->Set( (SCLY.ReadByte() != 0) ); + break; + } + + case eByteProperty: { + CByteProperty *pByteCast = static_cast(pProp); + pByteCast->Set(SCLY.ReadByte()); + break; + } + + case eShortProperty: { + CShortProperty *pShortCast = static_cast(pProp); + pShortCast->Set(SCLY.ReadShort()); + break; + } + + case eLongProperty: { + CLongProperty *pLongCast = static_cast(pProp); + pLongCast->Set(SCLY.ReadLong()); + break; + } + + case eFloatProperty: { + CFloatProperty *pFloatCast = static_cast(pProp); + pFloatCast->Set(SCLY.ReadFloat()); + break; + } + + case eStringProperty: { + CStringProperty *pStringCast = static_cast(pProp); + pStringCast->Set(SCLY.ReadString()); + break; + } + + case eVector3Property: { + CVector3Property *pVector3Cast = static_cast(pProp); + pVector3Cast->Set(CVector3f(SCLY)); + break; + } + + case eColorProperty: { + CColorProperty *pColorCast = static_cast(pProp); + CVector4f Color(SCLY); + pColorCast->Set(CColor(Color.x, Color.y, Color.z, Color.w)); + break; + } + + case eFileProperty: { + CFileProperty *pFileCast = static_cast(pProp); + + CUniqueID ResID = (mVersion < eCorruptionProto ? SCLY.ReadLong() : SCLY.ReadLongLong()); + const CStringList& Extensions = static_cast(pPropTemp)->Extensions(); + + CResource *pRes = nullptr; + + // Check for each extension individually until we find a match + // This could be done better with a function to fetch the extension given the resource ID + // and a "does resource exist" function, but this will do for now + bool hasIgnoredExt = false; + + if (ResID.IsValid()) + { + for (auto it = Extensions.begin(); it != Extensions.end(); it++) + { + const std::string& ext = *it; + + if ((ext != "MREA") && (ext != "MLVL")) { + pRes = gResCache.GetResource(ResID, ext); + if (pRes) break; + } + + else + hasIgnoredExt = true; + } + } + + // Property may have an incorrect extension listed - print error + if ((!pRes) && (CUniqueID(ResID).IsValid()) && (!hasIgnoredExt)) + { + std::string ExtList; + for (auto it = Extensions.begin(); it != Extensions.end(); it++) + { + if (it != Extensions.begin()) ExtList += "/"; + ExtList += *it; + } + Log::FileWarning(SCLY.GetSourceString(), "Incorrect resource type? " + ExtList + " " + StringUtil::ToHexString(PropertyID)); + } + + pFileCast->Set(pRes); + break; + } + + case eUnknownProperty: { + CUnknownProperty *pUnknownCast = static_cast(pProp); + std::vector buf(PropertyLength); + SCLY.ReadBytes(buf.data(), buf.size()); + pUnknownCast->Set(buf); + break; + } + + case eStructProperty: { + CPropertyStruct *pStructCast = static_cast(pProp); + LoadStructMP2(SCLY, pStructCast, static_cast(pPropTemp)); + break; + } + + } + } + + if (NextProperty > 0) + SCLY.Seek(NextProperty, SEEK_SET); + } +} + +CScriptObject* CScriptLoader::LoadObjectMP2(CInputStream& SCLY) +{ + u32 ObjStart = SCLY.Tell(); + u32 ObjectID = SCLY.ReadLong(); + u16 ObjectSize = SCLY.ReadShort(); + u32 ObjEnd = SCLY.Tell() + ObjectSize; + + CScriptTemplate *pTemplate = mpMaster->TemplateByID(ObjectID); + + if (!pTemplate) + { + Log::FileError(SCLY.GetSourceString(), ObjStart, "Invalid object ID encountered: " + CFourCC(ObjectID).ToString()); + SCLY.Seek(ObjEnd, SEEK_SET); + return nullptr; + } + + mpObj = CScriptObject::CopyFromTemplate(pTemplate, mpArea, mpLayer); + mpObj->mpTemplate = pTemplate; + mpObj->mInstanceID = SCLY.ReadLong(); + + // Load connections + u32 NumConnections = SCLY.ReadShort(); + mpObj->mOutConnections.reserve(NumConnections); + + for (u32 iCon = 0; iCon < NumConnections; iCon++) + { + SLink con; + con.State = SCLY.ReadLong(); + con.Message = SCLY.ReadLong(); + con.ObjectID = SCLY.ReadLong(); + mpObj->mOutConnections.push_back(con); + } + + // Load object + CStructTemplate *pBase = pTemplate->BaseStruct(); + SCLY.Seek(0x6, SEEK_CUR); // Skip base struct ID + size + LoadStructMP2(SCLY, mpObj->mpProperties, pBase); + SetupAttribs(); + + SCLY.Seek(ObjEnd, SEEK_SET); + return mpObj; +} + +CScriptLayer* CScriptLoader::LoadLayerMP2(CInputStream& SCLY) +{ + CFourCC SCLY_Magic(SCLY); + + if (SCLY_Magic == "SCLY") SCLY.Seek(0x6, SEEK_CUR); + else if (SCLY_Magic == "SCGN") SCLY.Seek(0x2, SEEK_CUR); + else + { + Log::FileError(SCLY.GetSourceString(), SCLY.Tell() - 4, "Invalid script layer magic: " + StringUtil::ToHexString((u32) SCLY_Magic.ToLong())); + return nullptr; + } + + u32 NumObjects = SCLY.ReadLong(); + + mpLayer = new CScriptLayer(); + mpLayer->Reserve(NumObjects); + + for (u32 iObj = 0; iObj < NumObjects; iObj++) + { + CScriptObject *pObj = LoadObjectMP2(SCLY); + if (pObj) + mpLayer->AddObject(pObj); + } + + if (SCLY_Magic == "SCGN") + { + mpLayer->SetName("Generated"); + mpLayer->SetActive(true); + } + return mpLayer; +} + +void CScriptLoader::SetupAttribs() +{ + // Add template attributes + u32 numAttribs = mpObj->mpTemplate->AttribCount(); + for (u32 a = 0; a < numAttribs; a++) + { + CAttribTemplate *AttribTmp = mpObj->mpTemplate->Attrib(a); + CPropertyBase *prop = mpObj->PropertyByName( AttribTmp->Target() ); + + // Check for static resource + CResource *res = nullptr; + std::string ResStr = AttribTmp->Resource(); + if (!ResStr.empty()) + res = gResCache.GetResource(ResStr); + + mpObj->mAttribs.emplace_back(CScriptObject::SAttrib(AttribTmp->Type(), res, AttribTmp->Settings(), prop) ); + mpObj->mAttribFlags |= AttribTmp->Type(); + } + + // Initial attribute evaluation + mpObj->EvaluateInstanceName(); + mpObj->EvalutateXForm(); + mpObj->EvaluateTevColor(); + mpObj->EvaluateDisplayModel(); +} + +CScriptLayer* CScriptLoader::LoadLayer(CInputStream &SCLY, CGameArea *pArea, EGame version) +{ + if (!SCLY.IsValid()) return nullptr; + + CScriptLoader Loader; + Loader.mVersion = version; + Loader.mpMaster = CMasterTemplate::GetMasterForGame(version); + Loader.mpArea = pArea; + + if (!Loader.mpMaster) + { + Log::Write("This game doesn't have a master template; couldn't load script layer"); + return nullptr; + } + + if (version <= ePrime) + return Loader.LoadLayerMP1(SCLY); + else + return Loader.LoadLayerMP2(SCLY); +} diff --git a/Resource/factory/CScriptLoader.h b/Resource/factory/CScriptLoader.h new file mode 100644 index 00000000..cdcda658 --- /dev/null +++ b/Resource/factory/CScriptLoader.h @@ -0,0 +1,34 @@ +#ifndef CSCRIPTLOADER_H +#define CSCRIPTLOADER_H + +#include "../script/CScriptObject.h" +#include "../script/CScriptLayer.h" +#include "../script/CMasterTemplate.h" +#include "../CGameArea.h" +#include + +class CScriptLoader +{ + EGame mVersion; + CScriptObject *mpObj; + CScriptLayer *mpLayer; + CGameArea *mpArea; + CMasterTemplate *mpMaster; + + CScriptLoader(); + + CPropertyStruct* LoadStructMP1(CInputStream& SCLY, CStructTemplate *tmp); + CScriptObject* LoadObjectMP1(CInputStream& SCLY); + CScriptLayer* LoadLayerMP1(CInputStream& SCLY); + + void LoadStructMP2(CInputStream& SCLY, CPropertyStruct *pStruct, CStructTemplate *pTemp); + CScriptObject* LoadObjectMP2(CInputStream& SCLY); + CScriptLayer* LoadLayerMP2(CInputStream& SCLY); + + void SetupAttribs(); + +public: + static CScriptLayer* LoadLayer(CInputStream& SCLY, CGameArea *pArea, EGame version); +}; + +#endif // CSCRIPTLOADER_H diff --git a/Resource/factory/CStringLoader.cpp b/Resource/factory/CStringLoader.cpp new file mode 100644 index 00000000..8be65451 --- /dev/null +++ b/Resource/factory/CStringLoader.cpp @@ -0,0 +1,221 @@ +#include "CStringLoader.h" +#include + +CStringLoader::CStringLoader() +{ +} + +void CStringLoader::LoadPrimeDemoSTRG(CInputStream& STRG) +{ + // This function starts at 0x4 in the file - right after the size + // This STRG version only supports one language per file + mpStringTable->mLangTables.resize(1); + CStringTable::SLangTable* Lang = &mpStringTable->mLangTables[1]; + Lang->Language = "ENGL"; + u32 TableStart = STRG.Tell(); + + // Header + u32 NumStrings = STRG.ReadLong(); + Lang->Strings.resize(NumStrings); + mpStringTable->mNumStrings = NumStrings; + + // String offsets (yeah, that wasn't much of a header) + std::vector StringOffsets(NumStrings); + for (u32 iOff = 0; iOff < StringOffsets.size(); iOff++) + StringOffsets[iOff] = STRG.ReadLong(); + + // Strings + for (u32 iStr = 0; iStr < NumStrings; iStr++) + { + STRG.Seek(TableStart + StringOffsets[iStr], SEEK_SET); + Lang->Strings[iStr] = STRG.ReadWString(); + } +} + +void CStringLoader::LoadPrimeSTRG(CInputStream& STRG) +{ + // This function starts at 0x8 in the file, after magic/version + // Header + u32 NumLanguages = STRG.ReadLong(); + u32 NumStrings = STRG.ReadLong(); + mpStringTable->mNumStrings = NumStrings; + + // Language definitions + mpStringTable->mLangTables.resize(NumLanguages); + std::vector LangOffsets(NumLanguages); + + for (u32 iLang = 0; iLang < NumLanguages; iLang++) + { + mpStringTable->mLangTables[iLang].Language = CFourCC(STRG); + LangOffsets[iLang] = STRG.ReadLong(); + if (mVersion == eEchoes) STRG.Seek(0x4, SEEK_CUR); // Skipping strings size + } + + // String names + if (mVersion == eEchoes) + LoadNameTable(STRG); + + // Strings + u32 StringsStart = STRG.Tell(); + for (u32 iLang = 0; iLang < NumLanguages; iLang++) + { + STRG.Seek(StringsStart + LangOffsets[iLang], SEEK_SET); + if (mVersion == ePrime) STRG.Seek(0x4, SEEK_CUR); // Skipping strings size + + u32 LangStart = STRG.Tell(); + CStringTable::SLangTable* pLang = &mpStringTable->mLangTables[iLang]; + pLang->Strings.resize(NumStrings); + + // Offsets + std::vector StringOffsets(NumStrings); + for (u32 iOff = 0; iOff < NumStrings; iOff++) + StringOffsets[iOff] = STRG.ReadLong(); + + // The actual strings + for (u32 iStr = 0; iStr < NumStrings; iStr++) + { + STRG.Seek(LangStart + StringOffsets[iStr], SEEK_SET); + pLang->Strings[iStr] = STRG.ReadWString(); + } + } +} + +void CStringLoader::LoadCorruptionSTRG(CInputStream& STRG) +{ + // This function starts at 0x8 in the file, after magic/version + // Header + u32 NumLanguages = STRG.ReadLong(); + u32 NumStrings = STRG.ReadLong(); + mpStringTable->mNumStrings = NumStrings; + + // String names + LoadNameTable(STRG); + + // Language definitions + mpStringTable->mLangTables.resize(NumLanguages); + std::vector> LangOffsets(NumLanguages); + + for (u32 iLang = 0; iLang < NumLanguages; iLang++) + mpStringTable->mLangTables[iLang].Language = CFourCC(STRG); + + for (u32 iLang = 0; iLang < NumLanguages; iLang++) + { + LangOffsets[iLang].resize(NumStrings); + + STRG.Seek(0x4, SEEK_CUR); // Skipping total string size + + for (u32 iStr = 0; iStr < NumStrings; iStr++) + LangOffsets[iLang][iStr] = STRG.ReadLong(); + } + + // Strings + u32 StringsStart = STRG.Tell(); + + for (u32 iLang = 0; iLang < NumLanguages; iLang++) + { + CStringTable::SLangTable *pLang = &mpStringTable->mLangTables[iLang]; + pLang->Strings.resize(NumStrings); + + for (u32 iStr = 0; iStr < NumStrings; iStr++) + { + STRG.Seek(StringsStart + LangOffsets[iLang][iStr], SEEK_SET); + STRG.Seek(0x4, SEEK_CUR); // Skipping string size + + pLang->Strings[iStr] = StringUtil::UTF8to16(STRG.ReadString()); + } + } +} + +void CStringLoader::LoadNameTable(CInputStream& STRG) +{ + // Name table header + u32 NameCount = STRG.ReadLong(); + u32 NameTableSize = STRG.ReadLong(); + u32 NameTableStart = STRG.Tell(); + u32 NameTableEnd = NameTableStart + NameTableSize; + + // Name definitions + struct SNameDef { + u32 NameOffset, StringIndex; + }; + std::vector NameDefs(NameCount); + + for (u32 iName = 0; iName < NameCount; iName++) + { + NameDefs[iName].NameOffset = STRG.ReadLong() + NameTableStart; + NameDefs[iName].StringIndex = STRG.ReadLong(); + } + + // Name strings + mpStringTable->mStringNames.resize(NameCount); + for (u32 iName = 0; iName < NameCount; iName++) + { + SNameDef *pDef = &NameDefs[iName]; + STRG.Seek(pDef->NameOffset, SEEK_SET); + mpStringTable->mStringNames[pDef->StringIndex] = STRG.ReadString(); + } + STRG.Seek(NameTableEnd, SEEK_SET); +} + +// ************ STATIC ************ +CStringTable* CStringLoader::LoadSTRG(CInputStream& STRG) +{ + // Verify that this is a valid STRG + if (!STRG.IsValid()) return nullptr; + Log::Write("Loading " + STRG.GetSourceString()); + + u32 Magic = STRG.ReadLong(); + EGame Version = eUnknownVersion; + + if (Magic != 0x87654321) + { + // Check for MP1 Demo STRG format - no magic/version; the first value is actually the filesize + // so the best I can do is verify the first value actually points to the end of the file + if (Magic <= (u32) STRG.Size()) + { + STRG.Seek(Magic, SEEK_SET); + if ((STRG.EoF()) || (STRG.ReadShort() == 0xFFFF)) + Version = ePrimeKioskDemo; + } + + if (Version != ePrimeKioskDemo) + { + Log::FileError(STRG.GetSourceString(), "Invalid STRG magic: " + StringUtil::ToHexString(Magic)); + return nullptr; + } + } + + else + { + u32 FileVersion = STRG.ReadLong(); + Version = GetFormatVersion(FileVersion); + + if (FileVersion == eUnknownVersion) + { + Log::FileError(STRG.GetSourceString(), "Unsupported STRG version: " + StringUtil::ToHexString(FileVersion)); + return nullptr; + } + } + + // Valid; now we create the loader and call the function that reads the rest of the file + CStringLoader Loader; + Loader.mpStringTable = new CStringTable(); + Loader.mVersion = Version; + + if (Version == ePrimeKioskDemo) Loader.LoadPrimeDemoSTRG(STRG); + else if (Version < eCorruption) Loader.LoadPrimeSTRG(STRG); + else Loader.LoadCorruptionSTRG(STRG); + + return Loader.mpStringTable; +} + +EGame CStringLoader::GetFormatVersion(u32 Version) +{ + switch (Version) + { + case 0x0: return ePrime; + case 0x1: return eEchoes; + case 0x3: return eCorruption; + default: return eUnknownVersion; + } +} diff --git a/Resource/factory/CStringLoader.h b/Resource/factory/CStringLoader.h new file mode 100644 index 00000000..ae1f6e46 --- /dev/null +++ b/Resource/factory/CStringLoader.h @@ -0,0 +1,24 @@ +#ifndef CSTRINGLOADER_H +#define CSTRINGLOADER_H + +#include "../CStringTable.h" +#include "../EFormatVersion.h" +#include + +class CStringLoader +{ + CStringTable* mpStringTable; + EGame mVersion; + + CStringLoader(); + void LoadPrimeDemoSTRG(CInputStream& STRG); + void LoadPrimeSTRG(CInputStream& STRG); + void LoadCorruptionSTRG(CInputStream& STRG); + void LoadNameTable(CInputStream& STRG); + +public: + static CStringTable* LoadSTRG(CInputStream& STRG); + static EGame GetFormatVersion(u32 Version); +}; + +#endif // CSTRINGLOADER_H diff --git a/Resource/factory/CTemplateLoader.cpp b/Resource/factory/CTemplateLoader.cpp new file mode 100644 index 00000000..d8ec03bc --- /dev/null +++ b/Resource/factory/CTemplateLoader.cpp @@ -0,0 +1,644 @@ +#include "CTemplateLoader.h" +#include "CWorldLoader.h" +#include "../script/EAttribType.h" +#include + +// ************ PROPERTY ************ +CPropertyTemplate* CTemplateLoader::LoadPropertyTemplate(tinyxml2::XMLElement *pElem, const std::string& TemplateName) +{ + const char *pElemName = pElem->Name(); + + // Load multi-property struct + if (strcmp(pElemName, "struct") == 0) + { + CStructTemplate *pStruct = LoadStructTemplate(pElem, TemplateName); + + if (pStruct) + pStruct->mIsSingleProperty = false; + + return pStruct; + } + + else if (strcmp(pElemName, "property") == 0) + { + // Get name, type, and ID + std::string Name; + EPropertyType Type; + u32 ID; + GetPropertyInfo(pElem, Name, Type, ID); + + // Error check + if (Type == eInvalidProperty) + { + const char *pType = pElem->Attribute("type"); + + if (pType) + Log::Error("Invalid property type in " + TemplateName + " template: " + pType); + else + Log::Error("Property " + Name + " in " + TemplateName + " template has no type"); + } + + // Load single-property struct + if (Type == eStructProperty) + { + CStructTemplate *pStruct = LoadStructTemplate(pElem, TemplateName); + pStruct->mIsSingleProperty = true; + return pStruct; + } + + // Load file property + else if (Type == eFileProperty) + { + // Fetch file extension + CFileTemplate *pFile = nullptr; + const char *pExt = pElem->Attribute("ext"); + + if (pExt) + pFile = new CFileTemplate(Name, ID, StringUtil::Tokenize(pExt, ",")); + + else + { + CFileTemplate *pSrc = (CFileTemplate*) mpMaster->GetProperty(ID); + + if (pSrc) + pFile = new CFileTemplate(Name, ID, pSrc->Extensions()); + } + + // Check if extensions are valid + if (!pFile) + { + Log::Error("File property " + Name + " in " + TemplateName + " template has no extension"); + return nullptr; + } + + else + return pFile; + } + + // Load regular property + else + { + CPropertyTemplate *pProperty = new CPropertyTemplate(Type, Name, ID); + return pProperty; + } + } + + return nullptr; +} + +CStructTemplate* CTemplateLoader::LoadStructTemplate(tinyxml2::XMLElement *pElem, const std::string& TemplateName) +{ + CStructTemplate *pStruct = new CStructTemplate(); + + // Get name, type, and ID + GetPropertyInfo(pElem, pStruct->mPropName, pStruct->mPropType, pStruct->mPropID); + const char *pTemp = pElem->Attribute("template"); + if (!pTemp) pTemp = pElem->Attribute("target"); + + // Get source template from the master property list, if it exists + CStructTemplate *pSrc = (CStructTemplate*) mpMaster->GetProperty(pStruct->mPropID); + + // "IsSingleProperty" means, does the struct contain multiple properties, each with separate IDs + // or does the entire struct as a whole count as just one property? + if (pSrc) + pStruct->mIsSingleProperty = pSrc->IsSingleProperty(); + else + pStruct->mIsSingleProperty = (strcmp(pElem->Name(), "property") == 0); + + // Read struct children. Priority is [Embedded -> Template -> Master]. + // Embedded + if (!pElem->NoChildren()) + { + // Get count + const char *pCount = pElem->Attribute("count"); + + if (pCount) + pStruct->mPropertyCount = std::stoul(pCount); + + // Parse sub-elements + tinyxml2::XMLElement *pChild = pElem->FirstChildElement(); + + while (pChild) + { + CPropertyTemplate *pProp = LoadPropertyTemplate(pChild, TemplateName); + + if (pProp) + pStruct->mProperties.push_back(pProp); + + pChild = pChild->NextSiblingElement(); + } + + } + + // Template + else if (pTemp) + { + // Get handle for XML + std::string TempPath = mMasterDir + pTemp; + + tinyxml2::XMLDocument TempXML; + TempXML.LoadFile(TempPath.c_str()); + + if (TempXML.Error()) + Log::Error("Couldn't open struct template: " + TempPath); + + else + { + tinyxml2::XMLElement *pVersionElem = TempXML.FirstChildElement()->FirstChildElement("version"); + tinyxml2::XMLElement *pPropertiesElem = TempXML.FirstChildElement()->FirstChildElement("properties"); + + if (!pVersionElem) Log::Error("Struct template has no version element: " + TempPath); + if (!pPropertiesElem) Log::Error("Struct template has no properties element: " + TempPath); + + if (pVersionElem && pPropertiesElem) + { + // Get version number + u32 VersionNumber = std::stoul(pVersionElem->GetText()); + + // Get property count + const char *pCount = pPropertiesElem->Attribute("count"); + + if (pCount) + pStruct->mPropertyCount = std::stoul(pCount); + + // Parse properties + tinyxml2::XMLElement *pPropElem = pPropertiesElem->FirstChildElement(); + + while (pPropElem) + { + if (!pPropElem) break; + + CPropertyTemplate *pProp = LoadPropertyTemplate(pPropElem, TemplateName); + + if (pProp) + pStruct->mProperties.push_back(pProp); + + pPropElem = pPropElem->NextSiblingElement(); + } + } + } + } + + // Master + else if (pSrc) + { + pStruct->mPropertyCount = pSrc->TemplateCount(); + + for (u32 p = 0; p < pSrc->Count(); p++) + pStruct->mProperties.push_back(pSrc->PropertyByIndex(p)); + } + + // If it's none of these things, then it probably has no children because it's a property list entry + return pStruct; +} + +void CTemplateLoader::GetPropertyInfo(tinyxml2::XMLElement *pElem, std::string& Name, EPropertyType& Type, u32& ID) +{ + const char *pNameStr = pElem->Attribute("name"); + const char *pTypeStr = pElem->Attribute("type"); + const char *pIDStr = pElem->Attribute("ID"); + bool IsBaseStruct = (strcmp(pElem->Name(), "properties") == 0); + + // Fetch source template, if available + CPropertyTemplate *pSrcTmp; + + if (pIDStr) + { + ID = std::stoul(pIDStr, 0, 16); + pSrcTmp = mpMaster->GetProperty(ID); + } + else + { + ID = 0xFFFFFFFF; + pSrcTmp = nullptr; + } + + // Get name + if (pNameStr) + Name = pNameStr; + else if (pSrcTmp) + Name = pSrcTmp->Name(); + else if (IsBaseStruct) + Name = "Base"; + else + Name = ""; + + // Get type + if (strcmp(pElem->Name(), "struct") == 0) + Type = eStructProperty; + else if (IsBaseStruct) + Type = eStructProperty; + else if (pTypeStr) + Type = PropStringToPropEnum(pTypeStr); + else if (pSrcTmp) + Type = pSrcTmp->Type(); + else + Type = eInvalidProperty; +} + +// ************ SCRIPT OBJECT ************ +CScriptTemplate* CTemplateLoader::LoadScriptTemplate(tinyxml2::XMLDocument *pDoc, const std::string& TemplateName, u32 ObjectID) +{ + tinyxml2::XMLElement *pBaseElement = pDoc->FirstChildElement(); + + CScriptTemplate *pScript = new CScriptTemplate(mpMaster); + pScript->mObjectID = ObjectID; + pScript->mTemplateName = pBaseElement->Name(); + + // Properties? + tinyxml2::XMLElement *pProperties = pBaseElement->FirstChildElement("properties"); + if (pProperties) + { + pScript->mpBaseStruct = LoadStructTemplate(pBaseElement->FirstChildElement("properties"), TemplateName); + pScript->mpBaseStruct->SetName(pScript->mTemplateName); + } + + // Attribs? + tinyxml2::XMLElement *pAttributes = pBaseElement->FirstChildElement("attributes"); + if (pAttributes) LoadScriptAttribs(pAttributes, pScript); + + return pScript; +} + +void CTemplateLoader::LoadScriptAttribs(tinyxml2::XMLElement *pElem, CScriptTemplate *pScript) +{ + // Parsing attribs + tinyxml2::XMLElement *pAttrib = pElem->FirstChildElement("attrib"); + while (pAttrib) + { + CAttribTemplate Attrib; + Attrib.ExtraSettings = -1; + + const char *pType = pAttrib->Attribute("type"); + if (!pType) + Log::Error("An attrib in " + pScript->TemplateName() + " template has no type set"); + + else + { + // Initialize attrib template values + Attrib.AttribType = AttribStringToAttribEnum(pType); + + if (Attrib.AttribType == eInvalidAttrib) + Log::Error("An attrib in " + pScript->TemplateName() + " template has an invalid type: " + pType); + + else + { + bool NoError = ParseAttribExtra(pAttrib, Attrib, pScript->TemplateName()); + + if (NoError) + { + Attrib.AttribTarget = pAttrib->Attribute("target"); + CPropertyTemplate *pTargetProp = nullptr; + + if (Attrib.ResFile.empty()) + { + pTargetProp = pScript->mpBaseStruct->PropertyByName(Attrib.AttribTarget); // Ensure target is valid if it points to a property + + if (!pTargetProp) + Log::Error("An attrib in " + pScript->TemplateName() + " template of type " + pType + " has an invalid target: " + Attrib.AttribTarget); + } + + if ((pTargetProp) || (!Attrib.ResFile.empty())) + pScript->mAttribs.push_back(Attrib); + } + } + } + + pAttrib = pAttrib->NextSiblingElement("attrib"); + } +} + +bool CTemplateLoader::ParseAttribExtra(tinyxml2::XMLElement *pElem, CAttribTemplate& Attrib, const std::string& TemplateName) +{ + // This function is for parsing extra tags that some attribs have, such as "source" for models or "forcenode" for animsets + + // AnimSet + if (Attrib.Type() == eAnimSetAttrib) + { + // Check res source + const char *pSource = pElem->Attribute("source"); + + if ((pSource) && (strcmp(pSource, "file") == 0)) + { + const char *pFileName = pElem->Attribute("target"); + if (pFileName) + Attrib.ResFile = std::string("../resources/") + pFileName; + else + { + Log::Error("An attrib in " + TemplateName + " template of type animset has an invalid target: \"" + pFileName + "\""); + return false; + } + } + + // Check forcenode + const char *pForceNode = pElem->Attribute("forcenode"); + if (pForceNode) + { + if (!StringUtil::IsHexString(pForceNode)) + { + Log::Error("An animset attrib in " + TemplateName + " has an invalid \"forcenode\" setting: \"" + pForceNode + "\""); + return false; + } + else + Attrib.ExtraSettings = std::stoul(pForceNode); + } + } + + // Model + if (Attrib.Type() == eModelAttrib) + { + // Check res source + const char *pSource = pElem->Attribute("source"); + if ((pSource) && (strcmp(pSource, "file") == 0)) + { + const char *pFileName = pElem->Attribute("target"); + if (pFileName) + Attrib.ResFile = std::string("../resources/") + pFileName; + else + { + Log::Error("An attrib in " + TemplateName + " template of type model has an invalid target: \"" + pFileName + "\""); + return false; + } + } + } + + // Volume + if (Attrib.Type() == eVolumeAttrib) + { + const char *pShape = pElem->Attribute("shape"); + + if (pShape) + { + if (strcmp(pShape, "Box") == 0) + Attrib.ExtraSettings = 0; + else if (strcmp(pShape, "OrientedBox") == 0) + Attrib.ExtraSettings = 1; + else if (strcmp(pShape, "Sphere") == 0) + Attrib.ExtraSettings = 2; + else + { + Log::Error("Volume attrib in " + TemplateName + " template has an invalid shape: " + pShape); + return false; + } + } + else + { + Log::Error("Volume attrib in " + TemplateName + " template has no shape attribute"); + return false; + } + } + + return true; +} + +// ************ MASTER ************ +void CTemplateLoader::LoadMasterTemplate(tinyxml2::XMLDocument *pDoc) +{ + tinyxml2::XMLNode *pNode = pDoc->FirstChild()->NextSibling()->FirstChild(); + FILE *f = fopen("a.txt", "w"); + + while (pNode) + { + tinyxml2::XMLElement *pElem = pNode->ToElement(); + + // Version + if (strcmp(pElem->Name(), "version") == 0) + { + u32 Version = std::stoul(pElem->GetText()); + mpMaster->mVersion = Version; + } + + // Properties + else if (strcmp(pElem->Name(), "properties") == 0) + { + std::string PropListPath = mMasterDir + pElem->GetText(); + + tinyxml2::XMLDocument PropListXML; + PropListXML.LoadFile(PropListPath.c_str()); + + if (PropListXML.Error()) + Log::Error("Couldn't open property list: " + PropListPath); + + else + LoadPropertyList(&PropListXML, PropListPath); + } + + // Objects + else if (strcmp(pElem->Name(), "objects") == 0) + { + // Iterate categories + tinyxml2::XMLElement *pCat = pElem->FirstChildElement("category"); + + while (pCat) + { + CTemplateCategory Cat(pCat->Attribute("name")); + tinyxml2::XMLElement *pObj = pCat->FirstChildElement("object"); + + while (pObj) + { + // ID can either be a hex number or an ASCII fourCC + std::string StrID = pObj->Attribute("ID"); + u32 ID; + + if (StringUtil::IsHexString(StrID, true)) + ID = std::stoul(StrID, 0, 16); + else + ID = CFourCC(StrID).ToLong(); + + // Load up the object + std::string TemplateName = pObj->Attribute("template"); + std::string TemplatePath = mMasterDir + TemplateName; + + tinyxml2::XMLDocument ObjectXML; + ObjectXML.LoadFile(TemplatePath.c_str()); + + if (ObjectXML.Error()) + Log::Error("Couldn't open script template: " + TemplatePath); + + else + { + CScriptTemplate *pTemp = LoadScriptTemplate(&ObjectXML, TemplateName, ID); + + if (pTemp) + { + mpMaster->mTemplates[ID] = pTemp; + Cat.AddTemplate(pTemp); + + std::string ID = "| " + CFourCC(pTemp->ObjectID()).ToString() + "\n"; + std::string Name = "| [[" + pTemp->TemplateName() + " (Metroid Prime 2)|" + pTemp->TemplateName() + "]]\n"; + fprintf(f, ID.c_str()); + fprintf(f, Name.c_str()); + fprintf(f, "| \n"); + fprintf(f, "|-\n"); + } + } + + pObj = pObj->NextSiblingElement("object"); + } + + Cat.Sort(); + mpMaster->mCategories.push_back(Cat); + pCat = pCat->NextSiblingElement("category"); + } + } + + // States + else if (strcmp(pElem->Name(), "states") == 0) + { + tinyxml2::XMLElement *pState = pElem->FirstChildElement("state"); + + while (pState) + { + std::string StrID = pState->Attribute("ID"); + u32 StateID; + + if (StringUtil::IsHexString(StrID, true)) + StateID = std::stoul(StrID, 0, 16); + else + StateID = CFourCC(StrID).ToLong(); + + std::string StateName = pState->Attribute("name"); + mpMaster->mStates[StateID] = StateName; + pState = pState->NextSiblingElement("state"); + + std::string ID = "| " + StrID + "\n"; + std::string Name = "| " + StateName + "\n"; + fprintf(f, ID.c_str()); + fprintf(f, Name.c_str()); + fprintf(f, "|-\n"); + } + } + + // Messages + else if (strcmp(pElem->Name(), "messages") == 0) + { + tinyxml2::XMLElement *pMessage = pElem->FirstChildElement("message"); + + while (pMessage) + { + std::string StrID = pMessage->Attribute("ID"); + u32 MessageID; + + if (StringUtil::IsHexString(StrID, true)) + MessageID = std::stoul(StrID, 0, 16); + else + MessageID = CFourCC(StrID).ToLong(); + + std::string MessageName = pMessage->Attribute("name"); + mpMaster->mMessages[MessageID] = MessageName; + pMessage = pMessage->NextSiblingElement("message"); + + std::string ID = "| " + StrID + "\n"; + std::string Name = "| " + MessageName + "\n"; + fprintf(f, ID.c_str()); + fprintf(f, Name.c_str()); + fprintf(f, "|-\n"); + } + } + + pNode = pNode->NextSibling(); + } +} + +void CTemplateLoader::LoadPropertyList(tinyxml2::XMLDocument *pDoc, const std::string& ListName) +{ + tinyxml2::XMLElement *pElem = pDoc->FirstChildElement()->FirstChildElement(); + + while (pElem) + { + CPropertyTemplate *pProp = LoadPropertyTemplate(pElem, ListName); + + if (pProp) + mpMaster->mPropertyList[pProp->PropertyID()] = pProp; + + pElem = pElem->NextSiblingElement(); + } +} + +CMasterTemplate* CTemplateLoader::LoadGame(tinyxml2::XMLNode *pNode) +{ + tinyxml2::XMLElement *pGameElem = pNode->FirstChildElement(); + mpMaster = new CMasterTemplate(); + + // Parse game parameters + while (pGameElem) + { + if (strcmp(pGameElem->Name(), "name") == 0) + mpMaster->mGameName = pGameElem->GetText(); + + else if (strcmp(pGameElem->Name(), "mlvl") == 0) + { + u32 VersionNum = std::stoul(pGameElem->GetText(), 0, 16); + mpMaster->mGame = CWorldLoader::GetFormatVersion(VersionNum); + } + + else if (strcmp(pGameElem->Name(), "master") == 0) + { + std::string MasterPath = mTemplatesDir + pGameElem->GetText(); + mMasterDir = StringUtil::GetFileDirectory(MasterPath); + + tinyxml2::XMLDocument MasterXML; + MasterXML.LoadFile(MasterPath.c_str()); + + if (MasterXML.Error()) + Log::Error("Couldn't open master template at " + MasterPath + " - error " + std::to_string(MasterXML.ErrorID())); + else + LoadMasterTemplate(&MasterXML); + } + pGameElem = pGameElem->NextSiblingElement(); + } + + mpMaster->mFullyLoaded = true; + return mpMaster; +} + +// ************ PUBLIC ************ +void CTemplateLoader::LoadGameList() +{ + static const std::string TemplatesDir = "../templates/"; + static const std::string GameListPath = TemplatesDir + "GameList.xml"; + Log::Write("Loading game list"); + + // Load Game List XML + tinyxml2::XMLDocument GameListXML; + GameListXML.LoadFile(GameListPath.c_str()); + + if (GameListXML.Error()) + { + Log::Error("Couldn't open game list at " + GameListPath + " - error " + std::to_string(GameListXML.ErrorID())); + return; + } + + // Parse + tinyxml2::XMLNode *pNode = GameListXML.FirstChild()->NextSibling()->FirstChild(); + + while (pNode) + { + tinyxml2::XMLElement *pElement = pNode->ToElement(); + + // Game List version number + if (strcmp(pElement->Name(), "version") == 0) + { + u32 VersionNum = std::stoul(pElement->GetText()); + CMasterTemplate::smGameListVersion = VersionNum; + } + + // Games + else if (strcmp(pElement->Name(), "game") == 0) + { + CTemplateLoader Loader(TemplatesDir); + CMasterTemplate *pMaster = Loader.LoadGame(pNode); + + if (!pMaster->IsLoadedSuccessfully()) + { + Log::Error("Master template for " + pMaster->mGameName + " couldn't be loaded"); + delete pMaster; + } + + else + CMasterTemplate::smMasterMap[pMaster->mGame] = pMaster; + } + + pNode = pNode->NextSibling(); + } +} diff --git a/Resource/factory/CTemplateLoader.h b/Resource/factory/CTemplateLoader.h new file mode 100644 index 00000000..5a14f26e --- /dev/null +++ b/Resource/factory/CTemplateLoader.h @@ -0,0 +1,36 @@ +#ifndef CTEMPLATELOADER_H +#define CTEMPLATELOADER_H + +#include "../script/CMasterTemplate.h" +#include "../script/CScriptTemplate.h" +#include + +class CTemplateLoader +{ + CMasterTemplate *mpMaster; + std::string mTemplatesDir; + std::string mMasterDir; + + // Constructor + CTemplateLoader(const std::string& TemplatesDir) : mTemplatesDir(TemplatesDir) {} + + // Load Property + CPropertyTemplate* LoadPropertyTemplate(tinyxml2::XMLElement *pElem, const std::string& TemplateName); + CStructTemplate* LoadStructTemplate(tinyxml2::XMLElement *pElem, const std::string& TemplateName); + void GetPropertyInfo(tinyxml2::XMLElement *pElem, std::string& Name, EPropertyType& Type, u32& ID); + + // Load Script Object + CScriptTemplate* LoadScriptTemplate(tinyxml2::XMLDocument *pDoc, const std::string& TemplateName, u32 ObjectID); + void LoadScriptAttribs(tinyxml2::XMLElement *pElem, CScriptTemplate *pScript); + bool ParseAttribExtra(tinyxml2::XMLElement *pElem, CAttribTemplate& Attrib, const std::string& TemplateName); + + // Load Master + void LoadMasterTemplate(tinyxml2::XMLDocument *pDoc); + void LoadPropertyList(tinyxml2::XMLDocument *pDoc, const std::string& ListName); + CMasterTemplate* LoadGame(tinyxml2::XMLNode *pNode); + +public: + static void LoadGameList(); +}; + +#endif // CTEMPLATELOADER_H diff --git a/Resource/factory/CTextureDecoder.cpp b/Resource/factory/CTextureDecoder.cpp new file mode 100644 index 00000000..337b37fc --- /dev/null +++ b/Resource/factory/CTextureDecoder.cpp @@ -0,0 +1,875 @@ +#include +#include +#include "CTextureDecoder.h" + +// A cleanup is warranted at some point. Trying to support both partial + full decode ended up really messy. + +// Number of pixels * this = number of bytes +static const float gskPixelsToBytes[] = { + 1.f, 1.f, 2.f, 2.f, 4.f, 4.f, 0.f, 2.f, 4.f, 4.f, 0.5f +}; + +// Bits per pixel for each GX texture format +static const u32 gskSourceBpp[] = { + 4, 8, 8, 16, 4, 8, 16, 16, 16, 32, 4 +}; + +// Bits per pixel for each GX texture format when decoded +static const u32 gskOutputBpp[] = { + 16, 16, 16, 16, 16, 16, 16, 16, 32, 32, 4 +}; + +// Size of one pixel in output data in bytes +static const u32 gskOutputPixelStride[] = { + 2, 2, 2, 2, 2, 2, 2, 2, 4, 4, 8 +}; + +// Block width for each GX texture format +static const u32 gskBlockWidth[] = { + 8, 8, 8, 4, 8, 8, 4, 4, 4, 4, 2 +}; + +// Block height for each GX texture format +static const u32 gskBlockHeight[] = { + 8, 4, 4, 4, 8, 4, 4, 4, 4, 4, 2 +}; + +CTextureDecoder::CTextureDecoder() +{ +} + +CTextureDecoder::~CTextureDecoder() +{ +} + +CTexture* CTextureDecoder::CreateTexture() +{ + CTexture *pTex = new CTexture; + pTex->mSourceTexelFormat = mTexelFormat; + pTex->mWidth = mWidth; + pTex->mHeight = mHeight; + pTex->mNumMipMaps = mNumMipMaps; + pTex->mLinearSize = (u32) (mWidth * mHeight * gskPixelsToBytes[mTexelFormat]); + pTex->mImgDataBuffer = mpDataBuffer; + pTex->mImgDataSize = mDataBufferSize; + pTex->mBufferExists = true; + + switch (mTexelFormat) { + case eGX_I4: + case eGX_I8: + case eGX_IA4: + case eGX_IA8: + pTex->mTexelFormat = eLuminanceAlpha; + break; + case eGX_RGB565: + pTex->mTexelFormat = eRGB565; + break; + case eGX_C4: + case eGX_C8: + if (mPaletteFormat == ePalette_IA8) pTex->mTexelFormat = eLuminanceAlpha; + if (mPaletteFormat == ePalette_RGB565) pTex->mTexelFormat = eRGB565; + if (mPaletteFormat == ePalette_RGB5A3) pTex->mTexelFormat = eRGBA8; + break; + case eGX_RGB5A3: + case eGX_RGBA8: + pTex->mTexelFormat = eRGBA8; + break; + case eGX_CMPR: + pTex->mTexelFormat = eDXT1; + break; + case eDXT1: + pTex->mTexelFormat = eDXT1; + pTex->mLinearSize = mWidth * mHeight / 2; + break; + default: + pTex->mTexelFormat = mTexelFormat; + break; + } + + return pTex; +} + +// ************ STATIC ************ +CTexture* CTextureDecoder::LoadTXTR(CInputStream& TXTR) +{ + CTextureDecoder Decoder; + Decoder.ReadTXTR(TXTR); + Decoder.PartialDecodeGXTexture(TXTR); + return Decoder.CreateTexture(); +} + +CTexture* CTextureDecoder::DoFullDecode(CInputStream &TXTR) +{ + CTextureDecoder Decoder; + Decoder.ReadTXTR(TXTR); + Decoder.FullDecodeGXTexture(TXTR); + + CTexture *pTexture = Decoder.CreateTexture(); + pTexture->mTexelFormat = eRGBA8; + return pTexture; +} + +CTexture* CTextureDecoder::LoadDDS(CInputStream &DDS) +{ + CTextureDecoder Decoder; + Decoder.ReadDDS(DDS); + Decoder.DecodeDDS(DDS); + return Decoder.CreateTexture(); +} + +CTexture* CTextureDecoder::DoFullDecode(CTexture*) +{ + // Not using parameter 1 (CTexture* - pTexture) + return nullptr; +} + +// ************ READ ************ +void CTextureDecoder::ReadTXTR(CInputStream& TXTR) +{ + // Read TXTR header + Log::Write("Loading " + TXTR.GetSourceString()); + mTexelFormat = ETexelFormat(TXTR.ReadLong()); + mWidth = TXTR.ReadShort(); + mHeight = TXTR.ReadShort(); + mNumMipMaps = TXTR.ReadLong(); + + // For C4 and C8 images, read palette + if ((mTexelFormat == eGX_C4) || (mTexelFormat == eGX_C8)) + { + mHasPalettes = true; + mPaletteFormat = EGXPaletteFormat(TXTR.ReadLong()); + TXTR.Seek(0x4, SEEK_CUR); + + u32 PaletteEntryCount = (mTexelFormat == eGX_C4) ? 16 : 256; + mPalettes.resize(PaletteEntryCount * 2); + TXTR.ReadBytes(mPalettes.data(), mPalettes.size()); + + mPaletteInput.SetData(mPalettes.data(), mPalettes.size(), IOUtil::BigEndian); + } + else mHasPalettes = false; +} + +void CTextureDecoder::ReadDDS(CInputStream& DDS) +{ + Log::Write("Loading " + DDS.GetSourceString()); + + // Header + CFourCC Magic(DDS); + if (Magic != "DDS ") + { + Log::FileError(DDS.GetSourceString(), "Invalid DDS magic: " + StringUtil::ToHexString((u32) Magic.ToLong())); + return; + } + + u32 ImageDataStart = DDS.Tell() + DDS.ReadLong(); + DDS.Seek(0x4, SEEK_CUR); // Skipping flags + mHeight = (u16) DDS.ReadLong(); + mWidth = (u16) DDS.ReadLong(); + DDS.Seek(0x8, SEEK_CUR); // Skipping linear size + depth + mNumMipMaps = DDS.ReadLong() + 1; // DDS doesn't seem to count the first mipmap + DDS.Seek(0x2C, SEEK_CUR); // Skipping reserved + + // Pixel Format + DDS.Seek(0x4, SEEK_CUR); // Skipping size + mDDSInfo.Flags = DDS.ReadLong(); + CFourCC Format(DDS); + + if (Format == "DXT1") mDDSInfo.Format = SDDSInfo::DXT1; + else if (Format == "DXT2") mDDSInfo.Format = SDDSInfo::DXT2; + else if (Format == "DXT3") mDDSInfo.Format = SDDSInfo::DXT3; + else if (Format == "DXT4") mDDSInfo.Format = SDDSInfo::DXT4; + else if (Format == "DXT5") mDDSInfo.Format = SDDSInfo::DXT5; + else + { + mDDSInfo.Format = SDDSInfo::RGBA; + mDDSInfo.BitCount = DDS.ReadLong(); + mDDSInfo.RBitMask = DDS.ReadLong(); + mDDSInfo.GBitMask = DDS.ReadLong(); + mDDSInfo.BBitMask = DDS.ReadLong(); + mDDSInfo.ABitMask = DDS.ReadLong(); + mDDSInfo.RShift = CalculateShiftForMask(mDDSInfo.RBitMask); + mDDSInfo.GShift = CalculateShiftForMask(mDDSInfo.GBitMask); + mDDSInfo.BShift = CalculateShiftForMask(mDDSInfo.BBitMask); + mDDSInfo.AShift = CalculateShiftForMask(mDDSInfo.ABitMask); + mDDSInfo.RSize = CalculateMaskBitCount(mDDSInfo.RBitMask); + mDDSInfo.GSize = CalculateMaskBitCount(mDDSInfo.GBitMask); + mDDSInfo.BSize = CalculateMaskBitCount(mDDSInfo.BBitMask); + mDDSInfo.ASize = CalculateMaskBitCount(mDDSInfo.ABitMask); + } + + // Skip the rest + DDS.Seek(ImageDataStart, SEEK_SET); +} + +// ************ DECODE ************ +void CTextureDecoder::PartialDecodeGXTexture(CInputStream& TXTR) +{ + // Get image data size, create output buffer + u32 ImageStart = TXTR.Tell(); + TXTR.Seek(0x0, SEEK_END); + u32 ImageSize = TXTR.Tell() - ImageStart; + TXTR.Seek(ImageStart, SEEK_SET); + + mDataBufferSize = ImageSize * (gskOutputBpp[mTexelFormat] / gskSourceBpp[mTexelFormat]); + if ((mHasPalettes) && (mPaletteFormat == ePalette_RGB5A3)) mDataBufferSize *= 2; + mpDataBuffer = new u8[mDataBufferSize]; + + CMemoryOutStream Out(mpDataBuffer, mDataBufferSize, IOUtil::SystemEndianness); + + // Initializing more stuff before we start the mipmap loop + u32 MipW = mWidth, MipH = mHeight; + u32 MipOffset = 0; + + u32 BWidth = gskBlockWidth[mTexelFormat]; + u32 BHeight = gskBlockHeight[mTexelFormat]; + + u32 PixelStride = gskOutputPixelStride[mTexelFormat]; + if (mHasPalettes && (mPaletteFormat == ePalette_RGB5A3)) + PixelStride = 4; + + // With CMPR, we're using a little trick. + // CMPR stores pixels in 8x8 blocks, with four 4x4 subblocks. + // An easy way to convert it is to pretend each block is 2x2 and each subblock is one pixel. + // So to do that we need to calculate the "new" dimensions of the image, 1/4 the size of the original. + if (mTexelFormat == eGX_CMPR) { + MipW /= 4; + MipH /= 4; + } + + for (u32 iMip = 0; iMip < mNumMipMaps; iMip++) + { + for (u32 BlockY = 0; BlockY < MipH; BlockY += BHeight) + for (u32 BlockX = 0; BlockX < MipW; BlockX += BWidth) { + for (u32 ImgY = BlockY; ImgY < BlockY + BHeight; ImgY++) { + for (u32 ImgX = BlockX; ImgX < BlockX + BWidth; ImgX++) + { + u32 DstPos = ((ImgY * MipW) + ImgX) * PixelStride; + Out.Seek(MipOffset + DstPos, SEEK_SET); + + if (mTexelFormat == eGX_I4) ReadPixelsI4(TXTR, Out); + else if (mTexelFormat == eGX_I8) ReadPixelI8(TXTR, Out); + else if (mTexelFormat == eGX_IA4) ReadPixelIA4(TXTR, Out); + else if (mTexelFormat == eGX_IA8) ReadPixelIA8(TXTR, Out); + else if (mTexelFormat == eGX_C4) ReadPixelsC4(TXTR, Out); + else if (mTexelFormat == eGX_C8) ReadPixelC8(TXTR, Out); + else if (mTexelFormat == eGX_RGB565) ReadPixelRGB565(TXTR, Out); + else if (mTexelFormat == eGX_RGB5A3) ReadPixelRGB5A3(TXTR, Out); + else if (mTexelFormat == eGX_RGBA8) ReadPixelRGBA8(TXTR, Out); + else if (mTexelFormat == eGX_CMPR) ReadSubBlockCMPR(TXTR, Out); + + // I4 and C4 have 4bpp images, so I'm forced to read two pixels at a time. + if ((mTexelFormat == eGX_I4) || (mTexelFormat == eGX_C4)) ImgX++; + } + } + if (mTexelFormat == eGX_RGBA8) TXTR.Seek(0x20, SEEK_CUR); + } + + u32 MipSize = (u32) (MipW * MipH * gskPixelsToBytes[mTexelFormat]); + if (mTexelFormat == eGX_CMPR) MipSize *= 16; // Since we're pretending the image is 1/4 its actual size, we have to multiply the size by 16 to get the correct offset + + MipOffset += MipSize; + MipW /= 2; + MipH /= 2; + if (MipW < BWidth) MipW = BWidth; + if (MipH < BHeight) MipH = BHeight; + } +} + +void CTextureDecoder::FullDecodeGXTexture(CInputStream& TXTR) +{ + // Get image data size, create output buffer + u32 ImageStart = TXTR.Tell(); + TXTR.Seek(0x0, SEEK_END); + u32 ImageSize = TXTR.Tell() - ImageStart; + TXTR.Seek(ImageStart, SEEK_SET); + + mDataBufferSize = ImageSize * (32 / gskSourceBpp[mTexelFormat]); + mpDataBuffer = new u8[mDataBufferSize]; + + CMemoryOutStream Out(mpDataBuffer, mDataBufferSize, IOUtil::SystemEndianness); + + // Initializing more stuff before we start the mipmap loop + u32 MipW = mWidth, MipH = mHeight; + u32 MipOffset = 0; + + u32 BWidth = gskBlockWidth[mTexelFormat]; + u32 BHeight = gskBlockHeight[mTexelFormat]; + + // With CMPR, we're using a little trick. + // CMPR stores pixels in 8x8 blocks, with four 4x4 subblocks. + // An easy way to convert it is to pretend each block is 2x2 and each subblock is one pixel. + // So to do that we need to calculate the "new" dimensions of the image, 1/4 the size of the original. + if (mTexelFormat == eGX_CMPR) { + MipW /= 4; + MipH /= 4; + } + + for (u32 iMip = 0; iMip < mNumMipMaps; iMip++) + { + for (u32 BlockY = 0; BlockY < MipH; BlockY += BHeight) + for (u32 BlockX = 0; BlockX < MipW; BlockX += BWidth) { + for (u32 ImgY = BlockY; ImgY < BlockY + BHeight; ImgY++) { + for (u32 ImgX = BlockX; ImgX < BlockX + BWidth; ImgX++) + { + u32 DstPos = (mTexelFormat == eGX_CMPR) ? ((ImgY * (MipW * 4)) + ImgX) * 16 : ((ImgY * MipW) + ImgX) * 4; + Out.Seek(MipOffset + DstPos, SEEK_SET); + + // I4/C4/CMPR require reading more than one pixel at a time + if (mTexelFormat == eGX_I4) + { + u8 Byte = TXTR.ReadByte(); + Out.WriteLong( DecodePixelI4(Byte, 0).ToLongARGB() ); + Out.WriteLong( DecodePixelI4(Byte, 1).ToLongARGB() ); + } + else if (mTexelFormat == eGX_C4) + { + u8 Byte = TXTR.ReadByte(); + Out.WriteLong( DecodePixelC4(Byte, 0, mPaletteInput).ToLongARGB() ); + Out.WriteLong( DecodePixelC4(Byte, 1, mPaletteInput).ToLongARGB() ); + } + else if (mTexelFormat == eGX_CMPR) DecodeSubBlockCMPR(TXTR, Out, (u16) (MipW * 4)); + + else + { + CColor Pixel; + + if (mTexelFormat == eGX_I8) Pixel = DecodePixelI8(TXTR.ReadByte()); + else if (mTexelFormat == eGX_IA4) Pixel = DecodePixelIA4(TXTR.ReadByte()); + else if (mTexelFormat == eGX_IA8) Pixel = DecodePixelIA8(TXTR.ReadShort()); + else if (mTexelFormat == eGX_C8) Pixel = DecodePixelC8(TXTR.ReadByte(), mPaletteInput); + else if (mTexelFormat == eGX_RGB565) Pixel = DecodePixelRGB565(TXTR.ReadShort()); + else if (mTexelFormat == eGX_RGB5A3) Pixel = DecodePixelRGB5A3(TXTR.ReadShort()); + else if (mTexelFormat == eGX_RGBA8) Pixel = CColor(TXTR); + + Out.WriteLong(Pixel.ToLongARGB()); + } + } + } + if (mTexelFormat == eGX_RGBA8) TXTR.Seek(0x20, SEEK_CUR); + } + + u32 MipSize = MipW * MipH * 4; + if (mTexelFormat == eGX_CMPR) MipSize *= 16; + + MipOffset += MipSize; + MipW /= 2; + MipH /= 2; + if (MipW < BWidth) MipW = BWidth; + if (MipH < BHeight) MipH = BHeight; + } +} + +void CTextureDecoder::DecodeDDS(CInputStream &DDS) +{ + // Get image data size, create output buffer + u32 ImageStart = DDS.Tell(); + DDS.Seek(0x0, SEEK_END); + u32 ImageSize = DDS.Tell() - ImageStart; + DDS.Seek(ImageStart, SEEK_SET); + + mDataBufferSize = ImageSize; + if (mDDSInfo.Format == SDDSInfo::DXT1) mDataBufferSize *= 8; + else if (mDDSInfo.Format == SDDSInfo::RGBA) mDataBufferSize *= (32 / mDDSInfo.BitCount); + else mDataBufferSize *= 4; + mpDataBuffer = new u8[mDataBufferSize]; + + CMemoryOutStream Out(mpDataBuffer, mDataBufferSize, IOUtil::SystemEndianness); + + // Initializing more stuff before we start the mipmap loop + u32 MipW = mWidth, MipH = mHeight; + u32 MipOffset = 0; + + u32 BPP; + switch (mDDSInfo.Format) + { + case SDDSInfo::RGBA: + BPP = mDDSInfo.BitCount; + break; + case SDDSInfo::DXT1: + BPP = 4; + break; + case SDDSInfo::DXT2: + case SDDSInfo::DXT3: + case SDDSInfo::DXT4: + case SDDSInfo::DXT5: + BPP = 8; + break; + } + + // For DXT* decodes we can use the same trick as CMPR + if ((mDDSInfo.Format != SDDSInfo::RGBA) && (mDDSInfo.Format != SDDSInfo::DXT1)) + { + MipW /= 4; + MipH /= 4; + } + + for (u32 iMip = 0; iMip < mNumMipMaps; iMip++) + { + // For DXT1 we can copy the image data as-is to load it + if (mDDSInfo.Format == SDDSInfo::DXT1) + { + Out.Seek(MipOffset, SEEK_SET); + u32 MipSize = MipW * MipH / 2; + std::vector MipBuffer(MipSize); + DDS.ReadBytes(MipBuffer.data(), MipBuffer.size()); + Out.WriteBytes(MipBuffer.data(), MipBuffer.size()); + MipOffset += MipSize; + + MipW /= 2; + MipH /= 2; + if (MipW % 4) MipW += (4 - (MipW % 4)); + if (MipH % 4) MipH += (4 - (MipH % 4)); + } + + // Otherwise we do a full decode to RGBA8 + else + { + for (u32 y = 0; y < MipH; y++) + { + for (u32 x = 0; x < MipW; x++) + { + u32 OutPos = MipOffset; + + if (mDDSInfo.Format == SDDSInfo::RGBA) + { + OutPos += ((y * MipW) + x) * 4; + Out.Seek(OutPos, SEEK_SET); + + CColor Pixel = DecodeDDSPixel(DDS); + Out.WriteLong(Pixel.ToLongARGB()); + } + + else + { + OutPos += ((y * (MipW * 4)) + x) * 16; + Out.Seek(OutPos, SEEK_SET); + + if (mDDSInfo.Format == SDDSInfo::DXT1) + DecodeBlockBC1(DDS, Out, MipW * 4); + else if ((mDDSInfo.Format == SDDSInfo::DXT2) || (mDDSInfo.Format == SDDSInfo::DXT3)) + DecodeBlockBC2(DDS, Out, MipW * 4); + else if ((mDDSInfo.Format == SDDSInfo::DXT4) || (mDDSInfo.Format == SDDSInfo::DXT5)) + DecodeBlockBC3(DDS, Out, MipW * 4); + } + } + } + + u32 MipSize = (mWidth * mHeight) * 4; + if (mDDSInfo.Format != SDDSInfo::RGBA) MipSize *= 16; + MipOffset += MipSize; + + MipW /= 2; + MipH /= 2; + } + } + + if (mDDSInfo.Format == SDDSInfo::DXT1) + mTexelFormat = eDXT1; + else + mTexelFormat = eGX_RGBA8; +} + +// ************ READ PIXELS (PARTIAL DECODE) ************ +void CTextureDecoder::ReadPixelsI4(CInputStream& src, COutputStream& dst) +{ + u8 px = src.ReadByte(); + dst.WriteByte(Extend4to8(px >> 4)); + dst.WriteByte(Extend4to8(px >> 4)); + dst.WriteByte(Extend4to8(px)); + dst.WriteByte(Extend4to8(px)); +} + +void CTextureDecoder::ReadPixelI8(CInputStream& src, COutputStream& dst) +{ + u8 px = src.ReadByte(); + dst.WriteByte(px); + dst.WriteByte(px); +} + +void CTextureDecoder::ReadPixelIA4(CInputStream& src, COutputStream& dst) +{ + // this can be left as-is for DDS conversion, but opengl doesn't support two components in one byte... + u8 byte = src.ReadByte(); + u8 a = Extend4to8(byte >> 4); + u8 l = Extend4to8(byte); + dst.WriteShort((l << 8) | a); +} + +void CTextureDecoder::ReadPixelIA8(CInputStream& src, COutputStream& dst) +{ + dst.WriteShort(src.ReadShort()); +} + +void CTextureDecoder::ReadPixelsC4(CInputStream& src, COutputStream& dst) +{ + // This isn't how C4 works, but due to the way Retro packed font textures (which use C4) + // this is the only way to get them to decode correctly for now. + // Commented-out code is proper C4 decoding. Dedicated font texture-decoding function + // is probably going to be necessary in the future. + u8 byte = src.ReadByte(); + u8 indices[2]; + indices[0] = (byte >> 4) & 0xF; + indices[1] = byte & 0xF; + + for (u32 i = 0; i < 2; i++) + { + u8 r, g, b, a; + ((indices[i] >> 3) & 0x1) ? r = 0xFF : r = 0x0; + ((indices[i] >> 2) & 0x1) ? g = 0xFF : g = 0x0; + ((indices[i] >> 1) & 0x1) ? b = 0xFF : b = 0x0; + ((indices[i] >> 0) & 0x1) ? a = 0xFF : a = 0x0; + u32 rgba = (r << 24) | (g << 16) | (b << 8) | (a); + dst.WriteLong(rgba); + + /*paletteInput->Seek(indices[i] * 2, SEEK_SET); + + if (paletteFormat == PaletteIA8) readPixelIA8(*paletteInput, dst); + else if (paletteFormat == PaletteRGB565) readPixelRGB565(*paletteInput, dst); + else if (paletteFormat == PaletteRGB5A3) readPixelRGB5A3(*paletteInput, dst);*/ + } +} + +void CTextureDecoder::ReadPixelC8(CInputStream& src, COutputStream& dst) +{ + // DKCR fonts use C8 :| + u8 index = src.ReadByte(); + + /*u8 r, g, b, a; + ((index >> 3) & 0x1) ? r = 0xFF : r = 0x0; + ((index >> 2) & 0x1) ? g = 0xFF : g = 0x0; + ((index >> 1) & 0x1) ? b = 0xFF : b = 0x0; + ((index >> 0) & 0x1) ? a = 0xFF : a = 0x0; + u32 rgba = (r << 24) | (g << 16) | (b << 8) | (a); + dst.WriteLong(rgba);*/ + + mPaletteInput.Seek(index * 2, SEEK_SET); + + if (mPaletteFormat == ePalette_IA8) ReadPixelIA8(mPaletteInput, dst); + else if (mPaletteFormat == ePalette_RGB565) ReadPixelRGB565(mPaletteInput, dst); + else if (mPaletteFormat == ePalette_RGB5A3) ReadPixelRGB5A3(mPaletteInput, dst); +} + +void CTextureDecoder::ReadPixelRGB565(CInputStream& src, COutputStream& dst) +{ + // RGB565 can be used as-is. + dst.WriteShort(src.ReadShort()); +} + +void CTextureDecoder::ReadPixelRGB5A3(CInputStream& src, COutputStream& dst) +{ + u16 px = src.ReadShort(); + CColor c; + + if (px & 0x8000) // RGB5 + { + c.b = Extend5to8(px >> 10); + c.g = Extend5to8(px >> 5); + c.r = Extend5to8(px >> 0); + c.a = 0xFF; + } + + else // RGB4A3 + { + c.a = Extend3to8(px >> 12); + c.b = Extend4to8(px >> 8); + c.g = Extend4to8(px >> 4); + c.r = Extend4to8(px >> 0); + } + + dst.WriteLong(c.ToLongARGB()); +} + +void CTextureDecoder::ReadPixelRGBA8(CInputStream& src, COutputStream& dst) +{ + u16 ar = src.ReadShort(); + src.Seek(0x1E, SEEK_CUR); + u16 gb = src.ReadShort(); + src.Seek(-0x20, SEEK_CUR); + u32 px = (ar << 16) | gb; + dst.WriteLong(px); +} + +void CTextureDecoder::ReadSubBlockCMPR(CInputStream& src, COutputStream& dst) +{ + dst.WriteShort(src.ReadShort()); + dst.WriteShort(src.ReadShort()); + + for (u32 byte = 0; byte < 4; byte++) { + u8 b = src.ReadByte(); + b = ((b & 0x3) << 6) | ((b & 0xC) << 2) | ((b & 0x30) >> 2) | ((b & 0xC0) >> 6); + dst.WriteByte(b); + } +} + +// ************ DECODE PIXELS (FULL DECODE TO RGBA8) ************ +CColor CTextureDecoder::DecodePixelI4(u8 Byte, u8 WhichPixel) +{ + if (WhichPixel == 1) Byte >>= 4; + u8 px = Extend4to8(Byte); + return CColor(px, px, px, 0xFF); +} + +CColor CTextureDecoder::DecodePixelI8(u8 Byte) +{ + return CColor(Byte, Byte, Byte, 0xFF); +} + +CColor CTextureDecoder::DecodePixelIA4(u8 Byte) +{ + u8 Alpha = Extend4to8(Byte >> 4); + u8 Lum = Extend4to8(Byte); + return CColor(Lum, Lum, Lum, Alpha); +} + +CColor CTextureDecoder::DecodePixelIA8(u16 Short) +{ + u8 Alpha = (Short >> 8) & 0xFF; + u8 Lum = Short & 0xFF; + return CColor(Lum, Lum, Lum, Alpha); +} + +CColor CTextureDecoder::DecodePixelC4(u8 Byte, u8 WhichPixel, CInputStream& PaletteStream) +{ + if (WhichPixel == 1) Byte >>= 4; + Byte &= 0xF; + + PaletteStream.Seek(Byte * 2, SEEK_SET); + if (mPaletteFormat == ePalette_IA8) return DecodePixelIA8(PaletteStream.ReadShort()); + else if (mPaletteFormat == ePalette_RGB565) return DecodePixelIA8(PaletteStream.ReadShort()); + else if (mPaletteFormat == ePalette_RGB5A3) return DecodePixelIA8(PaletteStream.ReadShort()); + else return CColor::skTransparentBlack; +} + +CColor CTextureDecoder::DecodePixelC8(u8 Byte, CInputStream& PaletteStream) +{ + PaletteStream.Seek(Byte * 2, SEEK_SET); + if (mPaletteFormat == ePalette_IA8) return DecodePixelIA8(PaletteStream.ReadShort()); + else if (mPaletteFormat == ePalette_RGB565) return DecodePixelIA8(PaletteStream.ReadShort()); + else if (mPaletteFormat == ePalette_RGB5A3) return DecodePixelIA8(PaletteStream.ReadShort()); + else return CColor::skTransparentBlack; +} + +CColor CTextureDecoder::DecodePixelRGB565(u16 Short) +{ + u8 b = Extend5to8( (u8) (Short >> 11) ); + u8 g = Extend6to8( (u8) (Short >> 5) ); + u8 r = Extend5to8( (u8) (Short) ); + return CColor(r, g, b, 0xFF); +} + +CColor CTextureDecoder::DecodePixelRGB5A3(u16 Short) +{ + if (Short & 0x8000) // RGB5 + { + u8 b = Extend5to8( (u8) (Short >> 10)); + u8 g = Extend5to8( (u8) (Short >> 5)); + u8 r = Extend5to8( (u8) (Short) ); + return CColor(r, g, b, 0xFF); + } + + else // RGB4A3 + { + u8 a = Extend3to8( (u8) (Short >> 12) ); + u8 b = Extend4to8( (u8) (Short >> 8) ); + u8 g = Extend4to8( (u8) (Short >> 4) ); + u8 r = Extend4to8( (u8) (Short) ); + return CColor(r, g, b, a); + } +} + +void CTextureDecoder::DecodeSubBlockCMPR(CInputStream& src, COutputStream& dst, u16 Width) +{ + CColor Palettes[4]; + u16 PaletteA = src.ReadShort(); + u16 PaletteB = src.ReadShort(); + Palettes[0] = DecodePixelRGB565(PaletteA); + Palettes[1] = DecodePixelRGB565(PaletteB); + + if (PaletteA > PaletteB) + { + Palettes[2] = (Palettes[0] * 0.666666666f) + (Palettes[1] * 0.333333333f); + Palettes[3] = (Palettes[0] * 0.333333333f) + (Palettes[1] * 0.666666666f); + } + else + { + Palettes[2] = (Palettes[0] * 0.5f) + (Palettes[1] * 0.5f); + Palettes[3] = CColor::skTransparentBlack; + } + + for (u32 y = 0; y < 4; y++) + { + u8 Byte = src.ReadByte(); + + for (u32 x = 0; x < 4; x++) + { + u8 Shift = (u8) (6 - (x * 2)); + u8 PaletteIndex = (Byte >> Shift) & 0x3; + CColor Pixel = Palettes[PaletteIndex]; + dst.WriteLong(Pixel.ToLongARGB()); + } + + dst.Seek((Width - 4) * 4, SEEK_CUR); + } +} + +void CTextureDecoder::DecodeBlockBC1(CInputStream& src, COutputStream& dst, u32 Width) +{ + // Very similar to the CMPR subblock function, but unfortunately a slight + // difference in the order the pixel indices are read requires a separate function + CColor Palettes[4]; + u16 PaletteA = src.ReadShort(); + u16 PaletteB = src.ReadShort(); + Palettes[0] = DecodePixelRGB565(PaletteA); + Palettes[1] = DecodePixelRGB565(PaletteB); + + if (PaletteA > PaletteB) + { + Palettes[2] = (Palettes[0] * 0.666666666f) + (Palettes[1] * 0.333333333f); + Palettes[3] = (Palettes[0] * 0.333333333f) + (Palettes[1] * 0.666666666f); + } + else + { + Palettes[2] = (Palettes[0] * 0.5f) + (Palettes[1] * 0.5f); + Palettes[3] = CColor::skTransparentBlack; + } + + for (u32 y = 0; y < 4; y++) + { + u8 Byte = src.ReadByte(); + + for (u32 x = 0; x < 4; x++) + { + u8 Shift = (u8) (x * 2); + u8 PaletteIndex = (Byte >> Shift) & 0x3; + CColor Pixel = Palettes[PaletteIndex]; + dst.WriteLong(Pixel.ToLongARGB()); + } + + dst.Seek((Width - 4) * 4, SEEK_CUR); + } +} + +void CTextureDecoder::DecodeBlockBC2(CInputStream& src, COutputStream& dst, u32 Width) +{ + CColor CPalettes[4]; + u16 PaletteA = src.ReadShort(); + u16 PaletteB = src.ReadShort(); + CPalettes[0] = DecodePixelRGB565(PaletteA); + CPalettes[1] = DecodePixelRGB565(PaletteB); + + if (PaletteA > PaletteB) + { + CPalettes[2] = (CPalettes[0] * 0.666666666f) + (CPalettes[1] * 0.333333333f); + CPalettes[3] = (CPalettes[0] * 0.333333333f) + (CPalettes[1] * 0.666666666f); + } + else + { + CPalettes[2] = (CPalettes[0] * 0.5f) + (CPalettes[1] * 0.5f); + CPalettes[3] = CColor::skTransparentBlack; + } + + for (u32 y = 0; y < 4; y++) + { + u8 Byte = src.ReadByte(); + + for (u32 x = 0; x < 4; x++) + { + u8 Shift = (u8) (x * 2); + u8 PaletteIndex = (Byte >> Shift) & 0x3; + CColor Pixel = CPalettes[PaletteIndex]; + dst.WriteLong(Pixel.ToLongARGB()); + } + + dst.Seek((Width - 4) * 4, SEEK_CUR); + } +} + +void CTextureDecoder::DecodeBlockBC3(CInputStream& src, COutputStream& dst, u32 Width) +{ + CColor Palettes[4]; + u16 PaletteA = src.ReadShort(); + u16 PaletteB = src.ReadShort(); + Palettes[0] = DecodePixelRGB565(PaletteA); + Palettes[1] = DecodePixelRGB565(PaletteB); + + if (PaletteA > PaletteB) + { + Palettes[2] = (Palettes[0] * 0.666666666f) + (Palettes[1] * 0.333333333f); + Palettes[3] = (Palettes[0] * 0.333333333f) + (Palettes[1] * 0.666666666f); + } + else + { + Palettes[2] = (Palettes[0] * 0.5f) + (Palettes[1] * 0.5f); + Palettes[3] = CColor::skTransparentBlack; + } + + for (u32 y = 0; y < 4; y++) + { + u8 Byte = src.ReadByte(); + + for (u32 x = 0; x < 4; x++) + { + u8 Shift = (u8) (x * 2); + u8 PaletteIndex = (Byte >> Shift) & 0x3; + CColor Pixel = Palettes[PaletteIndex]; + dst.WriteLong(Pixel.ToLongARGB()); + } + + dst.Seek((Width - 4) * 4, SEEK_CUR); + } +} + +CColor CTextureDecoder::DecodeDDSPixel(CInputStream&) +{ + // Not using parameter 1 (CInputStream& - DDS) + return CColor::skWhite; +} + +// ************ UTILITY ************ +u8 CTextureDecoder::Extend3to8(u8 in) +{ + in &= 0x7; + return (in << 5) | (in << 2) | (in >> 1); +} + +u8 CTextureDecoder::Extend4to8(u8 in) +{ + in &= 0xF; + return (in << 4) | in; +} + +u8 CTextureDecoder::Extend5to8(u8 in) +{ + in &= 0x1F; + return (in << 3) | (in >> 2); +} + +u8 CTextureDecoder::Extend6to8(u8 in) +{ + in &= 0x3F; + return (in << 2) | (in >> 4); +} + +u32 CTextureDecoder::CalculateShiftForMask(u32 BitMask) +{ + u32 Shift = 32; + + while (BitMask) + { + BitMask <<= 1; + Shift--; + } + return Shift; +} + +u32 CTextureDecoder::CalculateMaskBitCount(u32 BitMask) +{ + u32 Count = 0; + + while (BitMask) + { + if (BitMask & 0x1) Count++; + BitMask >>= 1; + } + return Count; +} diff --git a/Resource/factory/CTextureDecoder.h b/Resource/factory/CTextureDecoder.h new file mode 100644 index 00000000..0bbf2f9c --- /dev/null +++ b/Resource/factory/CTextureDecoder.h @@ -0,0 +1,93 @@ +#ifndef CTEXTUREDECODER_H +#define CTEXTUREDECODER_H + +#include +#include +#include +#include "../CTexture.h" +#include "../ETexelFormat.h" + +class CTextureDecoder +{ + ETexelFormat mTexelFormat; + u16 mWidth, mHeight; + u32 mNumMipMaps; + + bool mHasPalettes; + EGXPaletteFormat mPaletteFormat; + std::vector mPalettes; + CMemoryInStream mPaletteInput; + + struct SDDSInfo + { + enum { DXT1, DXT2, DXT3, DXT4, DXT5, RGBA } Format; + u32 Flags; + u32 BitCount; + u32 RBitMask, GBitMask, BBitMask, ABitMask; + u32 RShift, GShift, BShift, AShift; + u32 RSize, GSize, BSize, ASize; + } mDDSInfo; + + u8 *mpDataBuffer; + u32 mDataBufferSize; + + // Private Functions + CTextureDecoder(); + ~CTextureDecoder(); + CTexture* CreateTexture(); + + // Read + void ReadTXTR(CInputStream& TXTR); + void ReadDDS(CInputStream& DDS); + + // Decode + void PartialDecodeGXTexture(CInputStream& TXTR); + void FullDecodeGXTexture(CInputStream& TXTR); + void DecodeDDS(CInputStream& DDS); + + // Decode Pixels (preserve compression) + void ReadPixelsI4(CInputStream& src, COutputStream& dst); + void ReadPixelI8(CInputStream& src, COutputStream& dst); + void ReadPixelIA4(CInputStream& src, COutputStream& dst); + void ReadPixelIA8(CInputStream& src, COutputStream& dst); + void ReadPixelsC4(CInputStream& src, COutputStream& dst); + void ReadPixelC8(CInputStream& src, COutputStream& dst); + void ReadPixelRGB565(CInputStream& src, COutputStream& dst); + void ReadPixelRGB5A3(CInputStream& src, COutputStream& dst); + void ReadPixelRGBA8(CInputStream& src, COutputStream& dst); + void ReadSubBlockCMPR(CInputStream& src, COutputStream& dst); + + // Decode Pixels (convert to RGBA8) + CColor DecodePixelI4(u8 Byte, u8 WhichPixel); + CColor DecodePixelI8(u8 Byte); + CColor DecodePixelIA4(u8 Byte); + CColor DecodePixelIA8(u16 Short); + CColor DecodePixelC4(u8 Byte, u8 WhichPixel, CInputStream& PaletteStream); + CColor DecodePixelC8(u8 Byte, CInputStream& PaletteStream); + CColor DecodePixelRGB565(u16 Short); + CColor DecodePixelRGB5A3(u16 Short); + CColor DecodePixelRGBA8(CInputStream& src, COutputStream& dst); + void DecodeSubBlockCMPR(CInputStream& src, COutputStream& dst, u16 Width); + + void DecodeBlockBC1(CInputStream& src, COutputStream& dst, u32 Width); + void DecodeBlockBC2(CInputStream& src, COutputStream& dst, u32 Width); + void DecodeBlockBC3(CInputStream& src, COutputStream& dst, u32 Width); + CColor DecodeDDSPixel(CInputStream& DDS); + + // Static +public: + static CTexture* LoadTXTR(CInputStream& TXTR); + static CTexture* LoadDDS(CInputStream& DDS); + static CTexture* DoFullDecode(CInputStream& TXTR); + static CTexture* DoFullDecode(CTexture *pTexture); + + // Utility + static u8 Extend3to8(u8 in); + static u8 Extend4to8(u8 in); + static u8 Extend5to8(u8 in); + static u8 Extend6to8(u8 in); + static u32 CalculateShiftForMask(u32 BitMask); + static u32 CalculateMaskBitCount(u32 BitMask); +}; + +#endif // CTEXTUREDECODER_H diff --git a/Resource/factory/CWorldLoader.cpp b/Resource/factory/CWorldLoader.cpp new file mode 100644 index 00000000..6e919ca7 --- /dev/null +++ b/Resource/factory/CWorldLoader.cpp @@ -0,0 +1,341 @@ +#include "CWorldLoader.h" +#include +#include +#include + +CWorldLoader::CWorldLoader() +{ +} + +void CWorldLoader::LoadPrimeMLVL(CInputStream& MLVL) +{ + /** + * This function loads MLVL files from Prime 1/2 + * Corruption isn't too different, but having to check for it on every file ID is obnoxious + * We start immediately after the "version" value (0x8 in the file) + */ + // Header + if (mVersion < eCorruptionProto) + { + mpWorld->mpWorldName = (CStringTable*) gResCache.GetResource(MLVL.ReadLong(), "STRG"); + if (mVersion == eEchoes) mpWorld->mpDarkWorldName = (CStringTable*) gResCache.GetResource(MLVL.ReadLong(), "STRG"); + if (mVersion > ePrime) mpWorld->mUnknown1 = MLVL.ReadLong(); + if (mVersion >= ePrime) mpWorld->mpSaveWorld = gResCache.GetResource(MLVL.ReadLong(), "SAVW"); + mpWorld->mpDefaultSkybox = (CModel*) gResCache.GetResource(MLVL.ReadLong(), "CMDL"); + } + + else + { + mpWorld->mpWorldName = (CStringTable*) gResCache.GetResource(MLVL.ReadLongLong(), "STRG"); + MLVL.Seek(0x4, SEEK_CUR); // Skipping unknown value + mpWorld->mpSaveWorld = gResCache.GetResource(MLVL.ReadLongLong(), "SAVW"); + mpWorld->mpDefaultSkybox = (CModel*) gResCache.GetResource(MLVL.ReadLongLong(), "CMDL"); + } + + // Memory relays - only in MP1 + if (mVersion == ePrime) + { + u32 NumMemoryRelays = MLVL.ReadLong(); + mpWorld->mMemoryRelays.reserve(NumMemoryRelays); + + for (u32 iMem = 0; iMem < NumMemoryRelays; iMem++) + { + CWorld::SMemoryRelay MemRelay; + MemRelay.InstanceID = MLVL.ReadLong(); + MemRelay.TargetID = MLVL.ReadLong(); + MemRelay.Message = MLVL.ReadShort(); + MemRelay.Unknown = MLVL.ReadByte(); + mpWorld->mMemoryRelays.push_back(MemRelay); + } + } + + // Areas - here's the real meat of the file + u32 NumAreas = MLVL.ReadLong(); + if (mVersion == ePrime) mpWorld->mUnknownAreas = MLVL.ReadLong(); + mpWorld->mAreas.resize(NumAreas); + + for (u32 iArea = 0; iArea < NumAreas; iArea++) + { + // Area header + CWorld::SArea *pArea = &mpWorld->mAreas[iArea]; + + if (mVersion < eCorruptionProto) + pArea->pAreaName = (CStringTable*) gResCache.GetResource(MLVL.ReadLong(), "STRG"); + else + pArea->pAreaName = (CStringTable*) gResCache.GetResource(MLVL.ReadLongLong(), "STRG"); + + pArea->AreaNameToken = CToken(pArea->pAreaName); + pArea->Transform = CTransform4f(MLVL); + pArea->AetherBox = CAABox(MLVL); + + if (mVersion < eCorruptionProto) + { + pArea->FileID = MLVL.ReadLong() & 0xFFFFFFFF; // This is the MREA ID; not actually loading it for obvious reasons + pArea->AreaID = MLVL.ReadLong() & 0xFFFFFFFF; + } + + else + { + pArea->FileID = MLVL.ReadLongLong(); + pArea->AreaID = MLVL.ReadLongLong(); + } + + // Attached areas + u32 NumAttachedAreas = MLVL.ReadLong(); + pArea->AttachedAreaIDs.reserve(NumAttachedAreas); + for (u32 iAttached = 0; iAttached < NumAttachedAreas; iAttached++) + pArea->AttachedAreaIDs.push_back( MLVL.ReadShort() ); + + if (mVersion < eCorruptionProto) + MLVL.Seek(0x4, SEEK_CUR); // Skipping unknown value (always 0) + + // Depedencies + if (mVersion < eCorruptionProto) + { + u32 NumDependencies = MLVL.ReadLong(); + pArea->Dependencies.reserve(NumDependencies); + + for (u32 iDep = 0; iDep < NumDependencies; iDep++) + { + SDependency Dependency; + Dependency.ResID = MLVL.ReadLong() & 0xFFFFFFFF; + Dependency.ResType = MLVL.ReadLong(); + pArea->Dependencies.push_back(Dependency); + } + + /** + * Dependency offsets - indicates an offset into the dependency list where each layer's dependencies start + * The count is the layer count + 1 because the last offset is for common dependencies, like terrain textures + */ + u32 NumDependencyOffsets = MLVL.ReadLong(); + pArea->Layers.resize(NumDependencyOffsets - 1); + + for (u32 iOff = 0; iOff < NumDependencyOffsets; iOff++) + { + u32 *Target; + if (iOff == NumDependencyOffsets - 1) Target = &pArea->CommonDependenciesStart; + else Target = &pArea->Layers[iOff].LayerDependenciesStart; + + *Target = MLVL.ReadLong(); + } + } + + // Docks + u32 NumDocks = MLVL.ReadLong(); + pArea->Docks.resize(NumDocks); + + for (u32 iDock = 0; iDock < NumDocks; iDock++) + { + u32 NumConnectingDocks = MLVL.ReadLong(); + + CWorld::SArea::SDock* pDock = &pArea->Docks[iDock]; + pDock->ConnectingDocks.reserve(NumConnectingDocks); + + for (u32 iConnect = 0; iConnect < NumConnectingDocks; iConnect++) + { + CWorld::SArea::SDock::SConnectingDock ConnectingDock; + ConnectingDock.AreaIndex = MLVL.ReadLong(); + ConnectingDock.DockIndex = MLVL.ReadLong(); + pDock->ConnectingDocks.push_back(ConnectingDock); + } + + u32 NumCoordinates = MLVL.ReadLong(); + if (NumCoordinates != 4) std::cout << "\rError: Dock coordinate count not 4\n"; + + for (u32 iCoord = 0; iCoord < NumCoordinates; iCoord++) + pDock->DockCoordinates[iCoord] = CVector3f(MLVL); + } + + // Rels + if (mVersion == eEchoes) + { + u32 NumRels = MLVL.ReadLong(); + pArea->RelFilenames.resize(NumRels); + + for (u32 iRel = 0; iRel < NumRels; iRel++) + pArea->RelFilenames[iRel] = MLVL.ReadString(); + + u32 NumRelOffsets = MLVL.ReadLong(); // Don't know what these offsets correspond to + pArea->RelOffsets.resize(NumRelOffsets); + + for (u32 iOff = 0; iOff < NumRelOffsets; iOff++) + pArea->RelOffsets[iOff] = MLVL.ReadLong(); + } + + // Footer + if (mVersion >= eEchoes) + pArea->InternalName = MLVL.ReadString(); + } + + // MapWorld + if (mVersion < eCorruptionProto) + mpWorld->mpMapWorld = gResCache.GetResource(MLVL.ReadLong(), "MAPW"); + else + mpWorld->mpMapWorld = gResCache.GetResource(MLVL.ReadLongLong(), "MAPW"); + MLVL.Seek(0x5, SEEK_CUR); // Unknown values which are always 0 + + // AudioGrps + if (mVersion == ePrime) + { + u32 NumAudioGrps = MLVL.ReadLong(); + mpWorld->mAudioGrps.reserve(NumAudioGrps); + + for (u32 iGrp = 0; iGrp < NumAudioGrps; iGrp++) + { + CWorld::SAudioGrp AudioGrp; + AudioGrp.Unknown = MLVL.ReadLong(); + AudioGrp.ResID = MLVL.ReadLong() & 0xFFFFFFFF; + mpWorld->mAudioGrps.push_back(AudioGrp); + } + + MLVL.Seek(0x1, SEEK_CUR); // Unknown values which are always 0 + } + + // Layer flags + MLVL.Seek(0x4, SEEK_CUR); // Skipping redundant area count + for (u32 iArea = 0; iArea < NumAreas; iArea++) + { + CWorld::SArea* pArea = &mpWorld->mAreas[iArea]; + u32 NumLayers = MLVL.ReadLong(); + if (NumLayers != pArea->Layers.size()) pArea->Layers.resize(NumLayers); + + u64 LayerFlags = MLVL.ReadLongLong(); + for (u32 iLayer = 0; iLayer < NumLayers; iLayer++) + pArea->Layers[iLayer].EnabledByDefault = (((LayerFlags >> iLayer) & 0x1) == 1); + } + + // Layer names + MLVL.Seek(0x4, SEEK_CUR); // Skipping redundant layer count + for (u32 iArea = 0; iArea < NumAreas; iArea++) + { + CWorld::SArea* pArea = &mpWorld->mAreas[iArea]; + u32 NumLayers = pArea->Layers.size(); + + for (u32 iLayer = 0; iLayer < NumLayers; iLayer++) + pArea->Layers[iLayer].LayerName = MLVL.ReadString(); + } + + // Last part of the file is layer name offsets, but we don't need it + // todo: Layer ID support for MP3 + mpWorld->mResTokens[0] = CToken(mpWorld->mpWorldName); + mpWorld->mResTokens[1] = CToken(mpWorld->mpDarkWorldName); + mpWorld->mResTokens[2] = CToken(mpWorld->mpSaveWorld); + mpWorld->mResTokens[3] = CToken(mpWorld->mpDefaultSkybox); + mpWorld->mResTokens[4] = CToken(mpWorld->mpMapWorld); +} + +void CWorldLoader::LoadReturnsMLVL(CInputStream& MLVL) +{ + mpWorld->mpWorldName = (CStringTable*) gResCache.GetResource(MLVL.ReadLongLong(), "STRG"); + + bool Check = MLVL.ReadByte(); + if (Check) + { + MLVL.ReadString(); + MLVL.Seek(0x10, SEEK_CUR); + } + + mpWorld->mpSaveWorld = gResCache.GetResource(MLVL.ReadLongLong(), "SAVW"); + mpWorld->mpDefaultSkybox = (CModel*) gResCache.GetResource(MLVL.ReadLongLong(), "CMDL"); + + // Areas + u32 NumAreas = MLVL.ReadLong(); + mpWorld->mAreas.resize(NumAreas); + + for (u32 iArea = 0; iArea < NumAreas; iArea++) + { + // Area header + CWorld::SArea *pArea = &mpWorld->mAreas[iArea]; + + pArea->pAreaName = (CStringTable*) gResCache.GetResource(MLVL.ReadLongLong(), "STRG"); + pArea->Transform = CTransform4f(MLVL); + pArea->AetherBox = CAABox(MLVL); + pArea->FileID = MLVL.ReadLongLong(); + pArea->AreaID = MLVL.ReadLongLong(); + pArea->AreaNameToken = CToken(pArea->pAreaName); + + MLVL.Seek(0x4, SEEK_CUR); + pArea->InternalName = MLVL.ReadString(); + } + + // Layer flags + MLVL.Seek(0x4, SEEK_CUR); // Skipping redundant area count + + for (u32 iArea = 0; iArea < NumAreas; iArea++) + { + CWorld::SArea* pArea = &mpWorld->mAreas[iArea]; + u32 NumLayers = MLVL.ReadLong(); + pArea->Layers.resize(NumLayers); + + u64 LayerFlags = MLVL.ReadLongLong(); + for (u32 iLayer = 0; iLayer < NumLayers; iLayer++) + pArea->Layers[iLayer].EnabledByDefault = (((LayerFlags >> iLayer) & 0x1) == 1); + } + + // Layer names + MLVL.Seek(0x4, SEEK_CUR); // Skipping redundant layer count + for (u32 iArea = 0; iArea < NumAreas; iArea++) + { + CWorld::SArea* pArea = &mpWorld->mAreas[iArea]; + u32 NumLayers = pArea->Layers.size(); + + for (u32 iLayer = 0; iLayer < NumLayers; iLayer++) + pArea->Layers[iLayer].LayerName = MLVL.ReadString(); + } + + // Last part of the file is layer name offsets, but we don't need it + // todo: Layer ID support + mpWorld->mResTokens[0] = CToken(mpWorld->mpWorldName); + mpWorld->mResTokens[1] = CToken(mpWorld->mpDarkWorldName); + mpWorld->mResTokens[2] = CToken(mpWorld->mpSaveWorld); + mpWorld->mResTokens[3] = CToken(mpWorld->mpDefaultSkybox); + mpWorld->mResTokens[4] = CToken(mpWorld->mpMapWorld); +} + +CWorld* CWorldLoader::LoadMLVL(CInputStream& MLVL) +{ + if (!MLVL.IsValid()) return nullptr; + Log::Write("Loading " + MLVL.GetSourceString()); + + u32 Magic = MLVL.ReadLong(); + if (Magic != 0xDEAFBABE) + { + Log::FileError(MLVL.GetSourceString(), "Invalid MLVL magic: " + StringUtil::ToHexString(Magic)); + return nullptr; + } + + u32 FileVersion = MLVL.ReadLong(); + EGame Version = GetFormatVersion(FileVersion); + if (Version == eUnknownVersion) + { + Log::FileError(MLVL.GetSourceString(), "Unsupported MLVL version: " + StringUtil::ToHexString(FileVersion)); + return nullptr; + } + + // Filestream is valid, magic+version are valid; everything seems good! + CWorldLoader Loader; + Loader.mpWorld = new CWorld(); + Loader.mpWorld->mWorldVersion = Version; + Loader.mVersion = Version; + + if (Version != eReturns) + Loader.LoadPrimeMLVL(MLVL); + else + Loader.LoadReturnsMLVL(MLVL); + + return Loader.mpWorld; +} + +EGame CWorldLoader::GetFormatVersion(u32 Version) +{ + switch (Version) + { + case 0xD: return ePrimeKioskDemo; + case 0x11: return ePrime; + case 0x14: return eEchoesDemo; + case 0x17: return eEchoes; + case 0x19: return eCorruption; + case 0x1B: return eReturns; + default: return eUnknownVersion; + } +} diff --git a/Resource/factory/CWorldLoader.h b/Resource/factory/CWorldLoader.h new file mode 100644 index 00000000..18304d40 --- /dev/null +++ b/Resource/factory/CWorldLoader.h @@ -0,0 +1,23 @@ +#ifndef CWORLDLOADER_H +#define CWORLDLOADER_H + +#include "../CWorld.h" +#include "../EFormatVersion.h" +#include +#include + +class CWorldLoader +{ + CWorld *mpWorld; + EGame mVersion; + + CWorldLoader(); + void LoadPrimeMLVL(CInputStream& MLVL); + void LoadReturnsMLVL(CInputStream& MLVL); + +public: + static CWorld* LoadMLVL(CInputStream& MLVL); + static EGame GetFormatVersion(u32 Version); +}; + +#endif // CWORLDLOADER_H diff --git a/Resource/model/CBasicModel.cpp b/Resource/model/CBasicModel.cpp new file mode 100644 index 00000000..3070f718 --- /dev/null +++ b/Resource/model/CBasicModel.cpp @@ -0,0 +1,57 @@ +#include "CBasicModel.h" +#include +#include + +CBasicModel::CBasicModel() : CResource() +{ + mVertexCount = 0; + mTriangleCount = 0; + mBuffered = false; +} + +CBasicModel::~CBasicModel() +{ + if (mHasOwnSurfaces) + for (u32 iSurf = 0; iSurf < mSurfaces.size(); iSurf++) + delete mSurfaces[iSurf]; +} + +EResType CBasicModel::Type() +{ + return eModel; +} + +u32 CBasicModel::GetVertexCount() +{ + return mVertexCount; +} + +u32 CBasicModel::GetTriangleCount() +{ + return mTriangleCount; +} + +CAABox CBasicModel::AABox() +{ + return mAABox; +} + +bool CBasicModel::IsBuffered() +{ + return mBuffered; +} + +u32 CBasicModel::GetSurfaceCount() +{ + return mSurfaces.size(); +} + +CAABox CBasicModel::GetSurfaceAABox(u32 Surface) +{ + return mSurfaces[Surface]->AABox; +} + +SSurface* CBasicModel::GetSurface(u32 Surface) +{ + return mSurfaces[Surface]; +} diff --git a/Resource/model/CBasicModel.h b/Resource/model/CBasicModel.h new file mode 100644 index 00000000..c8de8141 --- /dev/null +++ b/Resource/model/CBasicModel.h @@ -0,0 +1,37 @@ +#ifndef CBASICMODEL_H +#define CBASICMODEL_H + +#include "../CResource.h" +#include "SSurface.h" +#include +#include + +class CBasicModel : public CResource +{ +protected: + CAABox mAABox; + u32 mVertexCount; + u32 mTriangleCount; + bool mBuffered; + bool mHasOwnMaterials; + bool mHasOwnSurfaces; + + CVertexBuffer mVBO; + std::vector mSurfaces; + +public: + CBasicModel(); + ~CBasicModel(); + EResType Type(); + + u32 GetVertexCount(); + u32 GetTriangleCount(); + CAABox AABox(); + bool IsBuffered(); + u32 GetSurfaceCount(); + CAABox GetSurfaceAABox(u32 Surface); + SSurface* GetSurface(u32 Surface); + virtual void ClearGLBuffer() = 0; +}; + +#endif // CBASICMODEL_H diff --git a/Resource/model/CModel.cpp b/Resource/model/CModel.cpp new file mode 100644 index 00000000..d578b635 --- /dev/null +++ b/Resource/model/CModel.cpp @@ -0,0 +1,207 @@ +#include "CModel.h" +#include +#include + +CModel::CModel() : CBasicModel() +{ + mHasOwnMaterials = true; + mHasOwnSurfaces = true; +} + +CModel::CModel(SModelData *pModelData) : CBasicModel() +{ + SetData(pModelData); + mHasOwnMaterials = false; + mHasOwnSurfaces = true; +} + +CModel::CModel(SModelData *data, CMaterialSet *pMatSet) : CBasicModel() +{ + SetData(data); + mMaterialSets.resize(1); + mMaterialSets[0] = pMatSet; + mHasOwnMaterials = false; + mHasOwnSurfaces = true; +} + +CModel::~CModel() +{ + if (mHasOwnMaterials) + for (u32 m = 0; m < mMaterialSets.size(); m++) + delete mMaterialSets[m]; +} + +void CModel::SetData(SModelData *pModelData) +{ + mAABox = pModelData->mAABox; + mSurfaces = pModelData->mSurfaces; + + mVertexCount = 0; + mTriangleCount = 0; + + for (u32 iSurf = 0; iSurf < mSurfaces.size(); iSurf++) + { + SSurface *pSurf = mSurfaces[iSurf]; + mVertexCount += pSurf->VertexCount; + mTriangleCount += pSurf->TriangleCount; + } +} + +void CModel::BufferGL() +{ + mVBO.Clear(); + mSubmeshIndexBuffers.clear(); + + mSubmeshIndexBuffers.resize(mSurfaces.size()); + + for (u32 iSurf = 0; iSurf < mSurfaces.size(); iSurf++) + { + SSurface *pSurf = mSurfaces[iSurf]; + + u16 VBOStartOffset = (u16) mVBO.Size(); + mVBO.Reserve((u16) pSurf->VertexCount); + + for (u32 iPrim = 0; iPrim < pSurf->Primitives.size(); iPrim++) + { + SSurface::SPrimitive *pPrim = &pSurf->Primitives[iPrim]; + CIndexBuffer *pIBO = InternalGetIBO(iSurf, pPrim->Type); + pIBO->Reserve(pPrim->Vertices.size() + 1); // Allocate enough space for this primitive, plus the restart index + + std::vector Indices(pPrim->Vertices.size()); + for (u32 iVert = 0; iVert < pPrim->Vertices.size(); iVert++) + Indices[iVert] = mVBO.AddIfUnique(pPrim->Vertices[iVert], VBOStartOffset); + + // then add the indices to the IBO. We convert some primitives to strips to minimize draw calls. + switch (pPrim->Type) { + case eGX_Triangles: + pIBO->TrianglesToStrips(Indices.data(), Indices.size()); + break; + case eGX_TriangleFan: + pIBO->FansToStrips(Indices.data(), Indices.size()); + break; + case eGX_Quads: + pIBO->QuadsToStrips(Indices.data(), Indices.size()); + break; + default: + pIBO->AddIndices(Indices.data(), Indices.size()); + pIBO->AddIndex(0xFFFF); // primitive restart + break; + } + } + } + + mBuffered = true; +} + +void CModel::ClearGLBuffer() +{ + mVBO.Clear(); + mSubmeshIndexBuffers.clear(); + mBuffered = false; +} + +void CModel::Draw(ERenderOptions Options, u32 MatSet) +{ + if (!mBuffered) BufferGL(); + for (u32 iSurf = 0; iSurf < mSurfaces.size(); iSurf++) + DrawSurface(Options, iSurf, MatSet); +} + +void CModel::DrawSurface(ERenderOptions Options, u32 Surface, u32 MatSet) +{ + if (!mBuffered) BufferGL(); + + // Check that mat set index is valid + if (MatSet >= mMaterialSets.size()) + MatSet = mMaterialSets.size() - 1; + + // Bind material + SSurface *pSurf = mSurfaces[Surface]; + CMaterial *pMat = mMaterialSets[MatSet]->materials[pSurf->MaterialID]; + + if ((Options & eNoMaterialSetup) == 0) + { + if ((!(Options & eEnableOccluders)) && (pMat->Options() & CMaterial::eOccluder)) + return; + + pMat->SetCurrent(Options); + } + + // Draw IBOs + mVBO.Bind(); + + for (u32 iIBO = 0; iIBO < mSubmeshIndexBuffers[Surface].size(); iIBO++) + { + CIndexBuffer *pIBO = &mSubmeshIndexBuffers[Surface][iIBO]; + pIBO->DrawElements(); + } + + mVBO.Unbind(); +} + +u32 CModel::GetMatSetCount() +{ + return mMaterialSets.size(); +} + +u32 CModel::GetMatCount() +{ + if (mMaterialSets.empty()) return 0; + else return mMaterialSets[0]->materials.size(); +} + +CMaterialSet* CModel::GetMatSet(u32 MatSet) +{ + return mMaterialSets[MatSet]; +} + +CMaterial* CModel::GetMaterialByIndex(u32 MatSet, u32 Index) +{ + if (MatSet >= mMaterialSets.size()) + MatSet = mMaterialSets.size() - 1; + + if (GetMatCount() == 0) + return nullptr; + + return mMaterialSets[MatSet]->materials[Index]; +} + +CMaterial* CModel::GetMaterialBySurface(u32 MatSet, u32 Surface) +{ + return GetMaterialByIndex(MatSet, mSurfaces[Surface]->MaterialID); +} + +bool CModel::HasTransparency(u32 MatSet) +{ + if (MatSet >= mMaterialSets.size()) + MatSet = mMaterialSets.size() - 1; + + for (u32 iMat = 0; iMat < mMaterialSets[MatSet]->materials.size(); iMat++) + if (mMaterialSets[MatSet]->materials[iMat]->Options() & CMaterial::eTransparent ) return true; + + return false; +} + +bool CModel::IsSurfaceTransparent(u32 Surface, u32 MatSet) +{ + if (MatSet >= mMaterialSets.size()) + MatSet = mMaterialSets.size() - 1; + + u32 matID = mSurfaces[Surface]->MaterialID; + return (mMaterialSets[MatSet]->materials[matID]->Options() & CMaterial::eTransparent) != 0; +} + +CIndexBuffer* CModel::InternalGetIBO(u32 Surface, EGXPrimitiveType Primitive) +{ + std::vector *pIBOs = &mSubmeshIndexBuffers[Surface]; + GLenum Type = GXPrimToGLPrim(Primitive); + + for (u32 iIBO = 0; iIBO < pIBOs->size(); iIBO++) + { + if ((*pIBOs)[iIBO].GetPrimitiveType() == Type) + return &(*pIBOs)[iIBO]; + } + + pIBOs->emplace_back(CIndexBuffer(Type)); + return &pIBOs->back(); +} diff --git a/Resource/model/CModel.h b/Resource/model/CModel.h new file mode 100644 index 00000000..519f125a --- /dev/null +++ b/Resource/model/CModel.h @@ -0,0 +1,46 @@ +#ifndef CMODEL_H +#define CMODEL_H + +#include "CBasicModel.h" +#include "SModelData.h" +#include "SSurface.h" +#include "../CMaterialSet.h" + +#include +#include +#include + +class CModel : public CBasicModel +{ + friend class CModelLoader; + friend class CModelCooker; + + std::vector mMaterialSets; + std::vector> mSubmeshIndexBuffers; + bool mHasOwnMaterials; + +public: + CModel(); + CModel(SModelData *pModelData); + CModel(SModelData *pModelData, CMaterialSet *pMatSet); + ~CModel(); + void SetData(SModelData *pModelData); + + void BufferGL(); + void ClearGLBuffer(); + void Draw(ERenderOptions Options, u32 MatSet); + void DrawSurface(ERenderOptions Options, u32 Surface, u32 MatSet); + + u32 GetMatSetCount(); + u32 GetMatCount(); + CMaterialSet* GetMatSet(u32 MatSet); + CMaterial* GetMaterialByIndex(u32 MatSet, u32 Index); + CMaterial* GetMaterialBySurface(u32 MatSet, u32 Surface); + bool HasTransparency(u32 MatSet); + bool IsSurfaceTransparent(u32 Surface, u32 MatSet); + +private: + CIndexBuffer* InternalGetIBO(u32 Surface, EGXPrimitiveType Primitive); +}; + +#endif // MODEL_H diff --git a/Resource/model/CStaticModel.cpp b/Resource/model/CStaticModel.cpp new file mode 100644 index 00000000..ddfa828f --- /dev/null +++ b/Resource/model/CStaticModel.cpp @@ -0,0 +1,177 @@ +#include "CStaticModel.h" +#include +#include + +CStaticModel::CStaticModel() : CBasicModel() +{ + mpMaterial = nullptr; + mTransparent = false; + mHasOwnSurfaces = false; + mHasOwnMaterials = false; +} + +CStaticModel::CStaticModel(CMaterial *pMat) : CBasicModel() +{ + mpMaterial = pMat; + mTransparent = ((pMat->Options() & CMaterial::eTransparent) != 0); + mHasOwnSurfaces = false; + mHasOwnMaterials = false; +} + +CStaticModel::~CStaticModel() +{ +} + +void CStaticModel::AddSurface(SSurface *pSurface) +{ + mSurfaces.push_back(pSurface); + mAABox.ExpandBounds(pSurface->AABox); + + mVertexCount += pSurface->VertexCount; + mTriangleCount += pSurface->TriangleCount; +} + +void CStaticModel::BufferGL() +{ + mVBO.Clear(); + mIBOs.clear(); + + for (u32 iSurf = 0; iSurf < mSurfaces.size(); iSurf++) + { + SSurface *pSurf = mSurfaces[iSurf]; + + u16 VBOStartOffset = (u16) mVBO.Size(); + mVBO.Reserve((u16) pSurf->VertexCount); + + for (u32 iPrim = 0; iPrim < pSurf->Primitives.size(); iPrim++) + { + SSurface::SPrimitive *pPrim = &pSurf->Primitives[iPrim]; + CIndexBuffer *pIBO = InternalGetIBO(pPrim->Type); + pIBO->Reserve(pPrim->Vertices.size() + 1); // Allocate enough space for this primitive, plus the restart index + + // Next step: add new vertices to the VBO and create a small index buffer for the current primitive + std::vector Indices(pPrim->Vertices.size()); + for (u32 iVert = 0; iVert < pPrim->Vertices.size(); iVert++) + Indices[iVert] = mVBO.AddIfUnique(pPrim->Vertices[iVert], VBOStartOffset); + + // then add the indices to the IBO. We convert some primitives to strips to minimize draw calls. + switch (pPrim->Type) { + case eGX_Triangles: + pIBO->TrianglesToStrips(Indices.data(), Indices.size()); + break; + case eGX_TriangleFan: + pIBO->FansToStrips(Indices.data(), Indices.size()); + break; + case eGX_Quads: + pIBO->QuadsToStrips(Indices.data(), Indices.size()); + break; + default: + pIBO->AddIndices(Indices.data(), Indices.size()); + pIBO->AddIndex(0xFFFF); // primitive restart + break; + } + } + + // Make sure the number of submesh offset vectors matches the number of IBOs, then add the offsets + while (mIBOs.size() > mSubmeshEndOffsets.size()) + mSubmeshEndOffsets.emplace_back(std::vector(mSurfaces.size())); + + for (u32 iIBO = 0; iIBO < mIBOs.size(); iIBO++) + mSubmeshEndOffsets[iIBO][iSurf] = mIBOs[iIBO].GetSize(); + } + + mVBO.Buffer(); + + for (u32 iIBO = 0; iIBO < mIBOs.size(); iIBO++) + mIBOs[iIBO].Buffer(); + + mBuffered = true; +} + +void CStaticModel::ClearGLBuffer() +{ + mVBO.Clear(); + mIBOs.clear(); + mSubmeshEndOffsets.clear(); + mBuffered = false; +} + +void CStaticModel::Draw(ERenderOptions Options) +{ + if (!mBuffered) BufferGL(); + + if ((Options & eNoMaterialSetup) == 0) mpMaterial->SetCurrent(Options); + + // Draw IBOs + mVBO.Bind(); + + for (u32 iIBO = 0; iIBO < mIBOs.size(); iIBO++) + { + CIndexBuffer *pIBO = &mIBOs[iIBO]; + pIBO->Bind(); + glDrawElements(pIBO->GetPrimitiveType(), pIBO->GetSize(), GL_UNSIGNED_SHORT, (void*) 0); + pIBO->Unbind(); + gDrawCount++; + } + + mVBO.Unbind(); +} + +void CStaticModel::DrawSurface(ERenderOptions Options, u32 Surface) +{ + if (!mBuffered) BufferGL(); + + mVBO.Bind(); + if ((Options & eNoMaterialSetup) == 0) mpMaterial->SetCurrent(Options); + + for (u32 iIBO = 0; iIBO < mIBOs.size(); iIBO++) + { + // Since there is a shared IBO for every mesh, we need two things to draw a single one: an offset and a size + u32 Offset = 0; + if (Surface > 0) Offset = mSubmeshEndOffsets[iIBO][Surface - 1]; + u32 Size = mSubmeshEndOffsets[iIBO][Surface] - Offset; + + if (!Size) continue; // The chosen submesh doesn't use this IBO + + // Now we have both, so we can draw + mIBOs[iIBO].DrawElements(Offset, Size); + gDrawCount++; + } + + mVBO.Unbind(); +} + +CMaterial* CStaticModel::GetMaterial() +{ + return mpMaterial; +} + +void CStaticModel::SetMaterial(CMaterial *pMat) +{ + mpMaterial = pMat; + mTransparent = ((pMat->Options() & CMaterial::eTransparent) != 0); +} + +bool CStaticModel::IsTransparent() +{ + return mTransparent; +} + +bool CStaticModel::IsOccluder() +{ + return ((mpMaterial->Options() & CMaterial::eOccluder) != 0); +} + +CIndexBuffer* CStaticModel::InternalGetIBO(EGXPrimitiveType Primitive) +{ + GLenum type = GXPrimToGLPrim(Primitive); + + for (u32 iIBO = 0; iIBO < mIBOs.size(); iIBO++) + { + if (mIBOs[iIBO].GetPrimitiveType() == type) + return &mIBOs[iIBO]; + } + + mIBOs.emplace_back(CIndexBuffer(type)); + return &mIBOs.back(); +} diff --git a/Resource/model/CStaticModel.h b/Resource/model/CStaticModel.h new file mode 100644 index 00000000..930d608a --- /dev/null +++ b/Resource/model/CStaticModel.h @@ -0,0 +1,39 @@ +#ifndef CSTATICMODEL_H +#define CSTATICMODEL_H + +#include "CBasicModel.h" +#include +#include + +// A CStaticModel is meant for meshes that don't move. It's built specifically with terrain in mind. +// It only links to one material, and what it does best is combining submeshes from different models +// into shared VBOs and IBOs. This allows for a significantly reduced number of draw calls. + +class CStaticModel : public CBasicModel +{ + CMaterial *mpMaterial; + std::vector mIBOs; + std::vector> mSubmeshEndOffsets; + bool mTransparent; + +public: + CStaticModel(); + CStaticModel(CMaterial *pMat); + ~CStaticModel(); + void AddSurface(SSurface *pSurface); + + void BufferGL(); + void ClearGLBuffer(); + void Draw(ERenderOptions Options); + void DrawSurface(ERenderOptions Options, u32 Surface); + + CMaterial* GetMaterial(); + void SetMaterial(CMaterial *pMat); + bool IsTransparent(); + bool IsOccluder(); + +private: + CIndexBuffer* InternalGetIBO(EGXPrimitiveType Primitive); +}; + +#endif // CSTATICMODEL_H diff --git a/Resource/model/CVertex.h b/Resource/model/CVertex.h new file mode 100644 index 00000000..8474f1d4 --- /dev/null +++ b/Resource/model/CVertex.h @@ -0,0 +1,42 @@ +#ifndef CVERTEX_H +#define CVERTEX_H + +#include +#include +#include + +class CVertex +{ +public: + u32 ArrayPosition; // Position of this vertex in the input model file. + // This is needed to resave without breaking rigging. + CVector3f Position; + CVector3f Normal; + CColor Color[2]; + CVector2f Tex[8]; + u8 MatrixIndices[8]; + + CVertex() {} + + CVertex(CVector3f& Pos) + { + Position = Pos; + } + + bool operator==(const CVertex& other) { + return ((Position == other.Position) && + (Normal == other.Normal) && + (Color[0] == other.Color[0]) && + (Color[1] == other.Color[1]) && + (Tex[0] == other.Tex[0]) && + (Tex[1] == other.Tex[1]) && + (Tex[2] == other.Tex[2]) && + (Tex[3] == other.Tex[3]) && + (Tex[4] == other.Tex[4]) && + (Tex[5] == other.Tex[5]) && + (Tex[6] == other.Tex[6]) && + (Tex[7] == other.Tex[7])); + } +}; + +#endif // CVERTEX_H diff --git a/Resource/model/EVertexDescription.h b/Resource/model/EVertexDescription.h new file mode 100644 index 00000000..edb81d79 --- /dev/null +++ b/Resource/model/EVertexDescription.h @@ -0,0 +1,33 @@ +#ifndef EVERTEXDESCRIPTION +#define EVERTEXDESCRIPTION + +#include + +enum EVertexDescription +{ + eNoAttributes = 0x0, + ePosition = 0x3, + eNormal = 0xC, + eColor0 = 0x30, + eColor1 = 0xC0, + eTex0 = 0x300, + eTex1 = 0xC00, + eTex2 = 0x3000, + eTex3 = 0xC000, + eTex4 = 0x30000, + eTex5 = 0xC0000, + eTex6 = 0x300000, + eTex7 = 0xC00000, + ePosMtx = 0x1000000, + eTex0Mtx = 0x2000000, + eTex1Mtx = 0x4000000, + eTex2Mtx = 0x8000000, + eTex3Mtx = 0x10000000, + eTex4Mtx = 0x20000000, + eTex5Mtx = 0x40000000, + eTex6Mtx = 0x80000000 +}; +DEFINE_ENUM_FLAGS(EVertexDescription) + +#endif // EVERTEXDESCRIPTION + diff --git a/Resource/model/SModelData.h b/Resource/model/SModelData.h new file mode 100644 index 00000000..d05f395d --- /dev/null +++ b/Resource/model/SModelData.h @@ -0,0 +1,13 @@ +#ifndef SMODELDATA_H +#define SMODELDATA_H + +#include "SSurface.h" +#include + +struct SModelData +{ + CAABox mAABox; + std::vector mSurfaces; +}; + +#endif // SMODELDATA_H diff --git a/Resource/model/SSurface.cpp b/Resource/model/SSurface.cpp new file mode 100644 index 00000000..563e1040 --- /dev/null +++ b/Resource/model/SSurface.cpp @@ -0,0 +1,89 @@ +#include "SSurface.h" +#include +#include +#include + +std::pair SSurface::IntersectsRay(const CRay& Ray, const CTransform4f& Transform) +{ + //CDrawUtil::DrawWireCube(AABox.Transformed(Transform), CColor::skRed); + bool Hit = false; + float HitDist; + + for (u32 iPrim = 0; iPrim < Primitives.size(); iPrim++) + { + SPrimitive *pPrim = &Primitives[iPrim]; + u32 NumVerts = pPrim->Vertices.size(); + + if ((pPrim->Type == eGX_Triangles) || (pPrim->Type == eGX_TriangleFan) || (pPrim->Type == eGX_TriangleStrip)) + { + u32 NumTris; + + if (pPrim->Type == eGX_Triangles) + NumTris = NumVerts / 3; + else + NumTris = NumVerts - 2; + + CColor LineColor; + if (pPrim->Type == eGX_Triangles) + LineColor = CColor::skRed; + else if (pPrim->Type == eGX_TriangleStrip) + LineColor = CColor::skYellow; + else if (pPrim->Type == eGX_TriangleFan) + LineColor = CColor::skGreen; + + for (u32 iTri = 0; iTri < NumTris; iTri++) + { + CVector3f vtxA, vtxB, vtxC; + + // Get the three vertices that make up the current tri + if (pPrim->Type == eGX_Triangles) + { + u32 VertIndex = iTri * 3; + vtxA = pPrim->Vertices[VertIndex].Position; + vtxB = pPrim->Vertices[VertIndex+1].Position; + vtxC = pPrim->Vertices[VertIndex+2].Position; + } + + else if (pPrim->Type == eGX_TriangleFan) + { + vtxA = pPrim->Vertices[0].Position; + vtxB = pPrim->Vertices[iTri+1].Position; + vtxC = pPrim->Vertices[iTri+2].Position; + } + + else if (pPrim->Type = eGX_TriangleStrip) + { + if (iTri & 0x1) + { + vtxA = pPrim->Vertices[iTri+2].Position; + vtxB = pPrim->Vertices[iTri+1].Position; + vtxC = pPrim->Vertices[iTri].Position; + } + + else + { + vtxA = pPrim->Vertices[iTri].Position; + vtxB = pPrim->Vertices[iTri+1].Position; + vtxC = pPrim->Vertices[iTri+2].Position; + } + } + + // Intersection test + std::pair TriResult = Math::RayTriangleIntersection(Ray, vtxA, vtxB, vtxC); + + if (TriResult.first) + { + if ((!Hit) || (TriResult.second < HitDist)) + { + Hit = true; + HitDist = TriResult.second; + } + } + } + } + + // todo: Intersection tests for line primitives + } + + return std::pair(Hit, HitDist); +} diff --git a/Resource/model/SSurface.h b/Resource/model/SSurface.h new file mode 100644 index 00000000..6a704600 --- /dev/null +++ b/Resource/model/SSurface.h @@ -0,0 +1,39 @@ +#ifndef SSURFACE_H +#define SSURFACE_H + +#include "../CMaterialSet.h" +#include "CVertex.h" +#include +#include +#include +#include +#include +#include +#include +#include + +struct SSurface +{ + u32 VertexCount; + u32 TriangleCount; + CAABox AABox; + CVector3f CenterPoint; + u32 MaterialID; + CVector3f ReflectionDirection; + + struct SPrimitive + { + EGXPrimitiveType Type; + std::vector Vertices; + }; + std::vector Primitives; + + SSurface() { + VertexCount = 0; + TriangleCount = 0; + } + + std::pair IntersectsRay(const CRay& Ray, const CTransform4f& Transform = CTransform4f::skIdentity); +}; + +#endif // SSURFACE_H diff --git a/Resource/script/CMasterTemplate.cpp b/Resource/script/CMasterTemplate.cpp new file mode 100644 index 00000000..8d705990 --- /dev/null +++ b/Resource/script/CMasterTemplate.cpp @@ -0,0 +1,112 @@ +#include "CMasterTemplate.h" +#include "../factory/CWorldLoader.h" +#include + +CMasterTemplate::CMasterTemplate() +{ + mVersion = 0; + mFullyLoaded = false; +} + +CMasterTemplate::~CMasterTemplate() +{ + for (auto it = mTemplates.begin(); it != mTemplates.end(); it++) + delete it->second; +} + +EGame CMasterTemplate::GetGame() +{ + return mGame; +} + +CScriptTemplate* CMasterTemplate::TemplateByID(u32 ObjectID) +{ + auto it = mTemplates.find(ObjectID); + + if (it != mTemplates.end()) + return it->second; + else + return nullptr; +} + +CScriptTemplate* CMasterTemplate::TemplateByID(const CFourCC& ObjectID) +{ + return TemplateByID(ObjectID.ToLong()); +} + +CScriptTemplate* CMasterTemplate::TemplateByIndex(u32 Index) +{ + auto it = mTemplates.begin(); + return (std::next(it, Index))->second; +} + +std::string CMasterTemplate::StateByID(u32 StateID) +{ + auto it = mStates.find(StateID); + + if (it != mStates.end()) + return it->second; + else + return "Invalid"; +} + +std::string CMasterTemplate::StateByID(const CFourCC& State) +{ + return StateByID(State.ToLong()); +} + +std::string CMasterTemplate::StateByIndex(u32 Index) +{ + auto it = mStates.begin(); + return (std::next(it, Index))->second; +} + +std::string CMasterTemplate::MessageByID(u32 MessageID) +{ + auto it = mMessages.find(MessageID); + + if (it != mMessages.end()) + return it->second; + else + return "Invalid"; +} + +std::string CMasterTemplate::MessageByID(const CFourCC& MessageID) +{ + return MessageByID(MessageID.ToLong()); +} + +std::string CMasterTemplate::MessageByIndex(u32 Index) +{ + auto it = mMessages.begin(); + return (std::next(it, Index))->second; +} + +CPropertyTemplate* CMasterTemplate::GetProperty(u32 PropertyID) +{ + auto it = mPropertyList.find(PropertyID); + + if (it != mPropertyList.end()) + return it->second; + else + return nullptr; +} + +bool CMasterTemplate::IsLoadedSuccessfully() +{ + return mFullyLoaded; +} + +// ************ STATIC ************ +std::unordered_map CMasterTemplate::smMasterMap; +u32 CMasterTemplate::smGameListVersion; + +CMasterTemplate* CMasterTemplate::GetMasterForGame(EGame Game) +{ + auto it = smMasterMap.find(Game); + + if (it != smMasterMap.end()) + return it->second; + else + return nullptr; +} diff --git a/Resource/script/CMasterTemplate.h b/Resource/script/CMasterTemplate.h new file mode 100644 index 00000000..2c7b1325 --- /dev/null +++ b/Resource/script/CMasterTemplate.h @@ -0,0 +1,66 @@ +#ifndef CMASTERTEMPLATE_H +#define CMASTERTEMPLATE_H + +#include "CScriptTemplate.h" +#include "CTemplateCategory.h" +#include "../EFormatVersion.h" +#include +#include +#include + +class CMasterTemplate +{ + friend class CTemplateLoader; + + EGame mGame; + std::string mGameName; + u32 mVersion; + bool mFullyLoaded; + + std::unordered_map mTemplates; + std::unordered_map mStates; + std::unordered_map mMessages; + std::vector mCategories; + + bool mHasPropList; + std::unordered_map mPropertyList; + + static std::unordered_map smMasterMap; + static u32 smGameListVersion; + +public: + CMasterTemplate(); + ~CMasterTemplate(); + EGame GetGame(); + u32 NumScriptTemplates(); + u32 NumStates(); + u32 NumMessages(); + CScriptTemplate* TemplateByID(u32 ObjectID); + CScriptTemplate* TemplateByID(const CFourCC& ObjectID); + CScriptTemplate* TemplateByIndex(u32 Index); + std::string StateByID(u32 StateID); + std::string StateByID(const CFourCC& StateID); + std::string StateByIndex(u32 Index); + std::string MessageByID(u32 MessageID); + std::string MessageByID(const CFourCC& MessageID); + std::string MessageByIndex(u32 Index); + CPropertyTemplate* GetProperty(u32 PropertyID); + bool IsLoadedSuccessfully(); + + static CMasterTemplate* GetMasterForGame(EGame Game); +}; + +// ************ INLINE ************ +inline u32 CMasterTemplate::NumScriptTemplates() { + return mTemplates.size(); +} + +inline u32 CMasterTemplate::NumStates() { + return mStates.size(); +} + +inline u32 CMasterTemplate::NumMessages() { + return mMessages.size(); +} + +#endif // CMASTERTEMPLATE_H diff --git a/Resource/script/CProperty.h b/Resource/script/CProperty.h new file mode 100644 index 00000000..322cdb6e --- /dev/null +++ b/Resource/script/CProperty.h @@ -0,0 +1,227 @@ +#ifndef CPROPERTY +#define CPROPERTY + +/* + * This header file declares some classes used to track script object properties + * CPropertyBase, __CProperty (and typedefs), CPropertyStruct + */ +#include "../CResource.h" +#include "CScriptTemplate.h" +#include "EPropertyType.h" +#include +#include +#include +#include +#include + +/* + * CPropertyBase is the base class, containing just some virtual function definitions + * Virtual destructor is mainly there to make cleanup easy; don't need to cast to delete + */ +class CPropertyBase +{ + friend class CScriptLoader; +protected: + CPropertyTemplate *tmp; +public: + virtual ~CPropertyBase() {} + virtual EPropertyType Type() = 0; + CPropertyTemplate *Template() { return tmp; } + void SetTemplate(CPropertyTemplate *_tmp) { tmp = _tmp; } + std::string Name() { return tmp->Name(); } + u32 ID() { return tmp->PropertyID(); } +}; + +/* + * __CProperty is a template subclass for actual properties. + * Don't use this class directly. Typedefs are provided for every possible property type. + */ +template +class __CProperty : public CPropertyBase +{ + friend class CScriptLoader; + t Value; +public: + __CProperty() {} + __CProperty(t v) { Set(v); } + ~__CProperty() {} + EPropertyType Type() { return type; } + t Get() { return Value; } + void Set(t v) { Value = v; } +}; +typedef __CProperty CBoolProperty; +typedef __CProperty CByteProperty; +typedef __CProperty CShortProperty; +typedef __CProperty CLongProperty; +typedef __CProperty CFloatProperty; +typedef __CProperty CStringProperty; +typedef __CProperty CVector3Property; +typedef __CProperty CColorProperty; +typedef __CProperty CFileProperty; +typedef __CProperty, eUnknownProperty> CUnknownProperty; + +/* + * Template specialization for CFileProperty to allow a token for resources + */ +template <> +class __CProperty : public CPropertyBase +{ + CResource *Value; + CToken mToken; + +public: + __CProperty() { + Value = nullptr; + } + + __CProperty(CResource* v) { + Value = v; + mToken = CToken(v); + } + + ~__CProperty() {} + + EPropertyType Type() { return eFileProperty; } + CResource* Get() { return Value; } + void Set(CResource *v) + { + if (Value != v) + { + Value = v; + mToken = CToken(v); + } + } +}; + +/* + * CPropertyStruct is for defining structs of properties. + */ +class CPropertyStruct : public CPropertyBase +{ + friend class CScriptLoader; + std::vector Properties; +public: + // Destructor simply iterates through the list and deletes them. Nothing complicated. + ~CPropertyStruct() + { + for (auto it = Properties.begin(); it != Properties.end(); it++) + delete *it; + } + + EPropertyType Type() { return eStructProperty; } + u32 Count() { return Properties.size(); } + void Reserve(u32 amount) { Properties.reserve(amount); } + CPropertyBase* PropertyByIndex(u32 index) { return Properties[index]; } + CPropertyBase* PropertyByName(std::string name) + { + // Resolve namespace + std::string::size_type NsStart = name.find_first_of("::"); + std::string::size_type PropStart = NsStart + 2; + + // Namespace; the requested property is within a struct + if (NsStart != std::string::npos) + { + std::string StructName = name.substr(0, NsStart); + std::string PropName = name.substr(PropStart, name.length() - PropStart); + + CPropertyStruct *Struct = StructByName(StructName); + if (!Struct) return nullptr; + else return Struct->PropertyByName(PropName); + } + + // No namespace; fetch the property from this struct + else + { + // ID string lookup + if (StringUtil::IsHexString(name)) + return PropertyByID(std::stoul(name, 0, 16)); + + // Name lookup + else + { + for (auto it = Properties.begin(); it != Properties.end(); it++) + { + if ((*it)->Name() == name) + return *it; + } + return nullptr; + } + } + } + CPropertyBase* PropertyByID(u32 ID) + { + for (auto it = Properties.begin(); it != Properties.end(); it++) + { + if ((*it)->ID() == ID) + return *it; + } + return nullptr; + } + CPropertyStruct* StructByIndex(u32 index) + { + CPropertyBase *prop = PropertyByIndex(index); + + if (prop->Type() == eStructProperty) + return static_cast(prop); + else + return nullptr; + } + CPropertyStruct* StructByName(std::string name) + { + CPropertyBase *prop = PropertyByName(name); + + if (prop->Type() == eStructProperty) + return static_cast(prop); + else + return nullptr; + } + CPropertyStruct* StructByID(u32 ID) + { + CPropertyBase *prop = PropertyByID(ID); + + if (prop->Type() == eStructProperty) + return static_cast(prop); + else + return nullptr; + } + inline CPropertyBase* operator[](u32 index) { return Properties[index]; } + + static CPropertyStruct* CopyFromTemplate(CStructTemplate *pTemp) + { + CPropertyStruct *pStruct = new CPropertyStruct(); + pStruct->tmp = pTemp; + pStruct->Reserve(pTemp->Count()); + + for (u32 iProp = 0; iProp < pTemp->Count(); iProp++) + { + CPropertyTemplate *pPropTemp = pTemp->PropertyByIndex(iProp); + CPropertyBase *pProp = nullptr; + + switch (pPropTemp->Type()) + { + case eBoolProperty: pProp = new CBoolProperty(false); break; + case eByteProperty: pProp = new CByteProperty(0); break; + case eShortProperty: pProp = new CShortProperty(0); break; + case eLongProperty: pProp = new CLongProperty(0); break; + case eFloatProperty: pProp = new CFloatProperty(0.f); break; + case eStringProperty: pProp = new CStringProperty(""); break; + case eVector3Property: pProp = new CVector3Property(CVector3f::skZero); break; + case eColorProperty: pProp = new CColorProperty(CColor::skBlack); break; + case eFileProperty: pProp = new CFileProperty(); break; + case eUnknownProperty: pProp = new CUnknownProperty(); break; + case eStructProperty: pProp = CPropertyStruct::CopyFromTemplate(static_cast(pPropTemp)); break; + } + + if (pProp) + { + pProp->SetTemplate(pPropTemp); + pStruct->Properties.push_back(pProp); + } + } + + return pStruct; + } +}; + +#endif // CPROPERTY + diff --git a/Resource/script/CScriptLayer.cpp b/Resource/script/CScriptLayer.cpp new file mode 100644 index 00000000..f9028b00 --- /dev/null +++ b/Resource/script/CScriptLayer.cpp @@ -0,0 +1,95 @@ +#include "CScriptLayer.h" + +CScriptLayer::CScriptLayer() +{ + mLayerName = "New Layer"; + mActive = true; + mVisible = true; +} + +CScriptLayer::~CScriptLayer() +{ + for (auto it = mObjects.begin(); it != mObjects.end(); it++) + delete *it; +} + +// ************* DATA MANIPULATION ************* +void CScriptLayer::AddObject(CScriptObject* object) +{ + mObjects.push_back(object); +} + +void CScriptLayer::DeleteObjectByIndex(u32 index) +{ + delete mObjects[index]; + mObjects.erase(mObjects.begin() + index, mObjects.begin() + index); +} + +void CScriptLayer::DeleteObjectByID(u32 ID) +{ + for (auto it = mObjects.begin(); it != mObjects.end(); it++) + { + if ((*it)->InstanceID() == ID) + { + delete *it; + mObjects.erase(it, it); + break; + } + } +} + +void CScriptLayer::Reserve(u32 amount) +{ + mObjects.reserve(amount); +} + +// ************* GETTERS ************* +std::string CScriptLayer::Name() +{ + return mLayerName; +} + +bool CScriptLayer::IsActive() +{ + return mActive; +} + +bool CScriptLayer::IsVisible() +{ + return mVisible; +} + +u32 CScriptLayer::GetNumObjects() +{ + return mObjects.size(); +} + +CScriptObject* CScriptLayer::ObjectByIndex(u32 index) +{ + return mObjects[index]; +} + +CScriptObject* CScriptLayer::ObjectByID(u32 ID) +{ + for (auto it = mObjects.begin(); it != mObjects.end(); it++) + if ((*it)->InstanceID() == ID) + return *it; + + return nullptr; +} + +// ************* SETTERS ************* +void CScriptLayer::SetName(std::string name) +{ + mLayerName = name; +} + +void CScriptLayer::SetActive(bool active) +{ + mActive = active; +} + +void CScriptLayer::SetVisible(bool visible) +{ + mVisible = visible; +} diff --git a/Resource/script/CScriptLayer.h b/Resource/script/CScriptLayer.h new file mode 100644 index 00000000..9817dbe9 --- /dev/null +++ b/Resource/script/CScriptLayer.h @@ -0,0 +1,48 @@ +#ifndef SSCRIPTLAYER_H +#define SSCRIPTLAYER_H + +#include "CScriptObject.h" +#include +#include +#include + +class CScriptLayer +{ + std::string mLayerName; + bool mActive; + bool mVisible; + std::vector mObjects; +public: + CScriptLayer(); + ~CScriptLayer(); + + // Data Manipulation + void AddObject(CScriptObject* object); + void DeleteObjectByIndex(u32 index); + void DeleteObjectByID(u32 ID); + void Reserve(u32 amount); + + // Getters and Setters + std::string Name(); + bool IsActive(); + bool IsVisible(); + u32 GetNumObjects(); + CScriptObject* ObjectByIndex(u32 index); + CScriptObject* ObjectByID(u32 ID); + + void SetName(std::string name); + void SetActive(bool active); + void SetVisible(bool visible); + + // Operators + CScriptObject* operator[](u32 index); +}; + +// ************* INLINE FUNCTIONS ************* +inline CScriptObject* CScriptLayer::operator[](u32 index) +{ + return mObjects[index]; +} + + +#endif // SSCRIPTLAYER_H diff --git a/Resource/script/CScriptObject.cpp b/Resource/script/CScriptObject.cpp new file mode 100644 index 00000000..996b5127 --- /dev/null +++ b/Resource/script/CScriptObject.cpp @@ -0,0 +1,287 @@ +#include "CScriptObject.h" +#include "../CAnimSet.h" +#include "CMasterTemplate.h" + +CScriptObject::CScriptObject(CGameArea *pArea, CScriptLayer *pLayer, CScriptTemplate *pTemplate) +{ + mpArea = pArea; + mpLayer = pLayer; + mpTemplate = pTemplate; + mpProperties = nullptr; + mAttribFlags = 0; + mpTemplate->AddObject(this); +} + +CScriptObject::~CScriptObject() +{ + if (mpProperties) delete mpProperties; + mpTemplate->RemoveObject(this); +} + +// ************ DATA MANIPULATION ************ +void CScriptObject::EvalutateXForm() +{ + // Reset XForm values to defaults + mPosition = CVector3f(0); + mRotation = CVector3f(0); + mScale = CVector3f(1); + mVolumeSize = CVector3f(0); + mVolumeShape = -1; + + // Look for PRS attribs + for (u32 a = 0; a < mAttribs.size(); a++) + { + if ((mAttribs[a].Type == ePositionAttrib) || + (mAttribs[a].Type == eRotationAttrib) || + (mAttribs[a].Type == eScaleAttrib) || + (mAttribs[a].Type == eVolumeAttrib)) + { + CVector3Property *attrib = static_cast(mAttribs[a].Prop); + + if (mAttribs[a].Type == ePositionAttrib) + mPosition = attrib->Get(); + else if (mAttribs[a].Type == eRotationAttrib) + mRotation = attrib->Get(); + else if (mAttribs[a].Type == eScaleAttrib) + mScale = attrib->Get(); + else if (mAttribs[a].Type == eVolumeAttrib) { + mVolumeSize = attrib->Get(); + mVolumeShape = mAttribs[a].Settings; + } + } + } +} + +void CScriptObject::EvaluateInstanceName() +{ + // Reset instance name to default + mInstanceName = mpTemplate->TemplateName(); + + // Simply look for an instance name - set if we find it + for (u32 a = 0; a < mAttribs.size(); a++) + { + if (mAttribs[a].Type == eNameAttrib) + { + CStringProperty *str = static_cast(mAttribs[a].Prop); + mInstanceName = str->Get(); + return; + } + } +} + +void CScriptObject::EvaluateTevColor() +{ + // Evaluate the TEV color initializer - this is used for beam troopers + mTevColor = CColor::skWhite; // Initialize to white in case there's no vulnerability attrib + + for (u32 a = 0; a < mAttribs.size(); a++) + { + if (mAttribs[a].Type == eVulnerabilityAttrib) + { + CPropertyStruct* vuln = static_cast(mAttribs[a].Prop); + + u32 Power = static_cast(vuln->PropertyByIndex(0))->Get(); + u32 Ice = static_cast(vuln->PropertyByIndex(1))->Get(); + u32 Wave = static_cast(vuln->PropertyByIndex(2))->Get(); + u32 Plasma = static_cast(vuln->PropertyByIndex(3))->Get(); + + if (Plasma != 2) mTevColor = CColor::skRed; + else if (Ice != 2) mTevColor = CColor::skWhite; + else if (Power != 2) mTevColor = CColor::skYellow; + else if (Wave != 2) mTevColor = CColor::skPurple; + else mTevColor = CColor::skWhite; + + break; + } + } +} + +void CScriptObject::EvaluateDisplayModel() +{ + // Look for animset or model + for (u32 a = 0; a < mAttribs.size(); a++) + { + // Evaluate AnimSet attrib + if (mAttribs[a].Type == eAnimSetAttrib) + { + // Get the AnimationParameters struct so we can fetch relevant values from it... + SAttrib *Attrib = &mAttribs[a]; + CPropertyStruct *AnimParams = static_cast(Attrib->Prop); + EGame game = mpTemplate->MasterTemplate()->GetGame(); + + CResource *ANCS; + if (Attrib->Res) + ANCS = Attrib->Res; + else if (game <= eCorruption) + ANCS = static_cast( (*AnimParams)[0] )->Get(); + else + ANCS = static_cast( (*AnimParams)[1] )->Get(); + + if ((ANCS) && (ANCS->Type() == eCharacter)) + { + // Get animset + node index and return the relevant model + CAnimSet *set = static_cast(ANCS); + u32 node; + + if (mpTemplate->MasterTemplate()->GetGame() >= eCorruptionProto) + node = 0; + else if (Attrib->Settings == -1) + node = static_cast( (*AnimParams)[1] )->Get(); + else + node = Attrib->Settings; + + CModel *model = set->getNodeModel(node); + if (model && (model->Type() == eModel)) + { + mpDisplayModel = model; + return; + } + } + } + + // Evaluate Model attrib + else if (mAttribs[a].Type == eModelAttrib) + { + SAttrib *Attrib = &mAttribs[a]; + CResource *CMDL; + + if (Attrib->Res) + CMDL = Attrib->Res; + else + CMDL = static_cast(Attrib->Prop)->Get(); + + if (CMDL && (CMDL->Type() == eModel)) + { + mpDisplayModel = static_cast(CMDL); + return; + } + } + } + + // No valid display asset + mpDisplayModel = nullptr; + return; +} + +// ************ GETTERS ************ +CPropertyBase* CScriptObject::PropertyByIndex(u32 index) +{ + return mpProperties->PropertyByIndex(index); +} + +CPropertyBase* CScriptObject::PropertyByName(std::string name) +{ + return mpProperties->PropertyByName(name); +} + +CScriptTemplate* CScriptObject::Template() +{ + return mpTemplate; +} + +CMasterTemplate* CScriptObject::MasterTemplate() +{ + return mpTemplate->MasterTemplate(); +} + +CGameArea* CScriptObject::Area() +{ + return mpArea; +} + +CScriptLayer* CScriptObject::Layer() +{ + return mpLayer; +} + +CPropertyStruct* CScriptObject::Properties() +{ + return mpProperties; +} + +u32 CScriptObject::ObjectTypeID() const +{ + return mpTemplate->ObjectID(); +} + +u32 CScriptObject::InstanceID() const +{ + return mInstanceID; +} + +u32 CScriptObject::NumInLinks() const +{ + return mInConnections.size(); +} + +u32 CScriptObject::NumOutLinks() const +{ + return mOutConnections.size(); +} + +const SLink& CScriptObject::InLink(u32 index) const +{ + return mInConnections[index]; +} + +const SLink& CScriptObject::OutLink(u32 index) const +{ + return mOutConnections[index]; +} + +// Attribs +CVector3f CScriptObject::GetPosition() const +{ + return mPosition; +} + +CVector3f CScriptObject::GetRotation() const +{ + return mRotation; +} + +CVector3f CScriptObject::GetScale() const +{ + return mScale; +} + +CVector3f CScriptObject::GetVolume() const +{ + return mVolumeSize; +} + +u32 CScriptObject::GetVolumeShape() const +{ + return mVolumeShape; +} + +std::string CScriptObject::GetInstanceName() const +{ + return mInstanceName; +} + +CColor CScriptObject::GetTevColor() const +{ + return mTevColor; +} + +CModel* CScriptObject::GetDisplayModel() const +{ + return mpDisplayModel; +} + +int CScriptObject::GetAttribFlags() const +{ + return mAttribFlags; +} + +// ************ STATIC ************ +CScriptObject* CScriptObject::CopyFromTemplate(CScriptTemplate *pTemp, CGameArea *pArea, CScriptLayer *pLayer) +{ + CScriptObject *pObj = new CScriptObject(pArea, pLayer, pTemp); + + CStructTemplate *pBaseStruct = pTemp->BaseStruct(); + pObj->mpProperties = CPropertyStruct::CopyFromTemplate(pBaseStruct); + + return pObj; +} diff --git a/Resource/script/CScriptObject.h b/Resource/script/CScriptObject.h new file mode 100644 index 00000000..22be2623 --- /dev/null +++ b/Resource/script/CScriptObject.h @@ -0,0 +1,93 @@ +#ifndef CSCRIPTOBJECT_H +#define CSCRIPTOBJECT_H + +#include "SConnection.h" +#include "CProperty.h" +#include "CScriptTemplate.h" +#include "EAttribType.h" +#include "../model/CModel.h" + +class CGameArea; +class CScriptLayer; + +class CScriptObject +{ + friend class CScriptLoader; + friend class CAreaLoader; + + CScriptTemplate *mpTemplate; + CGameArea *mpArea; + CScriptLayer *mpLayer; + + u32 mInstanceID; + std::vector mOutConnections; + std::vector mInConnections; + CPropertyStruct *mpProperties; + + CVector3f mPosition, mRotation, mScale; + CVector3f mVolumeSize; + u32 mVolumeShape; + std::string mInstanceName; + CColor mTevColor; + CModel* mpDisplayModel; + + struct SAttrib + { + EAttribType Type; + u32 Settings; + CResource *Res; + CToken ResToken; + CPropertyBase *Prop; + + // Convenience constructor + SAttrib(EAttribType type, CResource *res, u32 settings, CPropertyBase *prop) { + Type = type; + Res = res; + ResToken = CToken(res); + Settings = settings; + Prop = prop; + } + }; + std::vector mAttribs; + + int mAttribFlags; // int container for EAttribType flags + +public: + CScriptObject(CGameArea *pArea, CScriptLayer *pLayer, CScriptTemplate *pTemplate); + ~CScriptObject(); + + void EvaluateDisplayModel(); + void EvaluateInstanceName(); + void EvaluateTevColor(); + void EvalutateXForm(); + + CScriptTemplate* Template(); + CMasterTemplate* MasterTemplate(); + CGameArea* Area(); + CScriptLayer* Layer(); + CPropertyStruct* Properties(); + u32 ObjectTypeID() const; + u32 InstanceID() const; + u32 NumInLinks() const; + u32 NumOutLinks() const; + const SLink& InLink(u32 index) const; + const SLink& OutLink(u32 index) const; + + CPropertyBase* PropertyByIndex(u32 index); + CPropertyBase* PropertyByName(std::string name); + + CVector3f GetPosition() const; + CVector3f GetRotation() const; + CVector3f GetScale() const; + CVector3f GetVolume() const; + u32 GetVolumeShape() const; + std::string GetInstanceName() const; + CColor GetTevColor() const; + CModel* GetDisplayModel() const; + int GetAttribFlags() const; + + // Static + static CScriptObject* CopyFromTemplate(CScriptTemplate *pTemp, CGameArea *pArea, CScriptLayer *pLayer); +}; + +#endif // CSCRIPTOBJECT_H diff --git a/Resource/script/CScriptTemplate.cpp b/Resource/script/CScriptTemplate.cpp new file mode 100644 index 00000000..f077290c --- /dev/null +++ b/Resource/script/CScriptTemplate.cpp @@ -0,0 +1,316 @@ +#include "CScriptTemplate.h" +#include "CScriptObject.h" +#include "CMasterTemplate.h" +#include +#include +#include +#include + +EPropertyType PropStringToPropEnum(std::string prop) +{ + if (prop == "bool") return eBoolProperty; + if (prop == "byte") return eByteProperty; + if (prop == "short") return eShortProperty; + if (prop == "long") return eLongProperty; + if (prop == "float") return eFloatProperty; + if (prop == "string") return eStringProperty; + if (prop == "color") return eColorProperty; + if (prop == "vector3f") return eVector3Property; + if (prop == "file") return eFileProperty; + if (prop == "struct") return eStructProperty; + if (prop == "unknown") return eUnknownProperty; + return eInvalidProperty; +} + +std::string PropEnumToPropString(EPropertyType prop) +{ + switch (prop) + { + case eBoolProperty: return "bool"; + case eByteProperty: return "byte"; + case eShortProperty: return "short"; + case eLongProperty: return "long"; + case eFloatProperty: return "float"; + case eStringProperty: return "string"; + case eColorProperty: return "color"; + case eVector3Property: return "vector3f"; + case eFileProperty: return "file"; + case eStructProperty: return "struct"; + case eUnknownProperty: return "unknown"; + + case eInvalidProperty: + default: + return "invalid"; + } +} + +EAttribType AttribStringToAttribEnum(const std::string& Attrib) +{ + if (Attrib == "name") return eNameAttrib; + if (Attrib == "position") return ePositionAttrib; + if (Attrib == "rotation") return eRotationAttrib; + if (Attrib == "scale") return eScaleAttrib; + if (Attrib == "model") return eModelAttrib; + if (Attrib == "animset") return eAnimSetAttrib; + if (Attrib == "volume") return eVolumeAttrib; + if (Attrib == "vulnerability") return eVulnerabilityAttrib; + return eInvalidAttrib; +} + +std::string AttribEnumToAttribString(EAttribType Attrib) +{ + switch (Attrib) + { + case eNameAttrib: return "name"; + case ePositionAttrib: return "position"; + case eRotationAttrib: return "rotation"; + case eScaleAttrib: return "scale"; + case eModelAttrib: return "model"; + case eAnimSetAttrib: return "animset"; + case eVolumeAttrib: return "volume"; + case eVulnerabilityAttrib: return "vulnerability"; + + case eInvalidAttrib: + default: + return "invalid"; + } +} + +/******************* + * CStructTemplate * + *******************/ +CStructTemplate::CStructTemplate() : CPropertyTemplate(-1) +{ + mIsSingleProperty = false; + mPropertyCount = -1; + mPropType = eStructProperty; +} + +CStructTemplate::~CStructTemplate() +{ + for (auto it = mProperties.begin(); it != mProperties.end(); it++) + delete *it; +} + +// ************ GETTERS ************ +EPropertyType CStructTemplate::Type() const +{ + return eStructProperty; +} + +bool CStructTemplate::IsSingleProperty() const +{ + return mIsSingleProperty; +} + +s32 CStructTemplate::TemplateCount() const +{ + return mPropertyCount; +} + +u32 CStructTemplate::Count() const +{ + return mProperties.size(); +} + +CPropertyTemplate* CStructTemplate::PropertyByIndex(u32 index) +{ + if (mProperties.size() > index) + return mProperties[index]; + else + return nullptr; +} + +CPropertyTemplate* CStructTemplate::PropertyByName(std::string name) +{ + // Resolve namespace + std::string::size_type NsStart = name.find_first_of("::"); + std::string::size_type PropStart = NsStart + 2; + + // Namespace; the requested property is within a struct + if (NsStart != std::string::npos) + { + std::string StructName = name.substr(0, NsStart); + std::string PropName = name.substr(PropStart, name.length() - PropStart); + + CStructTemplate *tmp = StructByName(StructName); + if (!tmp) return nullptr; + else return tmp->PropertyByName(PropName); + } + + // No namespace; fetch the property from this struct + else + { + // ID string lookup + if (StringUtil::IsHexString(name)) + return PropertyByID(std::stoul(name, 0, 16)); + + // Name lookup + else + { + for (auto it = mProperties.begin(); it != mProperties.end(); it++) + { + if ((*it)->Name() == name) + return *it; + } + return nullptr; + } + } +} + +CPropertyTemplate* CStructTemplate::PropertyByID(u32 ID) +{ + for (auto it = mProperties.begin(); it != mProperties.end(); it++) + { + if ((*it)->PropertyID() == ID) + return *it; + } + return nullptr; +} + +CStructTemplate* CStructTemplate::StructByIndex(u32 index) +{ + CPropertyTemplate *prop = PropertyByIndex(index); + + if (prop->Type() == eStructProperty) + return static_cast(prop); + else + return nullptr; +} + +CStructTemplate* CStructTemplate::StructByName(std::string name) +{ + CPropertyTemplate *prop = PropertyByName(name); + + if (prop && prop->Type() == eStructProperty) + return static_cast(prop); + else + return nullptr; +} + +CStructTemplate* CStructTemplate::StructByID(u32 ID) +{ + CPropertyTemplate *prop = PropertyByID(ID); + + if (prop && prop->Type() == eStructProperty) + return static_cast(prop); + else + return nullptr; +} + +// ************ DEBUG ************ +void CStructTemplate::DebugPrintProperties(std::string base) +{ + base = base + Name() + "::"; + for (auto it = mProperties.begin(); it != mProperties.end(); it++) + { + CPropertyTemplate *tmp = *it; + if (tmp->Type() == eStructProperty) + { + CStructTemplate *tmp2 = static_cast(tmp); + tmp2->DebugPrintProperties(base); + } + else + std::cout << base << tmp->Name() << "\n"; + } +} + +/******************* + * CScriptTemplate * + *******************/ +CScriptTemplate::CScriptTemplate(CMasterTemplate *pMaster) +{ + mpBaseStruct = nullptr; + mpMaster = pMaster; + mVisible = true; +} + +CScriptTemplate::~CScriptTemplate() +{ + if (mpBaseStruct) + delete mpBaseStruct; +} + +CMasterTemplate* CScriptTemplate::MasterTemplate() +{ + return mpMaster; +} + +std::string CScriptTemplate::TemplateName() const +{ + return mTemplateName; +} + +CStructTemplate* CScriptTemplate::BaseStruct() +{ + return mpBaseStruct; +} + +u32 CScriptTemplate::AttribCount() const +{ + return mAttribs.size(); +} + +CAttribTemplate* CScriptTemplate::Attrib(u32 index) +{ + if (mAttribs.size() > index) + return &mAttribs[index]; + else + return nullptr; +} + +u32 CScriptTemplate::ObjectID() const +{ + return mObjectID; +} + +u32 CScriptTemplate::NumObjects() const +{ + return mObjectList.size(); +} + +const std::list& CScriptTemplate::ObjectList() const +{ + return mObjectList; +} + +void CScriptTemplate::AddObject(CScriptObject *pObject) +{ + mObjectList.push_back(pObject); +} + +void CScriptTemplate::RemoveObject(CScriptObject *pObject) +{ + for (auto it = mObjectList.begin(); it != mObjectList.end(); it++) + { + if (*it == pObject) + { + mObjectList.erase(it); + break; + } + } +} + +void CScriptTemplate::SortObjects() +{ + // todo: make this function take layer names into account + mObjectList.sort([](CScriptObject *pA, CScriptObject *pB) -> bool { + return (pA->InstanceID() < pB->InstanceID()); + }); +} + +void CScriptTemplate::SetVisible(bool Visible) +{ + mVisible = Visible; +} + +bool CScriptTemplate::IsVisible() +{ + return mVisible; +} + +// Debug function +void CScriptTemplate::DebugPrintProperties() +{ + mpBaseStruct->DebugPrintProperties(""); +} diff --git a/Resource/script/CScriptTemplate.h b/Resource/script/CScriptTemplate.h new file mode 100644 index 00000000..c3239c93 --- /dev/null +++ b/Resource/script/CScriptTemplate.h @@ -0,0 +1,141 @@ +#ifndef CSCRIPTTEMPLATE_H +#define CSCRIPTTEMPLATE_H + +#include "EPropertyType.h" +#include "EAttribType.h" +#include +#include +#include +#include +#include +#include + +class CMasterTemplate; + +/** + * CPropertyTemplate and CStructTemplate each define the layout of a single property/struct. + * The reason they're classes instead of structs is so their internal values can't be externally modified. + * CFileTemplate is a very simple subclass with one extra value - a file extension fourCC + */ +class CPropertyTemplate +{ + friend class CTemplateLoader; + +protected: + EPropertyType mPropType; + std::string mPropName; + u32 mPropID; +public: + CPropertyTemplate(u32 ID) { mPropID = ID; } + CPropertyTemplate(EPropertyType type, std::string name, u32 ID) : mPropType(type), mPropName(name), mPropID(ID) {} + + virtual EPropertyType Type() const { return mPropType; } + inline std::string Name() const { return mPropName; } + inline u32 PropertyID() const { return mPropID; } + inline void SetName(const std::string& Name) { mPropName = Name; } +}; + +class CFileTemplate : public CPropertyTemplate +{ + friend class CTemplateLoader; + + CStringList mAcceptedExtensions; +public: + CFileTemplate(u32 ID) : CPropertyTemplate(ID) { mPropType = eFileProperty; } + + CFileTemplate(std::string name, u32 ID, const CStringList& extensions) + : CPropertyTemplate(ID) { + mPropType = eFileProperty; mPropName = name; mAcceptedExtensions = extensions; + } + + EPropertyType Type() const { return eFileProperty; } + const CStringList& Extensions() const { return mAcceptedExtensions; } +}; + +class CStructTemplate : public CPropertyTemplate +{ + friend class CTemplateLoader; + + bool mIsSingleProperty; + s32 mPropertyCount; + std::vector mProperties; +public: + CStructTemplate(); + ~CStructTemplate(); + + EPropertyType Type() const; + bool IsSingleProperty() const; + s32 TemplateCount() const; + u32 Count() const; + CPropertyTemplate* PropertyByIndex(u32 index); + CPropertyTemplate* PropertyByName(std::string name); + CPropertyTemplate* PropertyByID(u32 ID); + CStructTemplate* StructByIndex(u32 index); + CStructTemplate* StructByName(std::string name); + CStructTemplate* StructByID(u32 ID); + void DebugPrintProperties(std::string base); +}; + +/** + * CAttribTemplate defines editor attributes. + * They enable PWE to access and use object properties for use in the world editor. + */ +class CAttribTemplate +{ + friend class CTemplateLoader; + + EAttribType AttribType; + std::string AttribTarget; + std::string ResFile; + u32 ExtraSettings; +public: + CAttribTemplate() {} + EAttribType Type() const { return AttribType; } + std::string Target() const { return AttribTarget; } + std::string Resource() const { return ResFile; } + u32 Settings() const { return ExtraSettings; } +}; + +/** + * CScriptTemplate is a class that encases the data contained in one of the XML templates. + * It essentially sets the layout of any given script object. + * + * It contains any data that applies globally to every instance of the object, such as + * property names, editor attribute properties, etc. + */ +class CScriptObject; + +class CScriptTemplate +{ + friend class CTemplateLoader; + + CMasterTemplate *mpMaster; + CStructTemplate *mpBaseStruct; + std::list mObjectList; + std::string mTemplateName; + std::vector mAttribs; + u32 mObjectID; + bool mVisible; + +public: + CScriptTemplate(CMasterTemplate *pMaster); + ~CScriptTemplate(); + + CMasterTemplate* MasterTemplate(); + std::string TemplateName() const; + CStructTemplate* BaseStruct(); + u32 AttribCount() const; + CAttribTemplate* Attrib(u32 index); + u32 ObjectID() const; + u32 NumObjects() const; + const std::list& ObjectList() const; + void AddObject(CScriptObject *pObject); + void RemoveObject(CScriptObject *pObject); + void SortObjects(); + void SetVisible(bool Visible); + bool IsVisible(); + + void DebugPrintProperties(); +}; + +#endif // CSCRIPTTEMPLATE_H diff --git a/Resource/script/CTemplateCategory.h b/Resource/script/CTemplateCategory.h new file mode 100644 index 00000000..e9fcfd72 --- /dev/null +++ b/Resource/script/CTemplateCategory.h @@ -0,0 +1,46 @@ +#ifndef CTEMPLATECATEGORY_H +#define CTEMPLATECATEGORY_H + +#include "CScriptTemplate.h" +#include + +class CTemplateCategory +{ + std::string mCategoryName; + std::vector mTemplates; + +public: + CTemplateCategory() {} + + inline CTemplateCategory(const std::string& Name) { + SetName(Name); + } + + inline void SetName(const std::string& Name) { + mCategoryName = Name; + } + + inline void AddTemplate(CScriptTemplate *pTmp) { + mTemplates.push_back(pTmp); + } + + inline void Sort() { + std::sort(mTemplates.begin(), mTemplates.end(), [](CScriptTemplate* pA, CScriptTemplate* pB) -> bool { + return (pA->TemplateName() < pB->TemplateName()); + }); + } + + inline u32 NumTemplates() { + return mTemplates.size(); + } + + inline CScriptTemplate* GetTemplate(u32 index) { + return mTemplates[index]; + } + + inline CScriptTemplate* operator[](u32 index) { + return mTemplates[index]; + } +}; + +#endif // CTEMPLATECATEGORY_H diff --git a/Resource/script/EAttribType.h b/Resource/script/EAttribType.h new file mode 100644 index 00000000..5f1bc011 --- /dev/null +++ b/Resource/script/EAttribType.h @@ -0,0 +1,26 @@ +#ifndef EATTRIBTYPE +#define EATTRIBTYPE + +#include +#include + +enum EAttribType +{ + eNameAttrib = 0x1, + ePositionAttrib = 0x2, + eRotationAttrib = 0x4, + eScaleAttrib = 0x8, + eModelAttrib = 0x10, + eAnimSetAttrib = 0x20, + eVolumeAttrib = 0x40, + eVulnerabilityAttrib = 0x80, + eInvalidAttrib = 0x80000000 +}; +DEFINE_ENUM_FLAGS(EAttribType) + +// functions defined in CScriptTemplate.cpp +EAttribType AttribStringToAttribEnum(const std::string& Attrib); +std::string AttribEnumToAttribString(EAttribType Attrib); + +#endif // EATTRIBTYPE + diff --git a/Resource/script/EObjectType.h b/Resource/script/EObjectType.h new file mode 100644 index 00000000..0e51c81a --- /dev/null +++ b/Resource/script/EObjectType.h @@ -0,0 +1,130 @@ +#ifndef EOBJECTTYPE_H +#define EOBJECTTYPE_H + +// dunno if this is actually needed, but here it is. +enum EObjectType +{ + Actor = 0x0, + Waypoint = 0x2, + DoorArea = 0x3, + Trigger = 0x4, + Timer = 0x5, + Counter = 0x6, + Effect = 0x7, + Platform = 0x8, + Sound = 0x9, + Generator = 0xA, + Dock = 0xB, + Camera = 0xC, + CameraWaypoint = 0xD, + NewIntroBoss = 0xE, + SpawnPoint = 0xF, + CameraHint = 0x10, + Pickup = 0x11, + MemoryRelay = 0x13, + RandomRelay = 0x14, + Relay = 0x15, + Beetle = 0x16, + HUDMemo = 0x17, + CameraFilterKeyframe = 0x18, + CameraBlurKeyframe = 0x19, + DamageableTrigger = 0x1A, + Debris = 0x1B, + CameraShaker = 0x1C, + ActorKeyFrame = 0x1D, + Water = 0x20, + Warwasp = 0x21, + SpacePirate = 0x24, + FlyingPirate = 0x25, + ElitePirate = 0x26, + MetroidBeta = 0x27, + ChozoGhost = 0x28, + CoverPoint = 0x2A, + SpiderBallWaypoint = 0x2C, + BloodFlower = 0x2D, + FlickerBat = 0x2E, + PathCamera = 0x2F, + GrapplePoint = 0x30, + PuddleSpore = 0x31, + SpiderBallAttractionSurface = 0x33, + PuddleToadGamma = 0x34, + Fog = 0x35, + FireFlea = 0x36, + MetareeAlpha = 0x37, + ActorRotate = 0x39, + SpecialFunction = 0x3A, + SpankWeed = 0x3B, + Zoomer = 0x3D, + PlayerHint = 0x3E, + Ripper = 0x3F, + PickupGenerator = 0x40, + PointOfInterest = 0x42, + Drone = 0x43, + MetroidAlpha = 0x44, + DebrisExtended = 0x45, + Steam = 0x46, + Ripple = 0x47, + BallTrigger = 0x48, + TargetingPoint = 0x49, + ElectroMagneticPulse = 0x4A, + IceSheegoth = 0x4B, + PlayerActor = 0x4C, + Flaahgra = 0x4D, + AreaAttributes = 0x4E, + FishCloud = 0x4F, + FishCloudModifier = 0x50, + VisorFlare = 0x51, + VisorGoo = 0x53, + JellyZap = 0x54, + ControllerAction = 0x55, + Switch = 0x56, + PlayerStateChange = 0x57, + Thardus = 0x58, + WallCrawlerSwarm = 0x5A, + AIJumpPoint = 0x5B, + FlaahgraTentacle = 0x5C, + RoomAcoustics = 0x5D, + ColorModulate = 0x5E, + ThardusRockProjectile = 0x5F, + Midi = 0x60, + StreamedAudio = 0x61, + WorldTeleporter = 0x62, + Repulsor = 0x63, + GunTurret = 0x64, + Babygoth = 0x66, + Eyeball = 0x67, + RadialKnockback = 0x68, + CameraPitchVolume = 0x69, + EnvFxDensityController = 0x6A, + Magdolite = 0x6B, + TeamAIMgr = 0x6C, + SnakeWeedSwarm = 0x6D, + ActorContraption = 0x6E, + Oculus = 0x6F, + Geemer = 0x70, + SpindleCamera = 0x71, + AtomicAlpha = 0x72, + CameraHintTrigger = 0x73, + RumbleEffect = 0x74, + AmbientAI = 0x75, + AtomicBeta = 0x77, + Puffer = 0x79, + Tryclops = 0x7A, + Ridley = 0x7B, + Seedling = 0x7C, + ThermalHeatFader = 0x7D, + Burrower = 0x7F, + ScriptBeam = 0x81, + WorldLightFader = 0x82, + MetroidPrimeStage2 = 0x83, + MetroidPrimeRelay = 0x84, + MazeNode = 0x85, + OmegaPirate = 0x86, + PhazonPool = 0x87, + PhazonHealingNodule = 0x88, + NewCameraShaker = 0x89, + ShadowProjector = 0x8A, + BeamEnergyBall = 0x8B +}; + +#endif // EOBJECTTYPE_H diff --git a/Resource/script/EPropertyType.h b/Resource/script/EPropertyType.h new file mode 100644 index 00000000..4424a211 --- /dev/null +++ b/Resource/script/EPropertyType.h @@ -0,0 +1,28 @@ +#ifndef EPROPERTYTYPE +#define EPROPERTYTYPE + +#include + +enum EPropertyType +{ + eBoolProperty, + eByteProperty, + eShortProperty, + eLongProperty, + eFloatProperty, + eStringProperty, + eVector3Property, + eColorProperty, + eEnumProperty, + eFileProperty, + eStructProperty, + eUnknownProperty, + eInvalidProperty +}; + +// functions defined in CScriptTemplate.cpp +EPropertyType PropStringToPropEnum(std::string prop); +std::string PropEnumToPropString(EPropertyType prop); + +#endif // EPROPERTYTYPE + diff --git a/Resource/script/SConnection.h b/Resource/script/SConnection.h new file mode 100644 index 00000000..7f3e4676 --- /dev/null +++ b/Resource/script/SConnection.h @@ -0,0 +1,13 @@ +#ifndef SCONNECTION_H +#define SCONNECTION_H + +#include + +struct SLink +{ + u32 State; + u32 Message; + u32 ObjectID; // not a pointer because it can refer to objects outside the current area +}; + +#endif // SCONNECTION_H diff --git a/Scene/CBoundingBoxNode.cpp b/Scene/CBoundingBoxNode.cpp new file mode 100644 index 00000000..47f467cf --- /dev/null +++ b/Scene/CBoundingBoxNode.cpp @@ -0,0 +1,12 @@ +#include "CBoundingBoxNode.h" + +CBoundingBoxNode::CBoundingBoxNode() +{ + +} + +CBoundingBoxNode::~CBoundingBoxNode() +{ + +} + diff --git a/Scene/CBoundingBoxNode.h b/Scene/CBoundingBoxNode.h new file mode 100644 index 00000000..ab28107c --- /dev/null +++ b/Scene/CBoundingBoxNode.h @@ -0,0 +1,16 @@ +#ifndef CBOUNDINGBOXNODE_H +#define CBOUNDINGBOXNODE_H + +#include "CSceneNode.h" + +class CBoundingBoxNode +{ + CColor mColor; + +public: + CBoundingBoxNode(); + ~CBoundingBoxNode(); + +}; + +#endif // CBOUNDINGBOXNODE_H diff --git a/Scene/CCollisionNode.cpp b/Scene/CCollisionNode.cpp new file mode 100644 index 00000000..8b7dd638 --- /dev/null +++ b/Scene/CCollisionNode.cpp @@ -0,0 +1,58 @@ +#include "CCollisionNode.h" +#include +#include +#include + +CCollisionNode::CCollisionNode(CSceneManager *pScene, CSceneNode *pParent, CCollisionMesh *pMesh) + : CSceneNode(pScene, pParent) +{ + mpMesh = pMesh; + mMeshToken = CToken(pMesh); + SetName("Collision"); +} + +ENodeType CCollisionNode::NodeType() +{ + return eCollisionNode; +} + +void CCollisionNode::AddToRenderer(CRenderer *pRenderer) +{ + if (!mpMesh) return; + + pRenderer->AddOpaqueMesh(this, 0, AABox(), eDrawMesh); + + if (mSelected) + pRenderer->AddOpaqueMesh(this, 0, AABox(), eDrawSelection); +} + +void CCollisionNode::Draw(ERenderOptions) +{ + // Not using parameter 1 (ERenderOptions - Options) + if (!mpMesh) return; + + LoadModelMatrix(); + + glBlendFunc(GL_ONE, GL_ZERO); + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + glDepthMask(GL_TRUE); + + CDrawUtil::UseCollisionShader(); + mpMesh->Draw(); + CDrawUtil::UseColorShader(CColor::skTransparentBlack); + mpMesh->DrawLines(); +} + +void CCollisionNode::DrawAsset(ERenderOptions, u32) +{ + // Not using parameter 1 (ERenderOptions - Options) + // Not using parameter 2 (u32 - asset) +} + +SRayIntersection CCollisionNode::RayNodeIntersectTest(const CRay &Ray, u32 AssetID) +{ + // todo + SRayIntersection Result; + Result.Hit = false; + return Result; +} diff --git a/Scene/CCollisionNode.h b/Scene/CCollisionNode.h new file mode 100644 index 00000000..c0bec6fd --- /dev/null +++ b/Scene/CCollisionNode.h @@ -0,0 +1,21 @@ +#ifndef CCOLLISIONNODE_H +#define CCOLLISIONNODE_H + +#include "CSceneNode.h" +#include + +class CCollisionNode : public CSceneNode +{ + CCollisionMesh *mpMesh; + CToken mMeshToken; + +public: + CCollisionNode(CSceneManager *pScene, CSceneNode *pParent = 0, CCollisionMesh *pMesh = 0); + ENodeType NodeType(); + void AddToRenderer(CRenderer *pRenderer); + void Draw(ERenderOptions Options); + void DrawAsset(ERenderOptions Options, u32 asset); + SRayIntersection RayNodeIntersectTest(const CRay &Ray, u32 AssetID); +}; + +#endif // CCOLLISIONNODE_H diff --git a/Scene/CLightNode.cpp b/Scene/CLightNode.cpp new file mode 100644 index 00000000..b5fe9bb7 --- /dev/null +++ b/Scene/CLightNode.cpp @@ -0,0 +1,78 @@ +#include "CLightNode.h" +#include +#include +#include + +CLightNode::CLightNode(CSceneManager *pScene, CSceneNode *pParent, CLight *Light) + : CSceneNode(pScene, pParent) +{ + mpLight = Light; + mLocalAABox = CAABox::skOne; + mPosition = Light->GetPosition(); + + switch (Light->GetType()) + { + case eLocalAmbient: SetName("Ambient Light"); break; + case eDirectional: SetName("Directional Light"); break; + case eSpot: SetName("Spot Light"); break; + case eCustom: SetName("Custom Light"); break; + } +} + +ENodeType CLightNode::NodeType() +{ + return eLightNode; +} + +void CLightNode::AddToRenderer(CRenderer *pRenderer) +{ + pRenderer->AddOpaqueMesh(this, 0, CAABox(mPosition + 0.5f, mPosition - 0.5f), eDrawMesh); + + if (IsSelected()) + pRenderer->AddOpaqueMesh(this, 0, AABox(), eDrawSelection); +} + +void CLightNode::Draw(ERenderOptions) +{ + // Not using parameter 1 (ERenderOptions - Options) + glBlendFunc(GL_ONE, GL_ZERO); + glDepthMask(GL_TRUE); + LoadModelMatrix(); + CGraphics::SetDefaultLighting(); + CGraphics::UpdateLightBlock(); + CGraphics::sVertexBlock.COLOR0_Amb = CVector4f(1.f); + CGraphics::sVertexBlock.COLOR0_Mat = CVector4f(1.f); + CGraphics::UpdateVertexBlock(); + CDrawUtil::DrawShadedCube(mpLight->GetColor()); + + // Below commented-out code is for light radius visualization as a bounding box + /*float r = mLight->GetRadius(); + CAABox AABB(Position + r, Position - r); + pRenderer->DrawBoundingBox(mLight->GetColor(), AABB);*/ +} + +void CLightNode::DrawAsset(ERenderOptions, u32) +{ + // Not using parameter 1 (ERenderOptions - Options) + // Not using parameter 2 (u32 - asset) +} + +SRayIntersection CLightNode::RayNodeIntersectTest(const CRay &Ray, u32 AssetID) +{ + // Needs redo if I ever make these look like something other than boxes + if (AABox().IsPointInBox(Ray.Origin())) + return SRayIntersection(false, 0.f, nullptr, 0); + + std::pair BoxResult = AABox().IntersectsRay(Ray); + + if (BoxResult.first) + return SRayIntersection(true, BoxResult.second, this, 0); + + else + return SRayIntersection(false, 0.f, nullptr, 0); +} + +CLight* CLightNode::Light() +{ + return mpLight; +} diff --git a/Scene/CLightNode.h b/Scene/CLightNode.h new file mode 100644 index 00000000..22585d25 --- /dev/null +++ b/Scene/CLightNode.h @@ -0,0 +1,20 @@ +#ifndef CLIGHTNODE_H +#define CLIGHTNODE_H + +#include "CSceneNode.h" +#include + +class CLightNode : public CSceneNode +{ + CLight *mpLight; +public: + CLightNode(CSceneManager *pScene, CSceneNode *pParent = 0, CLight *Light = 0); + ENodeType NodeType(); + void AddToRenderer(CRenderer *pRenderer); + void Draw(ERenderOptions Options); + void DrawAsset(ERenderOptions Options, u32 asset); + SRayIntersection RayNodeIntersectTest(const CRay &Ray, u32 AssetID); + CLight* Light(); +}; + +#endif // CLIGHTNODE_H diff --git a/Scene/CModelNode.cpp b/Scene/CModelNode.cpp new file mode 100644 index 00000000..536e1bc2 --- /dev/null +++ b/Scene/CModelNode.cpp @@ -0,0 +1,148 @@ +#include "CModelNode.h" +#include +#include +#include + +CModelNode::CModelNode(CSceneManager *pScene, CSceneNode *pParent, CModel *pModel) : CSceneNode(pScene, pParent) +{ + SetModel(pModel); + mScale = CVector3f(1.f); + mLightingEnabled = true; + mForceAlphaOn = false; +} + +ENodeType CModelNode::NodeType() +{ + return eModelNode; +} + +void CModelNode::AddToRenderer(CRenderer *pRenderer) +{ + if (!mpModel) return; + + if (!mpModel->HasTransparency(mActiveMatSet)) + pRenderer->AddOpaqueMesh(this, 0, AABox(), eDrawMesh); + + else + { + u32 SurfaceCount = mpModel->GetSurfaceCount(); + + for (u32 iSurf = 0; iSurf < SurfaceCount; iSurf++) + { + if (!mpModel->IsSurfaceTransparent(iSurf, mActiveMatSet)) + pRenderer->AddOpaqueMesh(this, iSurf, mpModel->GetSurfaceAABox(iSurf).Transformed(Transform()), eDrawAsset); + else + pRenderer->AddTransparentMesh(this, iSurf, mpModel->GetSurfaceAABox(iSurf).Transformed(Transform()), eDrawAsset); + } + } + + if (mSelected) + pRenderer->AddOpaqueMesh(this, 0, AABox(), eDrawSelection); +} + +void CModelNode::Draw(ERenderOptions Options) +{ + if (!mpModel) return; + if (mForceAlphaOn) Options = (ERenderOptions) (Options & ~eNoAlpha); + + if (mLightingEnabled) + { + CGraphics::SetDefaultLighting(); + CGraphics::UpdateLightBlock(); + CGraphics::sVertexBlock.COLOR0_Amb = CGraphics::skDefaultAmbientColor.ToVector4f(); + } + else + { + CGraphics::sNumLights = 0; + CGraphics::sVertexBlock.COLOR0_Amb = CColor::skBlack.ToVector4f(); + } + + CGraphics::sPixelBlock.TevColor = CVector4f(1,1,1,1); + LoadModelMatrix(); + + mpModel->Draw(Options, mActiveMatSet); +} + +void CModelNode::DrawAsset(ERenderOptions Options, u32 Asset) +{ + if (!mpModel) return; + if (mForceAlphaOn) Options = (ERenderOptions) (Options & ~eNoAlpha); + + if (mLightingEnabled) + { + CGraphics::SetDefaultLighting(); + CGraphics::UpdateLightBlock(); + CGraphics::sVertexBlock.COLOR0_Amb = CGraphics::skDefaultAmbientColor.ToVector4f(); + } + else + { + CGraphics::sNumLights = 0; + CGraphics::sVertexBlock.COLOR0_Amb = CColor::skBlack.ToVector4f(); + } + + CGraphics::sPixelBlock.TevColor = CVector4f(1,1,1,1); + LoadModelMatrix(); + + mpModel->DrawSurface(Options, Asset, mActiveMatSet); +} + +void CModelNode::RayAABoxIntersectTest(CRayCollisionTester &Tester) +{ + if (!mpModel) return; + + const CRay& Ray = Tester.Ray(); + std::pair BoxResult = AABox().IntersectsRay(Ray); + + if (BoxResult.first) + { + for (u32 iSurf = 0; iSurf < mpModel->GetSurfaceCount(); iSurf++) + { + std::pair SurfResult = mpModel->GetSurfaceAABox(iSurf).IntersectsRay(Ray); + + if (SurfResult.first) + Tester.AddNode(this, iSurf, SurfResult.second); + } + } +} + +SRayIntersection CModelNode::RayNodeIntersectTest(const CRay &Ray, u32 AssetID) +{ + SRayIntersection out; + out.pNode = this; + out.AssetIndex = AssetID; + + CRay TransformedRay = Ray.Transformed(Transform().Inverse()); + std::pair Result = mpModel->GetSurface(AssetID)->IntersectsRay(TransformedRay, Transform()); + + if (Result.first) + { + out.Hit = true; + + CVector3f HitPoint = TransformedRay.PointOnRay(Result.second); + CVector3f WorldHitPoint = Transform() * HitPoint; + out.Distance = Math::Distance(Ray.Origin(), WorldHitPoint); + } + + else + out.Hit = false; + + return out; +} + +void CModelNode::SetModel(CModel *pModel) +{ + mpModel = pModel; + mModelToken = CToken(pModel); + mActiveMatSet = 0; + + if (pModel) + { + SetName(pModel->Source()); + mLocalAABox = mpModel->AABox(); + } +} + +void CModelNode::ForceAlphaEnabled(bool Enable) +{ + mForceAlphaOn = Enable; +} diff --git a/Scene/CModelNode.h b/Scene/CModelNode.h new file mode 100644 index 00000000..f0689849 --- /dev/null +++ b/Scene/CModelNode.h @@ -0,0 +1,61 @@ +#ifndef CMODELNODE_H +#define CMODELNODE_H + +#include "CSceneNode.h" +#include + +class CModelNode : public CSceneNode +{ + CModel *mpModel; + CToken mModelToken; + u32 mActiveMatSet; + bool mLightingEnabled; + bool mForceAlphaOn; + +public: + explicit CModelNode(CSceneManager *pScene, CSceneNode *pParent = 0, CModel *pModel = 0); + + virtual ENodeType NodeType(); + virtual void AddToRenderer(CRenderer *pRenderer); + virtual void Draw(ERenderOptions Options); + virtual void DrawAsset(ERenderOptions Options, u32 asset); + virtual void RayAABoxIntersectTest(CRayCollisionTester &Tester); + virtual SRayIntersection RayNodeIntersectTest(const CRay &Ray, u32 AssetID); + + void SetModel(CModel *pModel); + void SetMatSet(u32 MatSet); + void SetDynamicLighting(bool Enable); + void ForceAlphaEnabled(bool Enable); + CModel* Model(); + u32 MatSet(); + bool IsDynamicLightingEnabled(); +}; + + +// ************ INLINE FUNCTIONS ************ +inline void CModelNode::SetMatSet(u32 MatSet) +{ + mActiveMatSet = MatSet; +} + +inline void CModelNode::SetDynamicLighting(bool Enable) +{ + mLightingEnabled = Enable; +} + +inline CModel* CModelNode::Model() +{ + return mpModel; +} + +inline u32 CModelNode::MatSet() +{ + return mActiveMatSet; +} + +inline bool CModelNode::IsDynamicLightingEnabled() +{ + return mLightingEnabled; +} + +#endif // CMODELNODE_H diff --git a/Scene/CRootNode.h b/Scene/CRootNode.h new file mode 100644 index 00000000..49c14b7e --- /dev/null +++ b/Scene/CRootNode.h @@ -0,0 +1,30 @@ +#ifndef CROOTNODE_H +#define CROOTNODE_H + +#include "CSceneNode.h" +#include + +// CRootNode's main purpose is to manage groups of other nodes as its children. +class CRootNode : public CSceneNode +{ +public: + explicit CRootNode(CSceneManager *pScene, CSceneNode *pParent = 0) : CSceneNode(pScene, pParent) {} + ~CRootNode() {} + + inline ENodeType NodeType() { + return eRootNode; + } + + inline void AddToRenderer(CRenderer *) {} + inline void Draw(ERenderOptions) {} + inline void DrawAsset(ERenderOptions, u32) {} + inline void RayAABoxIntersectTest(CRayCollisionTester &) {} + + inline SRayIntersection RayNodeIntersectTest(const CRay &, u32) { + return SRayIntersection(false, 0.f, nullptr, 0); + } + + inline void DrawSelection() {} +}; + +#endif // CROOTNODE_H diff --git a/Scene/CSceneNode.cpp b/Scene/CSceneNode.cpp new file mode 100644 index 00000000..86ba0ec5 --- /dev/null +++ b/Scene/CSceneNode.cpp @@ -0,0 +1,372 @@ +#include "CSceneNode.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +u32 CSceneNode::smNumNodes = 0; + +CSceneNode::CSceneNode(CSceneManager *pScene, CSceneNode *pParent) +{ + smNumNodes++; + mpScene = pScene; + mpParent = pParent; + + mPosition = CVector3f::skZero; + mRotation = CQuaternion::skIdentity; + mScale = CVector3f::skOne; + _mTransformOutdated = true; + + _mInheritsPosition = true; + _mInheritsRotation = true; + _mInheritsScale = true; + + mMouseHovering = false; + mSelected = false; + mVisible = true; + + if (mpParent) + mpParent->mChildren.push_back(this); +} + +CSceneNode::~CSceneNode() +{ + smNumNodes--; + for (auto it = mChildren.begin(); it != mChildren.end(); it++) + delete (*it); +} + +// ************ VIRTUAL ************ +std::string CSceneNode::PrefixedName() const +{ + return Name(); +} + +void CSceneNode::DrawSelection() +{ + // Default implementation for virtual function + CDrawUtil::DrawWireCube(AABox(), CColor::skWhite); +} + +void CSceneNode::RayAABoxIntersectTest(CRayCollisionTester& Tester) +{ + // Default implementation for virtual function + std::pair result = AABox().IntersectsRay(Tester.Ray()); + + if (result.first) + Tester.AddNode(this, -1, result.second); +} + +bool CSceneNode::IsVisible() const +{ + return mVisible; +} + +// ************ MAIN FUNCTIONALITY ************ +void CSceneNode::Unparent() +{ + // May eventually want to reset XForm so global position = local position + // Seems like a waste performance wise for the time being though + if (mpParent) + mpParent->RemoveChild(this); + + mpParent = nullptr; +} + +void CSceneNode::RemoveChild(CSceneNode *pChild) +{ + for (auto it = mChildren.begin(); it != mChildren.end(); it++) + { + if (*it == pChild) + { + mChildren.erase(it); + break; + } + } +} + +void CSceneNode::DeleteChildren() +{ + for (auto it = mChildren.begin(); it != mChildren.end(); it++) + delete *it; + + mChildren.clear(); +} + +void CSceneNode::SetInheritance(bool InheritPos, bool InheritRot, bool InheritScale) +{ + _mInheritsPosition = InheritPos; + _mInheritsRotation = InheritRot; + _mInheritsScale = InheritScale; + MarkTransformChanged(); +} + +void CSceneNode::LoadModelMatrix() +{ + CGraphics::sMVPBlock.ModelMatrix = Transform().ToMatrix4f(); + CGraphics::UpdateMVPBlock(); +} + +void CSceneNode::BuildLightList(CGameArea *pArea) +{ + mLightCount = 0; + + u32 LayerCount = pArea->GetLightLayerCount(); + + struct SLightEntry { + CLight *pLight; + float Distance; + + SLightEntry(CLight *_pLight, float _Distance) + : pLight(_pLight), Distance(_Distance) {} + + bool operator<(const SLightEntry& other) { + return (Distance < other.Distance); + } + }; + std::vector LightEntries; + + for (u32 iLayer = 0; iLayer < LayerCount; iLayer++) + { + u32 LightCount = pArea->GetLightCount(iLayer); + + for (u32 iLight = 0; iLight < LightCount; iLight++) + { + CLight* pLight = pArea->GetLight(iLayer, iLight); + + // Directional/Spot lights take priority over custom lights. + if ((pLight->GetType() == eDirectional) || (pLight->GetType() == eSpot)) + LightEntries.push_back(SLightEntry(pLight, 0)); + + // Custom lights will be used depending which are closest to the node + else if (pLight->GetType() == eCustom) + { + bool IsInRange = AABox().IntersectsSphere(pLight->GetPosition(), pLight->GetRadius()); + + if (IsInRange) + { + float Dist = mPosition.Distance(pLight->GetPosition()); + LightEntries.push_back(SLightEntry(pLight, Dist)); + } + } + } + } + + // Determine which lights are closest + std::sort(LightEntries.begin(), LightEntries.end()); + mLightCount = (LightEntries.size() > 8) ? 8 : LightEntries.size(); + + for (u32 i = 0; i < mLightCount; i++) + mLights[i] = LightEntries[i].pLight; +} + +void CSceneNode::LoadLights() +{ + CGraphics::sNumLights = 0; + + if (CGraphics::sLightMode == CGraphics::BasicLighting) + CGraphics::SetDefaultLighting(); + + else if (CGraphics::sLightMode == CGraphics::WorldLighting) + for (u32 iLight = 0; iLight < mLightCount; iLight++) + mLights[iLight]->Load(); + + CGraphics::UpdateLightBlock(); +} + +void CSceneNode::DrawBoundingBox() +{ + CDrawUtil::DrawWireCube(AABox(), CColor::skWhite); +} + +// ************ TRANSFORM ************ +void CSceneNode::Translate(const CVector3f& Translation) +{ + mPosition += Translation; + MarkTransformChanged(); +} + +void CSceneNode::Scale(const CVector3f& Scale) +{ + mScale *= Scale; + MarkTransformChanged(); +} + +void CSceneNode::UpdateTransform() +{ + if (_mTransformOutdated) + { + ForceRecalculateTransform(); + _mTransformOutdated = false; + } +} + +void CSceneNode::ForceRecalculateTransform() +{ + _mCachedTransform = CTransform4f::skIdentity; + _mCachedTransform.Scale(GetAbsoluteScale()); + _mCachedTransform.Rotate(GetAbsoluteRotation()); + _mCachedTransform.Translate(GetAbsolutePosition()); + _mCachedAABox = mLocalAABox.Transformed(_mCachedTransform); + + // Sync with children - only needed if caller hasn't marked transform changed already + // If so, the children will already be marked + if (!_mTransformOutdated) + { + for (auto it = mChildren.begin(); it != mChildren.end(); it++) + (*it)->MarkTransformChanged(); + } + _mTransformOutdated = false; +} + +void CSceneNode::MarkTransformChanged() +{ + if (!_mTransformOutdated) + { + for (auto it = mChildren.begin(); it != mChildren.end(); it++) + (*it)->MarkTransformChanged(); + } + + _mTransformOutdated = true; +} + +const CTransform4f& CSceneNode::Transform() +{ + if (_mTransformOutdated) + ForceRecalculateTransform(); + + return _mCachedTransform; +} + +// ************ GETTERS ************ +std::string CSceneNode::Name() const +{ + return mName; +} + +CSceneNode* CSceneNode::Parent() const +{ + return mpParent; +} + +CSceneManager* CSceneNode::Scene() +{ + return mpScene; +} + +CVector3f CSceneNode::GetPosition() const +{ + return mPosition; +} + +CVector3f CSceneNode::GetAbsolutePosition() const +{ + CVector3f ret = mPosition; + + if ((mpParent) && (InheritsPosition())) + ret += mpParent->GetAbsolutePosition(); + + return ret; +} + +CQuaternion CSceneNode::GetRotation() const +{ + return mRotation; +} + +CQuaternion CSceneNode::GetAbsoluteRotation() const +{ + CQuaternion ret = mRotation; + + if ((mpParent) && (InheritsRotation())) + ret *= mpParent->GetAbsoluteRotation(); + + return ret; +} + +CVector3f CSceneNode::GetScale() const +{ + return mScale; +} + +CVector3f CSceneNode::GetAbsoluteScale() const +{ + CVector3f ret = mScale; + + if ((mpParent) && (InheritsScale())) + ret *= mpParent->GetAbsoluteScale(); + + return ret; +} + +CAABox CSceneNode::AABox() +{ + if (_mTransformOutdated) + ForceRecalculateTransform(); + + return _mCachedAABox; +} + +CVector3f CSceneNode::CenterPoint() +{ + return AABox().Center(); +} + +bool CSceneNode::MarkedVisible() const +{ + // The reason I have this function is because the instance view needs to know whether a node is marked + // visible independently from other factors that may affect node visibility (as returned by IsVisible()). + // It's a little confusing, so maybe there's a better way to set this up. + return mVisible; +} + +bool CSceneNode::IsMouseHovering() const +{ + return mMouseHovering; +} + +bool CSceneNode::IsSelected() const +{ + return mSelected; +} + +bool CSceneNode::InheritsPosition() const +{ + return _mInheritsPosition; +} + +bool CSceneNode::InheritsRotation() const +{ + return _mInheritsRotation; +} + +bool CSceneNode::InheritsScale() const +{ + return _mInheritsScale; +} + +// ************ SETTERS ************ +void CSceneNode::SetName(const std::string& Name) +{ + mName = Name; +} + +void CSceneNode::SetMouseHovering(bool Hovering) +{ + mMouseHovering = Hovering; +} + +void CSceneNode::SetSelected(bool Selected) +{ + mSelected = Selected; +} + +void CSceneNode::SetVisible(bool Visible) +{ + mVisible = Visible; +} diff --git a/Scene/CSceneNode.h b/Scene/CSceneNode.h new file mode 100644 index 00000000..82464e8f --- /dev/null +++ b/Scene/CSceneNode.h @@ -0,0 +1,113 @@ +#ifndef CSCENENODE_H +#define CSCENENODE_H + +#include "ENodeType.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class CRenderer; +class CSceneManager; + +class CSceneNode +{ +private: + CTransform4f _mCachedTransform; + CAABox _mCachedAABox; + bool _mTransformOutdated; + + bool _mInheritsPosition; + bool _mInheritsRotation; + bool _mInheritsScale; + +protected: + static u32 smNumNodes; + std::string mName; + CSceneNode *mpParent; + CSceneManager *mpScene; + + CVector3f mPosition; + CQuaternion mRotation; + CVector3f mScale; + CAABox mLocalAABox; + bool mMouseHovering; + bool mSelected; + bool mVisible; + std::list mChildren; + + u32 mLightCount; + CLight* mLights[8]; + +public: + explicit CSceneNode(CSceneManager *pScene, CSceneNode *pParent = 0); + virtual ~CSceneNode(); + virtual ENodeType NodeType() = 0; + virtual std::string PrefixedName() const; + virtual void AddToRenderer(CRenderer *pRenderer) = 0; + virtual void Draw(ERenderOptions Options) = 0; + virtual void DrawAsset(ERenderOptions Options, u32 Asset) = 0; + virtual void DrawSelection(); + virtual void RayAABoxIntersectTest(CRayCollisionTester& Tester); + virtual SRayIntersection RayNodeIntersectTest(const CRay& Ray, u32 AssetID) = 0; + virtual bool IsVisible() const; + + void Unparent(); + void RemoveChild(CSceneNode *pChild); + void DeleteChildren(); + void SetInheritance(bool InheritPos, bool InheritRot, bool InheritScale); + void LoadModelMatrix(); + void BuildLightList(CGameArea *pArea); + void LoadLights(); + void DrawBoundingBox(); + + // Transform + void Translate(const CVector3f& Translation); + void Scale(const CVector3f& Scale); + void UpdateTransform(); + void ForceRecalculateTransform(); + void MarkTransformChanged(); + const CTransform4f& Transform(); + + // Getters + std::string Name() const; + CSceneNode* Parent() const; + CSceneManager* Scene(); + CVector3f GetPosition() const; + CVector3f GetAbsolutePosition() const; + CQuaternion GetRotation() const; + CQuaternion GetAbsoluteRotation() const; + CVector3f GetScale() const; + CVector3f GetAbsoluteScale() const; + CAABox AABox(); + CVector3f CenterPoint(); + bool MarkedVisible() const; + bool IsMouseHovering() const; + bool IsSelected() const; + bool InheritsPosition() const; + bool InheritsRotation() const; + bool InheritsScale() const; + + // Setters + void SetName(const std::string& Name); + void SetMouseHovering(bool Hovering); + void SetSelected(bool Selected); + void SetVisible(bool Visible); + + // Static + static int NumNodes(); +}; + +// ************ INLINE FUNCTIONS ************ +inline int CSceneNode::NumNodes() +{ + return smNumNodes; +} + +#endif // CSCENENODE_H diff --git a/Scene/CScriptNode.cpp b/Scene/CScriptNode.cpp new file mode 100644 index 00000000..57e76d6b --- /dev/null +++ b/Scene/CScriptNode.cpp @@ -0,0 +1,320 @@ +#include "CScriptNode.h" +#include +#include +#include +#include +#include +#include +#include +#include + +CScriptNode::CScriptNode(CSceneManager *pScene, CSceneNode *pParent, CScriptObject *pObject) + : CSceneNode(pScene, pParent) +{ + mpVolumePreviewNode = nullptr; + + // Evaluate instance + mpInstance = pObject; + mpActiveModel = nullptr; + + if (mpInstance) + { + mpActiveModel = mpInstance->GetDisplayModel(); + mPosition = mpInstance->GetPosition(); + mRotation = CQuaternion::FromEuler(mpInstance->GetRotation()); + mScale = mpInstance->GetScale(); + SetName("[" + mpInstance->Template()->TemplateName() + "] " + mpInstance->GetInstanceName()); + MarkTransformChanged(); + + mHasValidPosition = ((mpInstance->GetAttribFlags() & ePositionAttrib) != 0); + mHasVolumePreview = ((mpInstance->GetAttribFlags() & eVolumeAttrib) != 0); + + // Create volume preview node + if (mHasVolumePreview) + { + u32 VolumeShape = mpInstance->GetVolumeShape(); + CModel *pVolumeModel = nullptr; + + if ((VolumeShape == 0) || (VolumeShape == 1)) // Box/OrientedBox + pVolumeModel = (CModel*) gResCache.GetResource("../resources/VolumeBox.cmdl"); + + else if (VolumeShape == 2) // Sphere + pVolumeModel = (CModel*) gResCache.GetResource("../resources/VolumeSphere.cmdl"); + + if (pVolumeModel) + { + mpVolumePreviewNode = new CModelNode(pScene, this, pVolumeModel); + mpVolumePreviewNode->SetInheritance(true, (VolumeShape == 1), false); + mpVolumePreviewNode->Scale(mpInstance->GetVolume()); + mpVolumePreviewNode->ForceAlphaEnabled(true); + } + } + } + + else + { + // Shouldn't ever happen + SetName("ScriptNode - NO INSTANCE"); + } + + if (mpActiveModel) + mLocalAABox = mpActiveModel->AABox(); + else + mLocalAABox = CAABox::skOne; +} + +ENodeType CScriptNode::NodeType() +{ + return eScriptNode; +} + +std::string CScriptNode::PrefixedName() const +{ + return "[" + mpInstance->Template()->TemplateName() + "] " + mpInstance->GetInstanceName(); +} + +void CScriptNode::AddToRenderer(CRenderer *pRenderer) +{ + if (!mpInstance) return; + + if (!mpActiveModel) + pRenderer->AddOpaqueMesh(this, 0, AABox(), eDrawMesh); + + else + { + if (!mpActiveModel->IsBuffered()) + mpActiveModel->BufferGL(); + + if (!mpActiveModel->HasTransparency(0)) + pRenderer->AddOpaqueMesh(this, 0, AABox(), eDrawMesh); + + else + { + u32 SubmeshCount = mpActiveModel->GetSurfaceCount(); + + for (u32 s = 0; s < SubmeshCount; s++) + { + if (!mpActiveModel->IsSurfaceTransparent(s, 0)) + pRenderer->AddOpaqueMesh(this, s, mpActiveModel->GetSurfaceAABox(s).Transformed(Transform()), eDrawAsset); + else + pRenderer->AddTransparentMesh(this, s, mpActiveModel->GetSurfaceAABox(s).Transformed(Transform()), eDrawAsset); + } + } + } + + if (IsSelected()) + { + pRenderer->AddOpaqueMesh(this, 0, AABox(), eDrawSelection); + + if (mHasVolumePreview) + mpVolumePreviewNode->AddToRenderer(pRenderer); + } +} + +void CScriptNode::Draw(ERenderOptions Options) +{ + if (!mpInstance) return; + + if (!mpActiveModel) + { + glBlendFunc(GL_ONE, GL_ZERO); + glDepthMask(GL_TRUE); + + LoadModelMatrix(); + CGraphics::SetDefaultLighting(); + CGraphics::UpdateLightBlock(); + CDrawUtil::DrawShadedCube(CColor::skTransparentPurple); + return; + } + + if (CGraphics::sLightMode == CGraphics::WorldLighting) + CGraphics::sVertexBlock.COLOR0_Amb = CGraphics::sAreaAmbientColor.ToVector4f() * CGraphics::sWorldLightMultiplier; + else + CGraphics::sVertexBlock.COLOR0_Amb = CGraphics::skDefaultAmbientColor.ToVector4f(); + + LoadModelMatrix(); + LoadLights(); + + CGraphics::sPixelBlock.TevColor = mpInstance->GetTevColor().ToVector4f(); + + mpActiveModel->Draw(Options, 0); +} + +void CScriptNode::DrawAsset(ERenderOptions Options, u32 Asset) +{ + if (!mpInstance) return; + if (!mpActiveModel) return; + + if (CGraphics::sLightMode == CGraphics::WorldLighting) + CGraphics::sVertexBlock.COLOR0_Amb = CGraphics::sAreaAmbientColor.ToVector4f() * CGraphics::sWorldLightMultiplier; + else + CGraphics::sVertexBlock.COLOR0_Amb = CGraphics::skDefaultAmbientColor.ToVector4f(); + + LoadModelMatrix(); + LoadLights(); + + CGraphics::sPixelBlock.TevColor = mpInstance->GetTevColor().ToVector4f(); + + mpActiveModel->DrawSurface(Options, Asset, 0); +} + +void CScriptNode::DrawSelection() +{ + CDrawUtil::DrawWireCube(AABox(), CColor::skTransparentWhite); + + if (mpInstance) + { + for (u32 iIn = 0; iIn < mpInstance->NumInLinks(); iIn++) + { + const SLink& con = mpInstance->InLink(iIn); + CScriptNode *pLinkNode = mpScene->ScriptNodeByID(con.ObjectID); + if (pLinkNode) CDrawUtil::DrawLine(CenterPoint(), pLinkNode->CenterPoint(), CColor::skTransparentRed); + } + + for (u32 iOut = 0; iOut < mpInstance->NumOutLinks(); iOut++) + { + const SLink& con = mpInstance->OutLink(iOut); + CScriptNode *pLinkNode = mpScene->ScriptNodeByID(con.ObjectID); + if (pLinkNode) CDrawUtil::DrawLine(CenterPoint(), pLinkNode->CenterPoint(), CColor::skTransparentGreen); + } + } +} + +void CScriptNode::RayAABoxIntersectTest(CRayCollisionTester &Tester) +{ + if (!mpInstance) + return; + + const CRay& Ray = Tester.Ray(); + std::pair BoxResult = AABox().IntersectsRay(Ray); + + if (BoxResult.first) + { + if (mpActiveModel) + { + for (u32 iSurf = 0; iSurf < mpActiveModel->GetSurfaceCount(); iSurf++) + { + std::pair SurfResult = mpActiveModel->GetSurfaceAABox(iSurf).Transformed(Transform()).IntersectsRay(Ray); + + if (SurfResult.first) + Tester.AddNode(this, iSurf, SurfResult.second); + } + } + else Tester.AddNode(this, 0, BoxResult.second); + } +} + +SRayIntersection CScriptNode::RayNodeIntersectTest(const CRay &Ray, u32 AssetID) +{ + SRayIntersection out; + out.pNode = this; + out.AssetIndex = AssetID; + + CRay TransformedRay = Ray.Transformed(Transform().Inverse()); + CModel *pModel = (mpActiveModel ? mpActiveModel : CDrawUtil::GetCubeModel()); + std::pair Result = pModel->GetSurface(AssetID)->IntersectsRay(TransformedRay, Transform()); + + if (Result.first) + { + out.Hit = true; + + CVector3f HitPoint = TransformedRay.PointOnRay(Result.second); + CVector3f WorldHitPoint = Transform() * HitPoint; + out.Distance = Math::Distance(Ray.Origin(), WorldHitPoint); + } + + else + out.Hit = false; + + return out; +} + +bool CScriptNode::IsVisible() const +{ + // Reimplementation of CSceneNode::IsHidden() to allow for layer and template visiblity to be taken into account + return (mVisible && mpInstance->Layer()->IsVisible() && mpInstance->Template()->IsVisible()); +} + +CScriptObject* CScriptNode::Object() +{ + return mpInstance; +} + +CModel* CScriptNode::ActiveModel() +{ + return mpActiveModel; +} + +void CScriptNode::GeneratePosition() +{ + if (!mHasValidPosition) + { + // Default to center of the active area; this is to preven recursion issues + CTransform4f& AreaTransform = mpScene->GetActiveArea()->GetTransform(); + mPosition = CVector3f(AreaTransform[0][3], AreaTransform[1][3], AreaTransform[2][3]); + mHasValidPosition = true; + MarkTransformChanged(); + + // Ideal way to generate the position is to find a spot close to where it's being used. + // To do this I check the location of the objects that this one is linked to. + u32 NumLinks = mpInstance->NumInLinks() + mpInstance->NumOutLinks(); + + // In the case of one link, apply an offset so the new position isn't the same place as the object it's linked to + if (NumLinks == 1) + { + const SLink& link = (mpInstance->NumInLinks() > 0 ? mpInstance->InLink(0) : mpInstance->OutLink(0)); + CScriptNode *pNode = mpScene->ScriptNodeByID(link.ObjectID); + pNode->GeneratePosition(); + mPosition = pNode->GetAbsolutePosition(); + mPosition.z += (pNode->AABox().GetSize().z / 2.f); + mPosition.z += (AABox().GetSize().z / 2.f); + mPosition.z += 2.f; + } + + // For two or more links, average out the position of the connected objects. + else if (NumLinks >= 2) + { + CVector3f NewPos = CVector3f::skZero; + + for (u32 iIn = 0; iIn < mpInstance->NumInLinks(); iIn++) + { + CScriptNode *pNode = mpScene->ScriptNodeByID(mpInstance->InLink(iIn).ObjectID); + + if (pNode) + { + pNode->GeneratePosition(); + NewPos += pNode->AABox().Center(); + } + } + + for (u32 iOut = 0; iOut < mpInstance->NumOutLinks(); iOut++) + { + CScriptNode *pNode = mpScene->ScriptNodeByID(mpInstance->OutLink(iOut).ObjectID); + + if (pNode) + { + pNode->GeneratePosition(); + NewPos += pNode->AABox().Center(); + } + } + + mPosition = NewPos / NumLinks; + mPosition.x += 2.f; + } + + MarkTransformChanged(); + } +} + +bool CScriptNode::HasPreviewVolume() +{ + return mHasVolumePreview; +} + +CAABox CScriptNode::PreviewVolumeAABox() +{ + if (!mHasVolumePreview) + return CAABox::skZero; + else + return mpVolumePreviewNode->AABox(); +} diff --git a/Scene/CScriptNode.h b/Scene/CScriptNode.h new file mode 100644 index 00000000..9ea376c3 --- /dev/null +++ b/Scene/CScriptNode.h @@ -0,0 +1,35 @@ +#ifndef CSCRIPTNODE_H +#define CSCRIPTNODE_H + +#include "CSceneNode.h" +#include "CModelNode.h" +#include + +class CScriptNode : public CSceneNode +{ + CScriptObject *mpInstance; + CModel *mpActiveModel; + + bool mHasValidPosition; + bool mHasVolumePreview; + CModelNode *mpVolumePreviewNode; + +public: + CScriptNode(CSceneManager *pScene, CSceneNode *pParent = 0, CScriptObject *pObject = 0); + ENodeType NodeType(); + std::string PrefixedName() const; + void AddToRenderer(CRenderer *pRenderer); + void Draw(ERenderOptions Options); + void DrawAsset(ERenderOptions Options, u32 Asset); + void DrawSelection(); + void RayAABoxIntersectTest(CRayCollisionTester &Tester); + SRayIntersection RayNodeIntersectTest(const CRay &Ray, u32 AssetID); + bool IsVisible() const; + CScriptObject* Object(); + CModel* ActiveModel(); + void GeneratePosition(); + bool HasPreviewVolume(); + CAABox PreviewVolumeAABox(); +}; + +#endif // CSCRIPTNODE_H diff --git a/Scene/CStaticNode.cpp b/Scene/CStaticNode.cpp new file mode 100644 index 00000000..e0675b8f --- /dev/null +++ b/Scene/CStaticNode.cpp @@ -0,0 +1,113 @@ +#include "CStaticNode.h" +#include +#include +#include +#include +#include + +CStaticNode::CStaticNode(CSceneManager *pScene, CSceneNode *pParent, CStaticModel *pModel) + : CSceneNode(pScene, pParent) +{ + mpModel = pModel; + mLocalAABox = mpModel->AABox(); + mScale = CVector3f(1.f, 1.f, 1.f); + SetName("Static Node"); +} + +ENodeType CStaticNode::NodeType() +{ + return eStaticNode; +} + +void CStaticNode::AddToRenderer(CRenderer *pRenderer) +{ + if (!mpModel) return; + if (mpModel->IsOccluder()) return; + + if (!mpModel->IsTransparent()) + pRenderer->AddOpaqueMesh(this, 0, AABox(), eDrawMesh); + + else + { + u32 sm_count = mpModel->GetSurfaceCount(); + for (u32 s = 0; s < sm_count; s++) + { + pRenderer->AddTransparentMesh(this, s, mpModel->GetSurfaceAABox(s).Transformed(Transform()), eDrawAsset); + } + } + + if (mSelected) + pRenderer->AddOpaqueMesh(this, 0, AABox(), eDrawSelection); +} + +void CStaticNode::Draw(ERenderOptions Options) +{ + if (!mpModel) return; + + CGraphics::sVertexBlock.COLOR0_Amb = CVector4f(0, 0, 0, 1); + float Multiplier = CGraphics::sWorldLightMultiplier; + CGraphics::sPixelBlock.TevColor = CVector4f(Multiplier,Multiplier,Multiplier,1); + CGraphics::sNumLights = 0; + CGraphics::UpdateLightBlock(); + LoadModelMatrix(); + + mpModel->Draw(Options); +} + +void CStaticNode::DrawAsset(ERenderOptions Options, u32 Asset) +{ + if (!mpModel) return; + + CGraphics::sVertexBlock.COLOR0_Amb = CVector4f(0,0,0,1); + CGraphics::sPixelBlock.TevColor = CVector4f(1,1,1,1); + CGraphics::sNumLights = 0; + CGraphics::UpdateLightBlock(); + LoadModelMatrix(); + + mpModel->DrawSurface(Options, Asset); + //CDrawUtil::DrawWireCube(mpModel->GetSurfaceAABox(Asset), CColor::skWhite); +} + +void CStaticNode::RayAABoxIntersectTest(CRayCollisionTester &Tester) +{ + if ((!mpModel) || (mpModel->IsOccluder())) + return; + + const CRay& Ray = Tester.Ray(); + std::pair BoxResult = AABox().IntersectsRay(Ray); + + if (BoxResult.first) + { + for (u32 iSurf = 0; iSurf < mpModel->GetSurfaceCount(); iSurf++) + { + std::pair SurfResult = mpModel->GetSurfaceAABox(iSurf).Transformed(Transform()).IntersectsRay(Ray); + + if (SurfResult.first) + Tester.AddNode(this, iSurf, SurfResult.second); + } + } +} + +SRayIntersection CStaticNode::RayNodeIntersectTest(const CRay &Ray, u32 AssetID) +{ + SRayIntersection out; + out.pNode = this; + out.AssetIndex = AssetID; + + CRay TransformedRay = Ray.Transformed(Transform().Inverse()); + std::pair Result = mpModel->GetSurface(AssetID)->IntersectsRay(TransformedRay, Transform()); + + if (Result.first) + { + out.Hit = true; + + CVector3f HitPoint = TransformedRay.PointOnRay(Result.second); + CVector3f WorldHitPoint = Transform() * HitPoint; + out.Distance = Math::Distance(Ray.Origin(), WorldHitPoint); + } + + else + out.Hit = false; + + return out; +} diff --git a/Scene/CStaticNode.h b/Scene/CStaticNode.h new file mode 100644 index 00000000..057d22cc --- /dev/null +++ b/Scene/CStaticNode.h @@ -0,0 +1,21 @@ +#ifndef CSTATICNODE_H +#define CSTATICNODE_H + +#include "CSceneNode.h" +#include + +class CStaticNode : public CSceneNode +{ + CStaticModel *mpModel; + +public: + CStaticNode(CSceneManager *pScene, CSceneNode *pParent = 0, CStaticModel *pModel = 0); + ENodeType NodeType(); + void AddToRenderer(CRenderer *pRenderer); + void Draw(ERenderOptions Options); + void DrawAsset(ERenderOptions Options, u32 asset); + void RayAABoxIntersectTest(CRayCollisionTester &Tester); + SRayIntersection RayNodeIntersectTest(const CRay &Ray, u32 AssetID); +}; + +#endif // CSTATICNODE_H diff --git a/Scene/ENodeType.h b/Scene/ENodeType.h new file mode 100644 index 00000000..6a2677f7 --- /dev/null +++ b/Scene/ENodeType.h @@ -0,0 +1,15 @@ +#ifndef ENODETYPE +#define ENODETYPE + +enum ENodeType +{ + eRootNode, + eModelNode, + eStaticNode, + eCollisionNode, + eScriptNode, + eLightNode +}; + +#endif // ENODETYPE + diff --git a/UI/CDarkStyle.cpp b/UI/CDarkStyle.cpp new file mode 100644 index 00000000..c800a513 --- /dev/null +++ b/UI/CDarkStyle.cpp @@ -0,0 +1,5 @@ +#include "CDarkStyle.h" + +CDarkStyle::CDarkStyle() +{ +} diff --git a/UI/CDarkStyle.h b/UI/CDarkStyle.h new file mode 100644 index 00000000..cbb1619c --- /dev/null +++ b/UI/CDarkStyle.h @@ -0,0 +1,14 @@ +#ifndef CDARKSTYLE_H +#define CDARKSTYLE_H + +#include + +class CDarkStyle : public QProxyStyle +{ + Q_OBJECT + +public: + CDarkStyle(); +}; + +#endif // CDARKSTYLE_H diff --git a/UI/CEditorGLWidget.cpp b/UI/CEditorGLWidget.cpp new file mode 100644 index 00000000..58853861 --- /dev/null +++ b/UI/CEditorGLWidget.cpp @@ -0,0 +1,247 @@ +#include +#include +#include +#include +#include +#include +#include "CEditorGLWidget.h" +#include +#include +#include +#include +#include +#include +#include +#include + +QTimer CEditorGLWidget::sRefreshTimer; + +CEditorGLWidget::CEditorGLWidget(QWidget *pParent) : + QOpenGLWidget(pParent) +{ + setMouseTracking(true); + mLastDrawTime = CTimer::GlobalTime(); + mKeysPressed = 0; + mButtonsPressed = 0; + mCursorState = Qt::ArrowCursor; + mCursorVisible = true; + mCamera.SetAspectRatio((float) width() / height()); + + connect(&sRefreshTimer, SIGNAL(timeout()), this, SLOT(update())); + + if (!sRefreshTimer.isActive()) + sRefreshTimer.start(0); +} + +CEditorGLWidget::~CEditorGLWidget() +{ +} + +void CEditorGLWidget::initializeGL() +{ + // Initialize CGraphics + CGraphics::Initialize(); + + // Setting various GL flags + glEnable(GL_DEPTH_TEST); + glEnable(GL_PRIMITIVE_RESTART); + glPrimitiveRestartIndex(0xFFFF); + glDepthFunc(GL_LEQUAL); + glEnable(GL_BLEND); + glEnable(GL_POLYGON_OFFSET_FILL); + glPolygonOffset(1.f, 5.f); + + // Clear cached material + CMaterial::KillCachedMaterial(); + CShader::KillCachedShader(); + + // Initialize renderer + emit ViewportResized(width(), height()); +} + +void CEditorGLWidget::paintGL() +{ + double DeltaTime = CTimer::GlobalTime() - mLastDrawTime; + mLastDrawTime = CTimer::GlobalTime(); + + // Camera movement is processed here in order to sync it with the paint event + // This way movement happens exactly once per frame - no more, no less + ProcessInput(DeltaTime); + mCamera.LoadMatrices(); + + // Pre-render signal allows for per-frame operations to be performed before the draw happens + emit PreRender(); + + // We emit a signal to indicate it's time to render the viewport instead of doing the rendering here. + // This allows the editor GL widget class to be reused among multiple editors with different rendering needs. + emit Render(mCamera); + + // Post-render signal allows for the frame to be completed with post-processing + emit PostRender(); +} + +void CEditorGLWidget::resizeGL(int w, int h) +{ + mCamera.SetAspectRatio((float) w / h); + glViewport(0, 0, w, h); + emit ViewportResized(w, h); +} + +void CEditorGLWidget::mouseMoveEvent(QMouseEvent *pEvent) +{ + if ((!IsMouseInputActive()) && (mButtonsPressed & eLeftButton)) + emit MouseDrag(pEvent); +} + +void CEditorGLWidget::mousePressEvent(QMouseEvent *pEvent) +{ + setFocus(); + + if (pEvent->button() == Qt::MidButton) mButtonsPressed |= eMiddleButton; + if (pEvent->button() == Qt::RightButton) mButtonsPressed |= eRightButton; + + if (IsMouseInputActive()) + SetCursorVisible(false); + + // Left click only activates if mouse input is inactive to prevent the user from + // clicking on things and creating selection rectangles while the cursor is hidden + else if (pEvent->button() == Qt::LeftButton) + mButtonsPressed |= eLeftButton; + + mLastMousePos = pEvent->globalPos(); +} + +void CEditorGLWidget::mouseReleaseEvent(QMouseEvent *pEvent) +{ + if (pEvent->button() == Qt::LeftButton) mButtonsPressed &= ~eLeftButton; + if (pEvent->button() == Qt::MidButton) mButtonsPressed &= ~eMiddleButton; + if (pEvent->button() == Qt::RightButton) mButtonsPressed &= ~eRightButton; + + // Make cursor visible and emit mouse click event if middle/right mouse buttons are both released + if (!IsMouseInputActive()) + { + SetCursorVisible(true); + emit MouseClick(pEvent); + } +} + +void CEditorGLWidget::keyPressEvent(QKeyEvent *pEvent) +{ + switch (pEvent->key()) + { + case Qt::Key_Q: mKeysPressed |= eQKey; break; + case Qt::Key_W: mKeysPressed |= eWKey; break; + case Qt::Key_E: mKeysPressed |= eEKey; break; + case Qt::Key_A: mKeysPressed |= eAKey; break; + case Qt::Key_S: mKeysPressed |= eSKey; break; + case Qt::Key_D: mKeysPressed |= eDKey; break; + case Qt::Key_Control: mKeysPressed |= eCtrlKey; break; + } +} + +void CEditorGLWidget::keyReleaseEvent(QKeyEvent *pEvent) +{ + switch (pEvent->key()) + { + case Qt::Key_Q: mKeysPressed &= ~eQKey; break; + case Qt::Key_W: mKeysPressed &= ~eWKey; break; + case Qt::Key_E: mKeysPressed &= ~eEKey; break; + case Qt::Key_A: mKeysPressed &= ~eAKey; break; + case Qt::Key_S: mKeysPressed &= ~eSKey; break; + case Qt::Key_D: mKeysPressed &= ~eDKey; break; + case Qt::Key_Control: mKeysPressed &= ~eCtrlKey; break; + } +} + +void CEditorGLWidget::wheelEvent(QWheelEvent *pEvent) +{ + // Maybe track a "wheel delta" member variable and let CCamera decide what to do with it? + mCamera.Zoom(pEvent->angleDelta().y() / 6000.f); +} + +void CEditorGLWidget::focusOutEvent(QFocusEvent*) +{ + // When the widget loses focus, release all input. + mButtonsPressed = 0; + mKeysPressed = 0; + SetCursorVisible(true); +} + +void CEditorGLWidget::SetCursorState(const QCursor &Cursor) +{ + mCursorState = Cursor; + + if (IsCursorVisible()) + setCursor(Cursor); +} + +void CEditorGLWidget::SetCursorVisible(bool visible) +{ + mCursorVisible = visible; + + if (visible) + setCursor(mCursorState); + else + setCursor(Qt::BlankCursor); +} + +bool CEditorGLWidget::IsCursorVisible() +{ + return mCursorVisible; +} + +bool CEditorGLWidget::IsMouseInputActive() +{ + static const int skMoveButtons = eMiddleButton | eRightButton; + return ((mButtonsPressed & skMoveButtons) != 0); +} + +bool CEditorGLWidget::IsKeyboardInputActive() +{ + static const int skMoveKeys = eQKey | eWKey | eEKey | eAKey | eSKey | eDKey; + return ((mKeysPressed & skMoveKeys) != 0); +} + +CCamera& CEditorGLWidget::Camera() +{ + return mCamera; +} + +CRay CEditorGLWidget::CastRay() +{ + CVector2f MouseCoords = MouseDeviceCoordinates(); + return mCamera.CastRay(MouseCoords); +} + +CVector2f CEditorGLWidget::MouseDeviceCoordinates() +{ + QPoint MousePos = QCursor::pos(); + QPoint ThisPos = this->mapToGlobal(pos()); + MousePos -= ThisPos; + + CVector2f Device( + (((2.f * MousePos.x()) / width()) - 1.f), + (1.f - ((2.f * MousePos.y()) / height())) + ); + return Device; +} + + +// ************ PRIVATE ************ +void CEditorGLWidget::ProcessInput(double DeltaTime) +{ + if (IsMouseInputActive()) + { + float XMovement = (QCursor::pos().x() - mLastMousePos.x()) * 0.01f; + float YMovement = (QCursor::pos().y() - mLastMousePos.y()) * 0.01f; + + if ((XMovement != 0) || (YMovement != 0)) + { + mCamera.ProcessMouseInput((EKeyInputs) mKeysPressed, (EMouseInputs) mButtonsPressed, XMovement, YMovement); + QCursor::setPos(mLastMousePos); + } + } + + if (IsKeyboardInputActive()) + mCamera.ProcessKeyInput((EKeyInputs) mKeysPressed, DeltaTime); +} diff --git a/UI/CEditorGLWidget.h b/UI/CEditorGLWidget.h new file mode 100644 index 00000000..12b840d1 --- /dev/null +++ b/UI/CEditorGLWidget.h @@ -0,0 +1,63 @@ +#ifndef CEDITORGLWIDGET_H +#define CEDITORGLWIDGET_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class CEditorGLWidget : public QOpenGLWidget +{ + Q_OBJECT + + static QTimer sRefreshTimer; + CCamera mCamera; + QPoint mLastMousePos; + double mLastDrawTime; + QPoint mLeftClickPoint; + int mButtonsPressed; // int container for EMouseInputs flags + int mKeysPressed; // int container for EKeyInputs flags + QCursor mCursorState; + bool mCursorVisible; + +public: + explicit CEditorGLWidget(QWidget *pParent = 0); + ~CEditorGLWidget(); + void initializeGL(); + void paintGL(); + void resizeGL(int w, int h); + void mouseMoveEvent(QMouseEvent *pEvent); + void mousePressEvent(QMouseEvent *pEvent); + void mouseReleaseEvent(QMouseEvent *pEvent); + void keyPressEvent(QKeyEvent *pEvent); + void keyReleaseEvent(QKeyEvent *pEvent); + void wheelEvent(QWheelEvent *pEvent); + void focusOutEvent(QFocusEvent *pEvent); + void SetCursorState(const QCursor& Cursor); + void SetCursorVisible(bool visible); + bool IsCursorVisible(); + bool IsMouseInputActive(); + bool IsKeyboardInputActive(); + CCamera& Camera(); + CRay CastRay(); + CVector2f MouseDeviceCoordinates(); + +signals: + void ViewportResized(int w, int h); + void PreRender(); + void Render(CCamera& Camera); + void PostRender(); + void MouseClick(QMouseEvent *pEvent); + void MouseDrag(QMouseEvent *pEvent); + +private: + void ProcessInput(double DeltaTime); +}; + +#endif diff --git a/UI/CMaterialEditor.cpp b/UI/CMaterialEditor.cpp new file mode 100644 index 00000000..9cd44254 --- /dev/null +++ b/UI/CMaterialEditor.cpp @@ -0,0 +1,14 @@ +#include "CMaterialEditor.h" +#include "ui_CMaterialEditor.h" + +CMaterialEditor::CMaterialEditor(QWidget *parent) : + QDialog(parent), + ui(new Ui::CMaterialEditor) +{ + ui->setupUi(this); +} + +CMaterialEditor::~CMaterialEditor() +{ + delete ui; +} diff --git a/UI/CMaterialEditor.h b/UI/CMaterialEditor.h new file mode 100644 index 00000000..68b03e5d --- /dev/null +++ b/UI/CMaterialEditor.h @@ -0,0 +1,22 @@ +#ifndef CMATERIALEDITOR_H +#define CMATERIALEDITOR_H + +#include + +namespace Ui { +class CMaterialEditor; +} + +class CMaterialEditor : public QDialog +{ + Q_OBJECT + +public: + explicit CMaterialEditor(QWidget *parent = 0); + ~CMaterialEditor(); + +private: + Ui::CMaterialEditor *ui; +}; + +#endif // CMATERIALEDITOR_H diff --git a/UI/CMaterialEditor.ui b/UI/CMaterialEditor.ui new file mode 100644 index 00000000..8fbb177d --- /dev/null +++ b/UI/CMaterialEditor.ui @@ -0,0 +1,18 @@ + + CMaterialEditor + + + + 0 + 0 + 400 + 300 + + + + Dialog + + + + + diff --git a/UI/CModelEditorWindow.cpp b/UI/CModelEditorWindow.cpp new file mode 100644 index 00000000..6d926983 --- /dev/null +++ b/UI/CModelEditorWindow.cpp @@ -0,0 +1,795 @@ +#include "CModelEditorWindow.h" +#include "ui_CModelEditorWindow.h" + +#include +#include + +#include "WResourceSelector.h" +#include +#include +#include +#include +#include +#include +#include "WColorPicker.h" + +#include +#include + +CModelEditorWindow::CModelEditorWindow(QWidget *parent) : + QMainWindow(parent), + ui(new Ui::CModelEditorWindow) +{ + ui->setupUi(this); + + mpScene = new CSceneManager(); + mpCurrentMat = nullptr; + mpCurrentModel = nullptr; + mpCurrentModelNode = new CModelNode(mpScene); + mpCurrentPass = nullptr; + mIgnoreSignals = false; + + mpRenderer = new CRenderer(); + mpRenderer->ToggleGrid(true); + mpRenderer->SetClearColor(CColor(0.3f, 0.3f, 0.3f, 1.f)); + + CCamera& Camera = ui->PreviewGLWidget->Camera(); + Camera.Snap(CVector3f(0, 3, 1)); + Camera.SetFree(); + Camera.SetMoveSpeed(0.5f); + mDrawMode = eDrawMesh; + + // UI initialization + UpdateAnimParamUI(-1); + ui->IndTextureResSelector->SetResType(eTexture); + ui->IndTextureResSelector->SetPreviewPanelEnabled(true); + ui->PassTextureResSelector->SetResType(eTexture); + ui->PassTextureResSelector->SetPreviewPanelEnabled(true); + ui->PassTable->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch); + ui->PassTable->horizontalHeader()->setSectionResizeMode(1, QHeaderView::ResizeToContents); + ui->ClearColorPicker->setColor(QColor(76, 76, 76, 255)); + + // Viewport Signal/Slot setup + connect(ui->PreviewGLWidget, SIGNAL(ViewportResized(int,int)), this, SLOT(SetViewportSize(int,int))); + connect(ui->PreviewGLWidget, SIGNAL(Render(CCamera&)), this, SLOT(PaintViewport(CCamera&))); + + // Editor UI Signal/Slot setup + ui->SetSelectionComboBox->setProperty ("ModelEditorWidgetType", eSetSelectComboBox); + ui->MatSelectionComboBox->setProperty ("ModelEditorWidgetType", eMatSelectComboBox); + ui->EnableTransparencyCheck->setProperty ("ModelEditorWidgetType", eEnableTransparencyCheckBox); + ui->EnablePunchthroughCheck->setProperty ("ModelEditorWidgetType", eEnablePunchthroughCheckBox); + ui->EnableReflectionCheck->setProperty ("ModelEditorWidgetType", eEnableReflectionCheckBox); + ui->EnableSurfaceReflectionCheck->setProperty ("ModelEditorWidgetType", eEnableSurfaceReflectionCheckBox); + ui->EnableDepthWriteCheck->setProperty ("ModelEditorWidgetType", eEnableDepthWriteCheckBox); + ui->EnableOccluderCheck->setProperty ("ModelEditorWidgetType", eEnableOccluderCheckBox); + ui->EnableLightmapCheck->setProperty ("ModelEditorWidgetType", eEnableLightmapCheckBox); + ui->EnableDynamicLightingCheck->setProperty ("ModelEditorWidgetType", eEnableLightingCheckBox); + ui->SourceBlendComboBox->setProperty ("ModelEditorWidgetType", eSourceBlendComboBox); + ui->DestBlendComboBox->setProperty ("ModelEditorWidgetType", eDestBlendComboBox); + ui->KonstColorPickerA->setProperty ("ModelEditorWidgetType", eKonstColorPickerA); + ui->KonstColorPickerB->setProperty ("ModelEditorWidgetType", eKonstColorPickerB); + ui->KonstColorPickerC->setProperty ("ModelEditorWidgetType", eKonstColorPickerC); + ui->KonstColorPickerD->setProperty ("ModelEditorWidgetType", eKonstColorPickerD); + ui->IndTextureResSelector->setProperty ("ModelEditorWidgetType", eIndTextureResSelector); + ui->PassTable->setProperty ("ModelEditorWidgetType", ePassTableWidget); + ui->TevKColorSelComboBox->setProperty ("ModelEditorWidgetType", eTevKColorSelComboBox); + ui->TevKAlphaSelComboBox->setProperty ("ModelEditorWidgetType", eTevKAlphaSelComboBox); + ui->TevRasSelComboBox->setProperty ("ModelEditorWidgetType", eTevRasSelComboBox); + ui->TexCoordSrcComboBox->setProperty ("ModelEditorWidgetType", eTevTexSourceComboBox); + ui->PassTextureResSelector->setProperty ("ModelEditorWidgetType", ePassTextureResSelector); + ui->TevColor1ComboBox->setProperty ("ModelEditorWidgetType", eTevColorComboBoxA); + ui->TevColor2ComboBox->setProperty ("ModelEditorWidgetType", eTevColorComboBoxB); + ui->TevColor3ComboBox->setProperty ("ModelEditorWidgetType", eTevColorComboBoxC); + ui->TevColor4ComboBox->setProperty ("ModelEditorWidgetType", eTevColorComboBoxD); + ui->TevColorOutputComboBox->setProperty ("ModelEditorWidgetType", eTevColorOutputComboBox); + ui->TevAlpha1ComboBox->setProperty ("ModelEditorWidgetType", eTevAlphaComboBoxA); + ui->TevAlpha2ComboBox->setProperty ("ModelEditorWidgetType", eTevAlphaComboBoxB); + ui->TevAlpha3ComboBox->setProperty ("ModelEditorWidgetType", eTevAlphaComboBoxC); + ui->TevAlpha4ComboBox->setProperty ("ModelEditorWidgetType", eTevAlphaComboBoxD); + ui->TevAlphaOutputComboBox->setProperty ("ModelEditorWidgetType", eTevAlphaOutputComboBox); + ui->AnimTypeComboBox->setProperty ("ModelEditorWidgetType", eAnimModeComboBox); + ui->AnimParamASpinBox->setProperty ("ModelEditorWidgetType", eAnimParamASpinBox); + ui->AnimParamBSpinBox->setProperty ("ModelEditorWidgetType", eAnimParamBSpinBox); + ui->AnimParamCSpinBox->setProperty ("ModelEditorWidgetType", eAnimParamCSpinBox); + ui->AnimParamDSpinBox->setProperty ("ModelEditorWidgetType", eAnimParamDSpinBox); + + connect(ui->SetSelectionComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(UpdateUI(int))); + connect(ui->MatSelectionComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(UpdateUI(int))); + connect(ui->EnableTransparencyCheck, SIGNAL(toggled(bool)), this, SLOT(UpdateMaterial(bool))); + connect(ui->EnablePunchthroughCheck, SIGNAL(toggled(bool)), this, SLOT(UpdateMaterial(bool))); + connect(ui->EnableReflectionCheck, SIGNAL(toggled(bool)), this, SLOT(UpdateMaterial(bool))); + connect(ui->EnableSurfaceReflectionCheck, SIGNAL(toggled(bool)), this, SLOT(UpdateMaterial(bool))); + connect(ui->EnableDepthWriteCheck, SIGNAL(toggled(bool)), this, SLOT(UpdateMaterial(bool))); + connect(ui->EnableOccluderCheck, SIGNAL(toggled(bool)), this, SLOT(UpdateMaterial(bool))); + connect(ui->EnableLightmapCheck, SIGNAL(toggled(bool)), this, SLOT(UpdateMaterial(bool))); + connect(ui->EnableDynamicLightingCheck, SIGNAL(toggled(bool)), this, SLOT(UpdateMaterial(bool))); + connect(ui->SourceBlendComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(UpdateMaterial(int))); + connect(ui->DestBlendComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(UpdateMaterial(int))); + connect(ui->KonstColorPickerA, SIGNAL(colorChanged(QColor)), this, SLOT(UpdateMaterial(QColor))); + connect(ui->KonstColorPickerB, SIGNAL(colorChanged(QColor)), this, SLOT(UpdateMaterial(QColor))); + connect(ui->KonstColorPickerC, SIGNAL(colorChanged(QColor)), this, SLOT(UpdateMaterial(QColor))); + connect(ui->KonstColorPickerD, SIGNAL(colorChanged(QColor)), this, SLOT(UpdateMaterial(QColor))); + connect(ui->PassTable, SIGNAL(cellClicked(int,int)), this, SLOT(UpdateMaterial(int, int))); + connect(ui->TevKColorSelComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(UpdateMaterial(int))); + connect(ui->TevKAlphaSelComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(UpdateMaterial(int))); + connect(ui->TevRasSelComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(UpdateMaterial(int))); + connect(ui->TexCoordSrcComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(UpdateMaterial(int))); + connect(ui->TevColor1ComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(UpdateMaterial(int))); + connect(ui->TevColor2ComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(UpdateMaterial(int))); + connect(ui->TevColor3ComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(UpdateMaterial(int))); + connect(ui->TevColor4ComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(UpdateMaterial(int))); + connect(ui->TevColorOutputComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(UpdateMaterial(int))); + connect(ui->TevAlpha1ComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(UpdateMaterial(int))); + connect(ui->TevAlpha2ComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(UpdateMaterial(int))); + connect(ui->TevAlpha3ComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(UpdateMaterial(int))); + connect(ui->TevAlpha4ComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(UpdateMaterial(int))); + connect(ui->TevAlphaOutputComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(UpdateMaterial(int))); + connect(ui->AnimTypeComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(UpdateMaterial(int))); + connect(ui->AnimParamASpinBox, SIGNAL(valueChanged(double)), this, SLOT(UpdateMaterial(double))); + connect(ui->AnimParamBSpinBox, SIGNAL(valueChanged(double)), this, SLOT(UpdateMaterial(double))); + connect(ui->AnimParamCSpinBox, SIGNAL(valueChanged(double)), this, SLOT(UpdateMaterial(double))); + connect(ui->AnimParamDSpinBox, SIGNAL(valueChanged(double)), this, SLOT(UpdateMaterial(double))); + // That was fun +} + +CModelEditorWindow::~CModelEditorWindow() +{ + delete mpCurrentModelNode; + delete mpRenderer; + delete ui; +} + +void CModelEditorWindow::SetActiveModel(CModel *pModel) +{ + mpCurrentModelNode->SetModel(pModel); + mpCurrentModel = pModel; + mModelToken = CToken(pModel); + + ui->MeshInfoLabel->setText(QString::number(pModel->GetVertexCount()) + " vertices, " + QString::number(pModel->GetTriangleCount()) + " triangles"); + ui->MatInfoLabel->setText(QString::number(pModel->GetMatCount()) + " materials, " + QString::number(pModel->GetMatSetCount()) + " set" + (pModel->GetMatSetCount() == 1 ? "" : "s")); + + // Set items in matset combo box + ui->SetSelectionComboBox->blockSignals(true); + ui->SetSelectionComboBox->clear(); + + for (u32 iSet = 0; iSet < pModel->GetMatSetCount(); iSet++) + ui->SetSelectionComboBox->addItem("Set #" + QString::number(iSet + 1)); + + ui->SetSelectionComboBox->setCurrentIndex(0); + ui->SetSelectionComboBox->blockSignals(false); + + // Set items in mat combo box + ui->MatSelectionComboBox->blockSignals(true); + ui->MatSelectionComboBox->clear(); + + for (u32 iMat = 0; iMat < pModel->GetMatCount(); iMat++) + ui->MatSelectionComboBox->addItem("Material #" + QString::number(iMat + 1)); + + ui->MatSelectionComboBox->setCurrentIndex(0); + ui->MatSelectionComboBox->setEnabled( pModel->GetMatCount() > 1 ); + ui->MatSelectionComboBox->blockSignals(false); + + // Emit signals to set up UI + ui->SetSelectionComboBox->currentIndexChanged(0); + + // Gray out set selection for models with one set + ui->SetSelectionComboBox->setEnabled( pModel->GetMatSetCount() > 1 ); + ui->MatSelectionComboBox->setEnabled( pModel->GetMatCount() > 1 ); +} + +void CModelEditorWindow::SetActiveMaterial(int MatIndex) +{ + if (!mpCurrentModel) return; + + u32 SetIndex = ui->SetSelectionComboBox->currentIndex(); + mpCurrentMat = mpCurrentModel->GetMaterialByIndex(SetIndex, MatIndex); + if (!mpCurrentMat) return; + //mpCurrentMat->SetTint(CColor(1.f, 0.5f, 0.5f, 1.f)); + + // Set up UI + CMaterial::EMaterialOptions Settings = mpCurrentMat->Options(); + + mIgnoreSignals = true; + ui->EnableTransparencyCheck->setChecked( Settings & CMaterial::eTransparent ); + ui->EnablePunchthroughCheck->setChecked( Settings & CMaterial::ePunchthrough ); + ui->EnableReflectionCheck->setChecked( Settings & CMaterial::eReflection ); + ui->EnableSurfaceReflectionCheck->setChecked( Settings & CMaterial::eSurfaceReflection ); + ui->EnableDepthWriteCheck->setChecked( Settings & CMaterial::eDepthWrite ); + ui->EnableOccluderCheck->setChecked( Settings & CMaterial::eOccluder ); + ui->EnableLightmapCheck->setChecked( Settings & CMaterial::eLightmap ); + ui->EnableDynamicLightingCheck->setChecked( mpCurrentMat->IsLightingEnabled() ); + + u32 SrcFac = (u32) mpCurrentMat->BlendSrcFac(); + u32 DstFac = (u32) mpCurrentMat->BlendDstFac(); + if (SrcFac >= 0x300) SrcFac -= 0x2FE; + if (DstFac >= 0x300) DstFac -= 0x2FE; + ui->SourceBlendComboBox->setCurrentIndex(SrcFac); + ui->DestBlendComboBox->setCurrentIndex(DstFac); + + if (Settings & CMaterial::eIndStage) + ui->IndTextureResSelector->SetText(QString::fromStdString(mpCurrentMat->IndTexture()->FullSource())); + else + ui->IndTextureResSelector->SetText(""); + + for (u32 iKonst = 0; iKonst < 4; iKonst++) + { + QColor Color; + CColor KColor = mpCurrentMat->Konst(iKonst); + Color.setRed(KColor.r); + Color.setGreen(KColor.g); + Color.setBlue(KColor.b); + Color.setAlpha(KColor.a); + + if (iKonst == 0) ui->KonstColorPickerA->setColor(Color); + else if (iKonst == 1) ui->KonstColorPickerB->setColor(Color); + else if (iKonst == 2) ui->KonstColorPickerC->setColor(Color); + else if (iKonst == 3) ui->KonstColorPickerD->setColor(Color); + } + + u32 PassCount = mpCurrentMat->PassCount(); + ui->PassTable->clear(); + ui->PassTable->setRowCount(PassCount); + + for (u32 iPass = 0; iPass < PassCount; iPass++) + { + CMaterialPass *pPass = mpCurrentMat->Pass(iPass); + + QTableWidgetItem *pItemA = new QTableWidgetItem("Pass #" + QString::number(iPass + 1) + ": " + QString::fromStdString(pPass->NamedType())); + QTableWidgetItem *pItemB = new QTableWidgetItem(); + + if (pPass->IsEnabled()) + pItemB->setIcon(QIcon(":/icons/EditorAssets/Show.png")); + else + pItemB->setIcon(QIcon(":/icons/EditorAssets/Hide.png")); + + ui->PassTable->setItem(iPass, 0, pItemA); + ui->PassTable->setItem(iPass, 1, pItemB); + } + + // Set up the tex coord source combo box so it only shows vertex attributes that exist on this material + ui->TexCoordSrcComboBox->clear(); + EVertexDescription Desc = mpCurrentMat->VtxDesc(); + + ui->TexCoordSrcComboBox->addItem("None"); + if (Desc & ePosition) ui->TexCoordSrcComboBox->addItem("Position"); + if (Desc & eNormal) ui->TexCoordSrcComboBox->addItem("Normal"); + if (Desc & eTex0) ui->TexCoordSrcComboBox->addItem("Tex Coord 1"); + if (Desc & eTex1) ui->TexCoordSrcComboBox->addItem("Tex Coord 2"); + if (Desc & eTex2) ui->TexCoordSrcComboBox->addItem("Tex Coord 3"); + if (Desc & eTex3) ui->TexCoordSrcComboBox->addItem("Tex Coord 4"); + if (Desc & eTex4) ui->TexCoordSrcComboBox->addItem("Tex Coord 5"); + if (Desc & eTex5) ui->TexCoordSrcComboBox->addItem("Tex Coord 6"); + if (Desc & eTex6) ui->TexCoordSrcComboBox->addItem("Tex Coord 7"); + if (Desc & eTex7) ui->TexCoordSrcComboBox->addItem("Tex Coord 8"); + + // Emit signal from Pass Table to set up the Pass UI + mIgnoreSignals = false; + + if (PassCount > 0) + ui->PassTable->cellClicked(0,0); + + // Activate UI + ActivateMatEditUI(true); +} + +void CModelEditorWindow::SetActivePass(int PassIndex) +{ + // Some modifications have to be made to the values to match GX enums with combo box indices + mIgnoreSignals = true; + mpCurrentPass = mpCurrentMat->Pass(PassIndex); + + u32 KColor = mpCurrentPass->KColorSel(); + u32 KAlpha = mpCurrentPass->KAlphaSel(); + u32 Ras = mpCurrentPass->RasSel(); + u32 TexCoordSrc = mpCurrentPass->TexCoordSource(); + if (KColor >= 0xC) KColor -= 4; + if (KAlpha >= 0x10) KAlpha -= 8; + if (Ras == 0xFF) Ras = 10; + if (TexCoordSrc == 0xFF) TexCoordSrc = 0; + else TexCoordSrc++; + if (TexCoordSrc >= 5) TexCoordSrc -= 2; + + CTexture *pPassTex = mpCurrentPass->Texture(); + if (pPassTex) + ui->PassTextureResSelector->SetText(QString::fromStdString(pPassTex->FullSource())); + else + ui->PassTextureResSelector->SetText(""); + + ui->TevKColorSelComboBox->setCurrentIndex(KColor); + ui->TevKAlphaSelComboBox->setCurrentIndex(KAlpha); + ui->TevRasSelComboBox->setCurrentIndex(Ras); + ui->TexCoordSrcComboBox->setCurrentIndex(TexCoordSrc); + ui->TevColor1ComboBox->setCurrentIndex(mpCurrentPass->ColorInput(0)); + ui->TevColor2ComboBox->setCurrentIndex(mpCurrentPass->ColorInput(1)); + ui->TevColor3ComboBox->setCurrentIndex(mpCurrentPass->ColorInput(2)); + ui->TevColor4ComboBox->setCurrentIndex(mpCurrentPass->ColorInput(3)); + ui->TevColorOutputComboBox->setCurrentIndex(mpCurrentPass->ColorOutput()); + ui->TevAlpha1ComboBox->setCurrentIndex(mpCurrentPass->AlphaInput(0)); + ui->TevAlpha2ComboBox->setCurrentIndex(mpCurrentPass->AlphaInput(1)); + ui->TevAlpha3ComboBox->setCurrentIndex(mpCurrentPass->AlphaInput(2)); + ui->TevAlpha4ComboBox->setCurrentIndex(mpCurrentPass->AlphaInput(3)); + ui->TevAlphaOutputComboBox->setCurrentIndex(mpCurrentPass->AlphaOutput()); + + s32 AnimMode = mpCurrentPass->AnimMode(); + ui->AnimTypeComboBox->setCurrentIndex(AnimMode + 1); + UpdateAnimParamUI(AnimMode); + + mIgnoreSignals = false; +} + +void CModelEditorWindow::UpdateMaterial() +{ + // This function takes input from buttons + if (!mpCurrentMat) return; + if (mIgnoreSignals) return; + + EModelEditorWidget Widget = (EModelEditorWidget) sender()->property("ModelEditorWidgetType").toInt(); + + switch (Widget) + { + /*case eAddPassButton: + break; + + case eDeletePassButton: + break;*/ + } +} + +void CModelEditorWindow::UpdateMaterial(int Value) +{ + // This function takes input from combo boxes + if (!mpCurrentMat) return; + if (mIgnoreSignals) return; + + EModelEditorWidget Widget = (EModelEditorWidget) sender()->property("ModelEditorWidgetType").toInt(); + + switch (Widget) + { + + case eSourceBlendComboBox: + case eDestBlendComboBox: + { + GLenum SourceFac = ui->SourceBlendComboBox->currentIndex(); + GLenum DstFac = ui->DestBlendComboBox->currentIndex(); + if (SourceFac > 1) SourceFac += 0x2FE; + if (DstFac > 1) DstFac += 0x2FE; + mpCurrentMat->SetBlendMode(SourceFac, DstFac); + break; + } + + case eTevKColorSelComboBox: + if (Value >= 8) Value += 4; + mpCurrentPass->SetKColorSel((ETevKSel) Value); + break; + + case eTevKAlphaSelComboBox: + if (Value >= 8) Value += 8; + mpCurrentPass->SetKAlphaSel((ETevKSel) Value); + break; + + case eTevRasSelComboBox: + if (Value == 7) Value = eRasColorNull; + mpCurrentPass->SetRasSel((ETevRasSel) Value); + break; + + case eTevTexSelComboBox: + // todo + break; + + case eTevTexSourceComboBox: + if (Value >= 3) Value ++; + else Value--; + mpCurrentPass->SetTexCoordSource(Value); + break; + + case eTevColorComboBoxA: + case eTevColorComboBoxB: + case eTevColorComboBoxC: + case eTevColorComboBoxD: + { + ETevColorInput A = (ETevColorInput) ui->TevColor1ComboBox->currentIndex(); + ETevColorInput B = (ETevColorInput) ui->TevColor2ComboBox->currentIndex(); + ETevColorInput C = (ETevColorInput) ui->TevColor3ComboBox->currentIndex(); + ETevColorInput D = (ETevColorInput) ui->TevColor4ComboBox->currentIndex(); + mpCurrentPass->SetColorInputs(A, B, C, D); + break; + } + + case eTevColorOutputComboBox: + mpCurrentPass->SetColorOutput((ETevOutput) Value); + break; + + case eTevAlphaComboBoxA: + case eTevAlphaComboBoxB: + case eTevAlphaComboBoxC: + case eTevAlphaComboBoxD: + { + ETevAlphaInput A = (ETevAlphaInput) ui->TevAlpha1ComboBox->currentIndex(); + ETevAlphaInput B = (ETevAlphaInput) ui->TevAlpha2ComboBox->currentIndex(); + ETevAlphaInput C = (ETevAlphaInput) ui->TevAlpha3ComboBox->currentIndex(); + ETevAlphaInput D = (ETevAlphaInput) ui->TevAlpha4ComboBox->currentIndex(); + mpCurrentPass->SetAlphaInputs(A, B, C, D); + break; + } + + case eTevAlphaOutputComboBox: + mpCurrentPass->SetAlphaOutput((ETevOutput) Value); + break; + + case eAnimModeComboBox: + mpCurrentPass->SetAnimMode((EUVAnimMode) (Value - 1)); + UpdateAnimParamUI(Value - 1); + break; + } + + mpCurrentMat->GenerateShader(); +} + +void CModelEditorWindow::UpdateMaterial(int ValueA, int ValueB) +{ + // This function takes input from PassTable + if (!mpCurrentMat) return; + if (mIgnoreSignals) return; + + // Select Pass + if (ValueB == 0) + { + SetActivePass(ValueA); + ui->PassTable->setSelectionMode(QAbstractItemView::SingleSelection); + ui->PassTable->selectRow(ValueA); + ui->PassTable->setSelectionMode(QAbstractItemView::NoSelection); + } + + // Show/Hide Pass + else if (ValueB == 1) + { + bool Enabled = !mpCurrentMat->Pass(ValueA)->IsEnabled(); + mpCurrentMat->Pass(ValueA)->SetEnabled(Enabled); + + if (Enabled) + ui->PassTable->item(ValueA, ValueB)->setIcon(QIcon(":/icons/EditorAssets/Show.png")); + else + ui->PassTable->item(ValueA, ValueB)->setIcon(QIcon(":/icons/EditorAssets/Hide.png")); + } +} + +void CModelEditorWindow::UpdateMaterial(double Value) +{ + // This function takes input from WDraggableSpinBoxes + if (!mpCurrentMat) return; + if (mIgnoreSignals) return; + + EModelEditorWidget Widget = (EModelEditorWidget) sender()->property("ModelEditorWidgetType").toInt(); + + switch (Widget) + { + case eAnimParamASpinBox: + mpCurrentPass->SetAnimParam(0, (float) Value); + break; + case eAnimParamBSpinBox: + mpCurrentPass->SetAnimParam(1, (float) Value); + break; + case eAnimParamCSpinBox: + mpCurrentPass->SetAnimParam(2, (float) Value); + break; + case eAnimParamDSpinBox: + mpCurrentPass->SetAnimParam(3, (float) Value); + break; + } +} + +void CModelEditorWindow::UpdateMaterial(bool Value) +{ + // This function takes input from checkboxes + if (!mpCurrentMat) return; + if (mIgnoreSignals) return; + + EModelEditorWidget Widget = (EModelEditorWidget) sender()->property("ModelEditorWidgetType").toInt(); + + switch (Widget) + { + + case eEnableTransparencyCheckBox: + case eEnablePunchthroughCheckBox: + case eEnableReflectionCheckBox: + case eEnableSurfaceReflectionCheckBox: + case eEnableDepthWriteCheckBox: + case eEnableOccluderCheckBox: + case eEnableLightmapCheckBox: + { + CMaterial::EMaterialOptions Options = (CMaterial::EMaterialOptions) (mpCurrentMat->Options() & 0x2408); + Options |= (ui->EnableTransparencyCheck->isChecked() << 4); + Options |= (ui->EnablePunchthroughCheck->isChecked() << 5); + Options |= (ui->EnableReflectionCheck->isChecked() << 6); + Options |= (ui->EnableDepthWriteCheck->isChecked() << 7); + Options |= (ui->EnableSurfaceReflectionCheck->isChecked() << 8); + Options |= (ui->EnableOccluderCheck->isChecked() << 9); + Options |= (ui->EnableLightmapCheck->isChecked() << 11); + mpCurrentMat->SetOptions(Options); + break; + } + + case eEnableLightingCheckBox: + mpCurrentMat->SetLightingEnabled(Value); + break; + + } +} + +void CModelEditorWindow::UpdateMaterial(QColor Color) +{ + // This function takes input from WColorPickers + if (!mpCurrentMat) return; + if (mIgnoreSignals) return; + + EModelEditorWidget Widget = (EModelEditorWidget) sender()->property("ModelEditorWidgetType").toInt(); + CColor KColor((u8) Color.red(), (u8) Color.green(), (u8) Color.blue(), (u8) Color.alpha()); + + switch (Widget) + { + case eKonstColorPickerA: + mpCurrentMat->SetKonst(KColor, 0); + break; + case eKonstColorPickerB: + mpCurrentMat->SetKonst(KColor, 1); + break; + case eKonstColorPickerC: + mpCurrentMat->SetKonst(KColor, 2); + break; + case eKonstColorPickerD: + mpCurrentMat->SetKonst(KColor, 3); + break; + } +} + +void CModelEditorWindow::UpdateUI(int Value) +{ + EModelEditorWidget Widget = (EModelEditorWidget) sender()->property("ModelEditorWidgetType").toInt(); + + switch (Widget) + { + + case eSetSelectComboBox: + mpCurrentModelNode->SetMatSet(Value); + SetActiveMaterial(ui->MatSelectionComboBox->currentIndex()); + break; + + case eMatSelectComboBox: + SetActiveMaterial(Value); + ActivateMatEditUI(true); + break; + } +} + +void CModelEditorWindow::PaintViewport(CCamera& Camera) +{ + mpRenderer->BeginFrame(); + + Camera.LoadMatrices(); + + if (!mpCurrentModel) + { + CDrawUtil::DrawGrid(); + } + + else if (mDrawMode == eDrawMesh) + { + CDrawUtil::DrawGrid(); + mpCurrentModelNode->AddToRenderer(mpRenderer); + mpRenderer->RenderScene(Camera); + } + + else if (mDrawMode == eDrawSphere) + { + if (!mpCurrentMat) return; + glEnable(GL_CULL_FACE); + + CGraphics::sVertexBlock.COLOR0_Amb = CGraphics::skDefaultAmbientColor.ToVector4f(); + CGraphics::sMVPBlock.ModelMatrix = CMatrix4f::skIdentity; + CGraphics::UpdateMVPBlock(); + CGraphics::SetDefaultLighting(); + CGraphics::UpdateLightBlock(); // Note: vertex block is updated by the material + mpCurrentMat->SetCurrent(eEnableUVScroll | eEnableBackfaceCull | eEnableOccluders); + + CDrawUtil::DrawSphere(true); + } + + else if (mDrawMode == eDrawSquare) + { + if (!mpCurrentMat) return; + glDisable(GL_CULL_FACE); + + CGraphics::SetDefaultLighting(); + CGraphics::UpdateLightBlock(); + CGraphics::sVertexBlock.COLOR0_Amb = CGraphics::skDefaultAmbientColor.ToVector4f(); + + CGraphics::sMVPBlock.ModelMatrix = CMatrix4f::skIdentity; + CGraphics::sMVPBlock.ViewMatrix = CMatrix4f::skIdentity; + CGraphics::sMVPBlock.ProjectionMatrix = CMatrix4f::skIdentity; + CGraphics::UpdateMVPBlock(); + + mpCurrentMat->SetCurrent(eEnableUVScroll | eEnableOccluders); + CDrawUtil::DrawSquare(); + } + + mpRenderer->EndFrame(); +} + +void CModelEditorWindow::SetViewportSize(int Width, int Height) +{ + mViewportAspectRatio = (float) Width / (float) Height; + mpRenderer->SetViewportSize(Width, Height); +} + +// ************ PRIVATE ************ +void CModelEditorWindow::ActivateMatEditUI(bool Active) +{ + ui->MatSelectionComboBox->setEnabled(Active); + ui->GeneralGroupBox->setEnabled(Active); + ui->PassGroupBox->setEnabled(Active); +} + +void CModelEditorWindow::RefreshMaterial() +{ + if (mpCurrentMat) mpCurrentMat->GenerateShader(); +} + +void CModelEditorWindow::UpdateAnimParamUI(int Mode) +{ + // Update the param labels with actual names + hide unused params for each mode. + + switch (Mode) + { + case -1: // N/A + case 0: // ModelView No Translate + case 1: // ModelView + case 6: // Model + ui->AnimParamALabel->hide(); + ui->AnimParamBLabel->hide(); + ui->AnimParamCLabel->hide(); + ui->AnimParamDLabel->hide(); + ui->AnimParamASpinBox->hide(); + ui->AnimParamBSpinBox->hide(); + ui->AnimParamCSpinBox->hide(); + ui->AnimParamDSpinBox->hide(); + break; + + case 2: // UV Scroll + ui->AnimParamALabel->setText("Horizontal Offset:"); + ui->AnimParamBLabel->setText("Vertical Offset:"); + ui->AnimParamCLabel->setText("Horizontal Scale:"); + ui->AnimParamDLabel->setText("Vertical Scale:"); + ui->AnimParamASpinBox->setValue(mpCurrentPass->AnimParam(0)); + ui->AnimParamBSpinBox->setValue(mpCurrentPass->AnimParam(1)); + ui->AnimParamCSpinBox->setValue(mpCurrentPass->AnimParam(2)); + ui->AnimParamDSpinBox->setValue(mpCurrentPass->AnimParam(3)); + ui->AnimParamALabel->show(); + ui->AnimParamBLabel->show(); + ui->AnimParamCLabel->show(); + ui->AnimParamDLabel->show(); + ui->AnimParamASpinBox->show(); + ui->AnimParamBSpinBox->show(); + ui->AnimParamCSpinBox->show(); + ui->AnimParamDSpinBox->show(); + break; + + case 3: // Rotation + ui->AnimParamALabel->setText("Offset:"); + ui->AnimParamBLabel->setText("Scale:"); + ui->AnimParamASpinBox->setValue(mpCurrentPass->AnimParam(0)); + ui->AnimParamBSpinBox->setValue(mpCurrentPass->AnimParam(1)); + ui->AnimParamALabel->show(); + ui->AnimParamBLabel->show(); + ui->AnimParamCLabel->hide(); + ui->AnimParamDLabel->hide(); + ui->AnimParamASpinBox->show(); + ui->AnimParamBSpinBox->show(); + ui->AnimParamCSpinBox->hide(); + ui->AnimParamDSpinBox->hide(); + break; + + case 4: // Horizontal Filmstrip + case 5: // Vertical Filmstrip + ui->AnimParamALabel->setText("Scale:"); + ui->AnimParamBLabel->setText("Num Frames:"); + ui->AnimParamCLabel->setText("Step:"); + ui->AnimParamDLabel->setText("Time Offset:"); + ui->AnimParamASpinBox->setValue(mpCurrentPass->AnimParam(0)); + ui->AnimParamBSpinBox->setValue(mpCurrentPass->AnimParam(1)); + ui->AnimParamCSpinBox->setValue(mpCurrentPass->AnimParam(2)); + ui->AnimParamDSpinBox->setValue(mpCurrentPass->AnimParam(3)); + ui->AnimParamALabel->show(); + ui->AnimParamBLabel->show(); + ui->AnimParamCLabel->show(); + ui->AnimParamDLabel->show(); + ui->AnimParamASpinBox->show(); + ui->AnimParamBSpinBox->show(); + ui->AnimParamCSpinBox->show(); + ui->AnimParamDSpinBox->show(); + break; + + case 7: // Mysterious mode 7 + ui->AnimParamALabel->setText("ParamA:"); + ui->AnimParamBLabel->setText("ParamB:"); + ui->AnimParamASpinBox->setValue(mpCurrentPass->AnimParam(0)); + ui->AnimParamBSpinBox->setValue(mpCurrentPass->AnimParam(1)); + ui->AnimParamALabel->show(); + ui->AnimParamBLabel->show(); + ui->AnimParamCLabel->hide(); + ui->AnimParamDLabel->hide(); + ui->AnimParamASpinBox->show(); + ui->AnimParamBSpinBox->show(); + ui->AnimParamCSpinBox->hide(); + ui->AnimParamDSpinBox->hide(); + break; + } +} + +void CModelEditorWindow::on_actionConvert_to_DDS_triggered() +{ + QString TexFilename = QFileDialog::getOpenFileName(this, "Retro Texture (*.TXTR)", "", "*.TXTR"); + if (TexFilename.isEmpty()) return; + + CTexture *Tex = (CTexture*) gResCache.GetResource(TexFilename.toStdString()); + std::string OutName = StringUtil::GetPathWithoutExtension(TexFilename.toStdString()) + ".dds"; + + CFileOutStream Out(OutName, IOUtil::LittleEndian); + if (!Out.IsValid()) QMessageBox::warning(this, "Error", "Couldn't open output DDS!"); + + else + { + bool success = Tex->WriteDDS(Out); + if (!success) QMessageBox::warning(this, "Error", "Couldn't write output DDS!"); + else QMessageBox::information(this, "Success", "Successfully converted to DDS!"); + } +} + +void CModelEditorWindow::on_actionOpen_triggered() +{ + QString ModelFilename = QFileDialog::getOpenFileName(this, "Retro Model (*.CMDL)", "", "*.CMDL"); + if (ModelFilename.isEmpty()) return; + + CModel *Model = (CModel*) gResCache.GetResource(ModelFilename.toStdString()); + if (Model) + { + SetActiveModel(Model); + setWindowTitle("Prime World Editor - Model Editor: " + QString::fromStdString(Model->Source())); + } + + gResCache.Clean(); +} + +void CModelEditorWindow::on_actionSave_triggered() +{ + if (!mpCurrentModel) return; + + CFileOutStream CMDLOut(mpCurrentModel->Source(), IOUtil::BigEndian); + CModelCooker::WriteCookedModel(mpCurrentModel, ePrime, CMDLOut); + QMessageBox::information(this, "Saved", "Model saved!"); +} + +void CModelEditorWindow::closeEvent(QCloseEvent*) +{ + emit Closed(); +} + +void CModelEditorWindow::on_MeshPreviewButton_clicked() +{ + mDrawMode = eDrawMesh; +} + +void CModelEditorWindow::on_SpherePreviewButton_clicked() +{ + mDrawMode = eDrawSphere; +} + +void CModelEditorWindow::on_FlatPreviewButton_clicked() +{ + mDrawMode = eDrawSquare; +} + +void CModelEditorWindow::on_ClearColorPicker_colorChanged(const QColor &Color) +{ + CColor NewColor((u8) Color.red(), (u8) Color.green(), (u8) Color.blue(), 255); + mpRenderer->SetClearColor(NewColor); +} diff --git a/UI/CModelEditorWindow.h b/UI/CModelEditorWindow.h new file mode 100644 index 00000000..e460b569 --- /dev/null +++ b/UI/CModelEditorWindow.h @@ -0,0 +1,122 @@ +#ifndef CMODELEDITORWINDOW_H +#define CMODELEDITORWINDOW_H + +#include + +#include +#include +#include +#include +#include +#include + +namespace Ui { +class CModelEditorWindow; +} + +class CModelEditorWindow : public QMainWindow +{ + Q_OBJECT + + enum EDrawMode { + eDrawMesh, eDrawSphere, eDrawSquare + }; + + Ui::CModelEditorWindow *ui; + CRenderer *mpRenderer; + CSceneManager *mpScene; + CModel *mpCurrentModel; + CToken mModelToken; + CModelNode *mpCurrentModelNode; + CMaterial *mpCurrentMat; + CMaterialPass *mpCurrentPass; + bool mIgnoreSignals; + EDrawMode mDrawMode; + float mViewportAspectRatio; + +public: + explicit CModelEditorWindow(QWidget *parent = 0); + ~CModelEditorWindow(); + void SetActiveModel(CModel *pModel); + void closeEvent(QCloseEvent *pEvent); + +public slots: + void SetActiveMaterial(int MatIndex); + void SetActivePass(int PassIndex); + void UpdateMaterial(); + void UpdateMaterial(int Value); + void UpdateMaterial(int ValueA, int ValueB); + void UpdateMaterial(double Value); + void UpdateMaterial(bool Value); + void UpdateMaterial(QColor eColorProperty); + void UpdateUI(int Value); + void UpdateAnimParamUI(int Mode); + void PaintViewport(CCamera& Camera); + void SetViewportSize(int Width, int Height); + +private: + void ActivateMatEditUI(bool Active); + void RefreshMaterial(); + + enum EModelEditorWidget + { + eSetSelectComboBox, + eMatSelectComboBox, + eEnableTransparencyCheckBox, + eEnablePunchthroughCheckBox, + eEnableReflectionCheckBox, + eEnableSurfaceReflectionCheckBox, + eEnableDepthWriteCheckBox, + eEnableOccluderCheckBox, + eEnableLightmapCheckBox, + eEnableLightingCheckBox, + eSourceBlendComboBox, + eDestBlendComboBox, + eIndTextureResSelector, + eKonstColorPickerA, + eKonstColorPickerB, + eKonstColorPickerC, + eKonstColorPickerD, + ePassTableWidget, + eTevKColorSelComboBox, + eTevKAlphaSelComboBox, + eTevRasSelComboBox, + eTevTexSelComboBox, + eTevTexSourceComboBox, + ePassTextureResSelector, + eTevColorComboBoxA, + eTevColorComboBoxB, + eTevColorComboBoxC, + eTevColorComboBoxD, + eTevColorOutputComboBox, + eTevAlphaComboBoxA, + eTevAlphaComboBoxB, + eTevAlphaComboBoxC, + eTevAlphaComboBoxD, + eTevAlphaOutputComboBox, + eAnimModeComboBox, + eAnimParamASpinBox, + eAnimParamBSpinBox, + eAnimParamCSpinBox, + eAnimParamDSpinBox, + }; + +private slots: + void on_actionConvert_to_DDS_triggered(); + + void on_actionOpen_triggered(); + void on_actionSave_triggered(); + + void on_MeshPreviewButton_clicked(); + + void on_SpherePreviewButton_clicked(); + + void on_FlatPreviewButton_clicked(); + + void on_ClearColorPicker_colorChanged(const QColor &); + +signals: + void Closed(); +}; + +#endif // CMODELEDITORWINDOW_H diff --git a/UI/CModelEditorWindow.ui b/UI/CModelEditorWindow.ui new file mode 100644 index 00000000..83da12be --- /dev/null +++ b/UI/CModelEditorWindow.ui @@ -0,0 +1,2502 @@ + + + CModelEditorWindow + + + + 0 + 0 + 1280 + 720 + + + + + 400 + 400 + + + + Prime World Editor - Model Editor + + + + + + + Qt::Horizontal + + + + Qt::Vertical + + + + + 0 + 0 + + + + + 16777215 + 150 + + + + Mesh Info + + + + + + + 0 + 0 + + + + 0 vertices, 0 triangles + + + false + + + + + + + + 0 + 0 + + + + 0 materials, 0 sets + + + + + + + + + + 0 + 0 + + + + <b>Selected set:</b> + + + + + + + false + + + + 0 + 0 + + + + + + + + + 0 + 0 + + + + <html><head/><body><p><span style=" font-weight:600;">Selected material:</span></p></body></html> + + + + + + + false + + + + 0 + 0 + + + + + + + + + + + true + + + + 0 + 1 + + + + + 250 + 0 + + + + + 16777215 + 16777215 + + + + Qt::ScrollBarAsNeeded + + + Qt::ScrollBarAsNeeded + + + QAbstractScrollArea::AdjustToContents + + + true + + + + + 0 + 0 + 308 + 1184 + + + + + + + false + + + + 0 + 0 + + + + General + + + + + + + 0 + 0 + + + + Enables depth sorting of transparent materials. Required for transparent materials to render correctly. + + + Enable transparency + + + + + + + + 0 + 0 + + + + Enable for "cardboard cut-out" alpha. + + + Enable punchthrough alpha + + + + + + + + 0 + 0 + + + + This will make Samus's reflection appear in the material when the player stands near it. May not work properly on some surfaces. + + + Enable reflection + + + + + + + Enable surface reflection + + + + + + + + 0 + 0 + + + + Enable this material to write to the depth buffer. This should be enabled. + + + Enable depth writing + + + + + + + + 0 + 0 + + + + Meshes using this material won't be rendered, but they can be used to cast dynamic shadows on actors. Should only be enabled on terrain. + + + Shadow occluder mesh + + + + + + + Material uses a lightmap + + + + + + + + 0 + 0 + + + + Enable dynamic lighting on this material. If disabled, the material will appear fullbright. + + + Enable dynamic lighting + + + + + + + + + + 0 + 0 + + + + Source blend factor + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + When a new pixel is drawn, this value will be multiplied with the source pixel to determine how it gets blended with the destination pixel.<br><br>Common settings:<br>* <b>One</b> for source and <b>Zero</b> for dest, for opaque materials<br>* <b>One</b> for source and <b>One</b> for dest, for additive transparency<br>* <b>Source Alpha</b> for source and <b>Inverse Source Alpha</b> for dest, for alpha-blended transparency + + + + Zero + + + + + One + + + + + Source Color + + + + + Inverse Source Color + + + + + Source Alpha + + + + + Inverse Source Alpha + + + + + Dest Alpha + + + + + Inverse Dest Alpha + + + + + + + + + 0 + 0 + + + + Dest blend factor + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + When a new pixel is drawn, this value will be multiplied with the destination pixel to determine how it gets blended with the input pixel.<br><br>Common settings:<br>* <b>One</b> for source and <b>Zero</b> for dest, for opaque materials<br>* <b>One</b> for source and <b>One</b> for dest, for additive transparency<br>* <b>Source Alpha</b> for source and <b>Inverse Source Alpha</b> for dest, for alpha-blended transparency + + + + Zero + + + + + One + + + + + Dest Color + + + + + Inverse Dest Color + + + + + Source Alpha + + + + + Inverse Source Alpha + + + + + Dest Alpha + + + + + Inverse Dest Alpha + + + + + + + + + + + + Indirect texture + + + + + + + + 1 + 0 + + + + + + + + + + + 0 + 0 + + + + Custom RGBA colors that can be set to be used as TEV inputs. + + + Konst + + + + + + + 0 + 0 + + + + + 45 + 45 + + + + Qt::StrongFocus + + + + + + + + 0 + 0 + + + + + 45 + 45 + + + + Qt::StrongFocus + + + + + + + + 0 + 0 + + + + + 45 + 45 + + + + Qt::StrongFocus + + + + + + + + 0 + 0 + + + + + 45 + 45 + + + + Qt::StrongFocus + + + + + + + + + + + + + false + + + + 0 + 0 + + + + Pass + + + + + + + 0 + 100 + + + + outline: none; + + + QAbstractItemView::NoEditTriggers + + + QAbstractItemView::NoSelection + + + QAbstractItemView::SelectRows + + + + 16 + 16 + + + + QAbstractItemView::ScrollPerPixel + + + QAbstractItemView::ScrollPerPixel + + + false + + + false + + + true + + + false + + + 21 + + + 21 + + + + Pass + + + + + Show + + + + + + + + + 0 + 0 + + + + TEV Parameters + + + + + + + + + 0 + 0 + + + + <b>Konst Color: + + + + + + + + 0 + 0 + + + + Select the Konst RGB color used by this TEV stage. This can be one of the Konst colors set in the general settings above, or it can be a contant grayscale value. + + + + 1.0 + + + + + 0.875 + + + + + 0.75 + + + + + 0.625 + + + + + 0.5 + + + + + 0.375 + + + + + 0.25 + + + + + 0.125 + + + + + Konst 1 RGB + + + + + Konst 2 RGB + + + + + Konst 3 RGB + + + + + Konst 4 RGB + + + + + Konst 1 RRR + + + + + Konst 1 GGG + + + + + Konst 1 BBB + + + + + Konst 1 AAA + + + + + Konst 2 RRR + + + + + Konst 2 GGG + + + + + Konst 2 BBB + + + + + Konst 2 AAA + + + + + Konst 3 RRR + + + + + Konst 3 GGG + + + + + Konst 3 BBB + + + + + Konst 3 AAA + + + + + Konst 4 RRR + + + + + Konst 4 GGG + + + + + Konst 4 BBB + + + + + Konst 4 AAA + + + + + + + + + 0 + 0 + + + + <html><head/><body><p><span style=" font-weight:600;">Konst Alpha:</span></p></body></html> + + + + + + + + 0 + 0 + + + + Select the Konst alpha used by this TEV stage. This can be the alpha component from one of the Konst colors set in the general settings above, or it can be a contant value between 0 and 1. + + + + 1.0 + + + + + 0.875 + + + + + 0.75 + + + + + 0.625 + + + + + 0.5 + + + + + 0.375 + + + + + 0.25 + + + + + 0.125 + + + + + Konst 1 Red + + + + + Konst 2 Red + + + + + Konst 3 Red + + + + + Konst 4 Red + + + + + Konst 1 Green + + + + + Konst 2 Green + + + + + Konst 3 Green + + + + + Konst 4 Green + + + + + Konst 1 Blue + + + + + Konst 2 Blue + + + + + Konst 3 Blue + + + + + Konst 4 Blue + + + + + Konst 1 Alpha + + + + + Konst 2 Alpha + + + + + Konst 3 Alpha + + + + + Konst 4 Alpha + + + + + + + + + 0 + 0 + + + + <html><head/><body><p><span style=" font-weight:600;">Rasterized color:</span></p></body></html> + + + + + + + + 0 + 0 + + + + Rasterized vertex color after lighting calculations. Should be set to Color0A0, Zero, or Null. + + + 0 + + + + Color0 + + + + + Color1 + + + + + Alpha0 + + + + + Alpha1 + + + + + Color0A0 + + + + + Color1A1 + + + + + Zero + + + + + Null + + + + + + + + + 0 + 0 + + + + <b>Tex Coord Source:</b> + + + + + + + + 0 + 0 + + + + Select the vertex attribute that will be used to generate texture coordinates for this pass. + + + + None + + + + + Position + + + + + Normal + + + + + Tex Coord 1 + + + + + Tex Coord 2 + + + + + Tex Coord 3 + + + + + Tex Coord 4 + + + + + Tex Coord 5 + + + + + Tex Coord 6 + + + + + Tex Coord 7 + + + + + Tex Coord 8 + + + + + + + + + + + + <b>Texture:</b> + + + + + + + + 1 + 0 + + + + + + + + + + + 0 + 0 + + + + + 50 + false + + + + Color Inputs + + + + + + + 0 + 0 + + + + + Previous Stage RGB + + + + + Previous Stage AAA + + + + + Color 0 RGB + + + + + Color 0 AAA + + + + + Color 1 RGB + + + + + Color 1 AAA + + + + + Color 2 RGB + + + + + Color 2 AAA + + + + + Texture RGB + + + + + Texture AAA + + + + + Rasterized RGB + + + + + Rasterized AAA + + + + + One + + + + + Half + + + + + Konst RGB + + + + + Zero + + + + + + + + + 0 + 0 + + + + + Previous Stage RGB + + + + + Previous Stage AAA + + + + + Color 0 RGB + + + + + Color 0 AAA + + + + + Color 1 RGB + + + + + Color 1 AAA + + + + + Color 2 RGB + + + + + Color 2 AAA + + + + + Texture RGB + + + + + Texture AAA + + + + + Rasterized RGB + + + + + Rasterized AAA + + + + + One + + + + + Half + + + + + Konst RGB + + + + + Zero + + + + + + + + + 0 + 0 + + + + + Previous Stage RGB + + + + + Previous Stage AAA + + + + + Color 0 RGB + + + + + Color 0 AAA + + + + + Color 1 RGB + + + + + Color 1 AAA + + + + + Color 2 RGB + + + + + Color 2 AAA + + + + + Texture RGB + + + + + Texture AAA + + + + + Rasterized RGB + + + + + Rasterized AAA + + + + + One + + + + + Half + + + + + Konst RGB + + + + + Zero + + + + + + + + + 0 + 0 + + + + + Previous Stage RGB + + + + + Previous Stage AAA + + + + + Color 0 RGB + + + + + Color 0 AAA + + + + + Color 1 RGB + + + + + Color 1 AAA + + + + + Color 2 RGB + + + + + Color 2 AAA + + + + + Texture RGB + + + + + Texture AAA + + + + + Rasterized RGB + + + + + Rasterized AAA + + + + + One + + + + + Half + + + + + Konst RGB + + + + + Zero + + + + + + + + + + + + + + 0 + 0 + + + + <html><head/><body><p><span style=" font-weight:600;">Color Output:</span></p></body></html> + + + + + + + + 0 + 0 + + + + Choose the register that this TEV stage will save its output color into. You can use this register as an input source in the next stage. The final stage MUST save its output to the Previous Stage register. + + + + Previous Stage + + + + + Color 0 + + + + + Color 1 + + + + + Color 2 + + + + + + + + + + + 0 + 0 + + + + Alpha Inputs + + + + + + + 0 + 0 + + + + + Previous Stage + + + + + Color 0 + + + + + Color 1 + + + + + Color 2 + + + + + Texture + + + + + Rasterized + + + + + Konst + + + + + Zero + + + + + + + + + 0 + 0 + + + + + Previous Stage + + + + + Color 0 + + + + + Color 1 + + + + + Color 2 + + + + + Texture + + + + + Rasterized + + + + + Konst + + + + + Zero + + + + + + + + + 0 + 0 + + + + + Previous Stage + + + + + Color 0 + + + + + Color 1 + + + + + Color 2 + + + + + Texture + + + + + Rasterized + + + + + Konst + + + + + Zero + + + + + + + + + 0 + 0 + + + + + Previous Stage + + + + + Color 0 + + + + + Color 1 + + + + + Color 2 + + + + + Texture + + + + + Rasterized + + + + + Konst + + + + + Zero + + + + + + + + + + + + + + 0 + 0 + + + + <html><head/><body><p><span style=" font-weight:600;">Alpha Output: </span></p></body></html> + + + + + + + + 0 + 0 + + + + Choose the register that this TEV stage will save its output color into. You can use this register as an input source in the next stage. The final stage MUST save its output to the Previous Stage register. + + + + Previous Stage + + + + + Color 0 + + + + + Color 1 + + + + + Color 2 + + + + + + + + + + + + + + 0 + 0 + + + + Animation + + + + + + + + + 0 + 0 + + + + <html><head/><body><p><span style=" font-weight:600;">Anim Type:</span></p></body></html> + + + + + + + + 0 + 0 + + + + <html><head/><body><p>Choose one of eight available material animation modes.<br/><br/>* <span style=" font-weight:600;">ModelView No Translate</span>: The texture coords will be transformed based on both the camera's position and orientation as well as the model's position in the world. Translation in world space will be ignored. This is ideal for simulating specularity and reflective surfaces using spheremaps.<br/>* <span style=" font-weight:600;">ModelView</span>: This is the same as above, except translation in world space will not be ignored.<br/>* <span style=" font-weight:600;">UV Scroll</span>: A smooth, simple UV scroll will be applied on both axes. Settings for each axis can be set independently from each other.<br/>* <span style=" font-weight:600;">Rotation</span>: This will make the texture rotate.<br/>* <span style=" font-weight:600;">Horizontal Filmstrip</span>: The texture will scroll horizontally. This mode can't be used to scroll on both axes simultaneously; however, you get finer control over how the scroll is performed.<br/>* <span style=" font-weight:600;">Vertical Filmstrip</span>: Identical to Horizontal Filmstrip, except you will get a vertical scroll instead of a horizontal one.<br/>* <span style=" font-weight:600;">Model:</span> The texture will be transformed based on the model's position in the world. The camera's position and orientation will not be taken into account.<br/>* <span style=" font-weight:600;">Mode-Who-Must-Not-Be-Named</span>: This is another mode capable of simulating reflective surfaces. Often used for pipes and ice. Unfortunately, we aren't quite sure what to call this one.</p></body></html> + + + + No Animation + + + + + ModelView No Translation + + + + + ModelView + + + + + UV Scroll + + + + + Rotation + + + + + Horizontal Filmstrip + + + + + Vertical Filmstrip + + + + + Model + + + + + Mode-Who-Must-Not-Be-Named + + + + + + + + + + + + + 0 + 0 + + + + <b>ParamA:</b> + + + + + + + + 0 + 0 + + + + Qt::NoContextMenu + + + + + + 3 + + + -1000.000000000000000 + + + 1000.000000000000000 + + + 0.100000000000000 + + + + + + + + 0 + 0 + + + + <html><head/><body><p><span style=" font-weight:600;">ParamB:</span></p></body></html> + + + + + + + + 0 + 0 + + + + Qt::NoContextMenu + + + + + + 3 + + + -1000.000000000000000 + + + 1000.000000000000000 + + + 0.100000000000000 + + + + + + + + 0 + 0 + + + + <html><head/><body><p><span style=" font-weight:600;">ParamC:</span></p></body></html> + + + + + + + + 0 + 0 + + + + Qt::NoContextMenu + + + + + + 3 + + + -1000.000000000000000 + + + 1000.000000000000000 + + + 0.100000000000000 + + + + + + + + 0 + 0 + + + + <html><head/><body><p><span style=" font-weight:600;">ParamD:</span></p></body></html> + + + + + + + + 0 + 0 + + + + Qt::NoContextMenu + + + + + + 3 + + + -1000.000000000000000 + + + 1000.000000000000000 + + + 0.100000000000000 + + + + + + + + + + + + + + + + + + + 1 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 250 + 0 + + + + + 30 + 30 + + + + + + + + 50 + + + 0 + + + + + + 0 + 0 + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Grid + + + + + + + :/icons/EditorAssets/GridLight.png:/icons/EditorAssets/GridLight.png + + + + 32 + 32 + + + + true + + + true + + + true + + + + + + + Selection Tint + + + border: none; + + + + + + + :/icons/EditorAssets/Highlight.png:/icons/EditorAssets/Highlight.png + + + + 32 + 32 + + + + true + + + true + + + true + + + + + + + Qt::Vertical + + + + + + + + 0 + 0 + + + + + 32 + 16777215 + + + + Mesh Preview + + + + + + + :/icons/EditorAssets/Model Preview.png:/icons/EditorAssets/Model Preview.png + + + + 32 + 32 + + + + true + + + + + + + + 0 + 0 + + + + + 32 + 16777215 + + + + Sphere Preview + + + + + + + :/icons/EditorAssets/Sphere Preview.png:/icons/EditorAssets/Sphere Preview.png + + + + 32 + 32 + + + + true + + + + + + + + 0 + 0 + + + + + 32 + 16777215 + + + + Flat Preview + + + + + + + :/icons/EditorAssets/Square Preview.png:/icons/EditorAssets/Square Preview.png + + + + 32 + 32 + + + + true + + + + + + + Qt::Vertical + + + + + + + + 0 + 0 + + + + + 32 + 32 + + + + Background Color + + + + + + + + 0 + 0 + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + + + 0 + 0 + 1280 + 21 + + + + + File + + + + + + + + + Textures + + + + + + Window + + + + + + + + + toolBar + + + TopToolBarArea + + + false + + + + + + + Open + + + + + Exit + + + Esc + + + + + Save + + + + + Export to DDS + + + + + Export curent model's textures + + + + + + WResourceSelector + QWidget +
WResourceSelector.h
+ 1 +
+ + CEditorGLWidget + QWidget +
CEditorGLWidget.h
+ 1 +
+ + WColorPicker + QWidget +
WColorPicker.h
+ 1 + + colorChanged(QColor) + +
+ + WDraggableSpinBox + QDoubleSpinBox +
WDraggableSpinBox.h
+
+
+ + + + +
diff --git a/UI/CNodeSelection.cpp b/UI/CNodeSelection.cpp new file mode 100644 index 00000000..3c371c82 --- /dev/null +++ b/UI/CNodeSelection.cpp @@ -0,0 +1,58 @@ +#include "CNodeSelection.h" + +CSceneSelection::CSceneSelection(CSceneManager *pScene) +{ + mpScene = pScene; +} + +void CSceneSelection::SelectNode(CSceneNode *pNode) +{ + // There shouldn't be more than one selection per scene, so this should be safe. + if (!pNode->IsSelected()) + { + pNode->SetSelected(true); + mSelectedNodes.push_back(pNode); + } +} + +void CSceneSelection::DeselectNode(CSceneNode *pNode) +{ + if (pNode->IsSelected()) + { + pNode->SetSelected(false); + + for (auto it = mSelectedNodes.begin(); it != mSelectedNodes.end(); it++) + { + if (*it == pNode) + { + mSelectedNodes.erase(it); + break; + } + } + } +} + +u32 CSceneSelection::SelectionSize() +{ + return mSelectedNodes.size(); +} + +CSceneNode* CSceneSelection::NodeByIndex(u32 Index) +{ + if (Index >= SelectionSize()) return nullptr; + return mSelectedNodes[Index]; +} + +void CSceneSelection::ClearSelection() +{ + for (auto it = mSelectedNodes.begin(); it != mSelectedNodes.end(); it++) + (*it)->SetSelected(false); + + mSelectedNodes.clear(); +} + +// ************ OPERATORS ************ +CSceneNode* CSceneSelection::operator[](u32 Index) +{ + return NodeByIndex(Index); +} diff --git a/UI/CNodeSelection.h b/UI/CNodeSelection.h new file mode 100644 index 00000000..19b73c55 --- /dev/null +++ b/UI/CNodeSelection.h @@ -0,0 +1,24 @@ +#ifndef CNODESELECTION_H +#define CNODESELECTION_H + +#include +#include + +class CSceneSelection +{ + CSceneManager *mpScene; + std::vector mSelectedNodes; + +public: + CSceneSelection(CSceneManager *pScene); + void SelectNode(CSceneNode *pNode); + void DeselectNode(CSceneNode *pNode); + u32 SelectionSize(); + CSceneNode* NodeByIndex(u32 Index); + void ClearSelection(); + + // Operators + CSceneNode* operator[](u32 Index); +}; + +#endif // CNODESELECTION_H diff --git a/UI/CSimpleDelegate.cpp b/UI/CSimpleDelegate.cpp new file mode 100644 index 00000000..71665126 --- /dev/null +++ b/UI/CSimpleDelegate.cpp @@ -0,0 +1,12 @@ +#include "CSimpleDelegate.h" + +CSimpleDelegate::CSimpleDelegate() +{ + +} + +CSimpleDelegate::~CSimpleDelegate() +{ + +} + diff --git a/UI/CSimpleDelegate.h b/UI/CSimpleDelegate.h new file mode 100644 index 00000000..1fb3acca --- /dev/null +++ b/UI/CSimpleDelegate.h @@ -0,0 +1,12 @@ +#ifndef CSIMPLEDELEGATE_H +#define CSIMPLEDELEGATE_H + + +class CSimpleDelegate +{ +public: + CSimpleDelegate(); + ~CSimpleDelegate(); +}; + +#endif // CSIMPLEDELEGATE_H diff --git a/UI/CStartWindow.cpp b/UI/CStartWindow.cpp new file mode 100644 index 00000000..f52a8c43 --- /dev/null +++ b/UI/CStartWindow.cpp @@ -0,0 +1,181 @@ +#include "CStartWindow.h" +#include "ui_CStartWindow.h" + +#include +#include + +#include "CModelEditorWindow.h" +#include "CWorldEditor.h" +#include + +CStartWindow::CStartWindow(QWidget *parent) : + QMainWindow(parent), + ui(new Ui::CStartWindow) +{ + ui->setupUi(this); + mpWorld = nullptr; + mpWorldEditor = new CWorldEditor(0); + mpModelEditor = new CModelEditorWindow(this); +} + +CStartWindow::~CStartWindow() +{ + delete ui; + delete mpWorldEditor; + delete mpModelEditor; +} + +void CStartWindow::on_actionOpen_MLVL_triggered() +{ + QString WorldFile = QFileDialog::getOpenFileName(this, "Open MLVL", "", "Metroid Prime World (*.MLVL)"); + if (WorldFile.isEmpty()) return; + + gResCache.SetFolder(StringUtil::GetFileDirectory(WorldFile.toStdString())); + mpWorld = (CWorld*) gResCache.GetResource(WorldFile.toStdString()); + mWorldToken = CToken(mpWorld); + mpWorldEditor->close(); + + FillWorldUI(); +} + +void CStartWindow::FillWorldUI() +{ + + CStringTable *pWorldName = mpWorld->GetWorldName(); + if (pWorldName) + { + std::wstring WorldName = pWorldName->GetString("ENGL", 0); + ui->WorldNameLabel->setText( QString("") + QString::fromStdWString(WorldName) + QString("") ); + ui->WorldNameSTRGLineEdit->setText( QString::fromStdString(pWorldName->Source()) ); + } + else + { + ui->WorldNameLabel->clear(); + ui->WorldNameSTRGLineEdit->clear(); + } + + CStringTable *pDarkWorldName = mpWorld->GetDarkWorldName(); + if (pDarkWorldName) + ui->DarkWorldNameSTRGLineEdit->setText( QString::fromStdString(pDarkWorldName->Source()) ); + else + ui->DarkWorldNameSTRGLineEdit->clear(); + + CModel *pDefaultSkybox = mpWorld->GetDefaultSkybox(); + if (pDefaultSkybox) + ui->DefaultSkyboxCMDLLineEdit->setText( QString::fromStdString(pDefaultSkybox->Source()) ); + else + ui->DefaultSkyboxCMDLLineEdit->clear(); + + CResource *pSaveWorld = mpWorld->GetSaveWorld(); + if (pSaveWorld) + ui->WorldSAVWLineEdit->setText( QString::fromStdString(pSaveWorld->Source()) ); + else + ui->WorldSAVWLineEdit->clear(); + + CResource *pMapWorld = mpWorld->GetMapWorld(); + if (pMapWorld) + ui->WorldMAPWLineEdit->setText( QString::fromStdString(pMapWorld->Source()) ); + else + ui->WorldMAPWLineEdit->clear(); + + u32 NumAreas = mpWorld->GetNumAreas(); + ui->AreaSelectComboBox->blockSignals(true); + ui->AreaSelectComboBox->clear(); + ui->AreaSelectComboBox->blockSignals(false); + ui->AreaSelectComboBox->setDisabled(false); + for (u32 iArea = 0; iArea < NumAreas; iArea++) + { + CStringTable *pAreaName = mpWorld->GetAreaName(iArea); + QString AreaName = (pAreaName != nullptr) ? QString::fromStdWString(pAreaName->GetString("ENGL", 0)) : QString("!!") + QString::fromStdString(mpWorld->GetAreaInternalName(iArea)); + ui->AreaSelectComboBox->addItem(AreaName); + } +} + +void CStartWindow::FillAreaUI() +{ + ui->AreaNameLineEdit->setDisabled(false); + ui->AreaNameSTRGLineEdit->setDisabled(false); + ui->AreaMREALineEdit->setDisabled(false); + ui->AttachedAreasList->setDisabled(false); + + ui->AreaSelectComboBox->blockSignals(true); + ui->AreaSelectComboBox->setCurrentIndex(mSelectedAreaIndex); + ui->AreaSelectComboBox->blockSignals(false); + + ui->AreaNameLineEdit->setText( QString::fromStdString(mpWorld->GetAreaInternalName(mSelectedAreaIndex))); + + CStringTable *pAreaName = mpWorld->GetAreaName(mSelectedAreaIndex); + if (pAreaName) + ui->AreaNameSTRGLineEdit->setText( QString::fromStdString( pAreaName->Source() )); + else + ui->AreaNameSTRGLineEdit->clear(); + + u64 MREA = mpWorld->GetAreaResourceID(mSelectedAreaIndex); + std::string MREAStr; + if (MREA & 0xFFFFFFFF00000000) + MREAStr = StringUtil::ResToStr(MREA); + else + MREAStr = StringUtil::ResToStr( (u32) MREA ); + + ui->AreaMREALineEdit->setText(QString::fromStdString(MREAStr) + QString(".MREA") ); + + u32 NumAttachedAreas = mpWorld->GetAreaAttachedCount(mSelectedAreaIndex); + ui->AttachedAreasList->clear(); + + for (u32 iArea = 0; iArea < NumAttachedAreas; iArea++) + { + u32 AttachedAreaIndex = mpWorld->GetAreaAttachedID(mSelectedAreaIndex, iArea); + + CStringTable *AttachedAreaSTRG = mpWorld->GetAreaName(AttachedAreaIndex); + QString AttachedStr; + + if (AttachedAreaSTRG) + AttachedStr = QString::fromStdWString(AttachedAreaSTRG->GetString("ENGL", 0) ); + else + AttachedStr = QString("!!") + QString::fromStdString(mpWorld->GetAreaInternalName(AttachedAreaIndex)); + + ui->AttachedAreasList->addItem(AttachedStr); + } + + ui->LaunchWorldEditorButton->setDisabled(false); +} + +void CStartWindow::on_AreaSelectComboBox_currentIndexChanged(int index) +{ + mSelectedAreaIndex = index; + FillAreaUI(); +} + +void CStartWindow::on_AttachedAreasList_doubleClicked(const QModelIndex &index) +{ + mSelectedAreaIndex = mpWorld->GetAreaAttachedID(mSelectedAreaIndex, index.row()); + FillAreaUI(); +} + +void CStartWindow::on_LaunchWorldEditorButton_clicked() +{ + u64 AreaID = mpWorld->GetAreaResourceID(mSelectedAreaIndex); + CGameArea *pArea = (CGameArea*) gResCache.GetResource(AreaID, "MREA"); + + if (!pArea) + { + QMessageBox::warning(this, "Error", "Couldn't load area!"); + mpWorldEditor->close(); + } + + else + { + mpWorld->SetAreaLayerInfo(pArea, mSelectedAreaIndex); + mpWorldEditor->SetArea(mpWorld, pArea); + mpWorldEditor->setWindowModality(Qt::WindowModal); + mpWorldEditor->showMaximized(); + } + + gResCache.Clean(); +} + +void CStartWindow::on_actionLaunch_model_viewer_triggered() +{ + mpModelEditor->setWindowModality(Qt::ApplicationModal); + mpModelEditor->show(); +} diff --git a/UI/CStartWindow.h b/UI/CStartWindow.h new file mode 100644 index 00000000..4aec578f --- /dev/null +++ b/UI/CStartWindow.h @@ -0,0 +1,46 @@ +#ifndef PWESTARTWINDOW_H +#define PWESTARTWINDOW_H + +#include +#include +#include +#include "CModelEditorWindow.h" +#include "CWorldEditor.h" + +namespace Ui { +class CStartWindow; +} + +class CStartWindow : public QMainWindow +{ + Q_OBJECT + Ui::CStartWindow *ui; + + CWorld *mpWorld; + CToken mWorldToken; + u32 mSelectedAreaIndex; + + CWorldEditor *mpWorldEditor; + CModelEditorWindow *mpModelEditor; + +public: + explicit CStartWindow(QWidget *parent = 0); + ~CStartWindow(); + +private slots: + void on_actionOpen_MLVL_triggered(); + + void on_AreaSelectComboBox_currentIndexChanged(int index); + + void on_AttachedAreasList_doubleClicked(const QModelIndex &index); + + void on_LaunchWorldEditorButton_clicked(); + + void on_actionLaunch_model_viewer_triggered(); + +private: + void FillWorldUI(); + void FillAreaUI(); +}; + +#endif // PWESTARTWINDOW_H diff --git a/UI/CStartWindow.ui b/UI/CStartWindow.ui new file mode 100644 index 00000000..9bff275e --- /dev/null +++ b/UI/CStartWindow.ui @@ -0,0 +1,260 @@ + + + CStartWindow + + + + 0 + 0 + 327 + 487 + + + + Prime World Editor + + + + + + + + + + + + + + + + + 0 + 0 + + + + World + + + + + + + + World Name STRG + + + + + + + + + + Dark World Name STRG + + + + + + + + + + Default Skybox CMDL + + + + + + + + + + World SAVW + + + + + + + + + + World MAPW + + + + + + + + + + + + Area + + + false + + + + + + false + + + + 0 + 0 + + + + + + + + + + Area Name + + + + + + + false + + + + + + + Area Name STRG + + + + + + + false + + + + + + + Area MREA + + + + + + + false + + + + + + + + + QLayout::SetDefaultConstraint + + + 0 + + + + + Attached Areas + + + + + + + false + + + + + + + + + + + + + + + + + false + + + + 0 + 0 + + + + Launch World Editor + + + + + + + + + 0 + 0 + 327 + 21 + + + + + File + + + + + + + + Tools + + + + + + + + + Create new... + + + + + Open + + + + + Extract PAK + + + + + Launch model viewer + + + + + + diff --git a/UI/CWorldEditor.cpp b/UI/CWorldEditor.cpp new file mode 100644 index 00000000..e99cd090 --- /dev/null +++ b/UI/CWorldEditor.cpp @@ -0,0 +1,491 @@ +#include "CWorldEditor.h" +#include "ui_CWorldEditor.h" +#include "CEditorGLWidget.h" +#include +#include +#include +#include +#include +#include + +#include "WorldEditor/CLayerEditor.h" +#include "WorldEditor/WModifyTab.h" +#include "WorldEditor/WInstancesTab.h" + +CWorldEditor::CWorldEditor(QWidget *parent) : + QMainWindow(parent), + ui(new Ui::CWorldEditor) +{ + Log::Write("Creating World Editor"); + ui->setupUi(this); + + mpRenderer = new CRenderer(); + mpRenderer->SetClearColor(CColor::skBlack); + QSize ViewSize = ui->MainViewport->size(); + mpRenderer->SetViewportSize(ViewSize.width(), ViewSize.height()); + + mpSceneManager = new CSceneManager(); + + mpArea = nullptr; + mpWorld = nullptr; + mpHoverNode = nullptr; + //mpInstanceModel = new CInstanceModel(this); + //ui->InstancesTreeView->setModel(mpInstanceModel); + mDrawSky = true; + + mFrameCount = 0; + mFPSTimer.Start(); + + // Create blank title bar with some space to allow for dragging the dock + QWidget *pOldTitleBar = ui->MainDock->titleBarWidget(); + + QWidget *pNewTitleBar = new QWidget(ui->MainDock); + QVBoxLayout *pTitleLayout = new QVBoxLayout(pNewTitleBar); + pTitleLayout->setSpacing(10); + pNewTitleBar->setLayout(pTitleLayout); + ui->MainDock->setTitleBarWidget(pNewTitleBar); + + delete pOldTitleBar; + + ResetHover(); + + ui->ModifyTabContents->SetEditor(this); + ui->InstancesTabContents->SetEditor(this, mpSceneManager); + ui->MainDock->installEventFilter(this); + connect(ui->MainViewport, SIGNAL(PreRender()), this, SLOT(ViewportPreRender())); + connect(ui->MainViewport, SIGNAL(Render(CCamera&)), this, SLOT(ViewportRender(CCamera&))); + connect(ui->MainViewport, SIGNAL(ViewportResized(int,int)), this, SLOT(SetViewportSize(int,int))); + connect(ui->MainViewport, SIGNAL(frameSwapped()), this, SLOT(ViewportPostRender())); + connect(ui->MainViewport, SIGNAL(MouseClick(QMouseEvent*)), this, SLOT(ViewportMouseClick(QMouseEvent*))); +} + +CWorldEditor::~CWorldEditor() +{ + delete ui; +} + +bool CWorldEditor::eventFilter(QObject *pObj, QEvent *pEvent) +{ + if (pObj == ui->MainDock) + { + if (pEvent->type() == QEvent::Resize) + { + UpdateSelectionUI(); + } + } + + return false; +} + +void CWorldEditor::SetArea(CWorld *pWorld, CGameArea *pArea) +{ + ResetHover(); + ClearSelection(); + ui->ModifyTabContents->ClearUI(); + ui->ModifyTabContents->ClearCachedEditors(); + ui->InstancesTabContents->SetMaster(nullptr); + ui->InstancesTabContents->SetArea(pArea); + + // Clear old area - hack until better world/area loader is implemented + if ((mpArea) && (pArea != mpArea)) + mpArea->ClearScriptLayers(); + + // Load new area + mpArea = pArea; + mpWorld = pWorld; + mAreaToken = CToken(pArea); + mWorldToken = CToken(pWorld); + + mpSceneManager->SetActiveWorld(pWorld); + mpSceneManager->SetActiveArea(pArea); + + // Snap camera to location of area + CTransform4f AreaTransform = pArea->GetTransform(); + CVector3f AreaPosition(AreaTransform[0][3], AreaTransform[1][3], AreaTransform[2][3]); + ui->MainViewport->Camera().Snap(AreaPosition); + + // Default bloom to ON for Metroid Prime 3; disable for other games + if (mpWorld->Version() == eCorruption) + { + ui->menuBloom->setEnabled(true); + on_ActionBloom_triggered(); + } + + else + { + ui->menuBloom->setEnabled(false); + on_ActionNoBloom_triggered(); + } + + // Set up sidebar tabs + CMasterTemplate *pMaster = CMasterTemplate::GetMasterForGame(mpWorld->Version()); + ui->InstancesTabContents->SetMaster(pMaster); +} + +void CWorldEditor::ViewportRayCast(CRay Ray) +{ + if (!ui->MainViewport->IsMouseInputActive()) + { + SRayIntersection Result = mpSceneManager->SceneRayCast(Ray); + + if (Result.Hit) + { + if (mpHoverNode) + mpHoverNode->SetMouseHovering(false); + + mpHoverNode = Result.pNode; + mpHoverNode->SetMouseHovering(true); + + mHoverPoint = Ray.PointOnRay(Result.Distance); + } + else + ResetHover(); + } + else + ResetHover(); +} + +CRenderer* CWorldEditor::Renderer() +{ + return mpRenderer; +} + +CSceneManager* CWorldEditor::Scene() +{ + return mpSceneManager; +} + +CGameArea* CWorldEditor::ActiveArea() +{ + return mpArea; +} + +// ************ SELECTION ************ +void CWorldEditor::SelectNode(CSceneNode *pNode) +{ + if (!pNode->IsSelected()) + { + pNode->SetSelected(true); + mSelectedNodes.push_back(pNode); + mSelectionAABox.ExpandBounds(pNode->AABox()); + + if (pNode->NodeType() == eScriptNode) + { + CScriptNode *pScript = static_cast(pNode); + if (pScript->HasPreviewVolume()) + mSelectionAABox.ExpandBounds(pScript->PreviewVolumeAABox()); + } + } + + UpdateSelectionUI(); +} + +void CWorldEditor::DeselectNode(CSceneNode *pNode) +{ + if (pNode->IsSelected()) + { + pNode->SetSelected(false); + + for (auto it = mSelectedNodes.begin(); it != mSelectedNodes.end(); it++) + { + if (*it == pNode) + { + mSelectedNodes.erase(it); + break; + } + } + } + + RecalculateSelectionBounds(); + UpdateSelectionUI(); +} + +void CWorldEditor::ClearSelection() +{ + for (auto it = mSelectedNodes.begin(); it != mSelectedNodes.end(); it++) + (*it)->SetSelected(false); + + mSelectedNodes.clear(); + mSelectionAABox = CAABox::skInfinite; + UpdateSelectionUI(); +} + +// ************ SLOTS ************ +void CWorldEditor::ViewportMouseDrag(QMouseEvent *pEvent) +{ + // todo: gizmo translate/rotate/scale implementation +} + +void CWorldEditor::ViewportMouseClick(QMouseEvent *pEvent) +{ + // Process left click (button press) + if (pEvent->button() == Qt::LeftButton) + { + bool ValidNode = ((mpHoverNode) && (mpHoverNode->NodeType() != eStaticNode)); + bool AltPressed = ((pEvent->modifiers() & Qt::AltModifier) != 0); + bool CtrlPressed = ((pEvent->modifiers() & Qt::ControlModifier) != 0); + + // Alt pressed - deselect object + if (AltPressed) + { + // No valid node selected - do nothing + if (!ValidNode) + return; + + DeselectNode(mpHoverNode); + } + + // Other - select object + else + { + // Control not pressed - clear existing selection + if (!CtrlPressed) + ClearSelection(); + + // Add hover node to selection + if (ValidNode) + SelectNode(mpHoverNode); + } + + UpdateSelectionUI(); + } + + // Later, possibly expand to context menu creation for right-click +} + +// ************ SLOTS ************ +void CWorldEditor::ViewportPreRender() +{ + // Perform raycast + if (ui->MainViewport->underMouse()) + ViewportRayCast(ui->MainViewport->CastRay()); + else + ResetHover(); + + // Start frame + mFrameTimer.Start(); + mpRenderer->BeginFrame(); +} + +void CWorldEditor::ViewportRender(CCamera& Camera) +{ + mpSceneManager->AddSceneToRenderer(mpRenderer); + + if (mDrawSky) + { + CModel *pSky = mpSceneManager->GetActiveSkybox(); + if (pSky) mpRenderer->RenderSky(pSky, Camera.Position()); + } + + mpRenderer->RenderScene(Camera); + mpRenderer->EndFrame(); + mFrameTimer.Stop(); + mFrameCount++; +} + +void CWorldEditor::ViewportPostRender() +{ + // Update UI with raycast results + UpdateCursor(); + UpdateStatusBar(); +} + +void CWorldEditor::SetViewportSize(int Width, int Height) +{ + mpRenderer->SetViewportSize(Width, Height); +} + +// ************ PRIVATE ************ +void CWorldEditor::RecalculateSelectionBounds() +{ + mSelectionAABox = CAABox::skInfinite; + + for (auto it = mSelectedNodes.begin(); it != mSelectedNodes.end(); it++) + { + mSelectionAABox.ExpandBounds( (*it)->AABox() ); + + if ((*it)->NodeType() == eScriptNode) + { + CScriptNode *pScript = static_cast(*it); + if (pScript->HasPreviewVolume()) + mSelectionAABox.ExpandBounds(pScript->PreviewVolumeAABox()); + } + } +} + +void CWorldEditor::ResetHover() +{ + if (mpHoverNode) mpHoverNode->SetMouseHovering(false); + mpHoverNode = nullptr; + mHoverPoint = CVector3f::skZero; +} + +void CWorldEditor::UpdateCursor() +{ + if (ui->MainViewport->IsCursorVisible()) + { + if ((mpHoverNode) && (mpHoverNode->NodeType() != eStaticNode)) + ui->MainViewport->SetCursorState(Qt::PointingHandCursor); + else + ui->MainViewport->SetCursorState(Qt::ArrowCursor); + } +} + +void CWorldEditor::UpdateStatusBar() +{ + // Would be cool to do more frequent status bar updates with more info. Unfortunately, this causes lag. + QString StatusText = ""; + + if (mpHoverNode) + { + if (mpHoverNode->NodeType() != eStaticNode) + StatusText = QString::fromStdString(mpHoverNode->Name()); + } + + if (ui->statusbar->currentMessage() != StatusText) + ui->statusbar->showMessage(StatusText); +} + +void CWorldEditor::UpdateSelectionUI() +{ + // Update sidebar + ui->ModifyTabContents->GenerateUI(mSelectedNodes); + + // Update selection info text + QString SelectionText; + + if (mSelectedNodes.size() == 1) + SelectionText = QString::fromStdString(mSelectedNodes.front()->Name()); + else if (mSelectedNodes.size() > 1) + SelectionText = QString("%1 objects selected").arg(mSelectedNodes.size()); + + QFontMetrics Metrics(ui->SelectionInfoLabel->font()); + SelectionText = Metrics.elidedText(SelectionText, Qt::ElideRight, ui->SelectionInfoFrame->width() - 10); + ui->SelectionInfoLabel->setText(SelectionText); +} + +// ************ ACTIONS ************ +// These functions are from "Go to slot" in the designer +void CWorldEditor::on_ActionDrawWorld_triggered() +{ + mpSceneManager->SetWorld(ui->ActionDrawWorld->isChecked()); +} + +void CWorldEditor::on_ActionDrawCollision_triggered() +{ + mpSceneManager->SetCollision(ui->ActionDrawCollision->isChecked()); +} + +void CWorldEditor::on_ActionDrawObjects_triggered() +{ + mpSceneManager->SetObjects(ui->ActionDrawObjects->isChecked()); +} + +void CWorldEditor::on_ActionDrawLights_triggered() +{ + mpSceneManager->SetLights(ui->ActionDrawLights->isChecked()); +} + +void CWorldEditor::on_ActionDrawSky_triggered() +{ + mDrawSky = ui->ActionDrawSky->isChecked(); +} + +void CWorldEditor::on_ActionNoLighting_triggered() +{ + CGraphics::sLightMode = CGraphics::NoLighting; + ui->ActionNoLighting->setChecked(true); + ui->ActionBasicLighting->setChecked(false); + ui->ActionWorldLighting->setChecked(false); +} + +void CWorldEditor::on_ActionBasicLighting_triggered() +{ + CGraphics::sLightMode = CGraphics::BasicLighting; + ui->ActionNoLighting->setChecked(false); + ui->ActionBasicLighting->setChecked(true); + ui->ActionWorldLighting->setChecked(false); +} + +void CWorldEditor::on_ActionWorldLighting_triggered() +{ + CGraphics::sLightMode = CGraphics::WorldLighting; + ui->ActionNoLighting->setChecked(false); + ui->ActionBasicLighting->setChecked(false); + ui->ActionWorldLighting->setChecked(true); +} + +void CWorldEditor::on_ActionNoBloom_triggered() +{ + mpRenderer->SetBloom(CRenderer::eNoBloom); + ui->ActionNoBloom->setChecked(true); + ui->ActionBloomMaps->setChecked(false); + ui->ActionBloom->setChecked(false); +} + +void CWorldEditor::on_ActionBloomMaps_triggered() +{ + mpRenderer->SetBloom(CRenderer::eBloomMaps); + ui->ActionNoBloom->setChecked(false); + ui->ActionBloomMaps->setChecked(true); + ui->ActionBloom->setChecked(false); +} + +void CWorldEditor::on_ActionBloom_triggered() +{ + mpRenderer->SetBloom(CRenderer::eBloom); + ui->ActionNoBloom->setChecked(false); + ui->ActionBloomMaps->setChecked(false); + ui->ActionBloom->setChecked(true); +} + +void CWorldEditor::on_ActionZoomOnSelection_triggered() +{ + static const float skDistScale = 2.5f; + static const float skAreaDistScale = 0.8f; + + CCamera& Camera = ui->MainViewport->Camera(); + CVector3f CamDir = Camera.GetDirection(); + CVector3f NewPos; + + // Zoom on selection + if (mSelectedNodes.size() != 0) + { + CVector3f Min = mSelectionAABox.Min(); + CVector3f Max = mSelectionAABox.Max(); + float Dist = ((Max.x - Min.x) + (Max.y - Min.y) + (Max.z - Min.z)) / 3.f; + //float Dist = mSelectionAABox.Min().Distance(mSelectionAABox.Max()); + NewPos = mSelectionAABox.Center() + (CamDir * -(Dist * skDistScale)); + } + + // Zoom on area + else + { + CAABox AreaBox = mpArea->AABox(); + CVector3f Min = AreaBox.Min(); + CVector3f Max = AreaBox.Max(); + float Dist = ((Max.x - Min.x) + (Max.y - Min.y) + (Max.z - Min.z)) / 3.f; + //float Dist = AreaBox.Min().Distance(AreaBox.Max()); + NewPos = AreaBox.Center() + (CamDir * -(Dist * skAreaDistScale)); + } + + Camera.SetPosition(NewPos); +} + +void CWorldEditor::on_ActionDisableBackfaceCull_triggered() +{ + mpRenderer->ToggleBackfaceCull(!ui->ActionDisableBackfaceCull->isChecked()); +} + +void CWorldEditor::on_ActionDisableAlpha_triggered() +{ + mpRenderer->ToggleAlphaDisabled(ui->ActionDisableAlpha->isChecked()); +} + +void CWorldEditor::on_ActionEditLayers_triggered() +{ + // Launch layer editor + CLayerEditor Editor(this); + Editor.SetArea(mpArea); + Editor.exec(); +} diff --git a/UI/CWorldEditor.h b/UI/CWorldEditor.h new file mode 100644 index 00000000..d3284b4f --- /dev/null +++ b/UI/CWorldEditor.h @@ -0,0 +1,94 @@ +#ifndef CWORLDEDITOR_H +#define CWORLDEDITOR_H + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Ui { +class CWorldEditor; +} + +class CWorldEditor : public QMainWindow +{ + Q_OBJECT + CRenderer *mpRenderer; + CSceneManager *mpSceneManager; + CCamera mCamera; + CGameArea *mpArea; + CWorld *mpWorld; + CToken mAreaToken; + CToken mWorldToken; + CTimer mFrameTimer; + bool mDrawSky; + + CVector3f mHoverPoint; + CSceneNode *mpHoverNode; + std::list mSelectedNodes; + CAABox mSelectionAABox; + + CTimer mFPSTimer; + int mFrameCount; + +public: + explicit CWorldEditor(QWidget *parent = 0); + ~CWorldEditor(); + bool eventFilter(QObject *pObj, QEvent *pEvent); + void SetArea(CWorld *pWorld, CGameArea *pArea); + void ViewportRayCast(CRay Ray); + CRenderer* Renderer(); + CSceneManager* Scene(); + CGameArea* ActiveArea(); + + // Selection + void SelectNode(CSceneNode *pNode); + void DeselectNode(CSceneNode *pNode); + void ClearSelection(); + +public slots: + void ViewportPreRender(); + void ViewportRender(CCamera& Camera); + void ViewportPostRender(); + void ViewportMouseDrag(QMouseEvent *pEvent); + void ViewportMouseClick(QMouseEvent *pEvent); + void SetViewportSize(int Width, int Height); + +private: + Ui::CWorldEditor *ui; + void RecalculateSelectionBounds(); + void ResetHover(); + void UpdateCursor(); + + // UI + void OnSidebarResize(); + void UpdateSelectionUI(); + void UpdateStatusBar(); + +private slots: + void on_ActionDrawWorld_triggered(); + void on_ActionDrawCollision_triggered(); + void on_ActionDrawObjects_triggered(); + void on_ActionDrawLights_triggered(); + void on_ActionDrawSky_triggered(); + void on_ActionNoLighting_triggered(); + void on_ActionBasicLighting_triggered(); + void on_ActionWorldLighting_triggered(); + void on_ActionNoBloom_triggered(); + void on_ActionBloomMaps_triggered(); + void on_ActionBloom_triggered(); + void on_ActionZoomOnSelection_triggered(); + void on_ActionDisableBackfaceCull_triggered(); + void on_ActionDisableAlpha_triggered(); + void on_ActionEditLayers_triggered(); +}; + +#endif // CWORLDEDITOR_H diff --git a/UI/CWorldEditor.ui b/UI/CWorldEditor.ui new file mode 100644 index 00000000..1bd4907b --- /dev/null +++ b/UI/CWorldEditor.ui @@ -0,0 +1,608 @@ + + + CWorldEditor + + + + 0 + 0 + 1280 + 720 + + + + Prime World Editor + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + 0 + 0 + 1280 + 21 + + + + + File + + + + + + + Window + + + + + Models + + + + + + Edit + + + + + + + View + + + + Lighting + + + + + + + + Bloom + + + + + + + + + + + + + + + + + + + + + + Tools + + + + + + + + + + + + + + toolBar_2 + + + TopToolBarArea + + + false + + + + + + + QDockWidget::DockWidgetMovable + + + Qt::LeftDockWidgetArea|Qt::RightDockWidgetArea + + + + + + 2 + + + + + + + QFrame::Panel + + + QFrame::Sunken + + + 1 + + + 0 + + + + 3 + + + 3 + + + 3 + + + 3 + + + + + QFrame::Plain + + + 1 + + + 0 + + + + + + + + + + + + + QTabWidget::North + + + QTabWidget::Rounded + + + 1 + + + + 24 + 24 + + + + false + + + + + :/icons/EditorAssets/Create.png:/icons/EditorAssets/Create.png + + + + + + Create + + + + + + :/icons/EditorAssets/Modify.png:/icons/EditorAssets/Modify.png + + + + + + Modify + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + :/icons/EditorAssets/Instances.png:/icons/EditorAssets/Instances.png + + + + + + Instances + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + :/icons/EditorAssets/Display.png:/icons/EditorAssets/Display.png + + + + + + Display + + + + + + :/icons/EditorAssets/World.png:/icons/EditorAssets/World.png + + + + + + World + + + + + + + + + + toolBar + + + TopToolBarArea + + + true + + + + + + + + + + + Open + + + + + Save + + + + + true + + + + :/icons/EditorAssets/Translate.png:/icons/EditorAssets/Translate.png + + + Translate + + + Translate + + + + + true + + + + :/icons/EditorAssets/Rotate.png:/icons/EditorAssets/Rotate.png + + + Rotate + + + Rotate + + + + + true + + + + :/icons/EditorAssets/Scale.png:/icons/EditorAssets/Scale.png + + + Scale + + + Scale + + + + + Open Model Viewer + + + + + + :/icons/EditorAssets/Link.png:/icons/EditorAssets/Link.png + + + Link + + + Link + + + + + + :/icons/EditorAssets/Unlink.png:/icons/EditorAssets/Unlink.png + + + Unlink + + + Unlink + + + + + Undo + + + Ctrl+Z + + + + + Redo + + + Ctrl+Y + + + + + true + + + true + + + World + + + 1 + + + + + true + + + Collision + + + 2 + + + + + true + + + true + + + Objects + + + 3 + + + + + true + + + true + + + Lights + + + 4 + + + + + true + + + true + + + Sky + + + 5 + + + + + true + + + None + + + Ctrl+1 + + + + + true + + + Basic + + + Basic + + + Ctrl+2 + + + + + true + + + World + + + Ctrl+3 + + + + + true + + + None + + + + + true + + + Bloom Maps + + + + + true + + + Bloom + + + + + Zoom On Selection + + + Z + + + + + true + + + Disable Backface Culling + + + + + true + + + Disable Alpha + + + + + Edit Layers + + + + + + CEditorGLWidget + QWidget +
CEditorGLWidget.h
+ 1 +
+ + WModifyTab + QWidget +
WorldEditor/WModifyTab.h
+ 1 +
+ + WInstancesTab + QWidget +
WorldEditor/WInstancesTab.h
+ 1 +
+
+ + + + +
diff --git a/UI/CWorldEditorWindow.cpp b/UI/CWorldEditorWindow.cpp new file mode 100644 index 00000000..4d44be7b --- /dev/null +++ b/UI/CWorldEditorWindow.cpp @@ -0,0 +1,300 @@ +#include + +#include +#include + +#include "CWorldEditorWindow.h" +#include +#include +#include +#include +#include +#include +#include +#include "CEditorGLWidget.h" + +CWorldEditorWindow::CWorldEditorWindow(QWidget *parent) : + QMainWindow(parent), + ui(new Ui::CWorldEditorWindow) +{ + ui->setupUi(this); + + mpRenderer = new CRenderer(); + mpRenderer->ToggleGrid(false); + mpActiveWorld = nullptr; + mpActiveArea = nullptr; + mRendererInitialized = false; + mpSceneManager = new CSceneManager(); + mCamera.Snap(CVector3f(0, 3, 1)); + mCameraMode = eFreeCamera; + mViewportKeysPressed = 0; + mShouldDrawSky = true; + + connect(ui->CentralGLWidget, SIGNAL(ViewportResized(int,int)), this, SLOT(SetViewportSize(int,int))); + connect(ui->CentralGLWidget, SIGNAL(PaintViewport(double)), this, SLOT(PaintViewport(double))); + connect(ui->CentralGLWidget, SIGNAL(MouseClicked(QMouseEvent*)), this, SLOT(OnViewportRayCast(QMouseEvent*))); + connect(ui->CentralGLWidget, SIGNAL(MouseMoved(QMouseEvent*, float, float)), this, SLOT(OnViewportMouseMove(QMouseEvent*, float, float))); + connect(ui->CentralGLWidget, SIGNAL(KeyPressed(QKeyEvent*)), this, SLOT(OnViewportKeyPress(QKeyEvent*))); + connect(ui->CentralGLWidget, SIGNAL(KeyReleased(QKeyEvent*)), this, SLOT(OnViewportKeyRelease(QKeyEvent*))); + connect(ui->CentralGLWidget, SIGNAL(WheelScroll(int)), this, SLOT(OnViewportWheelScroll(int))); +} + +CWorldEditorWindow::~CWorldEditorWindow() +{ + delete ui; + delete mpRenderer; + delete mpSceneManager; +} + +void CWorldEditorWindow::InitializeWorld(CWorld *pWorld, CGameArea *pArea) +{ + mpSceneManager->SetActiveWorld(pWorld); + mpSceneManager->SetActiveArea(pArea); + mpRenderer->SetClearColor(CColor::skWhite); + + // Snap camera to location of area + CTransform4f AreaTransform = pArea->GetTransform(); + CVector3f AreaPosition(AreaTransform[0][3], AreaTransform[1][3], AreaTransform[2][3]); + mCamera.Snap(AreaPosition); + + // Set bloom based on world version + if (pWorld != mpActiveWorld) + { + if (pWorld->Version() != eCorruption) + { + ui->menuBloom->setEnabled(false); + on_actionDisableBloom_triggered(); + } + + else + { + ui->menuBloom->setEnabled(true); + on_actionEnableBloom_triggered(); + } + } + + mpActiveWorld = pWorld; + mpActiveArea = pArea; +} + +// ************ PUBLIC SLOTS ************ +void CWorldEditorWindow::PaintViewport(double DeltaTime) +{ + if (!mRendererInitialized) + { + mpRenderer->Init(); + mRendererInitialized = true; + } + + mCamera.ProcessKeyInput((EKeyInputs) mViewportKeysPressed, DeltaTime); + mCamera.LoadMatrices(); + mpRenderer->BeginFrame(); + mpSceneManager->AddSceneToRenderer(mpRenderer); + + + if (mShouldDrawSky) + { + CModel *pSky = mpSceneManager->GetActiveSkybox(); + if (pSky) mpRenderer->RenderSky(pSky, mCamera.Position()); + } + + mpRenderer->RenderScene(mCamera); + mpRenderer->EndFrame(); +} + +void CWorldEditorWindow::SetViewportSize(int Width, int Height) +{ + mViewportAspectRatio = (float) Width / (float) Height; + mpRenderer->SetViewportSize(Width, Height); +} + +void CWorldEditorWindow::OnViewportMouseMove(QMouseEvent *pEvent, float XMovement, float YMovement) +{ + int KeyInputs = 0; + if (pEvent->modifiers() & Qt::ControlModifier) KeyInputs |= eCtrlKey; + if (pEvent->modifiers() & Qt::AltModifier) KeyInputs |= eAltKey; + + int MouseInputs = 0; + if (pEvent->buttons() & Qt::LeftButton) MouseInputs |= eLeftButton; + if (pEvent->buttons() & Qt::MiddleButton) MouseInputs |= eMiddleButton; + if (pEvent->buttons() & Qt::RightButton) MouseInputs |= eRightButton; + + mCamera.ProcessMouseInput((EKeyInputs) KeyInputs, (EMouseInputs) MouseInputs, XMovement, YMovement); +} + +void CWorldEditorWindow::OnViewportKeyPress(QKeyEvent *pEvent) +{ + switch (pEvent->key()) + { + case Qt::Key_Q: mViewportKeysPressed |= eQKey; break; + case Qt::Key_W: mViewportKeysPressed |= eWKey; break; + case Qt::Key_E: mViewportKeysPressed |= eEKey; break; + case Qt::Key_A: mViewportKeysPressed |= eAKey; break; + case Qt::Key_S: mViewportKeysPressed |= eSKey; break; + case Qt::Key_D: mViewportKeysPressed |= eDKey; break; + } +} + +void CWorldEditorWindow::OnViewportKeyRelease(QKeyEvent *pEvent) +{ + switch (pEvent->key()) + { + case Qt::Key_Q: mViewportKeysPressed &= ~eQKey; break; + case Qt::Key_W: mViewportKeysPressed &= ~eWKey; break; + case Qt::Key_E: mViewportKeysPressed &= ~eEKey; break; + case Qt::Key_A: mViewportKeysPressed &= ~eAKey; break; + case Qt::Key_S: mViewportKeysPressed &= ~eSKey; break; + case Qt::Key_D: mViewportKeysPressed &= ~eDKey; break; + } +} + +void CWorldEditorWindow::OnViewportWheelScroll(int ScrollAmount) +{ + mCamera.Zoom(ScrollAmount / 6000.f); +} + +void CWorldEditorWindow::OnViewportRayCast(QMouseEvent *pEvent) +{ + // todo: ray cast +} + +// ************ PRIVATE SLOTS ************ +void CWorldEditorWindow::LoadScriptableLayerUI() +{ + +} + +void CWorldEditorWindow::on_actionExit_triggered() +{ + close(); +} + +void CWorldEditorWindow::on_actionBackface_culling_triggered() +{ + mpRenderer->ToggleBackfaceCull(ui->actionBackface_culling->isChecked()); +} + +void CWorldEditorWindow::on_actionWorld_triggered() +{ + mpSceneManager->SetWorld(ui->actionWorld->isChecked()); +} + +void CWorldEditorWindow::on_actionCollision_triggered() +{ + mpSceneManager->SetCollision(ui->actionCollision->isChecked()); +} + +void CWorldEditorWindow::on_actionObjects_triggered() +{ + mpSceneManager->SetObjects(ui->actionObjects->isChecked()); +} + +void CWorldEditorWindow::setupInstanceViewLayers() +{ +/* if (qApp->scene.MREAArray.empty()) return; + + mrea_GL *m = qApp->scene.MREAArray[0]; + if (!m->isSCLYRead()) return; + + u32 layer_count = m->getLayerCount(); + for (u32 l = 0; l < layer_count; l++) { + QTreeWidgetItem* layer = new QTreeWidgetItem; + layer->setText(0, "Layer " + QString::number(l)); + ui->InstanceViewTreeWidget->addTopLevelItem(layer); + + u32 object_count = m->getObjectCount(l); + for (u32 o = 0; o < object_count; o++) { + + PrimeObject object = m->getObject(l, o); + std::string name = object.getStringProperty("Name"); + if (name.empty()) name = "[no name]"; + + QTreeWidgetItem* obj = new QTreeWidgetItem; + obj->setText(0, QString::fromStdString(name)); + obj->setText(1, QString::fromStdString(qApp->scene.getObjectName(object.type))); + obj->setToolTip(0, obj->text(0)); + obj->setToolTip(1, obj->text(1)); + + layer->addChild(obj); + //layer->set + } + }*/ +} + +void CWorldEditorWindow::clearInstanceView() +{ + //ui->InstanceViewTreeWidget->clear(); +} + +void CWorldEditorWindow::on_actionMaterial_Animations_triggered() +{ + mpRenderer->ToggleUVAnimation(ui->actionMaterial_Animations->isChecked()); +} + +void CWorldEditorWindow::on_actionLights_triggered() +{ + mpSceneManager->SetLights(ui->actionLights->isChecked()); +} + +void CWorldEditorWindow::on_actionLightingNone_triggered() +{ + CGraphics::sLightMode = CGraphics::NoLighting; + ui->actionLightingNone->setChecked(true); + ui->actionLightingBasic->setChecked(false); + ui->actionLightingWorld->setChecked(false); +} + +void CWorldEditorWindow::on_actionLightingBasic_triggered() +{ + CGraphics::sLightMode = CGraphics::BasicLighting; + ui->actionLightingNone->setChecked(false); + ui->actionLightingBasic->setChecked(true); + ui->actionLightingWorld->setChecked(false); +} + +void CWorldEditorWindow::on_actionLightingWorld_triggered() +{ + CGraphics::sLightMode = CGraphics::WorldLighting; + ui->actionLightingNone->setChecked(false); + ui->actionLightingBasic->setChecked(false); + ui->actionLightingWorld->setChecked(true); +} + +void CWorldEditorWindow::on_actionSky_triggered() +{ + mShouldDrawSky = ui->actionSky->isChecked(); +} + +void CWorldEditorWindow::on_actionOccluder_meshes_triggered() +{ + mpRenderer->ToggleOccluders(ui->actionOccluder_meshes->isChecked()); +} + +void CWorldEditorWindow::closeEvent(QCloseEvent *) +{ + emit Closed(); +} + +void CWorldEditorWindow::on_actionDisableBloom_triggered() +{ + mpRenderer->SetBloom(CRenderer::eNoBloom); + ui->actionEnableBloom->setChecked(false); + ui->actionDisableBloom->setChecked(true); + ui->actionShowBloomMaps->setChecked(false); +} + +void CWorldEditorWindow::on_actionEnableBloom_triggered() +{ + mpRenderer->SetBloom(CRenderer::eBloom); + ui->actionDisableBloom->setChecked(false); + ui->actionEnableBloom->setChecked(true); + ui->actionShowBloomMaps->setChecked(false); +} + +void CWorldEditorWindow::on_actionShowBloomMaps_triggered() +{ + mpRenderer->SetBloom(CRenderer::eBloomMaps); + ui->actionDisableBloom->setChecked(false); + ui->actionEnableBloom->setChecked(false); + ui->actionShowBloomMaps->setChecked(true); +} diff --git a/UI/CWorldEditorWindow.h b/UI/CWorldEditorWindow.h new file mode 100644 index 00000000..6d3a5594 --- /dev/null +++ b/UI/CWorldEditorWindow.h @@ -0,0 +1,89 @@ +#ifndef CWORLDEDITORWINDOW_H +#define CWORLDEDITORWINDOW_H + +#include +#include "ui_CWorldEditorWindow.h" + +#include +#include +#include + +namespace Ui { +class CWorldEditorWindow; +} + +class CWorldEditorWindow : public QMainWindow +{ + Q_OBJECT + CRenderer *mpRenderer; + CSceneManager *mpSceneManager; + CWorld *mpActiveWorld; + CToken mWorldToken; + CGameArea *mpActiveArea; + CToken mAreaToken; + + bool mRendererInitialized; + CCamera mCamera; + ECameraMoveMode mCameraMode; + float mViewportAspectRatio; + int mViewportKeysPressed; + bool mShouldDrawSky; + + void LoadScriptableLayerUI(); + +public: + CWorldEditorWindow(QWidget *parent); + ~CWorldEditorWindow(); + void InitializeWorld(CWorld *pWorld, CGameArea *pArea); + +public slots: + void PaintViewport(double DeltaTime); + void SetViewportSize(int Width, int Height); + void OnViewportMouseMove(QMouseEvent *pEvent, float XMovement, float YMovement); + void OnViewportRayCast(QMouseEvent *pEvent); + void OnViewportKeyPress(QKeyEvent *pEvent); + void OnViewportKeyRelease(QKeyEvent *pEvent); + void OnViewportWheelScroll(int ScrollAmount); + +private slots: + void on_actionExit_triggered(); + + void on_actionBackface_culling_triggered(); + + void on_actionWorld_triggered(); + + void on_actionCollision_triggered(); + + void on_actionObjects_triggered(); + + void on_actionMaterial_Animations_triggered(); + + void on_actionLights_triggered(); + + void on_actionLightingNone_triggered(); + + void on_actionLightingBasic_triggered(); + + void on_actionLightingWorld_triggered(); + + void on_actionSky_triggered(); + + void on_actionOccluder_meshes_triggered(); + + void on_actionDisableBloom_triggered(); + + void on_actionEnableBloom_triggered(); + + void on_actionShowBloomMaps_triggered(); + +signals: + void Closed(); + +private: + Ui::CWorldEditorWindow *ui; + void setupInstanceViewLayers(); + void clearInstanceView(); + void closeEvent(QCloseEvent *); +}; + +#endif // CWORLDEDITORWINDOW_H diff --git a/UI/CWorldEditorWindow.ui b/UI/CWorldEditorWindow.ui new file mode 100644 index 00000000..69aa8090 --- /dev/null +++ b/UI/CWorldEditorWindow.ui @@ -0,0 +1,460 @@ + + + CWorldEditorWindow + + + + 0 + 0 + 1920 + 1250 + + + + Prime World Editor + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + 0 + 0 + 1920 + 21 + + + + + File + + + + + + View + + + + Dynamic lighting + + + + + + + + false + + + Bloom + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + 0 + + + + false + + + QDockWidget::AllDockWidgetFeatures + + + Qt::LeftDockWidgetArea|Qt::RightDockWidgetArea + + + Object Properties + + + 2 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + true + + + + + 0 + 0 + 294 + 654 + + + + + + + Connections + + + + + + + + true + + + + 0 + 0 + + + + Properties + + + + + + + + + + + + + QDockWidget::AllDockWidgetFeatures + + + Qt::LeftDockWidgetArea|Qt::RightDockWidgetArea + + + Instance View + + + 2 + + + + + + + true + + + + Instance + + + + + Type + + + + + + + + + + Open CMDL + + + + + true + + + true + + + Reset camera + + + + + true + + + true + + + Backface culling + + + + + true + + + true + + + World + + + 1 + + + + + true + + + Collision + + + 2 + + + + + true + + + true + + + Objects + + + 3 + + + + + Paths + + + + + Lightmaps + + + + + Material Editor + + + + + Extract + + + + + Repack + + + + + Dump file list + + + + + Convert TXTR to DDS + + + + + Convert DDS to TXTR + + + + + Close all + + + + + Exit + + + Esc + + + + + Open MREA + + + + + Read MREA + + + + + true + + + true + + + UV animations + + + 4 + + + + + true + + + true + + + Lights + + + 5 + + + + + true + + + None + + + Ctrl+1 + + + + + true + + + Basic + + + Ctrl+2 + + + + + true + + + true + + + World lights + + + Ctrl+3 + + + + + true + + + true + + + Sky + + + 7 + + + + + true + + + Occluder meshes + + + 6 + + + + + true + + + None + + + + + true + + + Bloom + + + + + true + + + Bloom Maps + + + + + + CEditorGLWidget + QWidget +
CEditorGLWidget.h
+ 1 +
+
+ + +
diff --git a/UI/IPreviewPanel.cpp b/UI/IPreviewPanel.cpp new file mode 100644 index 00000000..bab5cb68 --- /dev/null +++ b/UI/IPreviewPanel.cpp @@ -0,0 +1,24 @@ +#include "IPreviewPanel.h" +#include "WScanPreviewPanel.h" +#include "WStringPreviewPanel.h" +#include "WTexturePreviewPanel.h" + +IPreviewPanel::IPreviewPanel(QWidget *parent) : QFrame(parent) +{ + setFrameShape(QFrame::StyledPanel); + setFrameShadow(QFrame::Plain); + setLineWidth(2); +} + +// Can add more if more preview types are implemented +// Not every resource type is really suitable for this though unfortunately +IPreviewPanel* IPreviewPanel::CreatePanel(EResType Type, QWidget *pParent) +{ + switch (Type) + { + case eTexture: return new WTexturePreviewPanel(pParent); + case eStringTable: return new WStringPreviewPanel(pParent); + case eScan: return new WScanPreviewPanel(pParent); + default: return nullptr; + } +} diff --git a/UI/IPreviewPanel.h b/UI/IPreviewPanel.h new file mode 100644 index 00000000..76ae4b69 --- /dev/null +++ b/UI/IPreviewPanel.h @@ -0,0 +1,17 @@ +#ifndef IPREVIEWPANEL_H +#define IPREVIEWPANEL_H + +#include +#include +#include + +class IPreviewPanel : public QFrame +{ +public: + explicit IPreviewPanel(QWidget *parent = 0); + virtual EResType ResType() = 0; + virtual void SetResource(CResource *pRes) = 0; + static IPreviewPanel* CreatePanel(EResType Type, QWidget *pParent = 0); +}; + +#endif // IPREVIEWPANEL_H diff --git a/UI/PWEMaterialEditor.h b/UI/PWEMaterialEditor.h new file mode 100644 index 00000000..44fb6a47 --- /dev/null +++ b/UI/PWEMaterialEditor.h @@ -0,0 +1,22 @@ +#ifndef PWEMATERIALEDITOR_H +#define PWEMATERIALEDITOR_H + +#include + +namespace Ui { +class PWEMaterialEditor; +} + +class PWEMaterialEditor : public QDialog +{ + Q_OBJECT + +public: + explicit PWEMaterialEditor(QWidget *parent = 0); + ~PWEMaterialEditor(); + +private: + Ui::PWEMaterialEditor *ui; +}; + +#endif // PWEMATERIALEDITOR_H diff --git a/UI/TestDialog.cpp b/UI/TestDialog.cpp new file mode 100644 index 00000000..4ccb89c1 --- /dev/null +++ b/UI/TestDialog.cpp @@ -0,0 +1,22 @@ +#include "TestDialog.h" +#include "ui_TestDialog.h" +#include +#include +#include "WResourceSelector.h" +#include "WTextureGLWidget.h" +#include + +TestDialog::TestDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::TestDialog) +{ + ui->setupUi(this); + + CTexture *pTex = CTextureDecoder::LoadDDS(CFileInStream("E:/test2.dds", IOUtil::LittleEndian)); + ui->widget->SetTexture(pTex); +} + +TestDialog::~TestDialog() +{ + delete ui; +} diff --git a/UI/TestDialog.h b/UI/TestDialog.h new file mode 100644 index 00000000..46e1bd69 --- /dev/null +++ b/UI/TestDialog.h @@ -0,0 +1,22 @@ +#ifndef TESTDIALOG_H +#define TESTDIALOG_H + +#include + +namespace Ui { +class TestDialog; +} + +class TestDialog : public QDialog +{ + Q_OBJECT + +public: + explicit TestDialog(QWidget *parent = 0); + ~TestDialog(); + +private: + Ui::TestDialog *ui; +}; + +#endif // TESTDIALOG_H diff --git a/UI/TestDialog.ui b/UI/TestDialog.ui new file mode 100644 index 00000000..bcc3f11d --- /dev/null +++ b/UI/TestDialog.ui @@ -0,0 +1,125 @@ + + + TestDialog + + + + 0 + 0 + 300 + 535 + + + + Dialog + + + + + + + + + PushButton + + + + + + + PushButton + + + + + + + + + + + + + + + RadioButton + + + + + + + RadioButton + + + + + + + + + CheckBox + + + + + + + GroupBox + + + + + + PushButton + + + + + + + CheckBox + + + + + + + + + + + 0 + 0 + + + + + 256 + 256 + + + + + + + + + + + + WRollout + QWidget +
WRollout.h
+ 1 +
+ + WTextureGLWidget + QWidget +
WTextureGLWidget.h
+ 1 +
+
+ + +
diff --git a/UI/UICommon.cpp b/UI/UICommon.cpp new file mode 100644 index 00000000..8c0c4061 --- /dev/null +++ b/UI/UICommon.cpp @@ -0,0 +1,46 @@ +#include "UICommon.h" + +// This array is intended to be used with the EResType enum +const QString gskResourceFilters[] = { + "Animation (*.ANIM)", // eAnimation + "Animation Event Data (*.EVNT)", // eAnimEventData + "Area (*.MREA)", // eArea + "Audio Metadata (*.CAUD)", // eAudioData + "Audio Group (*.AGSC)", // eAudioGrp + "Audio Sample (*.CSMP)", // eAudioSample + "Audio Stream (*.STRM)", // eAudioStream + "Audio Lookup Table (*.ATBL)", // eAudioTable + "Character (*.ANCS *.CHAR)", // eCharacter + "Collision Mesh (*.DCLN)", // eCollisionMesh + "Collision Response Data (*.CRSC)", // eCollisionResponse + "Data Dump (*.DUMB)", // eDataDump + "Decal (*.DPSC)", // eDecal + "Dependency Group (*.DGRP)", // eDependencyGroup + "Font (*.FONT)", // eFont + "GUI Frame (*.FRME)", // eGuiFrame + "Hint System Data (*.HINT)", // eHintSystem + "Invalid resource type", // eInvalid + "Area Map (*.MAPA)", // eMapArea + "World Map (*.MAPW)", // eMapWorld + "Universe Map (*.MAPU)", // eMapUniverse + "MIDI Data (*.CSNG)", // eMidi + "Model (*.CMDL)", // eModel + "Music Track (*.DSP)", // eMusicTrack + "Navigation Mesh (*.PATH)", // eNavMesh + "Pack File (*.pak)", // ePackFile + "Particle (*.PART)", // eParticle + "Electricity Particle (*.ELSC)", // eParticleElectric + "Swoosh Particle (*.SWHC)", // eParticleSwoosh + "Projectile (*.WPSC)", // eProjectile + "Invalid resource type", // eResource + "World Save Data (*.SAVW)", // eSaveWorld + "Scannable Object Info (*.SCAN)", // eScan + "Skeleton (*.CINF)", // eSkeleton + "Skin (*.CSKR)", // eSkin + "State Machine (*.AFSM *.FSM2)", // eStateMachine + "String Table (*.STRG)", // eStringTable + "Texture (*.TXTR)", // eTexture + "Tweak (*.CTWK *.ntwk)", // eTweak + "Video (*.thp)", // eVideo + "World (*.MLVL)" // eWorld +}; diff --git a/UI/UICommon.h b/UI/UICommon.h new file mode 100644 index 00000000..26a8481c --- /dev/null +++ b/UI/UICommon.h @@ -0,0 +1,9 @@ +#ifndef UICOMMON +#define UICOMMON + +#include + +extern const QString gskResourceFilters[]; + +#endif // UICOMMON + diff --git a/UI/WCollapsibleGroupBox.cpp b/UI/WCollapsibleGroupBox.cpp new file mode 100644 index 00000000..63936307 --- /dev/null +++ b/UI/WCollapsibleGroupBox.cpp @@ -0,0 +1,12 @@ +#include "WCollapsibleGroupBox.h" + +WCollapsibleGroupBox::WCollapsibleGroupBox() +{ + +} + +WCollapsibleGroupBox::~WCollapsibleGroupBox() +{ + +} + diff --git a/UI/WCollapsibleGroupBox.h b/UI/WCollapsibleGroupBox.h new file mode 100644 index 00000000..3e1aa362 --- /dev/null +++ b/UI/WCollapsibleGroupBox.h @@ -0,0 +1,12 @@ +#ifndef WCOLLAPSIBLEGROUPBOX_H +#define WCOLLAPSIBLEGROUPBOX_H + + +class WCollapsibleGroupBox +{ +public: + WCollapsibleGroupBox(); + ~WCollapsibleGroupBox(); +}; + +#endif // WCOLLAPSIBLEGROUPBOX_H diff --git a/UI/WColorPicker.cpp b/UI/WColorPicker.cpp new file mode 100644 index 00000000..fcf42219 --- /dev/null +++ b/UI/WColorPicker.cpp @@ -0,0 +1,92 @@ +#include "WColorPicker.h" +#include +#include +#include +#include +#include + +WColorPicker::WColorPicker(QWidget *parent) : QWidget(parent) +{ + mColor = Qt::transparent; +} + +WColorPicker::~WColorPicker() +{ +} + +void WColorPicker::paintEvent(QPaintEvent *) +{ + QRect Area(QPoint(2,2), size() - QSize(5,5)); // Subtraction makes room for the stroke + QColor FillColor = mColor; + FillColor.setAlpha(255); + + QBrush Fill(FillColor); + QPen Outline(Qt::black, 1); + + QPainter Painter(this); + Painter.setBrush(Fill); + Painter.setPen(Outline); + Painter.drawRect(Area); + + if (hasFocus()) + { + QRect DottedLine(QPoint(0,0), size() - QSize(1,1)); + Fill.setColor(Qt::transparent); + Outline.setStyle(Qt::DotLine); + + Painter.setBrush(Fill); + Painter.setPen(Outline); + Painter.drawRect(DottedLine); + } +} + +void WColorPicker::keyPressEvent(QKeyEvent *Event) +{ + if (Event->key() == Qt::Key_Return) + { + QColorDialog ColorPick; + ColorPick.setOptions(QColorDialog::ShowAlphaChannel); + ColorPick.setCurrentColor(mColor); + int result = ColorPick.exec(); + + if (result) + { + mColor = ColorPick.currentColor(); + emit colorChanged(mColor); + } + } +} + +void WColorPicker::mousePressEvent(QMouseEvent *) +{ + setFocus(); +} + +void WColorPicker::mouseReleaseEvent(QMouseEvent *Event) +{ + if ((Event->x() < width()) && (Event->y() < height())) + { + QColorDialog ColorPick; + ColorPick.setOptions(QColorDialog::ShowAlphaChannel); + ColorPick.setCurrentColor(mColor); + int result = ColorPick.exec(); + + if (result) + { + mColor = ColorPick.currentColor(); + emit colorChanged(mColor); + } + } +} + +QColor WColorPicker::getColor() +{ + return mColor; +} + +void WColorPicker::setColor(QColor Color) +{ + mColor = Color; + emit colorChanged(mColor); + update(); +} diff --git a/UI/WColorPicker.h b/UI/WColorPicker.h new file mode 100644 index 00000000..231b0bd6 --- /dev/null +++ b/UI/WColorPicker.h @@ -0,0 +1,28 @@ +#ifndef WCOLORPICKER_H +#define WCOLORPICKER_H + +#include +#include + +class WColorPicker : public QWidget +{ + Q_OBJECT + QColor mColor; + +public: + explicit WColorPicker(QWidget *parent = 0); + ~WColorPicker(); + void paintEvent(QPaintEvent *); + void keyPressEvent(QKeyEvent *Event); + void mousePressEvent(QMouseEvent *); + void mouseReleaseEvent(QMouseEvent *Event); + QColor getColor(); + void setColor(QColor Color); + +signals: + void colorChanged(QColor NewColor); + +public slots: +}; + +#endif // WCOLORPICKER_H diff --git a/UI/WDraggableSpinBox.cpp b/UI/WDraggableSpinBox.cpp new file mode 100644 index 00000000..d2060d93 --- /dev/null +++ b/UI/WDraggableSpinBox.cpp @@ -0,0 +1,55 @@ +#include "WDraggableSpinBox.h" +#include +#include + +WDraggableSpinBox::WDraggableSpinBox(QWidget *parent) : QDoubleSpinBox(parent) +{ + mBeingDragged = false; + mDefaultValue = value(); + setMinimum(-1000000.0); + setMaximum(1000000.0); + setDecimals(4); +} + +WDraggableSpinBox::~WDraggableSpinBox() +{ +} + +void WDraggableSpinBox::mousePressEvent(QMouseEvent *Event) +{ + mBeingDragged = true; + mBeenDragged = false; + mInitialY = Event->y(); + mInitialValue = value(); +} + +void WDraggableSpinBox::mouseReleaseEvent(QMouseEvent *Event) +{ + mBeingDragged = false; + + if (Event->button() == Qt::LeftButton) + { + if (!mBeenDragged) + { + if (Event->y() <= height() / 2) + stepUp(); + else + stepDown(); + } + } + + else if (Event->button() == Qt::RightButton) + { + setValue(mDefaultValue); + } +} + +void WDraggableSpinBox::mouseMoveEvent(QMouseEvent *Event) +{ + if (mBeingDragged) + { + double DragAmount = (singleStep() / 10.0) * (mInitialY - Event->y()); + setValue(mInitialValue + DragAmount); + if (DragAmount != 0) mBeenDragged = true; + } +} diff --git a/UI/WDraggableSpinBox.h b/UI/WDraggableSpinBox.h new file mode 100644 index 00000000..e3618f47 --- /dev/null +++ b/UI/WDraggableSpinBox.h @@ -0,0 +1,23 @@ +#ifndef WDRAGGABLESPINBOX_H +#define WDRAGGABLESPINBOX_H + +#include + +class WDraggableSpinBox : public QDoubleSpinBox +{ + Q_OBJECT + bool mBeingDragged; + bool mBeenDragged; + double mInitialValue; + double mDefaultValue; + int mInitialY; + +public: + explicit WDraggableSpinBox(QWidget *parent = 0); + ~WDraggableSpinBox(); + void mousePressEvent(QMouseEvent *Event); + void mouseReleaseEvent(QMouseEvent *Event); + void mouseMoveEvent(QMouseEvent *Event); +}; + +#endif // WDRAGGABLESPINBOX_H diff --git a/UI/WPropertyEditor.cpp b/UI/WPropertyEditor.cpp new file mode 100644 index 00000000..727bd062 --- /dev/null +++ b/UI/WPropertyEditor.cpp @@ -0,0 +1,353 @@ +#include "WPropertyEditor.h" +#include "WDraggableSpinBox.h" +#include "WResourceSelector.h" +#include "WColorPicker.h" +#include "WVectorEditor.h" +#include +#include +#include +#include +#include +#include + +static const QString gskNullProperty = "[NULL]"; +static const QString gskUnsupportedType = "Invalid property type"; + +WPropertyEditor::WPropertyEditor(QWidget *pParent, CPropertyBase *pProperty) + : QWidget(pParent) +{ + mUI.PropertyName = new QLabel(gskNullProperty, this); + mUI.EditorWidget = nullptr; + mUI.Layout = new QHBoxLayout(this); + mUI.Layout->addWidget(mUI.PropertyName); + mUI.Layout->setContentsMargins(0,0,0,0); + setLayout(mUI.Layout); + + mpProperty = nullptr; + SetProperty(pProperty); +} + +WPropertyEditor::~WPropertyEditor() +{ + +} + +void WPropertyEditor::resizeEvent(QResizeEvent *pEvent) +{ + CreateLabelText(); +} + +void WPropertyEditor::SetProperty(CPropertyBase *pProperty) +{ + if (pProperty) + { + bool IsNewProperty = ((!mpProperty) || (pProperty->Template() != mpProperty->Template())); + mpProperty = pProperty; + + if (IsNewProperty) + CreateEditor(); + else + UpdateEditor(); + } + + else + { + delete mUI.EditorWidget; + mUI.EditorWidget = nullptr; + + mpProperty = pProperty; + mUI.PropertyName->setText(gskNullProperty); + } +} + +void WPropertyEditor::CreateEditor() +{ + // Clear existing edit widget (if any) + delete mUI.EditorWidget; + + // Set name + mUI.PropertyName->setText(QString::fromStdString(mpProperty->Name())); + mUI.PropertyName->setToolTip(QString::fromStdString(mpProperty->Name())); + + // Set editor widget + switch (mpProperty->Type()) + { + + // Bool - QCheckBox + case eBoolProperty: + { + CBoolProperty *pBoolCast = static_cast(mpProperty); + QCheckBox *pCheckBox = new QCheckBox(this); + + pCheckBox->setChecked(pBoolCast->Get()); + + mUI.EditorWidget = pCheckBox; + break; + } + + // Byte - QSpinBox + case eByteProperty: + { + CByteProperty *pByteCast = static_cast(mpProperty); + QSpinBox *pSpinBox = new QSpinBox(this); + + pSpinBox->setRange(-128, 128); + pSpinBox->setValue(pByteCast->Get()); + + mUI.EditorWidget = pSpinBox; + break; + } + + // Short - QSpinBox + case eShortProperty: + { + CShortProperty *pShortCast = static_cast(mpProperty); + QSpinBox *pSpinBox = new QSpinBox(this); + + pSpinBox->setRange(-32768, 32767); + pSpinBox->setValue(pShortCast->Get()); + + mUI.EditorWidget = pSpinBox; + break; + } + + // Long - QSpinBox + case eLongProperty: + { + CLongProperty *pLongCast = static_cast(mpProperty); + QSpinBox *pSpinBox = new QSpinBox(this); + + pSpinBox->setRange(-2147483648, 2147483647); + pSpinBox->setValue(pLongCast->Get()); + + mUI.EditorWidget = pSpinBox; + break; + } + + // Float - WDraggableSpinBox + case eFloatProperty: + { + CFloatProperty *pFloatCast = static_cast(mpProperty); + WDraggableSpinBox *pDraggableSpinBox = new WDraggableSpinBox(this); + + pDraggableSpinBox->setValue(pFloatCast->Get()); + + mUI.EditorWidget = pDraggableSpinBox; + break; + } + + // String - QLineEdit + case eStringProperty: + { + CStringProperty *pStringCast = static_cast(mpProperty); + QLineEdit *pLineEdit = new QLineEdit(this); + + pLineEdit->setText(QString::fromStdString(pStringCast->Get())); + pLineEdit->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); + pLineEdit->setCursorPosition(0); + + mUI.EditorWidget = pLineEdit; + break; + } + + // Vector3 - WVectorEditor + case eVector3Property: + { + CVector3Property *pVector3Cast = static_cast(mpProperty); + WVectorEditor *pVectorEditor = new WVectorEditor(this); + + pVectorEditor->SetValue(pVector3Cast->Get()); + pVectorEditor->SetText(QString::fromStdString(mpProperty->Name())); + mUI.PropertyName->hide(); + + mUI.EditorWidget = pVectorEditor; + break; + } + + // Color - WColorPicker + case eColorProperty: + { + CColorProperty *pColorCast = static_cast(mpProperty); + WColorPicker *pColorPicker = new WColorPicker(this); + + CColor color = pColorCast->Get(); + QColor qcolor = QColor(color.r, color.g, color.b, color.a); + pColorPicker->setColor(qcolor); + + mUI.EditorWidget = pColorPicker; + break; + } + + // Enum - todo (will be QComboBox) + case eEnumProperty: + mUI.EditorWidget = new QLabel("[placeholder]", this); + break; + + // File - WResourceSelector + case eFileProperty: + { + CFileProperty *pFileCast = static_cast(mpProperty); + WResourceSelector *pResourceSelector = new WResourceSelector(this); + + pResourceSelector->AdjustPreviewToParent(true); + pResourceSelector->SetResource(pFileCast->Get()); + + mUI.EditorWidget = pResourceSelector; + break; + } + + // Struct - QGroupBox + case eStructProperty: + { + CPropertyStruct *pStructCast = static_cast(mpProperty); + QGroupBox *pGroupBox = new QGroupBox(this); + + QVBoxLayout *pStructLayout = new QVBoxLayout(pGroupBox); + pGroupBox->setLayout(pStructLayout); + pGroupBox->setTitle(QString::fromStdString(pStructCast->Name())); + mUI.PropertyName->hide(); + + for (u32 p = 0; p < pStructCast->Count(); p++) + { + WPropertyEditor *pEditor = new WPropertyEditor(pGroupBox, pStructCast->PropertyByIndex(p)); + pStructLayout->addWidget(pEditor); + } + + mUI.EditorWidget = pGroupBox; + break; + } + + // Invalid + case eInvalidProperty: + default: + mUI.EditorWidget = new QLabel(gskUnsupportedType, this); + break; + } + + // For some reason setting a minimum size on group boxes flattens it... + if ((mpProperty->Type() != eStructProperty) && (mpProperty->Type() != eVector3Property)) + { + mUI.EditorWidget->setMinimumHeight(21); + mUI.EditorWidget->setMaximumHeight(21); + } + + mUI.Layout->addWidget(mUI.EditorWidget, 0); + CreateLabelText(); +} + +void WPropertyEditor::UpdateEditor() +{ + switch (mpProperty->Type()) + { + + case eBoolProperty: + { + CBoolProperty *pBoolCast = static_cast(mpProperty); + QCheckBox *pCheckBox = static_cast(mUI.EditorWidget); + pCheckBox->setChecked(pBoolCast->Get()); + break; + } + + case eByteProperty: + { + CByteProperty *pByteCast = static_cast(mpProperty); + QSpinBox *pSpinBox = static_cast(mUI.EditorWidget); + pSpinBox->setValue(pByteCast->Get()); + break; + } + + case eShortProperty: + { + CShortProperty *pShortCast = static_cast(mpProperty); + QSpinBox *pSpinBox = static_cast(mUI.EditorWidget); + pSpinBox->setValue(pShortCast->Get()); + break; + } + + case eLongProperty: + { + CLongProperty *pLongCast = static_cast(mpProperty); + QSpinBox *pSpinBox = static_cast(mUI.EditorWidget); + pSpinBox->setValue(pLongCast->Get()); + break; + } + + case eFloatProperty: + { + CFloatProperty *pFloatCast = static_cast(mpProperty); + WDraggableSpinBox *pDraggableSpinBox = static_cast(mUI.EditorWidget); + pDraggableSpinBox->setValue(pFloatCast->Get()); + break; + } + + case eStringProperty: + { + CStringProperty *pStringCast = static_cast(mpProperty); + QLineEdit *pLineEdit = static_cast(mUI.EditorWidget); + pLineEdit->setText(QString::fromStdString(pStringCast->Get())); + pLineEdit->setCursorPosition(0); + break; + } + + case eVector3Property: + { + CVector3Property *pVector3Cast = static_cast(mpProperty); + WVectorEditor *pVectorEditor = static_cast(mUI.EditorWidget); + pVectorEditor->SetValue(pVector3Cast->Get()); + break; + } + + case eColorProperty: + { + CColorProperty *pColorCast = static_cast(mpProperty); + WColorPicker *pColorPicker = static_cast(mUI.EditorWidget); + + CColor color = pColorCast->Get(); + QColor qcolor = QColor(color.r, color.g, color.b, color.a); + pColorPicker->setColor(qcolor); + + break; + } + + case eEnumProperty: + break; + + case eFileProperty: + { + CFileProperty *pFileCast = static_cast(mpProperty); + WResourceSelector *pResourceSelector = static_cast(mUI.EditorWidget); + pResourceSelector->SetResource(pFileCast->Get()); + break; + } + + case eStructProperty: + { + CPropertyStruct *pStructCast = static_cast(mpProperty); + QGroupBox *pGroupBox = static_cast(mUI.EditorWidget); + + QObjectList ChildList = pGroupBox->children(); + u32 PropNum = 0; + + foreach (QObject *pObj, ChildList) + { + if (pObj != pGroupBox->layout()) + { + CPropertyBase *pProp = pStructCast->PropertyByIndex(PropNum); + static_cast(pObj)->SetProperty(pProp); + PropNum++; + } + } + break; + } + + } + +} + +void WPropertyEditor::CreateLabelText() +{ + mUI.PropertyName->setText(QString::fromStdString(mpProperty->Name())); + QFontMetrics metrics(mUI.PropertyName->font()); + QString text = metrics.elidedText(QString::fromStdString(mpProperty->Name()), Qt::ElideRight, mUI.PropertyName->width()); + mUI.PropertyName->setText(text); +} diff --git a/UI/WPropertyEditor.h b/UI/WPropertyEditor.h new file mode 100644 index 00000000..233cafd7 --- /dev/null +++ b/UI/WPropertyEditor.h @@ -0,0 +1,36 @@ +#ifndef WPROPERTYEDITOR_H +#define WPROPERTYEDITOR_H + +#include +#include +#include +#include + +class WPropertyEditor : public QWidget +{ + Q_OBJECT + + // Editor + CPropertyBase *mpProperty; + + // UI + struct { + QLabel *PropertyName; + QWidget *EditorWidget; + QHBoxLayout *Layout; + } mUI; + +public: + explicit WPropertyEditor(QWidget *pParent = 0, CPropertyBase *pProperty = 0); + ~WPropertyEditor(); + void resizeEvent(QResizeEvent *pEvent); + + void SetProperty(CPropertyBase *pProperty); + +private: + void CreateEditor(); + void UpdateEditor(); + void CreateLabelText(); +}; + +#endif // WPROPERTYEDITOR_H diff --git a/UI/WResourceSelector.cpp b/UI/WResourceSelector.cpp new file mode 100644 index 00000000..a39b6d8e --- /dev/null +++ b/UI/WResourceSelector.cpp @@ -0,0 +1,335 @@ +#include "WResourceSelector.h" + +#include "UICommon.h" +#include "WTexturePreviewPanel.h" +#include + +#include +#include +#include +#include +#include +#include + +WResourceSelector::WResourceSelector(QWidget *parent) : QWidget(parent) +{ + // Initialize Members + mHasMultipleExtensions = false; + mShowEditButton = false; + mShowExportButton = false; + + mpPreviewPanel = nullptr; + mEnablePreviewPanel = true; + mPreviewPanelValid = false; + mShowingPreviewPanel = false; + mAdjustPreviewToParent = false; + + mpResource = nullptr; + mResType = eInvalidResType; + + // Create Widgets + mUI.LineEdit = new QLineEdit(this); + mUI.BrowseButton = new QPushButton(this); + mUI.EditButton = new QPushButton("Edit", this); + mUI.ExportButton = new QPushButton("Export", this); + + // Create Layout + mUI.Layout = new QHBoxLayout(this); + setLayout(mUI.Layout); + mUI.Layout->addWidget(mUI.LineEdit); + mUI.Layout->addWidget(mUI.BrowseButton); + mUI.Layout->addWidget(mUI.EditButton); + mUI.Layout->addWidget(mUI.ExportButton); + mUI.Layout->setContentsMargins(0,0,0,0); + mUI.Layout->setSpacing(1); + + // Set Up Widgets + mUI.LineEdit->installEventFilter(this); + mUI.LineEdit->setMouseTracking(true); + mUI.LineEdit->setMaximumHeight(23); + mUI.LineEdit->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + mUI.BrowseButton->installEventFilter(this); + mUI.BrowseButton->setMouseTracking(true); + mUI.BrowseButton->setText("..."); + mUI.BrowseButton->setMaximumSize(25, 23); + mUI.BrowseButton->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); + mUI.EditButton->installEventFilter(this); + mUI.EditButton->setMouseTracking(true); + mUI.EditButton->setMaximumSize(50, 23); + mUI.EditButton->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); + mUI.EditButton->hide(); + mUI.ExportButton->installEventFilter(this); + mUI.ExportButton->setMouseTracking(true); + mUI.ExportButton->setMaximumSize(50, 23); + mUI.ExportButton->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); + mUI.ExportButton->hide(); + + QCompleter *pCompleter = new QCompleter(this); + pCompleter->setModel(new QDirModel(pCompleter)); + mUI.LineEdit->setCompleter(pCompleter); + + connect(mUI.LineEdit, SIGNAL(editingFinished()), this, SLOT(OnLineEditTextEdited())); + connect(mUI.BrowseButton, SIGNAL(clicked()), this, SLOT(OnBrowseButtonClicked())); +} + +WResourceSelector::~WResourceSelector() +{ + delete mpPreviewPanel; +} + +bool WResourceSelector::event(QEvent *pEvent) +{ + if ((pEvent->type() == QEvent::Leave) || (pEvent->type() == QEvent::WindowDeactivate)) + HidePreviewPanel(); + + return false; +} + +bool WResourceSelector::eventFilter(QObject *pObj, QEvent *pEvent) +{ + if (pEvent->type() == QEvent::MouseMove) + if (mEnablePreviewPanel) + ShowPreviewPanel(); + + return false; +} + +// ************ GETTERS ************ +EResType WResourceSelector::GetResType() +{ + return mResType; +} + +QString WResourceSelector::GetText() +{ + return mUI.LineEdit->text(); +} + +bool WResourceSelector::IsEditButtonEnabled() +{ + return mShowEditButton; +} + +bool WResourceSelector::IsExportButtonEnabled() +{ + return mShowExportButton; +} + +bool WResourceSelector::IsPreviewPanelEnabled() +{ + return mEnablePreviewPanel; +} + + +// ************ SETTERS ************ +void WResourceSelector::SetResource(CResource *pRes) +{ + mpResource = pRes; + mResToken = CToken(pRes); + + if (pRes) + { + CFourCC ext = StringUtil::GetExtension(pRes->Source()); + mResType = CResource::ResTypeForExtension(ext); + mUI.LineEdit->setText(QString::fromStdString(pRes->Source())); + } + + else + { + mResType = eInvalidResType; + mUI.LineEdit->clear(); + } + + CreatePreviewPanel(); + SetButtonsBasedOnResType(); +} + +void WResourceSelector::SetResType(EResType Type) +{ + mResType = Type; + mpResource = nullptr; + mResToken.Unlock(); + mUI.LineEdit->clear(); + CreatePreviewPanel(); + SetButtonsBasedOnResType(); +} + +void WResourceSelector::SetResTypes(const CStringList &ExtensionList) +{ +} + +void WResourceSelector::SetText(const QString& ResPath) +{ + mUI.LineEdit->setText(ResPath); + LoadResource(ResPath); +} + +void WResourceSelector::SetEditButtonEnabled(bool Enabled) +{ + mShowEditButton = Enabled; + if (Enabled) mUI.EditButton->show(); + else mUI.EditButton->hide(); +} + +void WResourceSelector::SetExportButtonEnabled(bool Enabled) +{ + mShowExportButton = Enabled; + if (Enabled) mUI.ExportButton->show(); + else mUI.ExportButton->hide(); +} + +void WResourceSelector::SetPreviewPanelEnabled(bool Enabled) +{ + mEnablePreviewPanel = Enabled; + if (!mPreviewPanelValid) CreatePreviewPanel(); +} + +void WResourceSelector::AdjustPreviewToParent(bool adjust) +{ + mAdjustPreviewToParent = adjust; +} + +// ************ SLOTS ************ +void WResourceSelector::OnLineEditTextEdited() +{ + LoadResource(mUI.LineEdit->text()); +} + +void WResourceSelector::OnBrowseButtonClicked() +{ + QString Filter = gskResourceFilters[mResType]; + + std::string ResTypeStr = Filter.toStdString(); + size_t EndName = ResTypeStr.find_last_of("(") - 1; + ResTypeStr = ResTypeStr.substr(0, EndName); + QString ResType = QString::fromStdString(ResTypeStr); + + QString NewRes = QFileDialog::getOpenFileName(this, "Select " + ResType, "", Filter); + + if (!NewRes.isEmpty()) + { + mUI.LineEdit->setText(NewRes); + LoadResource(NewRes); + } +} + +void WResourceSelector::OnEditButtonClicked() +{ + Edit(); +} + +void WResourceSelector::OnExportButtonClicked() +{ + Export(); +} + +// ************ PRIVATE ************ +// Should the resource selector handle edit/export itself +// or delegate it entirely to the signals? +void WResourceSelector::Edit() +{ + emit EditResource(mpResource); +} + +void WResourceSelector::Export() +{ + emit ExportResource(mpResource); +} + +void WResourceSelector::CreatePreviewPanel() +{ + delete mpPreviewPanel; + mpPreviewPanel = IPreviewPanel::CreatePanel(mResType, this); + + if (!mpPreviewPanel) mPreviewPanelValid = false; + + else + { + mPreviewPanelValid = true; + mpPreviewPanel->setWindowFlags(Qt::ToolTip); + if (mpResource) mpPreviewPanel->SetResource(mpResource); + } +} + +void WResourceSelector::ShowPreviewPanel() +{ + if ((mPreviewPanelValid) && (mpResource != nullptr)) + { + // Preferred panel point is lower-right, but can move if there's not enough room + QPoint Position = parentWidget()->mapToGlobal(pos()); + QRect ScreenResolution = QApplication::desktop()->screenGeometry(); + QSize PanelSize = mpPreviewPanel->size(); + QPoint PanelPoint = Position; + + // Calculate parent adjustment with 9 pixels of buffer + int ParentAdjustLeft = (mAdjustPreviewToParent ? pos().x() + 9 : 0); + int ParentAdjustRight = (mAdjustPreviewToParent ? parentWidget()->width() - pos().x() + 9 : 0); + + // Is there enough space on the right? + if (Position.x() + width() + PanelSize.width() + ParentAdjustRight >= ScreenResolution.width()) + PanelPoint.rx() -= PanelSize.width() + ParentAdjustLeft; + else + PanelPoint.rx() += width() + ParentAdjustRight; + + // Is there enough space on the bottom? + if (Position.y() + PanelSize.height() >= ScreenResolution.height() - 30) + { + int Difference = Position.y() + PanelSize.height() - ScreenResolution.height() + 30; + PanelPoint.ry() -= Difference; + } + + mpPreviewPanel->move(PanelPoint); + mpPreviewPanel->show(); + mShowingPreviewPanel = true; + } +} + +void WResourceSelector::HidePreviewPanel() +{ + if (mPreviewPanelValid && mShowingPreviewPanel) + { + mpPreviewPanel->hide(); + mShowingPreviewPanel = false; + } +} + +void WResourceSelector::LoadResource(const QString& ResPath) +{ + mpResource = nullptr; + mResToken.Unlock(); + + if ((mResType != eArea) && (mResType != eWorld)) + { + std::string PathStr = ResPath.toStdString(); + CFourCC ResExt = StringUtil::GetExtension(PathStr).c_str(); + + if (CResource::ResTypeForExtension(ResExt) == mResType) + { + mpResource = gResCache.GetResource(PathStr); + mResToken = CToken(mpResource); + + if (mPreviewPanelValid) mpPreviewPanel->SetResource(mpResource); + } + } + + emit ResourceChanged(ResPath); +} + +void WResourceSelector::SetButtonsBasedOnResType() +{ + // Basically this function sets whether the "Export" and "Edit" + // buttons are present based on the resource type. + switch (mResType) + { + // Export button should be enabled here because CTexture already has a DDS export function + // However, need to figure out what sort of interface to create to do it. Disabling until then. + case eTexture: + SetEditButtonEnabled(false); + SetExportButtonEnabled(false); + break; + default: + SetEditButtonEnabled(false); + SetExportButtonEnabled(false); + break; + } +} diff --git a/UI/WResourceSelector.h b/UI/WResourceSelector.h new file mode 100644 index 00000000..26b1ae39 --- /dev/null +++ b/UI/WResourceSelector.h @@ -0,0 +1,91 @@ +#ifndef WRESOURCESELECTOR_H +#define WRESOURCESELECTOR_H + +#include "IPreviewPanel.h" +#include +#include +#include + +#include +#include +#include +#include +#include + +class WResourceSelector : public QWidget +{ + Q_OBJECT + + // Selector + QStringList mSupportedExtensions; + bool mHasMultipleExtensions; + bool mShowEditButton; + bool mShowExportButton; + + // Preview Panel + IPreviewPanel *mpPreviewPanel; + bool mEnablePreviewPanel; + bool mPreviewPanelValid; + bool mShowingPreviewPanel; + bool mAdjustPreviewToParent; + + // Resource + CResource *mpResource; + CToken mResToken; + EResType mResType; + + // UI + struct { + QLineEdit *LineEdit; + QPushButton *BrowseButton; + QPushButton *ExportButton; + QPushButton *EditButton; + QHBoxLayout *Layout; + } mUI; + +signals: + void ResourceChanged(const QString& NewResPath); + void EditResource(CResource *pRes); + void ExportResource(CResource *pRes); + +public: + explicit WResourceSelector(QWidget *parent = 0); + ~WResourceSelector(); + bool event(QEvent *); + bool eventFilter(QObject *, QEvent *); + + // Getters + EResType GetResType(); + QString GetText(); + bool IsEditButtonEnabled(); + bool IsExportButtonEnabled(); + bool IsPreviewPanelEnabled(); + + // Setters + void SetResource(CResource *pRes); + void SetResType(EResType Type); + void SetResTypes(const CStringList& ExtensionList); + void SetText(const QString& ResPath); + void SetEditButtonEnabled(bool Enabled); + void SetExportButtonEnabled(bool Enabled); + void SetPreviewPanelEnabled(bool Enabled); + void AdjustPreviewToParent(bool adjust); + + // Slots +public slots: + void OnLineEditTextEdited(); + void OnBrowseButtonClicked(); + void OnEditButtonClicked(); + void OnExportButtonClicked(); + +private: + void Edit(); + void Export(); + void CreatePreviewPanel(); + void ShowPreviewPanel(); + void HidePreviewPanel(); + void LoadResource(const QString& ResPath); + void SetButtonsBasedOnResType(); +}; + +#endif // WRESOURCESELECTOR_H diff --git a/UI/WRollout.cpp b/UI/WRollout.cpp new file mode 100644 index 00000000..80366d4b --- /dev/null +++ b/UI/WRollout.cpp @@ -0,0 +1,64 @@ +#include "WRollout.h" +#include +#include +#include +#include +#include +#include + +WRollout::WRollout(QWidget *parent) : QWidget(parent) +{ + this->setContentsMargins(10, 20, 10, 10); +} + +WRollout::~WRollout() +{ +} + +void WRollout::setCollapsed(bool collapsed) +{ + mCollapsed = collapsed; +} + +bool WRollout::isCollapsed() +{ + return mCollapsed; +} + +void WRollout::setName(const QString& name) +{ + mpTopButton->setText(name); +} + +QString WRollout::getName() +{ + return mpTopButton->text(); +} + +void WRollout::paintEvent(QPaintEvent *) +{ + QPainter Painter(this); + + // Draw box + QPen Pen; + Pen.setColor(Qt::white); + Painter.setPen(Pen); + QBrush Brush; + Brush.setColor(Qt::white); + Painter.setBrush(Brush); + + int AreaBoxTop = (mCollapsed) ? 7 : 10; + QRect Area(QPoint(0,AreaBoxTop), size() - QSize(Pen.width(), Pen.width() + AreaBoxTop)); + Painter.drawRoundedRect(Area, 5.f, 5.f); + + // Draw button + QRect TopButton(QPoint(10,0), QSize(width() - 20, 21)); + QPalette Palette = qApp->palette(); + Painter.setBrush(Palette.color(QPalette::Text)); + + Painter.drawRect(TopButton); +} + +void WRollout::resizeEvent(QResizeEvent *) +{ +} diff --git a/UI/WRollout.h b/UI/WRollout.h new file mode 100644 index 00000000..14baed66 --- /dev/null +++ b/UI/WRollout.h @@ -0,0 +1,29 @@ +#ifndef WROLLOUT_H +#define WROLLOUT_H + +#include +#include + +class WRollout : public QWidget +{ + Q_PROPERTY(bool mCollapsed READ isCollapsed WRITE setCollapsed) + QPushButton *mpTopButton; + QWidget *mpContainerWidget; + bool mCollapsed; + +public: + explicit WRollout(QWidget *parent = 0); + ~WRollout(); + void setCollapsed(bool collapsed); + bool isCollapsed(); + void setName(const QString& name); + QString getName(); + + void paintEvent(QPaintEvent *); + void resizeEvent(QResizeEvent *); + +private: + bool mouseInButton(int x, int y); +}; + +#endif // WROLLOUT_H diff --git a/UI/WScanPreviewPanel.cpp b/UI/WScanPreviewPanel.cpp new file mode 100644 index 00000000..b1eca7a0 --- /dev/null +++ b/UI/WScanPreviewPanel.cpp @@ -0,0 +1,75 @@ +#include "WScanPreviewPanel.h" +#include "ui_WScanPreviewPanel.h" +#include "WStringPreviewPanel.h" +#include + +WScanPreviewPanel::WScanPreviewPanel(QWidget *parent) : + IPreviewPanel(parent), + ui(new Ui::WScanPreviewPanel) +{ + ui->setupUi(this); + ui->ScanTextWidget->setFrameShape(QFrame::NoFrame); + setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum ); +} + +WScanPreviewPanel::~WScanPreviewPanel() +{ + delete ui; +} + +EResType WScanPreviewPanel::ResType() +{ + return eScan; +} + +void WScanPreviewPanel::SetResource(CResource *pRes) +{ + // Clear existing UI + ui->ScanTypeLabel->clear(); + ui->ScanSpeedLabel->clear(); + ui->ScanCategoryLabel->clear(); + + // Set up new UI + if (pRes->Type() == eScan) + { + CScan *pScan = static_cast(pRes); + + // Scan type + if (pScan->IsImportant()) + ui->ScanTypeLabel->setText("Important"); + else + ui->ScanTypeLabel->setText("Normal"); + + // Scan speed + if (pScan->IsSlow()) + ui->ScanSpeedLabel->setText("Slow"); + else + ui->ScanSpeedLabel->setText("Fast"); + + // Scan category + switch (pScan->LogbookCategory()) + { + case CScan::eNone: + ui->ScanCategoryLabel->setText("None"); + break; + case CScan::eChozoLore: + ui->ScanCategoryLabel->setText("Chozo Lore"); + break; + case CScan::ePirateData: + ui->ScanCategoryLabel->setText("Pirate Data"); + break; + case CScan::eCreatures: + ui->ScanCategoryLabel->setText("Creatures"); + break; + case CScan::eResearch: + ui->ScanCategoryLabel->setText("Research"); + break; + } + + // Scan text + ui->ScanTextWidget->SetResource(pScan->ScanText()); + } + + else + ui->ScanTextWidget->SetResource(nullptr); +} diff --git a/UI/WScanPreviewPanel.h b/UI/WScanPreviewPanel.h new file mode 100644 index 00000000..5e613cd4 --- /dev/null +++ b/UI/WScanPreviewPanel.h @@ -0,0 +1,24 @@ +#ifndef WSCANPREVIEWPANEL_H +#define WSCANPREVIEWPANEL_H + +#include "IPreviewPanel.h" + +namespace Ui { +class WScanPreviewPanel; +} + +class WScanPreviewPanel : public IPreviewPanel +{ + Q_OBJECT + +public: + explicit WScanPreviewPanel(QWidget *parent = 0); + ~WScanPreviewPanel(); + EResType ResType(); + void SetResource(CResource *pRes); + +private: + Ui::WScanPreviewPanel *ui; +}; + +#endif // WSCANPREVIEWPANEL_H diff --git a/UI/WScanPreviewPanel.ui b/UI/WScanPreviewPanel.ui new file mode 100644 index 00000000..266af950 --- /dev/null +++ b/UI/WScanPreviewPanel.ui @@ -0,0 +1,154 @@ + + + WScanPreviewPanel + + + + 0 + 0 + 400 + 114 + + + + Form + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 9 + + + 0 + + + 9 + + + 0 + + + + + + + Type: + + + + + + + TextLabel + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Speed: + + + + + + + TextLabel + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Logbook: + + + + + + + TextLabel + + + + + + + + + + + Qt::Horizontal + + + + + + + + 0 + 1 + + + + + + + + + WStringPreviewPanel + QWidget +
WStringPreviewPanel.h
+ 1 +
+
+ + +
diff --git a/UI/WStringPreviewPanel.cpp b/UI/WStringPreviewPanel.cpp new file mode 100644 index 00000000..e8430304 --- /dev/null +++ b/UI/WStringPreviewPanel.cpp @@ -0,0 +1,52 @@ +#include "WStringPreviewPanel.h" +#include +#include +#include + +WStringPreviewPanel::WStringPreviewPanel(QWidget *pParent) : IPreviewPanel(pParent) +{ + mpTextLabel = new QLabel(this); + mpTextLabel->setWordWrap(true); + mpLayout = new QVBoxLayout(this); + mpLayout->setAlignment(Qt::AlignTop); + mpLayout->addWidget(mpTextLabel); + setLayout(mpLayout); + + QFontMetrics metrics(mpTextLabel->font()); + this->resize(400, 100); + this->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Minimum); +} + +WStringPreviewPanel::~WStringPreviewPanel() +{ +} + +EResType WStringPreviewPanel::ResType() +{ + return eStringTable; +} + +void WStringPreviewPanel::SetResource(CResource *pRes) +{ + mpTextLabel->clear(); + + if ((pRes) && (pRes->Type() == eStringTable)) + { + CStringTable *pString = static_cast(pRes); + QString text; + + // Build text string using first four strings from table (or less if there aren't 4) + u32 numStrings = (pString->GetStringCount() < 4 ? pString->GetStringCount() : 4); + for (u32 iStr = 0; iStr < numStrings; iStr++) + { + text += QString::fromStdWString(pString->GetString(0, iStr)); + text += "\n"; + } + + // Build text layout to determine where to elide the label + QTextLayout layout(text); + + + mpTextLabel->setText(text); + } +} diff --git a/UI/WStringPreviewPanel.h b/UI/WStringPreviewPanel.h new file mode 100644 index 00000000..bf169617 --- /dev/null +++ b/UI/WStringPreviewPanel.h @@ -0,0 +1,24 @@ +#ifndef WSTRINGPREVIEWPANEL_H +#define WSTRINGPREVIEWPANEL_H + +#include "IPreviewPanel.h" +#include +#include +#include + +class WStringPreviewPanel : public IPreviewPanel +{ + Q_OBJECT + + QLabel *mpTextLabel; + QVBoxLayout *mpLayout; + QSpacerItem *mpSpacer; + +public: + explicit WStringPreviewPanel(QWidget *pParent = 0); + ~WStringPreviewPanel(); + EResType ResType(); + void SetResource(CResource *pRes); +}; + +#endif // WSTRINGPREVIEWPANEL_H diff --git a/UI/WTextureGLWidget.cpp b/UI/WTextureGLWidget.cpp new file mode 100644 index 00000000..8028591e --- /dev/null +++ b/UI/WTextureGLWidget.cpp @@ -0,0 +1,156 @@ +#include "WTextureGLWidget.h" +#include +#include +#include +#include +#include +#include +#include + +WTextureGLWidget::WTextureGLWidget(QWidget *parent, CTexture *pTex) : QOpenGLWidget(parent) +{ + SetTexture(pTex); + mInitialized = false; +} + +WTextureGLWidget::~WTextureGLWidget() +{ + if (mInitialized) CGraphics::ReleaseContext(mContextID); +} + +void WTextureGLWidget::initializeGL() +{ + CGraphics::Initialize(); + glEnable(GL_BLEND); + mContextID = CGraphics::GetContextIndex(); + mInitialized = true; +} + +void WTextureGLWidget::paintGL() +{ + CGraphics::SetActiveContext(mContextID); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glClearColor(1.f, 0.f, 0.f, 0.f); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + glDisable(GL_DEPTH_TEST); + + // Set matrices to identity + CGraphics::sMVPBlock.ModelMatrix = CMatrix4f::skIdentity; + CGraphics::sMVPBlock.ViewMatrix = CMatrix4f::skIdentity; + CGraphics::sMVPBlock.ProjectionMatrix = CMatrix4f::skIdentity; + CGraphics::UpdateMVPBlock(); + + // Draw checkerboard background + CDrawUtil::UseTextureShader(); + glDepthMask(GL_FALSE); + CDrawUtil::LoadCheckerboardTexture(0); + CDrawUtil::DrawSquare(&mCheckerCoords[0].x); + + // Make it darker + CDrawUtil::UseColorShader(CColor((u8) 0, 0, 0, 128)); + glDepthMask(GL_FALSE); + CDrawUtil::DrawSquare(); + + // Leave it at just the checkerboard if there's no texture + if (!mpTexture) return; + + // Draw texture + CDrawUtil::UseTextureShader(); + mpTexture->Bind(0); + CGraphics::sMVPBlock.ModelMatrix = mTexTransform.ToMatrix4f(); + CGraphics::UpdateMVPBlock(); + CDrawUtil::DrawSquare(); + + glEnable(GL_DEPTH_TEST); +} + +void WTextureGLWidget::resizeGL(int w, int h) +{ + mAspectRatio = (float) w / (float) h; + glViewport(0, 0, w, h); + + CalcTexTransform(); + CalcCheckerCoords(); + update(); +} + +void WTextureGLWidget::SetTexture(CTexture *pTex) +{ + mpTexture = pTex; + mTexToken = CToken(pTex); + + if (pTex) mTexAspectRatio = (float) pTex->Width() / (float) pTex->Height(); + else mTexAspectRatio = 0.f; + + CalcTexTransform(); + CalcCheckerCoords(); + update(); +} + +void WTextureGLWidget::CalcTexTransform() +{ + // This is a simple scale based on the dimensions of the viewport, in order to + // avoid stretching the texture if it doesn't match the viewport aspect ratio. + mTexTransform = CTransform4f::skIdentity; + float Diff = mTexAspectRatio / mAspectRatio; + + if (mAspectRatio >= mTexAspectRatio) + mTexTransform.Scale(Diff, 1.f, 1.f); + else + mTexTransform.Scale(1.f, 1.f / Diff, 1.f); +} + +void WTextureGLWidget::CalcCheckerCoords() +{ + // The translation vector is set up so the checkerboard stays centered on the screen + // rather than expanding from the bottom-left corner. This makes it look more natural. + CVector2f Trans; + float InvAspect = (mAspectRatio == 0.f) ? 0.f : 1.f / mAspectRatio; + float InvTexAspect = (mTexAspectRatio == 0.f) ? 0.f : 1.f / mTexAspectRatio; + float XBase, YBase, XScale, YScale; + + // Horizontal texture + if ((mpTexture != nullptr) && (mpTexture->Width() > mpTexture->Height())) + { + XBase = 1.f; + YBase = InvTexAspect; + XScale = InvTexAspect; + YScale = 1.f; + } + // Vertical texture + else + { + XBase = mTexAspectRatio; + YBase = 1.f; + XScale = 1.f; + YScale = mTexAspectRatio; + } + + // Space on left/right + if (mAspectRatio > mTexAspectRatio) + { + Trans = CVector2f(mAspectRatio / 2.f, 0.5f) * -XScale; + mCheckerCoords[0] = CVector2f(0.f, YBase); + mCheckerCoords[1] = CVector2f(mAspectRatio * XScale, YBase); + mCheckerCoords[2] = CVector2f(mAspectRatio * XScale, 0.f); + mCheckerCoords[3] = CVector2f(0.f, 0.f); + } + + // Space on top/bottom + else + { + Trans = CVector2f(0.5f, InvAspect / 2.f) * -YScale; + mCheckerCoords[0] = CVector2f(0.f, InvAspect * YScale); + mCheckerCoords[1] = CVector2f(XBase, InvAspect * YScale); + mCheckerCoords[2] = CVector2f(XBase, 0.f); + mCheckerCoords[3] = CVector2f(0.f, 0.f); + } + + // Finally, apply translation/scale + for (u32 iCoord = 0; iCoord < 4; iCoord++) + { + mCheckerCoords[iCoord] += Trans; + mCheckerCoords[iCoord] *= 10.f; + } + +} diff --git a/UI/WTextureGLWidget.h b/UI/WTextureGLWidget.h new file mode 100644 index 00000000..9bd9d5cb --- /dev/null +++ b/UI/WTextureGLWidget.h @@ -0,0 +1,39 @@ +#ifndef WTEXTUREGLWIDGET_H +#define WTEXTUREGLWIDGET_H + +#include +#include + +#include +#include +#include +#include +#include + +class WTextureGLWidget : public QOpenGLWidget +{ + Q_OBJECT + + float mAspectRatio; + CTexture *mpTexture; + CToken mTexToken; + float mTexAspectRatio; + CTransform4f mTexTransform; + CVector2f mCheckerCoords[4]; + u32 mContextID; + bool mInitialized; + +public: + explicit WTextureGLWidget(QWidget *parent = 0, CTexture *pTex = 0); + ~WTextureGLWidget(); + void initializeGL(); + void paintGL(); + void resizeGL(int w, int h); + void SetTexture(CTexture *pTex); + +private: + void CalcTexTransform(); + void CalcCheckerCoords(); +}; + +#endif // WTEXTUREGLWIDGET_H diff --git a/UI/WTexturePreviewPanel.cpp b/UI/WTexturePreviewPanel.cpp new file mode 100644 index 00000000..b154fdb2 --- /dev/null +++ b/UI/WTexturePreviewPanel.cpp @@ -0,0 +1,63 @@ +#include "WTexturePreviewPanel.h" +#include "ui_WTexturePreviewPanel.h" +#include "WTextureGLWidget.h" + +WTexturePreviewPanel::WTexturePreviewPanel(QWidget *parent, CTexture *pTexture) : + IPreviewPanel(parent), + ui(new Ui::WTexturePreviewPanel) +{ + ui->setupUi(this); + SetResource(pTexture); +} + +WTexturePreviewPanel::~WTexturePreviewPanel() +{ + delete ui; +} + +EResType WTexturePreviewPanel::ResType() +{ + return eTexture; +} + +void WTexturePreviewPanel::SetResource(CResource *pRes) +{ + CTexture *pTexture = (CTexture*) pRes; + ui->TextureGLWidget->SetTexture(pTexture); + + if (pTexture) + { + std::string Name = StringUtil::GetFileNameWithExtension(pTexture->Source()); + ui->TextureNameLabel->setText( QString::fromStdString(Name) ); + + QString TexInfo; + TexInfo += QString::number(pTexture->Width()) + "x" + QString::number(pTexture->Height()); + TexInfo += " "; + + switch (pTexture->SourceTexelFormat()) + { + case eGX_I4: TexInfo += "I4"; break; + case eGX_I8: TexInfo += "I8"; break; + case eGX_IA4: TexInfo += "IA4"; break; + case eGX_IA8: TexInfo += "IA8"; break; + case eGX_C4: TexInfo += "C4"; break; + case eGX_C8: TexInfo += "C8"; break; + case eGX_C14x2: TexInfo += "C14x2"; break; + case eGX_RGB565: TexInfo += "RGB565"; break; + case eGX_RGB5A3: TexInfo += "RGB5A3"; break; + case eGX_RGBA8: TexInfo += "RGBA8"; break; + case eGX_CMPR: TexInfo += "CMPR"; break; + default: TexInfo += "Invalid Format"; break; + } + + TexInfo += " / "; + TexInfo += QString::number(pTexture->NumMipMaps()) + " mipmaps"; + + ui->TextureInfoLabel->setText(TexInfo); + } + else + { + ui->TextureNameLabel->setText("No texture"); + ui->TextureInfoLabel->setText(""); + } +} diff --git a/UI/WTexturePreviewPanel.h b/UI/WTexturePreviewPanel.h new file mode 100644 index 00000000..bcab764d --- /dev/null +++ b/UI/WTexturePreviewPanel.h @@ -0,0 +1,25 @@ +#ifndef WTEXTUREPREVIEWPANEL_H +#define WTEXTUREPREVIEWPANEL_H + +#include "IPreviewPanel.h" +#include + +namespace Ui { +class WTexturePreviewPanel; +} + +class WTexturePreviewPanel : public IPreviewPanel +{ + Q_OBJECT + +public: + explicit WTexturePreviewPanel(QWidget *parent = 0, CTexture *pTexture = 0); + ~WTexturePreviewPanel(); + EResType ResType(); + void SetResource(CResource *pRes); + +private: + Ui::WTexturePreviewPanel *ui; +}; + +#endif // WTEXTUREPREVIEWPANEL_H diff --git a/UI/WTexturePreviewPanel.ui b/UI/WTexturePreviewPanel.ui new file mode 100644 index 00000000..590eb171 --- /dev/null +++ b/UI/WTexturePreviewPanel.ui @@ -0,0 +1,77 @@ + + + WTexturePreviewPanel + + + + 0 + 0 + 260 + 305 + + + + + 0 + 0 + + + + Form + + + + 2 + + + 2 + + + 2 + + + + + + 0 + 0 + + + + + 256 + 256 + + + + + + + + TextureName + + + true + + + + + + + TextureInfo + + + + + + + + WTextureGLWidget + QWidget +
WTextureGLWidget.h
+ 1 +
+
+ + +
diff --git a/UI/WVectorEditor.cpp b/UI/WVectorEditor.cpp new file mode 100644 index 00000000..c554d7a8 --- /dev/null +++ b/UI/WVectorEditor.cpp @@ -0,0 +1,117 @@ +#include "WVectorEditor.h" + +WVectorEditor::WVectorEditor(QWidget *pParent) : QWidget(pParent) +{ + mValue = CVector3f::skZero; + + mpSpinBoxX = new WDraggableSpinBox(this); + mpSpinBoxY = new WDraggableSpinBox(this); + mpSpinBoxZ = new WDraggableSpinBox(this); + connect(mpSpinBoxX, SIGNAL(valueChanged(double)), this, SLOT(SetX(double))); + connect(mpSpinBoxY, SIGNAL(valueChanged(double)), this, SLOT(SetY(double))); + connect(mpSpinBoxZ, SIGNAL(valueChanged(double)), this, SLOT(SetZ(double))); + + /*mpLayout = new QHBoxLayout(this); + mpLayout->setContentsMargins(0,0,0,0); + mpLayout->addWidget(mpSpinBoxX); + mpLayout->addWidget(mpSpinBoxY); + mpLayout->addWidget(mpSpinBoxZ); + setLayout(mpLayout);*/ + + mpGroupBox = new QGroupBox(this); + mpFormLayout = new QFormLayout(mpGroupBox); + mpFormLayout->addRow(new QLabel("X", mpGroupBox), mpSpinBoxX); + mpFormLayout->addRow(new QLabel("Y", mpGroupBox), mpSpinBoxY); + mpFormLayout->addRow(new QLabel("Z", mpGroupBox), mpSpinBoxZ); + mpGroupBox->setLayout(mpFormLayout); + + mpLayout = new QHBoxLayout(this); + mpLayout->addWidget(mpGroupBox); + setLayout(mpLayout); +} + +WVectorEditor::WVectorEditor(const CVector3f& Value, QWidget *pParent) : QWidget(pParent) +{ + mValue = Value; + + mpSpinBoxX = new WDraggableSpinBox(this); + mpSpinBoxY = new WDraggableSpinBox(this); + mpSpinBoxZ = new WDraggableSpinBox(this); + mpSpinBoxX->setValue((double) Value.x); + mpSpinBoxY->setValue((double) Value.y); + mpSpinBoxZ->setValue((double) Value.z); + mpSpinBoxX->setMinimumHeight(21); + mpSpinBoxY->setMinimumHeight(21); + mpSpinBoxZ->setMinimumHeight(21); + mpSpinBoxX->setMaximumHeight(21); + mpSpinBoxY->setMaximumHeight(21); + mpSpinBoxZ->setMaximumHeight(21); + connect(mpSpinBoxX, SIGNAL(valueChanged(double)), this, SLOT(SetX(double))); + connect(mpSpinBoxY, SIGNAL(valueChanged(double)), this, SLOT(SetY(double))); + connect(mpSpinBoxZ, SIGNAL(valueChanged(double)), this, SLOT(SetZ(double))); + + mpLayout = new QHBoxLayout(this); + mpLayout->setContentsMargins(0,0,0,0); + mpLayout->addWidget(mpSpinBoxX); + mpLayout->addWidget(mpSpinBoxY); + mpLayout->addWidget(mpSpinBoxZ); + setLayout(mpLayout); +} + +WVectorEditor::~WVectorEditor() +{ +} + + +CVector3f WVectorEditor::Value() +{ + return mValue; +} + +void WVectorEditor::SetValue(const CVector3f& Value) +{ + mValue = Value; + + mpSpinBoxX->blockSignals(true); + mpSpinBoxY->blockSignals(true); + mpSpinBoxZ->blockSignals(true); + mpSpinBoxX->setValue((double) Value.x); + mpSpinBoxY->setValue((double) Value.y); + mpSpinBoxZ->setValue((double) Value.z); + mpSpinBoxX->blockSignals(false); + mpSpinBoxY->blockSignals(false); + mpSpinBoxZ->blockSignals(false); +} + +void WVectorEditor::SetText(const QString &Text) +{ + mpGroupBox->setTitle(Text); +} + +// ************ SLOTS ************ +void WVectorEditor::SetX(double x) +{ + mValue.x = (float) x; + + mpSpinBoxX->blockSignals(true); + mpSpinBoxX->setValue((double) x); + mpSpinBoxX->blockSignals(false); +} + +void WVectorEditor::SetY(double y) +{ + mValue.y = (float) y; + + mpSpinBoxY->blockSignals(true); + mpSpinBoxY->setValue((double) y); + mpSpinBoxY->blockSignals(false); +} + +void WVectorEditor::SetZ(double z) +{ + mValue.z = (float) z; + + mpSpinBoxZ->blockSignals(true); + mpSpinBoxZ->setValue((double) z); + mpSpinBoxZ->blockSignals(false); +} diff --git a/UI/WVectorEditor.h b/UI/WVectorEditor.h new file mode 100644 index 00000000..e408883d --- /dev/null +++ b/UI/WVectorEditor.h @@ -0,0 +1,43 @@ +#ifndef WVECTOREDITOR_H +#define WVECTOREDITOR_H + +#include +#include +#include "WDraggableSpinBox.h" +#include +#include +#include +#include + +class WVectorEditor : public QWidget +{ + Q_OBJECT + + CVector3f mValue; + WDraggableSpinBox *mpSpinBoxX; + WDraggableSpinBox *mpSpinBoxY; + WDraggableSpinBox *mpSpinBoxZ; + QHBoxLayout *mpLayout; + + // new layout test + QGroupBox *mpGroupBox; + QLabel *mpLabelX; + QLabel *mpLabelY; + QLabel *mpLabelZ; + QFormLayout *mpFormLayout; + +public: + explicit WVectorEditor(QWidget *pParent = 0); + WVectorEditor(const CVector3f& Value, QWidget *pParent = 0); + ~WVectorEditor(); + CVector3f Value(); + void SetValue(const CVector3f& Value); + void SetText(const QString& Text); + +public slots: + void SetX(double x); + void SetY(double y); + void SetZ(double z); +}; + +#endif // WVECTOREDITOR_H diff --git a/UI/WorldEditor/CLayerEditor.cpp b/UI/WorldEditor/CLayerEditor.cpp new file mode 100644 index 00000000..e87e4afe --- /dev/null +++ b/UI/WorldEditor/CLayerEditor.cpp @@ -0,0 +1,54 @@ +#include "CLayerEditor.h" +#include "ui_CLayerEditor.h" + +CLayerEditor::CLayerEditor(QWidget *parent) : + QDialog(parent), + ui(new Ui::CLayerEditor) +{ + ui->setupUi(this); + + mpArea = nullptr; + mpModel = new CLayerModel(this); + ui->LayerSelectComboBox->setModel(mpModel); + + connect(ui->LayerSelectComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(SetCurrentIndex(int))); + connect(ui->NameLineEdit, SIGNAL(textEdited(QString)), this, SLOT(EditLayerName(QString))); + connect(ui->ActiveCheckBox, SIGNAL(toggled(bool)), this, SLOT(EditLayerActive(bool))); +} + +CLayerEditor::~CLayerEditor() +{ + delete ui; +} + +void CLayerEditor::SetArea(CGameArea *pArea) +{ + mpArea = pArea; + mpModel->SetArea(pArea); + SetCurrentIndex(0); +} + +// ************ SLOTS ************ +void CLayerEditor::SetCurrentIndex(int index) +{ + ui->LayerSelectComboBox->blockSignals(true); + ui->LayerSelectComboBox->setCurrentIndex(index); + ui->LayerSelectComboBox->blockSignals(false); + + QModelIndex ModelIndex = mpModel->index(index); + mpCurrentLayer = mpModel->Layer(ModelIndex); + + ui->NameLineEdit->setText(QString::fromStdString(mpCurrentLayer->Name())); + ui->ActiveCheckBox->setChecked(mpCurrentLayer->IsActive()); +} + +void CLayerEditor::EditLayerName(const QString &name) +{ + mpCurrentLayer->SetName(name.toStdString()); + ui->LayerSelectComboBox->update(); +} + +void CLayerEditor::EditLayerActive(bool active) +{ + mpCurrentLayer->SetActive(active); +} diff --git a/UI/WorldEditor/CLayerEditor.h b/UI/WorldEditor/CLayerEditor.h new file mode 100644 index 00000000..cab33f0a --- /dev/null +++ b/UI/WorldEditor/CLayerEditor.h @@ -0,0 +1,32 @@ +#ifndef CLAYEREDITOR_H +#define CLAYEREDITOR_H + +#include +#include "CLayerModel.h" + +namespace Ui { +class CLayerEditor; +} + +class CLayerEditor : public QDialog +{ + Q_OBJECT + CGameArea *mpArea; + CLayerModel *mpModel; + CScriptLayer *mpCurrentLayer; + +public: + explicit CLayerEditor(QWidget *parent = 0); + ~CLayerEditor(); + void SetArea(CGameArea *pArea); + +public slots: + void SetCurrentIndex(int index); + void EditLayerName(const QString& name); + void EditLayerActive(bool active); + +private: + Ui::CLayerEditor *ui; +}; + +#endif // CLAYEREDITOR_H diff --git a/UI/WorldEditor/CLayerEditor.ui b/UI/WorldEditor/CLayerEditor.ui new file mode 100644 index 00000000..9d19bc3e --- /dev/null +++ b/UI/WorldEditor/CLayerEditor.ui @@ -0,0 +1,60 @@ + + + CLayerEditor + + + + 0 + 0 + 400 + 68 + + + + Edit Layers + + + + + + + + + + + + + Name: + + + + + + + + + + + + + + Active: + + + + + + + + + + + + + + + + + + + diff --git a/UI/WorldEditor/CLayerModel.cpp b/UI/WorldEditor/CLayerModel.cpp new file mode 100644 index 00000000..d77543c6 --- /dev/null +++ b/UI/WorldEditor/CLayerModel.cpp @@ -0,0 +1,46 @@ +#include "CLayerModel.h" + +CLayerModel::CLayerModel(QObject *pParent) : QAbstractListModel(pParent) +{ + mpArea = nullptr; + mHasGenerateLayer = false; +} + +CLayerModel::~CLayerModel() +{ +} + +int CLayerModel::rowCount(const QModelIndex &parent) const +{ + if (!mpArea) return 0; + if (mHasGenerateLayer) return mpArea->GetScriptLayerCount() + 1; + else return mpArea->GetScriptLayerCount(); +} + +QVariant CLayerModel::data(const QModelIndex &index, int role) const +{ + if (mpArea && (role == Qt::DisplayRole) && (index.row() < rowCount(QModelIndex()))) + return QString::fromStdString(Layer(index)->Name()); + + return QVariant::Invalid; +} + +void CLayerModel::SetArea(CGameArea *pArea) +{ + mpArea = pArea; + mHasGenerateLayer = (pArea->GetGeneratorLayer() != nullptr); + emit layoutChanged(); +} + +CScriptLayer* CLayerModel::Layer(const QModelIndex& index) const +{ + if (!mpArea) return nullptr; + u32 NumLayers = mpArea->GetScriptLayerCount(); + + if (index.row() < NumLayers) + return mpArea->GetScriptLayer(index.row()); + if (mHasGenerateLayer && (index.row() == NumLayers)) + return mpArea->GetGeneratorLayer(); + + return nullptr; +} diff --git a/UI/WorldEditor/CLayerModel.h b/UI/WorldEditor/CLayerModel.h new file mode 100644 index 00000000..bb9c3870 --- /dev/null +++ b/UI/WorldEditor/CLayerModel.h @@ -0,0 +1,21 @@ +#ifndef CLAYERMODEL_H +#define CLAYERMODEL_H + +#include +#include + +class CLayerModel : public QAbstractListModel +{ + CGameArea *mpArea; + bool mHasGenerateLayer; + +public: + explicit CLayerModel(QObject *pParent = 0); + ~CLayerModel(); + int rowCount(const QModelIndex &parent) const; + QVariant data(const QModelIndex &index, int role) const; + void SetArea(CGameArea *pArea); + CScriptLayer* Layer(const QModelIndex& index) const; +}; + +#endif // CLAYERMODEL_H diff --git a/UI/WorldEditor/CLayersInstanceModel.cpp b/UI/WorldEditor/CLayersInstanceModel.cpp new file mode 100644 index 00000000..b73d8207 --- /dev/null +++ b/UI/WorldEditor/CLayersInstanceModel.cpp @@ -0,0 +1,119 @@ +#include "CLayersInstanceModel.h" + +/* The tree has 3 levels: + * 1. Node Type (Script Object, Light) - represented with ID of 0 + * 2. Layer - represented with flags + * 3. Instance - represented with pointer to instance (0x1 bit is guaranteed to be clear) + * + * Flags for Layer tree items: + * AAAAAAAAAAAAAAAAAAAAAAAAAAABBBBC + * A: Row index + * B: Node type row index + * C: Item type (ObjType, Instance) + */ +#define LAYERS_ROW_INDEX_MASK 0xFFFFFFE0 +#define LAYERS_NODE_TYPE_MASK 0x0000001E +#define LAYERS_ITEM_TYPE_MASK 0x00000001 +#define LAYERS_ROW_INDEX_SHIFT 5 +#define LAYERS_NODE_TYPE_SHIFT 1 +#define LAYERS_ITEM_TYPE_SHIFT 0 + +CLayersInstanceModel::CLayersInstanceModel(QObject *pParent) : QAbstractItemModel(pParent) +{ + +} + +CLayersInstanceModel::~CLayersInstanceModel() +{ + +} + +QVariant CLayersInstanceModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if ((orientation == Qt::Horizontal) && (role == Qt::DisplayRole)) + { + switch (section) + { + case 0: return "Name"; + case 1: return "Type"; + case 2: return "Show"; + } + } + + return QVariant::Invalid; +} + +QModelIndex CLayersInstanceModel::index(int row, int column, const QModelIndex &parent) const +{ + return QModelIndex(); +} + +QModelIndex CLayersInstanceModel::parent(const QModelIndex &child) const +{ + return QModelIndex(); +} + +int CLayersInstanceModel::rowCount(const QModelIndex &parent) const +{ + return 0; +} + +int CLayersInstanceModel::columnCount(const QModelIndex &parent) const +{ + return 3; +} + +QVariant CLayersInstanceModel::data(const QModelIndex &index, int role) const +{ + return QVariant::Invalid; +} + +void CLayersInstanceModel::SetEditor(CWorldEditor *pEditor) +{ + mpEditor = pEditor; + mpScene = (pEditor ? pEditor->Scene() : nullptr); + mpArea = (pEditor ? pEditor->ActiveArea() : nullptr); +} + +void CLayersInstanceModel::NodeCreated(CSceneNode *pNode) +{ + emit layoutChanged(); +} + +void CLayersInstanceModel::NodeDeleted(CSceneNode *pNode) +{ + emit layoutChanged(); +} + +CScriptLayer* CLayersInstanceModel::IndexLayer(const QModelIndex& index) const +{ + return nullptr; +} + +CScriptObject* CLayersInstanceModel::IndexObject(const QModelIndex& index) const +{ + return nullptr; +} + +// ************ STATIC ************ +CLayersInstanceModel::EIndexType CLayersInstanceModel::IndexType(const QModelIndex& index) +{ + if (!index.isValid()) return eRootIndex; + else if (index.internalId() == 0) return eNodeTypeIndex; + else if (((index.internalId() & LAYERS_ITEM_TYPE_MASK) >> LAYERS_ITEM_TYPE_SHIFT) == 1) return eLayerIndex; + else return eInstanceIndex; +} + +CLayersInstanceModel::ENodeType CLayersInstanceModel::IndexNodeType(const QModelIndex& index) +{ + EIndexType type = IndexType(index); + + switch (type) + { + case eRootIndex: return eInvalidType; + case eNodeTypeIndex: return (ENodeType) index.row(); + case eLayerIndex: return (ENodeType) index.parent().row(); + case eInstanceIndex: return (ENodeType) index.parent().parent().row(); + default: return eInvalidType; + } +} diff --git a/UI/WorldEditor/CLayersInstanceModel.h b/UI/WorldEditor/CLayersInstanceModel.h new file mode 100644 index 00000000..7bba66ca --- /dev/null +++ b/UI/WorldEditor/CLayersInstanceModel.h @@ -0,0 +1,49 @@ +#ifndef CLAYERSINSTANCEMODEL_H +#define CLAYERSINSTANCEMODEL_H + +#include +#include +#include "../CWorldEditor.h" + +// Only supports script layers atm - maybe light layers later...? +class CLayersInstanceModel : public QAbstractItemModel +{ + Q_OBJECT + +public: + enum EIndexType { + eRootIndex, eNodeTypeIndex, eLayerIndex, eInstanceIndex + }; + + enum ENodeType { + eScriptType = 0x0, + eLightType = 0x1, + eInvalidType = 0xFF + }; + +private: + CWorldEditor *mpEditor; + CSceneManager *mpScene; + CGameArea *mpArea; + +public: + explicit CLayersInstanceModel(QObject *pParent = 0); + ~CLayersInstanceModel(); + QVariant headerData(int section, Qt::Orientation orientation, int role) const; + QModelIndex index(int row, int column, const QModelIndex &parent) const; + QModelIndex parent(const QModelIndex &child) const; + int rowCount(const QModelIndex &parent) const; + int columnCount(const QModelIndex &parent) const; + QVariant data(const QModelIndex &index, int role) const; + void SetEditor(CWorldEditor *pEditor); + void NodeCreated(CSceneNode *pNode); + void NodeDeleted(CSceneNode *pNode); + CScriptLayer* IndexLayer(const QModelIndex& index) const; + CScriptObject* IndexObject(const QModelIndex& index) const; + + // Static + static EIndexType IndexType(const QModelIndex& index); + static ENodeType IndexNodeType(const QModelIndex& index); +}; + +#endif // CLAYERSINSTANCEMODEL_H diff --git a/UI/WorldEditor/CLinkModel.cpp b/UI/WorldEditor/CLinkModel.cpp new file mode 100644 index 00000000..00e3951e --- /dev/null +++ b/UI/WorldEditor/CLinkModel.cpp @@ -0,0 +1,101 @@ +#include "CLinkModel.h" +#include +#include + +CLinkModel::CLinkModel(QObject *pParent) : QAbstractTableModel(pParent) +{ + mpObject = nullptr; + mType = eOutgoing; +} + +void CLinkModel::SetObject(CScriptObject *pObj) +{ + mpObject = pObj; + emit layoutChanged(); +} + +void CLinkModel::SetConnectionType(EConnectionType type) +{ + mType = type; + emit layoutChanged(); +} + +int CLinkModel::rowCount(const QModelIndex&) const +{ + if (mpObject) + { + if (mType == eIncoming) + return mpObject->NumInLinks(); + else + return mpObject->NumOutLinks(); + } + + else return 0; +} + +int CLinkModel::columnCount(const QModelIndex &parent) const +{ + return 3; +} + +QVariant CLinkModel::data(const QModelIndex &index, int role) const +{ + if (!mpObject) return QVariant::Invalid; + + else if ((role == Qt::DisplayRole) || (role == Qt::ToolTipRole)) + { + SLink link = (mType == eIncoming ? mpObject->InLink(index.row()) : mpObject->OutLink(index.row())); + + switch (index.column()) + { + + case 0: // Column 0 - Target Object + { + CScriptObject *pTargetObj = mpObject->Area()->GetInstanceByID(link.ObjectID); + + if (role == Qt::DisplayRole) { + if (pTargetObj) return QString::fromStdString(pTargetObj->GetInstanceName()); + else return QString("0x") + QString::number(link.ObjectID, 16); + } + else { + QString ObjType = QString("[%1] ").arg(QString::fromStdString(pTargetObj->Template()->TemplateName())); + if (pTargetObj) return ObjType + QString::fromStdString(pTargetObj->GetInstanceName()); + else return ObjType + QString("0x") + QString::number(link.ObjectID, 16); + } + } + + case 1: // Column 1 - State + { + std::string StateName = mpObject->MasterTemplate()->StateByID(link.State); + return QString::fromStdString(StateName); + } + + case 2: // Column 2 - Message + { + std::string MessageName = mpObject->MasterTemplate()->MessageByID(link.Message); + return QString::fromStdString(MessageName); + } + + default: + return QVariant::Invalid; + } + } + + else return QVariant::Invalid; +} + +QVariant CLinkModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if ((orientation == Qt::Horizontal) && (role == Qt::DisplayRole)) + { + switch (section) + { + case 0: return (mType == eIncoming ? "Sender" : "Target"); + case 1: return "State"; + case 2: return "Message"; + default: return QVariant::Invalid; + } + } + + else return QVariant::Invalid; +} diff --git a/UI/WorldEditor/CLinkModel.h b/UI/WorldEditor/CLinkModel.h new file mode 100644 index 00000000..3bbb1560 --- /dev/null +++ b/UI/WorldEditor/CLinkModel.h @@ -0,0 +1,30 @@ +#ifndef CCONNECTIONMODEL_H +#define CCONNECTIONMODEL_H + +#include +#include + +class CLinkModel : public QAbstractTableModel +{ + Q_OBJECT + +public: + enum EConnectionType { + eIncoming, eOutgoing + }; + +private: + CScriptObject *mpObject; + EConnectionType mType; + +public: + explicit CLinkModel(QObject *pParent = 0); + void SetObject(CScriptObject *pObj); + void SetConnectionType(EConnectionType type); + int rowCount(const QModelIndex& parent = QModelIndex()) const; + int columnCount(const QModelIndex &parent) const; + QVariant data(const QModelIndex &index, int role) const; + QVariant headerData(int section, Qt::Orientation orientation, int role) const; +}; + +#endif // CCONNECTIONMODEL_H diff --git a/UI/WorldEditor/CTypesInstanceModel.cpp b/UI/WorldEditor/CTypesInstanceModel.cpp new file mode 100644 index 00000000..df2aa17d --- /dev/null +++ b/UI/WorldEditor/CTypesInstanceModel.cpp @@ -0,0 +1,451 @@ +#include "CTypesInstanceModel.h" +#include +#include +#include + +/* The tree has 3 levels: + * 1. Node Type (Script Object, Light, World Mesh, etc) - represented with ID of 0 + * 2. Object Type (Actor, Platform, SpawnPoint, etc) - represented with flags + * 3. Instance - represented with pointer to instance (0x1 bit is guaranteed to be clear) + * + * Flags for Object Type tree items + * AAAAAAAAAAAAAAAAAAAAAAAAAAABBBBC + * A: Row index + * B: Node type row index + * C: Item type (ObjType, Instance) + */ +#define TYPES_ROW_INDEX_MASK 0xFFFFFFE0 +#define TYPES_NODE_TYPE_MASK 0x0000001E +#define TYPES_ITEM_TYPE_MASK 0x00000001 +#define TYPES_ROW_INDEX_SHIFT 5 +#define TYPES_NODE_TYPE_SHIFT 1 +#define TYPES_ITEM_TYPE_SHIFT 0 + +bool SortTemplatesAlphabetical(CScriptTemplate *pA, CScriptTemplate *pB) +{ + return (pA->TemplateName() < pB->TemplateName()); +} + +CTypesInstanceModel::CTypesInstanceModel(QObject *pParent) : QAbstractItemModel(pParent) +{ + mpEditor = nullptr; + mpScene = nullptr; + mpArea = nullptr; + mpCurrentMaster = nullptr; + mModelType = eLayers; + mBaseItems << "Script"; +} + +CTypesInstanceModel::~CTypesInstanceModel() +{ +} + +QVariant CTypesInstanceModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if ((orientation == Qt::Horizontal) && (role == Qt::DisplayRole)) + { + switch (section) + { + case 0: return "Name"; + case 1: return (mModelType == eLayers ? "Type" : "Layer"); + case 2: return "Show"; + } + } + return QVariant::Invalid; +} + +QModelIndex CTypesInstanceModel::index(int row, int column, const QModelIndex &parent) const +{ + if (!hasIndex(row, column, parent)) + return QModelIndex(); + + EIndexType type = IndexType(parent); + + // Node type index + if (type == eRootIndex) + { + if (row < mBaseItems.count()) + return createIndex(row, column, quint32(0)); + else + return QModelIndex(); + } + + // Object type index + else if (type == eNodeTypeIndex) + return createIndex(row, column, ((row << TYPES_ROW_INDEX_SHIFT) | (parent.row() << TYPES_NODE_TYPE_SHIFT) | 1)); + + // Instance index + else if (type == eObjectTypeIndex) + { + u32 RootRow = parent.parent().row(); + + // Object + if (RootRow == 0) + { + if (mModelType == eLayers) + { + CScriptLayer *pLayer = mpArea->GetScriptLayer(parent.row()); + if (row >= pLayer->GetNumObjects()) + return QModelIndex(); + else + return createIndex(row, column, (*pLayer)[row]); + } + + else if (mModelType == eTypes) + { + const std::list& list = mTemplateList[parent.row()]->ObjectList(); + if (row >= list.size()) + return QModelIndex(); + else + { + auto it = std::next(list.begin(), row); + return createIndex(row, column, *it); + } + } + } + + // todo: implement getters for other types + } + + return QModelIndex(); +} + +QModelIndex CTypesInstanceModel::parent(const QModelIndex &child) const +{ + EIndexType type = IndexType(child); + + // Root parent + if (type == eNodeTypeIndex) + return QModelIndex(); + + // Node type parent + if (type == eObjectTypeIndex) + { + u32 NodeTypeRow = (child.internalId() & TYPES_NODE_TYPE_MASK) >> TYPES_NODE_TYPE_SHIFT; + return createIndex(NodeTypeRow, 0, quint32(0)); + } + + // Object type parent + else if (type == eInstanceIndex) + { + CScriptObject *pObj = static_cast (child.internalPointer()); + + if (mModelType == eLayers) + { + CScriptLayer *pLayer = pObj->Layer(); + + for (u32 iLyr = 0; iLyr < mpArea->GetScriptLayerCount(); iLyr++) + { + if (mpArea->GetScriptLayer(iLyr) == pLayer) + return createIndex(iLyr, 0, (iLyr << TYPES_ROW_INDEX_SHIFT) | 1); + } + } + + else if (mModelType == eTypes) + { + CScriptTemplate *pTemp = pObj->Template(); + + for (u32 iTemp = 0; iTemp < mTemplateList.size(); iTemp++) + { + if (mTemplateList[iTemp] == pTemp) + return createIndex(iTemp, 0, (iTemp << TYPES_ROW_INDEX_SHIFT) | 1); + } + } + } + + return QModelIndex(); +} + +int CTypesInstanceModel::rowCount(const QModelIndex &parent) const +{ + EIndexType type = IndexType(parent); + + // Node types + if (type == eRootIndex) + return mBaseItems.count(); + + // Object types + else if (type == eNodeTypeIndex) + { + // Script Objects + if (parent.row() == 0) { + if (mModelType == eLayers) + return (mpArea ? mpArea->GetScriptLayerCount() : 0); + else + return mTemplateList.size(); + } + else + return 0; + } + + // Instances + else if (type == eObjectTypeIndex) + { + u32 RowIndex = ((parent.internalId() & TYPES_ROW_INDEX_MASK) >> TYPES_ROW_INDEX_SHIFT); + if (mModelType == eLayers) + return (mpArea ? mpArea->GetScriptLayer(RowIndex)->GetNumObjects() : 0); + else + return mTemplateList[RowIndex]->NumObjects(); + } + + else + return 0; +} + +int CTypesInstanceModel::columnCount(const QModelIndex &parent) const +{ + return 3; +} + +QVariant CTypesInstanceModel::data(const QModelIndex &index, int role) const +{ + EIndexType type = IndexType(index); + + // Name/Layer + if ((role == Qt::DisplayRole) || (role == Qt::ToolTipRole)) + { + // Node types + if (type == eNodeTypeIndex) + { + if (index.column() == 0) + return mBaseItems[index.row()]; + + else + return QVariant::Invalid; + } + + // Object types + else if (type == eObjectTypeIndex) + { + if (index.column() == 0) { + if (mModelType == eLayers) + return QString::fromStdString(mpEditor->ActiveArea()->GetScriptLayer(index.row())->Name()); + else + return QString::fromStdString(mTemplateList[index.row()]->TemplateName()); + } + // todo: show/hide button in column 2 + else + return QVariant::Invalid; + } + + // Instances + else if (type == eInstanceIndex) + { + // todo: show/hide button + CScriptObject *pObj = static_cast(index.internalPointer()); + + if (index.column() == 0) + return QString::fromStdString(pObj->GetInstanceName()); + + else if (index.column() == 1) + { + if (mModelType == eLayers) + return QString::fromStdString(pObj->Template()->TemplateName()); + else if (mModelType == eTypes) + return QString::fromStdString(pObj->Layer()->Name()); + } + + else + return QVariant::Invalid; + } + } + + // Show/Hide Buttons + else if ((role == Qt::DecorationRole) && (index.column() == 2)) + { + if (!mpScene) return QVariant::Invalid; + + static QIcon Visible(":/icons/EditorAssets/Show.png"); + static QIcon Invisible(":/icons/EditorAssets/Hide.png"); + + // Show/Hide Node Types + if (type == eNodeTypeIndex) + { + // Commented out pending a proper implementation of turning node types on/off from the instance view + /*bool IsVisible; + + switch (index.row()) + { + case 0: IsVisible = mpScene->AreScriptObjectsEnabled(); + case 1: IsVisible = mpScene->AreLightsEnabled(); + default: IsVisible = false; + } + + if (IsVisible) return Visible; + else return Invisible;*/ + } + + // Show/Hide Object Types + else if (type == eObjectTypeIndex) + { + if (mModelType == eLayers) + { + CScriptLayer *pLayer = IndexLayer(index); + if (pLayer->IsVisible()) return Visible; + else return Invisible; + } + + else if (mModelType == eTypes) + { + CScriptTemplate *pTemp = IndexTemplate(index); + if (pTemp->IsVisible()) return Visible; + else return Invisible; + } + } + + // Show/Hide Instance + else if (type == eInstanceIndex) + { + CScriptObject *pObj = IndexObject(index); + CScriptNode *pNode = mpScene->NodeForObject(pObj); + if (pNode->MarkedVisible()) return Visible; + else return Invisible; + } + } + + return QVariant::Invalid; +} + +void CTypesInstanceModel::SetEditor(CWorldEditor *pEditor) +{ + mpEditor = pEditor; + mpScene = (pEditor ? pEditor->Scene() : nullptr); +} + +void CTypesInstanceModel::SetMaster(CMasterTemplate *pMaster) +{ + mpCurrentMaster = pMaster; + GenerateList(); +} + +void CTypesInstanceModel::SetArea(CGameArea *pArea) +{ + beginResetModel(); + mpArea = pArea; + endResetModel(); +} + +void CTypesInstanceModel::SetModelType(EInstanceModelType type) +{ + mModelType = type; +} + +void CTypesInstanceModel::NodeCreated(CSceneNode *pNode) +{ + if (mModelType == eTypes) + { + if (pNode->NodeType() == eScriptNode) + { + CScriptNode *pScript = static_cast(pNode); + CScriptObject *pObj = pScript->Object(); + pObj->Template()->SortObjects(); + + if (pObj->Template()->NumObjects() == 1) + { + mTemplateList << pObj->Template(); + qSort(mTemplateList.begin(), mTemplateList.end(), SortTemplatesAlphabetical); + emit layoutChanged(); + } + } + } +} + +void CTypesInstanceModel::NodeDeleted(CSceneNode *pNode) +{ + if (mModelType = eTypes) + { + if (pNode->NodeType() == eScriptNode) + { + CScriptNode *pScript = static_cast(pNode); + CScriptObject *pObj = pScript->Object(); + + if (pObj->Template()->NumObjects() == 0) + { + for (auto it = mTemplateList.begin(); it != mTemplateList.end(); it++) + { + if (*it == pObj->Template()) + { + mTemplateList.erase(it); + break; + } + } + + emit layoutChanged(); + } + } + } +} + +CScriptLayer* CTypesInstanceModel::IndexLayer(const QModelIndex& index) const +{ + if ((mModelType != eLayers) || (IndexNodeType(index) != eScriptType) || (IndexType(index) != eObjectTypeIndex)) + return nullptr; + + u32 RowIndex = ((index.internalId() & TYPES_ROW_INDEX_MASK) >> TYPES_ROW_INDEX_SHIFT); + return mpArea->GetScriptLayer(RowIndex); +} + +CScriptTemplate* CTypesInstanceModel::IndexTemplate(const QModelIndex& index) const +{ + if ((mModelType != eTypes) || (IndexNodeType(index) != eScriptType) || (IndexType(index) != eObjectTypeIndex)) + return nullptr; + + u32 RowIndex = ((index.internalId() & TYPES_ROW_INDEX_MASK) >> TYPES_ROW_INDEX_SHIFT); + return mTemplateList[RowIndex]; +} + +CScriptObject* CTypesInstanceModel::IndexObject(const QModelIndex& index) const +{ + if ((IndexNodeType(index) != eScriptType) || (IndexType(index) != eInstanceIndex)) + return nullptr; + + return static_cast(index.internalPointer()); +} + +// ************ STATIC ************ +CTypesInstanceModel::EIndexType CTypesInstanceModel::IndexType(const QModelIndex& index) +{ + if (!index.isValid()) return eRootIndex; + else if (index.internalId() == 0) return eNodeTypeIndex; + else if (((index.internalId() & TYPES_ITEM_TYPE_MASK) >> TYPES_ITEM_TYPE_SHIFT) == 1) return eObjectTypeIndex; + else return eInstanceIndex; +} + +CTypesInstanceModel::ENodeType CTypesInstanceModel::IndexNodeType(const QModelIndex& index) +{ + EIndexType type = IndexType(index); + + switch (type) + { + case eRootIndex: return eInvalidType; + case eNodeTypeIndex: return (ENodeType) index.row(); + case eObjectTypeIndex: return (ENodeType) index.parent().row(); + case eInstanceIndex: return (ENodeType) index.parent().parent().row(); + default: return eInvalidType; + } +} + +// ************ PRIVATE ************ +void CTypesInstanceModel::GenerateList() +{ + beginResetModel(); + + mTemplateList.clear(); + + if (mpCurrentMaster) + { + u32 NumTemplates = mpCurrentMaster->NumScriptTemplates(); + + for (u32 iTemp = 0; iTemp < NumTemplates; iTemp++) + { + CScriptTemplate *pTemp = mpCurrentMaster->TemplateByIndex(iTemp); + + if (pTemp->NumObjects() > 0) + mTemplateList << pTemp; + } + + qSort(mTemplateList.begin(), mTemplateList.end(), SortTemplatesAlphabetical); + } + + endResetModel(); +} diff --git a/UI/WorldEditor/CTypesInstanceModel.h b/UI/WorldEditor/CTypesInstanceModel.h new file mode 100644 index 00000000..eb731562 --- /dev/null +++ b/UI/WorldEditor/CTypesInstanceModel.h @@ -0,0 +1,67 @@ +#ifndef CTYPESINSTANCEMODEL_H +#define CTYPESINSTANCEMODEL_H + +#include +#include +#include +#include +#include +#include "../CWorldEditor.h" + +class CTypesInstanceModel : public QAbstractItemModel +{ + Q_OBJECT + +public: + enum EIndexType { + eRootIndex, eNodeTypeIndex, eObjectTypeIndex, eInstanceIndex + }; + + enum ENodeType { + eScriptType = 0x0, + eLightType = 0x1, + eInvalidType = 0xFF + }; + + enum EInstanceModelType { + eLayers, eTypes + }; + +private: + CWorldEditor *mpEditor; + CSceneManager *mpScene; + CGameArea *mpArea; + CMasterTemplate *mpCurrentMaster; + EInstanceModelType mModelType; + QList mTemplateList; + QStringList mBaseItems; + +public: + explicit CTypesInstanceModel(QObject *pParent = 0); + ~CTypesInstanceModel(); + QVariant headerData(int section, Qt::Orientation orientation, int role) const; + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const; + QModelIndex parent(const QModelIndex &child) const; + int rowCount(const QModelIndex &parent) const; + int columnCount(const QModelIndex &parent) const; + QVariant data(const QModelIndex &index, int role) const; + void SetEditor(CWorldEditor *pEditor); + void SetMaster(CMasterTemplate *pMaster); + void SetArea(CGameArea *pArea); + void SetModelType(EInstanceModelType type); + void NodeCreated(CSceneNode *pNode); + void NodeDeleted(CSceneNode *pNode); + CScriptLayer* IndexLayer(const QModelIndex& index) const; + CScriptTemplate* IndexTemplate(const QModelIndex& index) const; + CScriptObject* IndexObject(const QModelIndex& index) const; + + // Static + static EIndexType IndexType(const QModelIndex& index); + static ENodeType IndexNodeType(const QModelIndex& index); + +private: + void GenerateList(); + +}; + +#endif // CTYPESINSTANCEMODEL_H diff --git a/UI/WorldEditor/WCreateTab.cpp b/UI/WorldEditor/WCreateTab.cpp new file mode 100644 index 00000000..529bbc61 --- /dev/null +++ b/UI/WorldEditor/WCreateTab.cpp @@ -0,0 +1,14 @@ +#include "WCreateTab.h" +#include "ui_WCreateTab.h" + +WCreateTab::WCreateTab(QWidget *parent) : + QWidget(parent), + ui(new Ui::WCreateTab) +{ + ui->setupUi(this); +} + +WCreateTab::~WCreateTab() +{ + delete ui; +} diff --git a/UI/WorldEditor/WCreateTab.h b/UI/WorldEditor/WCreateTab.h new file mode 100644 index 00000000..78be581e --- /dev/null +++ b/UI/WorldEditor/WCreateTab.h @@ -0,0 +1,22 @@ +#ifndef WCREATETAB_H +#define WCREATETAB_H + +#include + +namespace Ui { +class WCreateTab; +} + +class WCreateTab : public QWidget +{ + Q_OBJECT + +public: + explicit WCreateTab(QWidget *parent = 0); + ~WCreateTab(); + +private: + Ui::WCreateTab *ui; +}; + +#endif // WCREATETAB_H diff --git a/UI/WorldEditor/WCreateTab.ui b/UI/WorldEditor/WCreateTab.ui new file mode 100644 index 00000000..020c02cc --- /dev/null +++ b/UI/WorldEditor/WCreateTab.ui @@ -0,0 +1,19 @@ + + + WCreateTab + + + + 0 + 0 + 216 + 421 + + + + Form + + + + + diff --git a/UI/WorldEditor/WInstancesTab.cpp b/UI/WorldEditor/WInstancesTab.cpp new file mode 100644 index 00000000..9b849a03 --- /dev/null +++ b/UI/WorldEditor/WInstancesTab.cpp @@ -0,0 +1,163 @@ +#include "WInstancesTab.h" +#include "ui_WInstancesTab.h" + +#include "../CWorldEditor.h" +#include + +WInstancesTab::WInstancesTab(QWidget *parent) : + QWidget(parent), + ui(new Ui::WInstancesTab) +{ + ui->setupUi(this); + + mpEditor = nullptr; + mpLayersModel = new CTypesInstanceModel(this); + mpLayersModel->SetModelType(CTypesInstanceModel::eLayers); + mpTypesModel = new CTypesInstanceModel(this); + mpTypesModel->SetModelType(CTypesInstanceModel::eTypes); + ui->LayersTreeView->setModel(mpLayersModel); + ui->LayersTreeView->header()->setSectionResizeMode(2, QHeaderView::Fixed); + ui->LayersTreeView->resizeColumnToContents(2); + ui->TypesTreeView->setModel(mpTypesModel); + ui->TypesTreeView->header()->setSectionResizeMode(2, QHeaderView::Fixed); + ui->TypesTreeView->resizeColumnToContents(2); + + // Create context menu + mpTreeContextMenu = new QMenu(this); + mpHideInstance = new QAction("Hide instance", this); + mpHideType = new QAction("", this); + mpHideAllExceptType = new QAction("", this); + mpTreeContextMenu->addAction(mpHideInstance); + mpTreeContextMenu->addAction(mpHideType); + mpTreeContextMenu->addAction(mpHideAllExceptType); + + // Configure signals/slots + connect(ui->LayersTreeView, SIGNAL(clicked(QModelIndex)), this, SLOT(OnTreeClick(QModelIndex))); + connect(ui->LayersTreeView, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(OnTreeDoubleClick(QModelIndex))); + connect(ui->TypesTreeView, SIGNAL(clicked(QModelIndex)), this, SLOT(OnTreeClick(QModelIndex))); + connect(ui->TypesTreeView, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(OnTreeDoubleClick(QModelIndex))); + connect(mpHideInstance, SIGNAL(triggered()), this, SLOT(OnHideInstanceAction())); + connect(mpHideType, SIGNAL(triggered()), this, SLOT(OnHideTypeAction())); + connect(mpHideAllExceptType, SIGNAL(triggered()), this, SLOT(OnHideAllExceptTypeAction())); +} + +WInstancesTab::~WInstancesTab() +{ + delete ui; +} + +void WInstancesTab::SetEditor(CWorldEditor *pEditor, CSceneManager *pScene) +{ + mpEditor = pEditor; + mpScene = pScene; + mpTypesModel->SetEditor(pEditor); + mpLayersModel->SetEditor(pEditor); +} + +void WInstancesTab::SetMaster(CMasterTemplate *pMaster) +{ + mpTypesModel->SetMaster(pMaster); + ExpandTopLevelItems(); +} + +void WInstancesTab::SetArea(CGameArea *pArea) +{ + mpLayersModel->SetArea(pArea); + ExpandTopLevelItems(); +} + +// ************ PRIVATE SLOTS ************ +void WInstancesTab::OnTreeClick(QModelIndex Index) +{ + // Single click is used to process show/hide events + if (Index.column() == 2) + { + // Show/Hide Instance + if (mpTypesModel->IndexType(Index) == CTypesInstanceModel::eInstanceIndex) + { + CScriptObject *pObj = mpTypesModel->IndexObject(Index); + CScriptNode *pNode = mpScene->NodeForObject(pObj); + pNode->SetVisible(!pNode->IsVisible()); + + } + + // Show/Hide Object Type + else if (mpTypesModel->IndexType(Index) == CTypesInstanceModel::eObjectTypeIndex) + { + if (sender() == ui->LayersTreeView) + { + CScriptLayer *pLayer = mpLayersModel->IndexLayer(Index); + pLayer->SetVisible(!pLayer->IsVisible()); + } + + else if (sender() == ui->TypesTreeView) + { + CScriptTemplate *pTmp = mpTypesModel->IndexTemplate(Index); + pTmp->SetVisible(!pTmp->IsVisible()); + } + } + + // Show/Hide Node Type + else if (mpTypesModel->IndexType(Index) == CTypesInstanceModel::eNodeTypeIndex) + { + CTypesInstanceModel::ENodeType type = mpTypesModel->IndexNodeType(Index); + + if (type == CTypesInstanceModel::eScriptType) + mpScene->SetObjects(!mpScene->AreScriptObjectsEnabled()); + } + + if (sender() == ui->LayersTreeView) + ui->LayersTreeView->update(Index); + else if (sender() == ui->TypesTreeView) + ui->TypesTreeView->update(Index); + } +} + +void WInstancesTab::OnTreeDoubleClick(QModelIndex Index) +{ + CTypesInstanceModel::EIndexType IndexType = mpTypesModel->IndexType(Index); + + if ((mpEditor) && (IndexType == CTypesInstanceModel::eInstanceIndex)) + { + CTypesInstanceModel::ENodeType NodeType = mpTypesModel->IndexNodeType(Index); + CSceneNode *pSelectedNode = nullptr; + + if (NodeType == CTypesInstanceModel::eScriptType) + pSelectedNode = mpScene->NodeForObject( static_cast(Index.internalPointer()) ); + + if (pSelectedNode) + { + mpEditor->ClearSelection(); + mpEditor->SelectNode(pSelectedNode); + } + } +} + +void WInstancesTab::OnHideInstanceAction() +{ +} + +void WInstancesTab::OnHideTypeAction() +{ +} + +void WInstancesTab::OnHideAllExceptTypeAction() +{ +} + +// ************ PRIVATE ************ +void WInstancesTab::ExpandTopLevelItems() +{ + for (u32 iModel = 0; iModel < 2; iModel++) + { + QAbstractItemModel *pModel = (iModel == 0 ? mpLayersModel : mpTypesModel); + QTreeView *pView = (iModel == 0 ? ui->LayersTreeView : ui->TypesTreeView); + QModelIndex Index = pModel->index(0,0); + + while (Index.isValid()) + { + pView->expand(Index); + Index = Index.sibling(Index.row() + 1, 0); + } + } +} diff --git a/UI/WorldEditor/WInstancesTab.h b/UI/WorldEditor/WInstancesTab.h new file mode 100644 index 00000000..19e80c71 --- /dev/null +++ b/UI/WorldEditor/WInstancesTab.h @@ -0,0 +1,51 @@ +#ifndef WINSTANCESTAB_H +#define WINSTANCESTAB_H + +#include +#include +#include +#include "CTypesInstanceModel.h" + +class CWorldEditor; +class CSceneManager; + +namespace Ui { +class WInstancesTab; +} + +class WInstancesTab : public QWidget +{ + Q_OBJECT + + CWorldEditor *mpEditor; + CSceneManager *mpScene; + CTypesInstanceModel *mpLayersModel; + CTypesInstanceModel *mpTypesModel; + + // Tree right-click context menu + QMenu *mpTreeContextMenu; + QAction *mpHideInstance; + QAction *mpHideType; + QAction *mpHideAllExceptType; + +public: + explicit WInstancesTab(QWidget *parent = 0); + ~WInstancesTab(); + void SetEditor(CWorldEditor *pEditor, CSceneManager *pScene); + void SetMaster(CMasterTemplate *pMaster); + void SetArea(CGameArea *pArea); + +private slots: + void OnTreeClick(QModelIndex Index); + void OnTreeDoubleClick(QModelIndex Index); + void OnHideInstanceAction(); + void OnHideTypeAction(); + void OnHideAllExceptTypeAction(); + +private: + Ui::WInstancesTab *ui; + + void ExpandTopLevelItems(); +}; + +#endif // WINSTANCESTAB_H diff --git a/UI/WorldEditor/WInstancesTab.ui b/UI/WorldEditor/WInstancesTab.ui new file mode 100644 index 00000000..7dab7b54 --- /dev/null +++ b/UI/WorldEditor/WInstancesTab.ui @@ -0,0 +1,74 @@ + + + WInstancesTab + + + + 0 + 0 + 253 + 437 + + + + Form + + + + 0 + + + 0 + + + + + 0 + + + + Layer + + + + + + QAbstractItemView::ScrollPerPixel + + + 13 + + + + + + + + Type + + + + + + + + + false + + + QAbstractItemView::ScrollPerPixel + + + 13 + + + + + + + + + + + + diff --git a/UI/WorldEditor/WModifyTab.cpp b/UI/WorldEditor/WModifyTab.cpp new file mode 100644 index 00000000..7edc2ee0 --- /dev/null +++ b/UI/WorldEditor/WModifyTab.cpp @@ -0,0 +1,145 @@ +#include "WModifyTab.h" +#include "ui_WModifyTab.h" +#include +#include +#include +#include "../CWorldEditor.h" + +WModifyTab::WModifyTab(QWidget *pParent) : + QWidget(pParent), + ui(new Ui::WModifyTab) +{ + ui->setupUi(this); + + mpCurPropEditor = nullptr; + + mpInLinkModel = new CLinkModel(this); + mpInLinkModel->SetConnectionType(CLinkModel::eIncoming); + mpOutLinkModel = new CLinkModel(this); + mpOutLinkModel->SetConnectionType(CLinkModel::eOutgoing); + + ui->InLinksTableView->setModel(mpInLinkModel); + ui->OutLinksTableView->setModel(mpOutLinkModel); + ui->InLinksTableView->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch); + ui->OutLinksTableView->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch); + connect(ui->InLinksTableView, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(OnLinkTableDoubleClick(QModelIndex))); + connect(ui->OutLinksTableView, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(OnLinkTableDoubleClick(QModelIndex))); + + ClearUI(); +} + +WModifyTab::~WModifyTab() +{ + delete ui; +} + +void WModifyTab::SetEditor(CWorldEditor *pEditor) +{ + mpWorldEditor = pEditor; +} + +void WModifyTab::GenerateUI(std::list& Selection) +{ + WPropertyEditor *pOldEditor = mpCurPropEditor; + ClearUI(); + + if (Selection.size() == 1) + { + mpSelectedNode = Selection.front(); + + // todo: set up editing UI for Light Nodes + if (mpSelectedNode->NodeType() == eScriptNode) + { + ui->ObjectsTabWidget->show(); + CScriptNode *pScriptNode = static_cast(mpSelectedNode); + CScriptObject *pObj = pScriptNode->Object(); + CScriptTemplate *pTemplate = pObj->Template(); + CPropertyStruct *pProperties = pObj->Properties(); + + // Check whether a cached UI for this object exists + auto it = mCachedPropEditors.find(pTemplate); + + // Load precached UI + if (it != mCachedPropEditors.end()) + { + mpCurPropEditor = *it; + mpCurPropEditor->SetProperty(pProperties); + } + + // Generate new UI + else + { + mpCurPropEditor = new WPropertyEditor(ui->PropertiesScrollContents, pProperties); + mCachedPropEditors[pTemplate] = mpCurPropEditor; + } + + ui->PropertiesScrollLayout->insertWidget(0, mpCurPropEditor); + mpCurPropEditor->show(); + + // Scroll back up to the top, but only if this is a new editor. + // (This is so clicking on multiple objects of the same type, or even + // the same object twice, won't cause you to lose your place.) + if (pOldEditor != mpCurPropEditor) + { + ui->PropertiesScrollArea->horizontalScrollBar()->setValue(0); + ui->PropertiesScrollArea->verticalScrollBar()->setValue(0); + } + + // Set up connection table model + mpInLinkModel->SetObject(pObj); + mpOutLinkModel->SetObject(pObj); + } + } + + else + ClearUI(); +} + +void WModifyTab::ClearUI() +{ + if (mpCurPropEditor) + { + ui->PropertiesScrollLayout->removeWidget(mpCurPropEditor); + mpCurPropEditor->hide(); + mpCurPropEditor = nullptr; + } + + ui->ObjectsTabWidget->hide(); + ui->LightGroupBox->hide(); +} + +void WModifyTab::ClearCachedEditors() +{ + foreach(WPropertyEditor *pEditor, mCachedPropEditors) + delete pEditor; + + mCachedPropEditors.clear(); +} + +void WModifyTab::OnLinkTableDoubleClick(QModelIndex Index) +{ + if (Index.column() == 0) + { + // The link table will only be visible if the selected node is a script node + CScriptNode *pNode = static_cast(mpSelectedNode); + SLink Link; + + if (sender() == ui->InLinksTableView) + Link = pNode->Object()->InLink(Index.row()); + else if (sender() == ui->OutLinksTableView) + Link = pNode->Object()->OutLink(Index.row()); + else + std::cout << "Error - OnLinkTableDoubleClick() activated by invalid sender\n"; + + CScriptNode *pLinkedNode = pNode->Scene()->ScriptNodeByID(Link.ObjectID); + + if (pLinkedNode) + { + mpWorldEditor->ClearSelection(); + mpWorldEditor->SelectNode(pLinkedNode); + } + + ui->InLinksTableView->clearSelection(); + ui->OutLinksTableView->clearSelection(); + } +} diff --git a/UI/WorldEditor/WModifyTab.h b/UI/WorldEditor/WModifyTab.h new file mode 100644 index 00000000..a76fecf5 --- /dev/null +++ b/UI/WorldEditor/WModifyTab.h @@ -0,0 +1,47 @@ +#ifndef WMODIFYTAB_H +#define WMODIFYTAB_H + +#include +#include +#include +#include +#include + +#include "CLinkModel.h" +#include "../WPropertyEditor.h" +#include + +class CWorldEditor; + +namespace Ui { +class WModifyTab; +} + +class WModifyTab : public QWidget +{ + Q_OBJECT + + CWorldEditor *mpWorldEditor; + CSceneNode *mpSelectedNode; + + QMap mCachedPropEditors; + WPropertyEditor *mpCurPropEditor; + CLinkModel *mpInLinkModel; + CLinkModel *mpOutLinkModel; + +public: + explicit WModifyTab(QWidget *pParent = 0); + ~WModifyTab(); + void SetEditor(CWorldEditor *pEditor); + void GenerateUI(std::list& Selection); + void ClearUI(); + void ClearCachedEditors(); + +private: + Ui::WModifyTab *ui; + +private slots: + void OnLinkTableDoubleClick(QModelIndex Index); +}; + +#endif // WMODIFYTAB_H diff --git a/UI/WorldEditor/WModifyTab.ui b/UI/WorldEditor/WModifyTab.ui new file mode 100644 index 00000000..606455d8 --- /dev/null +++ b/UI/WorldEditor/WModifyTab.ui @@ -0,0 +1,222 @@ + + + WModifyTab + + + + 0 + 0 + 282 + 502 + + + + Form + + + + 0 + + + 9 + + + 0 + + + 9 + + + + + 0 + + + + Properties + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + QFrame::NoFrame + + + QFrame::Plain + + + true + + + + + 0 + 0 + 276 + 439 + + + + + 0 + 0 + + + + + 9 + + + 9 + + + 9 + + + 9 + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + Connections + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + QFrame::NoFrame + + + true + + + + + 0 + 0 + 276 + 439 + + + + + + + + 0 + 1 + + + + Outgoing + + + + + + QAbstractScrollArea::AdjustToContents + + + QAbstractItemView::NoEditTriggers + + + QAbstractItemView::ScrollPerPixel + + + QAbstractItemView::ScrollPerPixel + + + 75 + + + false + + + + + + + + + + + 0 + 1 + + + + Incoming + + + + + + 75 + + + + + + + + + + + + + + + + + + Light + + + + + + + +